├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── ci.yaml │ ├── kind-with-registry.sh │ └── run_e2e.sh ├── .gitignore ├── .golangci.yml ├── .zappr.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── Dockerfile ├── Dockerfile.e2e ├── LICENSE.Apache2.txt ├── LICENSE.MIT.txt ├── MAINTAINERS ├── Makefile ├── README.md ├── SECURITY.md ├── cmd └── e2e │ ├── README.md │ ├── account_cdp.yaml │ ├── autoscale_test.go │ ├── basic_test.go │ ├── test_environment.go │ ├── test_utils.go │ └── upgrade_test.go ├── delivery.yaml ├── deploy └── e2e │ └── apply │ ├── es-operator.yaml │ ├── es7-config.yaml │ ├── es7-master-service.yaml │ ├── es7-master.yaml │ ├── es8-config.yaml │ ├── es8-master-service.yaml │ ├── es8-master.yaml │ └── rbac.yaml ├── docs ├── EMERGENCY.md ├── GETTING_STARTED.md ├── RELEASING.md ├── cluster-roles.yaml ├── elasticsearch-cluster.yaml ├── elasticsearchdataset-simple.yaml ├── es-operator.yaml ├── migrating-from-transient-settings.md ├── zalando.org_elasticsearchdatasets.yaml └── zalando.org_elasticsearchmetricsets.yaml ├── e2e.sh ├── elasticsearchdataset-vct.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── crd │ └── trim.go ├── tools.go └── update-codegen.sh ├── label_selector.go ├── label_selector_test.go ├── main.go ├── manifests ├── es-operator.yaml ├── metrics-server.yaml ├── operator_service_account.yaml ├── rbac.yaml └── sysctl.yaml ├── operator ├── autoscaler.go ├── autoscaler_test.go ├── elasticsearch.go ├── elasticsearch_test.go ├── es_client.go ├── es_client_test.go ├── metrics_collector.go ├── metrics_collector_test.go ├── null │ └── string.go ├── operator.go ├── operator_test.go └── recorder.go └── pkg ├── apis └── zalando.org │ ├── register.go │ └── v1 │ ├── register.go │ ├── types.go │ └── zz_generated.deepcopy.go ├── client ├── clientset │ └── versioned │ │ ├── clientset.go │ │ ├── doc.go │ │ ├── fake │ │ ├── clientset_generated.go │ │ ├── doc.go │ │ └── register.go │ │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ │ └── typed │ │ └── zalando.org │ │ └── v1 │ │ ├── doc.go │ │ ├── elasticsearchdataset.go │ │ ├── elasticsearchmetricset.go │ │ ├── fake │ │ ├── doc.go │ │ ├── fake_elasticsearchdataset.go │ │ ├── fake_elasticsearchmetricset.go │ │ └── fake_zalando.org_client.go │ │ ├── generated_expansion.go │ │ └── zalando.org_client.go ├── informers │ └── externalversions │ │ ├── factory.go │ │ ├── generic.go │ │ ├── internalinterfaces │ │ └── factory_interfaces.go │ │ └── zalando.org │ │ ├── interface.go │ │ └── v1 │ │ ├── elasticsearchdataset.go │ │ ├── elasticsearchmetricset.go │ │ └── interface.go └── listers │ └── zalando.org │ └── v1 │ ├── elasticsearchdataset.go │ ├── elasticsearchmetricset.go │ └── expansion_generated.go └── clientset └── clientset.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. 3 | * @mikkeloscar @otrosien @njuettner 4 | 5 | 6 | 7 | # Samples for assigning codeowners below: 8 | # Order is important; the last matching pattern takes the most 9 | # precedence. When someone opens a pull request that only 10 | # modifies JS files, only @js-owner and not the global 11 | # owner(s) will be requested for a review. 12 | # *.js @js-owner 13 | 14 | # You can also use email addresses if you prefer. They'll be 15 | # used to look up users just like we do for commit author 16 | # emails. 17 | # *.go docs@example.com 18 | 19 | # In this example, @doctocat owns any files in the build/logs 20 | # directory at the root of the repository and any of its 21 | # subdirectories. 22 | # /build/logs/ @doctocat 23 | 24 | # The `docs/*` pattern will match files like 25 | # `docs/getting-started.md` but not further nested files like 26 | # `docs/build-app/troubleshooting.md`. 27 | # docs/* docs@example.com 28 | 29 | # In this example, @octocat owns any file in an apps directory 30 | # anywhere in your repository. 31 | # apps/ @octocat 32 | 33 | # In this example, @doctocat owns any file in the `/docs` 34 | # directory in the root of your repository. 35 | # /docs/ @doctocat 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: 17 | - Subsystem: 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # One-line summary 2 | 3 | > Issue : #1234 (only if appropriate) 4 | 5 | ## Description 6 | A few sentences describing the overall goals of the pull request's 7 | commits. 8 | 9 | ## Types of Changes 10 | _What types of changes does your code introduce? Keep the ones that apply:_ 11 | 12 | - New feature (non-breaking change which adds functionality) 13 | - Bug fix (non-breaking change which fixes an issue) 14 | - Configuration change 15 | - Refactor/improvements 16 | - Documentation / non-code 17 | 18 | ## Tasks 19 | _List of tasks you will do to complete the PR_ 20 | - [ ] Created Task 1 21 | - [ ] Created Task 2 22 | - [ ] To-do Task 3 23 | 24 | ## Review 25 | _List of tasks the reviewer must do to review the PR_ 26 | - [ ] Tests 27 | - [ ] Documentation 28 | - [ ] CHANGELOG 29 | 30 | ## Deployment Notes 31 | These should highlight any db migrations, feature toggles, etc. 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "07:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: docker 10 | directory: "/" 11 | schedule: 12 | interval: monthly 13 | time: "07:00" 14 | open-pull-requests-limit: 10 15 | -------------------------------------------------------------------------------- /.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@v4 8 | - uses: actions/setup-go@v5 9 | with: 10 | go-version: '^1.23' 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: | 15 | make test 16 | make lint 17 | make 18 | make build/linux/e2e 19 | IMAGE=es-operator VERSION=local make build.docker 20 | - run: goveralls -coverprofile=profile.cov -service=github 21 | env: 22 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | # run e2e 24 | - uses: engineerd/setup-kind@v0.5.0 25 | with: 26 | version: "v0.11.0" 27 | skipClusterCreation: "true" 28 | - name: e2e tests 29 | run: | 30 | ./.github/workflows/kind-with-registry.sh 31 | docker tag es-operator:local localhost:5000/es-operator:local 32 | docker push localhost:5000/es-operator:local 33 | 34 | ./.github/workflows/run_e2e.sh 35 | - name: documentation tests 36 | run: | 37 | grep '^kubectl apply' docs/GETTING_STARTED.md | sh 38 | for i in {1..10}; do kubectl -n es-operator-demo get all ; echo '========='; if kubectl -n es-operator-demo get sts es-data-simple | grep '1/1'; then echo 'SUCCESS' ; exit 0 ; fi ; sleep 60 ; done; exit 1 39 | -------------------------------------------------------------------------------- /.github/workflows/kind-with-registry.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit 3 | 4 | # create registry container unless it already exists 5 | reg_name='kind-registry' 6 | reg_port='5000' 7 | running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" 8 | if [ "${running}" != 'true' ]; then 9 | docker run \ 10 | -d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \ 11 | registry:2 12 | fi 13 | 14 | # create a cluster with the local registry enabled in containerd 15 | cat < 2 | Oliver Trosien 3 | Nick Juettner -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean test lint build.local build.linux build.osx build.docker build.push 2 | 3 | BINARY ?= es-operator 4 | VERSION ?= $(shell git describe --tags --always --dirty) 5 | IMAGE ?= registry-write.opensource.zalan.do/poirot/$(BINARY) 6 | E2E_IMAGE ?= $(IMAGE)-e2e 7 | TAG ?= $(VERSION) 8 | SOURCES = $(shell find . -name '*.go') 9 | CRD_TYPE_SOURCE = pkg/apis/zalando.org/v1/types.go 10 | GENERATED_CRDS = docs/zalando.org_elasticsearchdatasets.yaml docs/zalando.org_elasticsearchmetricsets.yaml 11 | GENERATED = pkg/apis/zalando.org/v1/zz_generated.deepcopy.go 12 | DOCKERFILE ?= Dockerfile 13 | GOPKGS = $(shell go list ./... | grep -v /e2e) 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 vendor 22 | rm -rf $(GENERATED) 23 | 24 | test: $(GENERATED) 25 | go test -v -coverprofile=profile.cov $(GOPKGS) 26 | 27 | lint: 28 | golangci-lint run ./... 29 | 30 | $(GENERATED): go.mod $(CRD_TYPE_SOURCE) 31 | ./hack/update-codegen.sh 32 | 33 | $(GENERATED_CRDS): $(GENERATED) $(CRD_SOURCES) 34 | go run sigs.k8s.io/controller-tools/cmd/controller-gen crd:crdVersions=v1 paths=./pkg/apis/... output:crd:dir=docs 35 | go run hack/crd/trim.go < docs/zalando.org_elasticsearchdatasets.yaml > docs/zalando.org_elasticsearchdatasets_trimmed.yaml 36 | go run hack/crd/trim.go < docs/zalando.org_elasticsearchmetricsets.yaml > docs/zalando.org_elasticsearchmetricsets_trimmed.yaml 37 | mv docs/zalando.org_elasticsearchdatasets_trimmed.yaml docs/zalando.org_elasticsearchdatasets.yaml 38 | mv docs/zalando.org_elasticsearchmetricsets_trimmed.yaml docs/zalando.org_elasticsearchmetricsets.yaml 39 | 40 | build.local: build/$(BINARY) $(GENERATED_CRDS) 41 | build.linux: build/linux/$(BINARY) 42 | 43 | build/linux/e2e: $(GENERATED) $(SOURCES) 44 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go test -c -o build/linux/$(notdir $@) $(BUILD_FLAGS) ./cmd/$(notdir $@) 45 | 46 | build/linux/%: $(GENERATED) $(SOURCES) 47 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(BUILD_FLAGS) -o build/linux/$(notdir $@) -ldflags "$(LDFLAGS)" ./cmd/$(notdir $@) 48 | 49 | build/e2e: $(GENERATED) $(SOURCES) 50 | CGO_ENABLED=0 go test -c -o build/$(notdir $@) ./cmd/$(notdir $@) 51 | 52 | build/%: $(GENERATED) $(SOURCES) 53 | CGO_ENABLED=0 go build -o build/$(notdir $@) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" ./cmd/$(notdir $@) 54 | 55 | build.osx: build/osx/$(BINARY) 56 | 57 | build/$(BINARY): $(GENERATED) $(SOURCES) 58 | CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" . 59 | 60 | build/linux/$(BINARY): $(GENERATED) $(SOURCES) 61 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(BUILD_FLAGS) -o build/linux/$(BINARY) -ldflags "$(LDFLAGS)" . 62 | 63 | build/osx/$(BINARY): $(GENERATED) $(SOURCES) 64 | GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(BUILD_FLAGS) -o build/osx/$(BINARY) -ldflags "$(LDFLAGS)" . 65 | 66 | build.docker: build.linux build/linux/e2e 67 | docker build --rm -t "$(E2E_IMAGE):$(TAG)" -f Dockerfile.e2e . 68 | docker build --rm -t "$(IMAGE):$(TAG)" -f $(DOCKERFILE) . 69 | 70 | build.push: build.docker 71 | docker push "$(E2E_IMAGE):$(TAG)" 72 | docker push "$(IMAGE):$(TAG)" 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/e2e/README.md: -------------------------------------------------------------------------------- 1 | ### Running the End-To-End Tests 2 | 3 | The following environment variables should be set: 4 | 5 | 1. `E2E_NAMESPACE` is the namespace where the tests should be run. 6 | 3. `OPERATOR_ID` is set so that all stacks are only managed by the controller being currently tested. 7 | 4. `KUBECONFIG` with the path to the kubeconfig file 8 | 9 | To run the tests run the command: 10 | 11 | ``` 12 | go test -parallel $NUM_PARALLEL github.com/zalando-incubator/es-operator/cmd/e2e 13 | ``` 14 | 15 | Over here `$NUM_PARALLEL` can be set to a sufficiently high value which indicates how many 16 | of the parallel type tests can be run concurrently. 17 | -------------------------------------------------------------------------------- /cmd/e2e/account_cdp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: cdp 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: operator 10 | -------------------------------------------------------------------------------- /cmd/e2e/autoscale_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/cenk/backoff" 8 | "github.com/stretchr/testify/require" 9 | zv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 10 | ) 11 | 12 | func TestEDSCPUAutoscaleUP8(t *testing.T) { 13 | t.Parallel() 14 | runTestEDSCPUAutoScaleUP(t, "8.6.2", "es8-config") 15 | } 16 | 17 | func runTestEDSCPUAutoScaleUP(t *testing.T, version, configMap string) { 18 | edsName := "cpu-autoscale-up-" + strings.Replace(version, ".", "", -1) 19 | edsSpecFactory := NewTestEDSSpecFactory(edsName, version, configMap) 20 | edsSpecFactory.Scaling(&zv1.ElasticsearchDataSetScaling{ 21 | Enabled: true, 22 | MinReplicas: 1, 23 | MaxReplicas: 3, 24 | MinIndexReplicas: 0, 25 | MaxIndexReplicas: 2, 26 | MinShardsPerNode: 1, 27 | MaxShardsPerNode: 1, 28 | ScaleUpCPUBoundary: 50, 29 | ScaleUpThresholdDurationSeconds: 60, 30 | ScaleUpCooldownSeconds: 0, 31 | ScaleDownCPUBoundary: 1, 32 | ScaleDownThresholdDurationSeconds: 600, 33 | ScaleDownCooldownSeconds: 600, 34 | DiskUsagePercentScaledownWatermark: 0, 35 | }) 36 | edsSpec := edsSpecFactory.Create() 37 | edsSpec.Template.Spec = edsPodSpecCPULoadContainer(edsName, version, configMap) 38 | 39 | err := createEDS(edsName, edsSpec) 40 | require.NoError(t, err) 41 | 42 | esClient, err := setupESClient("http://"+edsName+":9200", version) 43 | require.NoError(t, err) 44 | createIndex := func() error { 45 | return esClient.CreateIndex(edsName, edsName, 1, 0) 46 | } 47 | backoffCfg := backoff.NewExponentialBackOff() 48 | err = backoff.Retry(createIndex, backoffCfg) 49 | require.NoError(t, err) 50 | verifyEDS(t, edsName, edsSpec, pint32(3)) 51 | err = esClient.DeleteIndex(edsName) 52 | require.NoError(t, err) 53 | err = deleteEDS(edsName) 54 | require.NoError(t, err) 55 | } 56 | 57 | func TestEDSAutoscaleUPOnShardCount6(t *testing.T) { 58 | t.Parallel() 59 | runTestEDSAutoscaleUPOnShardCount(t, "8.6.2", "es8-config") 60 | } 61 | 62 | func TestEDSAutoscaleUPOnShardCount7(t *testing.T) { 63 | t.Parallel() 64 | runTestEDSAutoscaleUPOnShardCount(t, "7.17.2", "es7-config") 65 | } 66 | 67 | func runTestEDSAutoscaleUPOnShardCount(t *testing.T, version, configMap string) { 68 | edsName := "shard-autoscale-up-" + strings.Replace(version, ".", "", -1) 69 | edsSpecFactory := NewTestEDSSpecFactory(edsName, version, configMap) 70 | edsSpecFactory.Scaling(&zv1.ElasticsearchDataSetScaling{ 71 | Enabled: true, 72 | MinReplicas: 1, 73 | MaxReplicas: 2, 74 | MinIndexReplicas: 0, 75 | MaxIndexReplicas: 1, 76 | MinShardsPerNode: 1, 77 | MaxShardsPerNode: 1, 78 | ScaleUpCPUBoundary: 50, 79 | ScaleUpThresholdDurationSeconds: 120, 80 | ScaleUpCooldownSeconds: 30, 81 | ScaleDownCPUBoundary: 20, 82 | ScaleDownThresholdDurationSeconds: 120, 83 | ScaleDownCooldownSeconds: 30, 84 | DiskUsagePercentScaledownWatermark: 0, 85 | }) 86 | edsSpec := edsSpecFactory.Create() 87 | 88 | err := createEDS(edsName, edsSpec) 89 | require.NoError(t, err) 90 | 91 | esClient, err := setupESClient("http://"+edsName+":9200", version) 92 | require.NoError(t, err) 93 | createIndex := func() error { 94 | return esClient.CreateIndex(edsName, edsName, 2, 0) 95 | } 96 | backoffCfg := backoff.NewExponentialBackOff() 97 | err = backoff.Retry(createIndex, backoffCfg) 98 | require.NoError(t, err) 99 | verifyEDS(t, edsName, edsSpec, pint32(2)) 100 | err = esClient.DeleteIndex(edsName) 101 | require.NoError(t, err) 102 | err = deleteEDS(edsName) 103 | require.NoError(t, err) 104 | } 105 | -------------------------------------------------------------------------------- /cmd/e2e/basic_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | zv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 10 | appsv1 "k8s.io/api/apps/v1" 11 | ) 12 | 13 | type TestEDSSpecFactory struct { 14 | edsName string 15 | replicas int32 16 | scaling *zv1.ElasticsearchDataSetScaling 17 | version string 18 | configMap string 19 | } 20 | 21 | func NewTestEDSSpecFactory(edsName, version, configMap string) *TestEDSSpecFactory { 22 | return &TestEDSSpecFactory{ 23 | edsName: edsName, 24 | version: version, 25 | configMap: configMap, 26 | replicas: 1, 27 | } 28 | } 29 | 30 | func (f *TestEDSSpecFactory) Replicas(replicas int32) *TestEDSSpecFactory { 31 | f.replicas = replicas 32 | return f 33 | } 34 | 35 | func (f *TestEDSSpecFactory) Scaling(scaling *zv1.ElasticsearchDataSetScaling) *TestEDSSpecFactory { 36 | f.scaling = scaling 37 | return f 38 | } 39 | 40 | func (f *TestEDSSpecFactory) Create() zv1.ElasticsearchDataSetSpec { 41 | var result = zv1.ElasticsearchDataSetSpec{ 42 | Replicas: &f.replicas, 43 | Scaling: f.scaling, 44 | Template: zv1.PodTemplateSpec{ 45 | EmbeddedObjectMeta: zv1.EmbeddedObjectMeta{ 46 | Labels: map[string]string{ 47 | "application": "es-operator", 48 | "component": "elasticsearch", 49 | }, 50 | }, 51 | Spec: edsPodSpec(f.edsName, f.version, f.configMap), 52 | }, 53 | } 54 | 55 | return result 56 | } 57 | 58 | func testEDSCreate(t *testing.T, edsName, version, configMap string) zv1.ElasticsearchDataSetSpec { 59 | edsSpecFactory := NewTestEDSSpecFactory(edsName, version, configMap) 60 | edsSpec := edsSpecFactory.Create() 61 | 62 | err := createEDS(edsName, edsSpec) 63 | require.NoError(t, err) 64 | return edsSpec 65 | } 66 | 67 | func verifyEDS(t *testing.T, edsName string, edsSpec zv1.ElasticsearchDataSetSpec, replicas *int32) *zv1.ElasticsearchDataSet { 68 | // Verify eds 69 | eds, err := waitForEDS(t, edsName) 70 | require.NoError(t, err) 71 | err = waitForEDSCondition(t, eds.Name, func(eds *zv1.ElasticsearchDataSet) error { 72 | if !assert.ObjectsAreEqualValues(replicas, eds.Spec.Replicas) { 73 | return fmt.Errorf("%s: replicas %d != expected %d", eds.Name, *eds.Spec.Replicas, *replicas) 74 | } 75 | if !assert.ObjectsAreEqualValues(edsSpec.Template.Labels, eds.Spec.Template.Labels) { 76 | return fmt.Errorf("EDS '%s' labels %v != expected %v", eds.Name, eds.Spec.Template.Labels, edsSpec.Template.Labels) 77 | } 78 | return nil 79 | }) 80 | require.NoError(t, err) 81 | 82 | // Verify statefulset 83 | sts, err := waitForStatefulSet(t, edsName) 84 | require.NoError(t, err) 85 | require.Equal(t, *replicas, *sts.Spec.Replicas) 86 | 87 | // Verify service 88 | service, err := waitForService(t, eds.Name) 89 | require.NoError(t, err) 90 | require.EqualValues(t, eds.Labels, service.Labels) 91 | require.EqualValues(t, sts.Spec.Selector.MatchLabels, service.Spec.Selector) 92 | 93 | // wait for this condition to be true 94 | err = waitForSTSCondition(t, sts.Name, func(sts *appsv1.StatefulSet) error { 95 | if !assert.ObjectsAreEqualValues(mergeLabels(edsSpec.Template.Labels, sts.Spec.Selector.MatchLabels), sts.Spec.Template.Labels) { 96 | return fmt.Errorf("EDS '%s' labels %v, does not match STS labels %v", eds.Name, mergeLabels(edsSpec.Template.Labels, sts.Spec.Selector.MatchLabels), sts.Spec.Template.Labels) 97 | } 98 | return nil 99 | }, 100 | expectedStsStatus{ 101 | replicas: replicas, 102 | updatedReplicas: replicas, 103 | readyReplicas: replicas, 104 | }.matches) 105 | require.NoError(t, err) 106 | return eds 107 | } 108 | 109 | func mergeLabels(labelsSlice ...map[string]string) map[string]string { 110 | newLabels := make(map[string]string) 111 | for _, labels := range labelsSlice { 112 | for k, v := range labels { 113 | newLabels[k] = v 114 | } 115 | } 116 | return newLabels 117 | } 118 | 119 | func TestEDSCreateBasic8(t *testing.T) { 120 | t.Parallel() 121 | edsName := "basic8" 122 | edsSpec := testEDSCreate(t, edsName, "8.6.2", "es8-config") 123 | verifyEDS(t, edsName, edsSpec, edsSpec.Replicas) 124 | err := deleteEDS(edsName) 125 | require.NoError(t, err) 126 | } 127 | 128 | func TestEDSCreateBasic7(t *testing.T) { 129 | t.Parallel() 130 | edsName := "basic7" 131 | edsSpec := testEDSCreate(t, edsName, "7.17.2", "es7-config") 132 | verifyEDS(t, edsName, edsSpec, edsSpec.Replicas) 133 | err := deleteEDS(edsName) 134 | require.NoError(t, err) 135 | } 136 | -------------------------------------------------------------------------------- /cmd/e2e/test_environment.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | "github.com/zalando-incubator/es-operator/operator" 11 | clientset "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 12 | zv1client "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/typed/zalando.org/v1" 13 | 14 | "k8s.io/client-go/kubernetes" 15 | appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" 16 | v1 "k8s.io/client-go/kubernetes/typed/core/v1" 17 | "k8s.io/client-go/rest" 18 | "k8s.io/client-go/tools/clientcmd" 19 | ) 20 | 21 | var ( 22 | kubernetesClient, edsClient = createClients() 23 | namespace = requiredEnvar("E2E_NAMESPACE") 24 | operatorId = requiredEnvar("OPERATOR_ID") 25 | ) 26 | 27 | func init() { 28 | logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) 29 | } 30 | 31 | func createClients() (kubernetes.Interface, clientset.Interface) { 32 | kubeconfig := os.Getenv("KUBECONFIG") 33 | 34 | var cfg *rest.Config 35 | var err error 36 | if kubeconfig != "" { 37 | cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 38 | } else { 39 | cfg, err = rest.InClusterConfig() 40 | } 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | kubeClient, err := kubernetes.NewForConfig(cfg) 46 | if err != nil { 47 | panic(err) 48 | } 49 | edsClient, err := clientset.NewForConfig(cfg) 50 | if err != nil { 51 | panic(err) 52 | } 53 | return kubeClient, edsClient 54 | } 55 | 56 | func edsInterface() zv1client.ElasticsearchDataSetInterface { 57 | return edsClient.ZalandoV1().ElasticsearchDataSets(namespace) 58 | } 59 | 60 | func statefulSetInterface() appsv1.StatefulSetInterface { 61 | return kubernetesClient.AppsV1().StatefulSets(namespace) 62 | } 63 | 64 | func serviceInterface() v1.ServiceInterface { 65 | return kubernetesClient.CoreV1().Services(namespace) 66 | } 67 | 68 | func requiredEnvar(envar string) string { 69 | namespace := os.Getenv(envar) 70 | if namespace == "" { 71 | panic(fmt.Sprintf("%s not set", envar)) 72 | } 73 | return namespace 74 | } 75 | 76 | func setupESClient(defaultServiceEndpoint, version string) (*operator.ESClient, error) { 77 | var envSuffix string 78 | if len(version) > 0 { 79 | switch version[0] { 80 | case '7': 81 | envSuffix = "_ES7" 82 | case '8': 83 | envSuffix = "_ES8" 84 | } 85 | } 86 | serviceEndpoint := os.Getenv("ES_SERVICE_ENDPOINT" + envSuffix) 87 | if serviceEndpoint == "" { 88 | serviceEndpoint = defaultServiceEndpoint 89 | } 90 | endpoint, err := url.Parse(serviceEndpoint) 91 | if err != nil { 92 | return nil, err 93 | } 94 | config := &operator.DrainingConfig{ 95 | MaxRetries: 999, 96 | MinimumWaitTime: 10 * time.Second, 97 | MaximumWaitTime: 30 * time.Second, 98 | } 99 | return &operator.ESClient{Endpoint: endpoint, DrainingConfig: config}, nil 100 | } 101 | -------------------------------------------------------------------------------- /cmd/e2e/test_utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | appsv1 "k8s.io/api/apps/v1" 11 | "k8s.io/apimachinery/pkg/api/resource" 12 | "k8s.io/apimachinery/pkg/util/intstr" 13 | 14 | zv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 15 | 16 | v1 "k8s.io/api/core/v1" 17 | 18 | apiErrors "k8s.io/apimachinery/pkg/api/errors" 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | ) 21 | 22 | const ( 23 | defaultWaitTimeout = 15 * time.Minute 24 | ) 25 | 26 | var ( 27 | edsPodSpec = func(nodeGroup, version, configMap string) v1.PodSpec { 28 | return v1.PodSpec{ 29 | SecurityContext: &v1.PodSecurityContext{ 30 | RunAsUser: pint64(1000), 31 | RunAsGroup: pint64(0), 32 | FSGroup: pint64(0), 33 | }, 34 | Containers: []v1.Container{ 35 | { 36 | Name: "elasticsearch", 37 | // gets replaced with desired version 38 | Image: fmt.Sprintf("docker.elastic.co/elasticsearch/elasticsearch:%s", version), 39 | Ports: []v1.ContainerPort{ 40 | { 41 | ContainerPort: 9200, 42 | }, 43 | { 44 | ContainerPort: 9300, 45 | }, 46 | }, 47 | Env: []v1.EnvVar{ 48 | {Name: "ES_JAVA_OPTS", Value: "-Xms356m -Xmx356m"}, 49 | {Name: "node.roles", Value: "data"}, 50 | {Name: "node.attr.group", Value: nodeGroup}, 51 | }, 52 | Resources: v1.ResourceRequirements{ 53 | Limits: v1.ResourceList{ 54 | v1.ResourceMemory: resource.MustParse("1Gi"), 55 | v1.ResourceCPU: resource.MustParse("100m"), 56 | }, 57 | Requests: v1.ResourceList{ 58 | v1.ResourceMemory: resource.MustParse("1Gi"), 59 | v1.ResourceCPU: resource.MustParse("100m"), 60 | }, 61 | }, 62 | ReadinessProbe: &v1.Probe{ 63 | InitialDelaySeconds: 15, 64 | ProbeHandler: v1.ProbeHandler{ 65 | HTTPGet: &v1.HTTPGetAction{ 66 | Path: "/_cluster/health?local=true", 67 | Port: intstr.FromInt(9200), 68 | Scheme: v1.URISchemeHTTP, 69 | }, 70 | }, 71 | }, 72 | VolumeMounts: []v1.VolumeMount{ 73 | { 74 | Name: "data", 75 | MountPath: "/usr/share/elasticsearch/data", 76 | }, 77 | { 78 | Name: "config", 79 | MountPath: "/usr/share/elasticsearch/config/elasticsearch.yml", 80 | SubPath: "elasticsearch.yml", 81 | }, 82 | }, 83 | }, 84 | }, 85 | TerminationGracePeriodSeconds: pint64(5), 86 | Volumes: []v1.Volume{ 87 | { 88 | Name: "data", 89 | VolumeSource: v1.VolumeSource{ 90 | EmptyDir: &v1.EmptyDirVolumeSource{ 91 | Medium: v1.StorageMediumMemory, 92 | }, 93 | }, 94 | }, 95 | { 96 | Name: "config", 97 | VolumeSource: v1.VolumeSource{ 98 | ConfigMap: &v1.ConfigMapVolumeSource{ 99 | LocalObjectReference: v1.LocalObjectReference{ 100 | Name: configMap, 101 | }, 102 | Items: []v1.KeyToPath{ 103 | { 104 | Key: "elasticsearch.yml", 105 | Path: "elasticsearch.yml", 106 | }, 107 | }, 108 | }, 109 | }, 110 | }, 111 | }, 112 | } 113 | } 114 | edsPodSpecCPULoadContainer = func(nodeGroup, version, configMap string) v1.PodSpec { 115 | podSpec := edsPodSpec(nodeGroup, version, configMap) 116 | podSpec.Containers = append(podSpec.Containers, v1.Container{ 117 | Name: "stress-ng", 118 | // https://hub.docker.com/r/alexeiled/stress-ng/ 119 | Image: "alexeiled/stress-ng", 120 | Args: []string{"--cpu=1", "--cpu-load=10"}, 121 | Resources: v1.ResourceRequirements{ 122 | Limits: v1.ResourceList{ 123 | v1.ResourceMemory: resource.MustParse("50Mi"), 124 | v1.ResourceCPU: resource.MustParse("100m"), 125 | }, 126 | Requests: v1.ResourceList{ 127 | v1.ResourceMemory: resource.MustParse("50Mi"), 128 | v1.ResourceCPU: resource.MustParse("100m"), 129 | }, 130 | }, 131 | }) 132 | return podSpec 133 | } 134 | ) 135 | 136 | type awaiter struct { 137 | t *testing.T 138 | description string 139 | timeout time.Duration 140 | poll func() (retry bool, err error) 141 | } 142 | 143 | func (a *awaiter) withTimeout(timeout time.Duration) *awaiter { 144 | a.timeout = timeout 145 | return a 146 | } 147 | 148 | func (a *awaiter) withPoll(poll func() (retry bool, err error)) *awaiter { 149 | a.poll = poll 150 | return a 151 | } 152 | 153 | func newAwaiter(t *testing.T, description string) *awaiter { 154 | return &awaiter{ 155 | t: t, 156 | description: description, 157 | timeout: defaultWaitTimeout, 158 | } 159 | } 160 | 161 | func (a *awaiter) await() error { 162 | deadline := time.Now().Add(a.timeout) 163 | a.t.Logf("Waiting for %s until %s (UTC)...", a.description, deadline.Format("3:04PM")) 164 | for { 165 | retry, err := a.poll() 166 | if err != nil { 167 | a.t.Logf("%v", err) 168 | if retry && time.Now().Before(deadline) { 169 | time.Sleep(30 * time.Second) 170 | continue 171 | } 172 | return err 173 | } 174 | a.t.Logf("Finished waiting for %s", a.description) 175 | return nil 176 | } 177 | } 178 | 179 | func resourceCreated(t *testing.T, kind string, name string, k8sInterface interface{}) *awaiter { 180 | get := reflect.ValueOf(k8sInterface).MethodByName("Get") 181 | return newAwaiter(t, fmt.Sprintf("creation of %s %s", kind, name)).withPoll(func() (bool, error) { 182 | result := get.Call([]reflect.Value{ 183 | reflect.ValueOf(context.Background()), 184 | reflect.ValueOf(name), 185 | reflect.ValueOf(metav1.GetOptions{}), 186 | }) 187 | err := result[1].Interface() 188 | if err != nil { 189 | t.Logf("%v", err) 190 | return apiErrors.IsNotFound(err.(error)), err.(error) 191 | } 192 | return false, nil 193 | }) 194 | } 195 | 196 | func waitForEDS(t *testing.T, name string) (*zv1.ElasticsearchDataSet, error) { 197 | err := resourceCreated(t, "eds", name, edsInterface()).await() 198 | if err != nil { 199 | return nil, err 200 | } 201 | return edsInterface().Get(context.Background(), name, metav1.GetOptions{}) 202 | } 203 | 204 | func waitForStatefulSet(t *testing.T, name string) (*appsv1.StatefulSet, error) { 205 | err := resourceCreated(t, "sts", name, statefulSetInterface()).await() 206 | if err != nil { 207 | return nil, err 208 | } 209 | return statefulSetInterface().Get(context.Background(), name, metav1.GetOptions{}) 210 | } 211 | 212 | func waitForService(t *testing.T, name string) (*v1.Service, error) { 213 | err := resourceCreated(t, "service", name, serviceInterface()).await() 214 | if err != nil { 215 | return nil, err 216 | } 217 | return serviceInterface().Get(context.Background(), name, metav1.GetOptions{}) 218 | } 219 | 220 | type expectedStsStatus struct { 221 | replicas *int32 222 | readyReplicas *int32 223 | updatedReplicas *int32 224 | } 225 | 226 | func (expected expectedStsStatus) matches(sts *appsv1.StatefulSet) error { 227 | status := sts.Status 228 | if sts.Generation != sts.Status.ObservedGeneration { 229 | return fmt.Errorf("%s: observedGeneration %d != expected %d", sts.Name, status.ObservedGeneration, sts.Generation) 230 | } 231 | if expected.replicas != nil && status.Replicas != *expected.replicas { 232 | return fmt.Errorf("%s: replicas %d != expected %d", sts.Name, status.Replicas, *expected.replicas) 233 | } 234 | if expected.updatedReplicas != nil && status.UpdatedReplicas != *expected.updatedReplicas { 235 | return fmt.Errorf("%s: updatedReplicas %d != expected %d", sts.Name, status.UpdatedReplicas, *expected.updatedReplicas) 236 | } 237 | if expected.readyReplicas != nil && status.ReadyReplicas != *expected.readyReplicas { 238 | return fmt.Errorf("%s: readyReplicas %d != expected %d", sts.Name, status.ReadyReplicas, *expected.readyReplicas) 239 | } 240 | return nil 241 | } 242 | 243 | func waitForEDSCondition(t *testing.T, name string, conditions ...func(eds *zv1.ElasticsearchDataSet) error) error { 244 | return newAwaiter(t, fmt.Sprintf("eds %s to reach desired condition", name)).withPoll(func() (retry bool, err error) { 245 | eds, err := edsInterface().Get(context.Background(), name, metav1.GetOptions{}) 246 | if err != nil { 247 | return false, err 248 | } 249 | for _, condition := range conditions { 250 | err := condition(eds) 251 | if err != nil { 252 | return true, err 253 | } 254 | } 255 | return true, nil 256 | }).await() 257 | } 258 | 259 | func waitForSTSCondition(t *testing.T, stsName string, conditions ...func(sts *appsv1.StatefulSet) error) error { 260 | return newAwaiter(t, fmt.Sprintf("sts %s to reach desired condition", stsName)).withPoll(func() (retry bool, err error) { 261 | sts, err := statefulSetInterface().Get(context.Background(), stsName, metav1.GetOptions{}) 262 | if err != nil { 263 | return false, err 264 | } 265 | for _, condition := range conditions { 266 | err := condition(sts) 267 | if err != nil { 268 | return true, err 269 | } 270 | } 271 | return true, nil 272 | }).await() 273 | } 274 | 275 | func createEDS(name string, spec zv1.ElasticsearchDataSetSpec) error { 276 | myspec := spec.DeepCopy() 277 | for i, env := range myspec.Template.Spec.Containers[0].Env { 278 | if env.Name == "node.attr.group" { 279 | myspec.Template.Spec.Containers[0].Env[i].Value = name 280 | break 281 | } 282 | } 283 | eds := &zv1.ElasticsearchDataSet{ 284 | ObjectMeta: metav1.ObjectMeta{ 285 | Name: name, 286 | Namespace: namespace, 287 | Annotations: map[string]string{ 288 | "es-operator.zalando.org/operator": operatorId, 289 | }, 290 | }, 291 | Spec: *myspec, 292 | } 293 | _, err := edsInterface().Create(context.Background(), eds, metav1.CreateOptions{}) 294 | return err 295 | } 296 | 297 | func updateEDS(name string, eds *zv1.ElasticsearchDataSet) error { 298 | _, err := edsInterface().Update(context.Background(), eds, metav1.UpdateOptions{}) 299 | return err 300 | } 301 | 302 | func deleteEDS(name string) error { 303 | err := edsInterface().Delete(context.Background(), name, metav1.DeleteOptions{GracePeriodSeconds: pint64(10)}) 304 | return err 305 | } 306 | 307 | func pbool(b bool) *bool { 308 | return &b 309 | } 310 | 311 | func pint64(i int64) *int64 { 312 | return &i 313 | } 314 | 315 | func pint32(i int32) *int32 { 316 | return &i 317 | } 318 | -------------------------------------------------------------------------------- /cmd/e2e/upgrade_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestEDSUpgradingEDS(t *testing.T) { 10 | t.Parallel() 11 | edsName := "upgrade" 12 | edsSpec := testEDSCreate(t, edsName, "8.6.2", "es8-config") 13 | eds := verifyEDS(t, edsName, edsSpec, edsSpec.Replicas) 14 | // this could become a test for a major version upgrade in the future. 15 | eds.Spec.Template.Spec.Containers[0].Image = "docker.elastic.co/elasticsearch/elasticsearch:8.6.0" 16 | 17 | var err error 18 | eds, err = waitForEDS(t, edsName) 19 | require.NoError(t, err) 20 | err = updateEDS(edsName, eds) 21 | require.NoError(t, err) 22 | 23 | verifyEDS(t, edsName, eds.Spec, eds.Spec.Replicas) 24 | err = deleteEDS(edsName) 25 | require.NoError(t, err) 26 | } 27 | -------------------------------------------------------------------------------- /delivery.yaml: -------------------------------------------------------------------------------- 1 | version: "2017-09-20" 2 | pipeline: 3 | - id: build 4 | vm_config: 5 | type: linux 6 | image: "cdp-runtime/go" 7 | cache: 8 | paths: 9 | - /go/pkg/mod # pkg cache for Go modules 10 | - ~/.cache/go-build # Go build cache 11 | type: script 12 | commands: 13 | - desc: test 14 | cmd: | 15 | make test 16 | - desc: build 17 | cmd: | 18 | make build.docker 19 | - desc: push 20 | cmd: | 21 | if [[ $CDP_TARGET_BRANCH == master && ! $CDP_PULL_REQUEST_NUMBER ]]; then 22 | IMAGE=registry-write.opensource.zalan.do/poirot/es-operator 23 | VERSION=$(git describe --tags --always --dirty) 24 | else 25 | IMAGE=registry-write.opensource.zalan.do/poirot/es-operator-test 26 | VERSION=$CDP_BUILD_VERSION 27 | fi 28 | IMAGE=$IMAGE VERSION=$VERSION make build.push 29 | - id: e2e 30 | type: process 31 | desc: "E2E Tests" 32 | target: search-test 33 | process: microservice_standard_test 34 | debug_mode: true 35 | when: 36 | event: pull_request 37 | config: 38 | apply_manifests: 39 | env: 40 | - name: APPLICATION 41 | value: es-operator 42 | - name: DEPLOYMENT_PATH 43 | value: deploy/e2e 44 | - name: IMAGE 45 | value: "registry.opensource.zalan.do/poirot/es-operator-test:#{CDP_BUILD_VERSION}" 46 | - name: OPERATOR_ID 47 | value: "#{CDP_BUILD_VERSION}" 48 | end2end_tests: 49 | metadata: 50 | name: e2e 51 | spec: 52 | serviceAccountName: es-operator 53 | restartPolicy: Never 54 | containers: 55 | - name: e2e 56 | image: "registry.opensource.zalan.do/poirot/es-operator-test-e2e:#{CDP_BUILD_VERSION}" 57 | command: ["/e2e"] 58 | env: 59 | - name: "OPERATOR_ID" 60 | value: "#{CDP_BUILD_VERSION}" 61 | - name: "E2E_NAMESPACE" 62 | valueFrom: 63 | fieldRef: 64 | fieldPath: metadata.namespace 65 | -------------------------------------------------------------------------------- /deploy/e2e/apply/es-operator.yaml: -------------------------------------------------------------------------------- 1 | ../../../manifests/es-operator.yaml -------------------------------------------------------------------------------- /deploy/e2e/apply/es7-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: es7-config 5 | data: 6 | elasticsearch.yml: | 7 | cluster.name: es7-operator-e2e 8 | network.host: "0.0.0.0" 9 | bootstrap.memory_lock: false 10 | discovery.seed_hosts: [es7-master] 11 | cluster.initial_master_nodes: [es7-master-0] 12 | xpack.security.enabled: false 13 | xpack.security.transport.ssl.enabled: false 14 | xpack.security.http.ssl.enabled: false 15 | -------------------------------------------------------------------------------- /deploy/e2e/apply/es7-master-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: es7-master 5 | spec: 6 | clusterIP: None 7 | publishNotReadyAddresses: true 8 | selector: 9 | application: es7 10 | role: master 11 | ports: 12 | - name: transport 13 | port: 9300 14 | targetPort: 9300 15 | - name: http 16 | port: 9200 17 | targetPort: 9200 18 | -------------------------------------------------------------------------------- /deploy/e2e/apply/es7-master.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: es7-master 5 | spec: 6 | replicas: 1 7 | serviceName: es7-master 8 | selector: 9 | matchLabels: 10 | application: es7 11 | role: master 12 | template: 13 | metadata: 14 | labels: 15 | application: es7 16 | role: master 17 | spec: 18 | securityContext: 19 | fsGroup: 1000 20 | runAsUser: 1000 21 | containers: 22 | - name: elasticsearch 23 | resources: 24 | requests: 25 | memory: 720Mi 26 | cpu: 100m 27 | limits: 28 | memory: 720Mi 29 | cpu: 100m 30 | image: "docker.elastic.co/elasticsearch/elasticsearch:7.17.2" 31 | env: 32 | - name: "node.name" 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.name 36 | - name: "ES_JAVA_OPTS" 37 | value: "-Xmx360m -Xms360m" 38 | - name: node.roles 39 | value: "master,data" 40 | readinessProbe: 41 | initialDelaySeconds: 10 42 | httpGet: 43 | scheme: HTTP 44 | path: /_cluster/health?local=true 45 | port: 9200 46 | ports: 47 | - containerPort: 9200 48 | name: es-http 49 | - containerPort: 9300 50 | name: es-transport 51 | volumeMounts: 52 | - name: data 53 | mountPath: /usr/share/elasticsearch/data 54 | - name: config 55 | mountPath: /usr/share/elasticsearch/config/elasticsearch.yml 56 | subPath: elasticsearch.yml 57 | volumes: 58 | - name: data 59 | emptyDir: {} 60 | - name: config 61 | configMap: 62 | name: es7-config 63 | items: 64 | - key: elasticsearch.yml 65 | path: elasticsearch.yml 66 | -------------------------------------------------------------------------------- /deploy/e2e/apply/es8-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: es8-config 5 | data: 6 | elasticsearch.yml: | 7 | cluster.name: es8-operator-e2e 8 | network.host: "0.0.0.0" 9 | bootstrap.memory_lock: false 10 | discovery.seed_hosts: [es8-master] 11 | cluster.initial_master_nodes: [es8-master-0] 12 | xpack.security.enabled: false 13 | xpack.security.transport.ssl.enabled: false 14 | xpack.security.http.ssl.enabled: false 15 | -------------------------------------------------------------------------------- /deploy/e2e/apply/es8-master-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: es8-master 5 | spec: 6 | clusterIP: None 7 | publishNotReadyAddresses: true 8 | selector: 9 | application: es8 10 | role: master 11 | ports: 12 | - name: transport 13 | port: 9300 14 | targetPort: 9300 15 | - name: http 16 | port: 9200 17 | targetPort: 9200 18 | -------------------------------------------------------------------------------- /deploy/e2e/apply/es8-master.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: es8-master 5 | spec: 6 | replicas: 1 7 | serviceName: es8-master 8 | podManagementPolicy: Parallel 9 | selector: 10 | matchLabels: 11 | application: es8 12 | role: master 13 | template: 14 | metadata: 15 | labels: 16 | application: es8 17 | role: master 18 | spec: 19 | securityContext: 20 | runAsUser: 1000 21 | runAsGroup: 0 22 | fsGroup: 0 23 | containers: 24 | - name: elasticsearch 25 | resources: 26 | requests: 27 | memory: 1Gi 28 | cpu: 100m 29 | limits: 30 | memory: 1Gi 31 | cpu: 100m 32 | image: "docker.elastic.co/elasticsearch/elasticsearch:8.6.2" 33 | env: 34 | - name: "node.name" 35 | valueFrom: 36 | fieldRef: 37 | fieldPath: metadata.name 38 | - name: "ES_JAVA_OPTS" 39 | value: "-Xms360m -Xmx360m" 40 | - name: node.roles 41 | value: "master,data" 42 | readinessProbe: 43 | initialDelaySeconds: 10 44 | httpGet: 45 | scheme: HTTP 46 | path: /_cluster/health?local=true 47 | port: 9200 48 | ports: 49 | - containerPort: 9200 50 | name: es-http 51 | - containerPort: 9300 52 | name: es-transport 53 | volumeMounts: 54 | - name: data 55 | mountPath: /usr/share/elasticsearch/data 56 | - name: config 57 | mountPath: /usr/share/elasticsearch/config/elasticsearch.yml 58 | subPath: elasticsearch.yml 59 | volumes: 60 | - name: data 61 | emptyDir: {} 62 | - name: config 63 | configMap: 64 | name: es8-config 65 | items: 66 | - key: elasticsearch.yml 67 | path: elasticsearch.yml 68 | -------------------------------------------------------------------------------- /deploy/e2e/apply/rbac.yaml: -------------------------------------------------------------------------------- 1 | ../../../manifests/rbac.yaml -------------------------------------------------------------------------------- /docs/EMERGENCY.md: -------------------------------------------------------------------------------- 1 | # Emergency mode 2 | 3 | ES-Operator is not able to operate the cluster in a non-green [health status](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html#cluster-health-api-desc). Therefore, manual intervention might be required to recover the Elasticsearch cluster and re-enable ES-Operator to manage it. 4 | 5 | ## Stopping the bleeding 6 | 7 | The first and foremost goal should be to get your cluster back into green health status. Opster lists a number of reasons for [cluster yellow](https://opster.com/guides/elasticsearch/operations/elasticsearch-yellow-status/) or [cluster red](https://opster.com/guides/elasticsearch/operations/elasticsearch-red-status/#:~:text=Overview,not%20right%20with%20the%20cluster), and how to recover from that. 8 | 9 | One option to solve the health status problem would be to use the [cluster reroute API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html#cluster-reroute-api-request-body) to temporarily allocate an empty primary (`allocate_empty_primary`) until you have recovered your data. Other options include deleting the broken index or [recovering the index from a snapshot](https://www.elastic.co/guide/en/elasticsearch/reference/master/snapshot-restore-apis.html). 10 | 11 | If, for whatever reason, this is not feasible in a timely manner, you will need to switch from auto-pilot to manual steering. For this, please do the following: 12 | 13 | ## Manual Recovery Steps 14 | 15 | * Stop the ES-Operator, e.g. 16 | 17 | `kubectl scale --replicas=0 deployment/es-operator -n ${namespace}` 18 | 19 | * If necessary, change the replicas in the stateful set (STS) attached to your EDS (it has the same name as the EDS), e.g. 20 | 21 | * `kubectl edit sts ${eds-name} -n ${namespace}` 22 | * set `spec.replicas` to the desired number 23 | 24 | **WARNING! Once the ES-Operator is started again, it will rigorously overwrite this setting, potentially leading to data loss by terminating pods without draining!** 25 | 26 | * Fix the cluster issue, restoring its green health status 27 | 28 | * Adjust your Elasticsearch data set (EDS) configuration, e.g. 29 | * `kubectl edit eds ${eds-name} -n ${namespace}` 30 | * set `spec.scaling.enabled: false` 31 | * set `spec.replicas` to the current number of replicas in the STS 32 | 33 | * Start the ES-Operator, e.g. 34 | 35 | `kubectl scale --replicas=1 deployment/es-operator -n ${namespace}` 36 | 37 | * Re-enable auto-scaling, e.g. 38 | * `kubectl edit eds ${eds-name} -n ${namespace}` 39 | * set `spec.scaling.enabled: true` 40 | -------------------------------------------------------------------------------- /docs/GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This tutorial takes you step by step through deploying an Elasticsearch cluster managed by the ES Operator. 4 | 5 | ## Prerequisites 6 | 7 | Have a Kubernetes cluster at hand (e.g. [kind](https://github.com/kubernetes-sigs/kind) or [minikube](https://github.com/kubernetes/minikube/)), and `kubectl` configured to point to it. 8 | 9 | ## Step 1 - Set up Roles 10 | 11 | The ES Operator needs special permissions to access Kubernetes APIs, and Elasticsearch needs privileged access to increase the operating system limits for memory-mapped files. 12 | 13 | Therefore as the first step we deploy a serviceAccount `operator` with the necessary RBAC roles attached. 14 | 15 | ``` 16 | kubectl apply -f docs/cluster-roles.yaml 17 | ``` 18 | 19 | ## Step 2 - Register Custom Resource Definitions 20 | 21 | The ES Operator manages two custom resources. These need to be registered in your cluster. 22 | 23 | ``` 24 | kubectl apply -f docs/zalando.org_elasticsearchdatasets.yaml 25 | kubectl apply -f docs/zalando.org_elasticsearchmetricsets.yaml 26 | ``` 27 | 28 | 29 | ## Step 3 - Deploy ES Operator 30 | 31 | Next, we'll deploy our operator. It will be created from a deployment manifest in the namespace `es-operator-demo`, and pull the latest image. 32 | 33 | ``` 34 | kubectl apply -f docs/es-operator.yaml 35 | ``` 36 | 37 | You can check if it was successfully launched: 38 | 39 | ``` 40 | kubectl -n es-operator-demo get pods 41 | ``` 42 | 43 | ## Step 4 - Bootstrap Your Elasticsearch Cluster 44 | 45 | The Elasticsearch will be boot-strapped from a set of master nodes. For the purpose of this demo, a single master is sufficient. For production a set of three masters is recommended. 46 | 47 | ``` 48 | kubectl apply -f docs/elasticsearch-cluster.yaml 49 | ``` 50 | 51 | The manifest also creates services for the transport and HTTP protocols. If you tunnel to port 9200 on the master, you should be able to communicate with your Elasticsearch cluster. 52 | 53 | ``` 54 | MASTER_POD=$(kubectl -n es-operator-demo get pods -l application=elasticsearch,role=master -o custom-columns=:metadata.name --no-headers | head -n 1) 55 | kubectl -n es-operator-demo port-forward $MASTER_POD 9200 56 | ``` 57 | 58 | ## Step 5 - Add Elasticsearch Data Sets 59 | 60 | Finally, let's add data nodes. For the purpose of this demo we have a simple stack will launch one data node, and has auto-scaling features turned off. 61 | 62 | ``` 63 | kubectl apply -f docs/elasticsearchdataset-simple.yaml 64 | ``` 65 | 66 | To check the results, first look for the custom resources. 67 | 68 | ``` 69 | kubectl -n es-operator-demo get eds 70 | ``` 71 | 72 | The ES Operator creates a StatefulSet, which will spawn the Pod. This can take a few minutes depending on your network and cluster performance. 73 | 74 | ``` 75 | kubectl -n es-operator-demo get sts 76 | kubectl -n es-operator-demo get pods 77 | ``` 78 | 79 | ## Step 6: Index Creation 80 | 81 | We differentiated the stacks using an Elasticsearch node tag called `group`. It is advised to use this tag to bind indices with the same scaling requirements to nodes with the same `group` tag, by using the shard allocation setting like this: 82 | 83 | ``` 84 | curl -XPUT localhost:9200/demo-index -HContent-type:application/json \ 85 | -d '{"settings": {"index": { "number_of_shards":5, "number_of_replicas":2, "routing.allocation.include.group": "group2"}}}' 86 | ``` 87 | 88 | ## Advanced Step: Auto-Scaling 89 | 90 | Once you have gathered some experience in how the ES Operator handles your data nodes, you can start experimenting with auto-scaling features. The README.md offers some examples of different scaling scenarios, or look at the manifests of our [demo at the microXchg 2019](https://github.com/otrosien/microxchg19-demo). 91 | 92 | ## Advanced Step: Production-Readiness Features 93 | 94 | If you understood how auto-scaling works, you can tackle the next steps towards production readiness. 95 | 96 | The [Official Helm Charts](https://github.com/elastic/helm-charts/blob/master/elasticsearch/templates/statefulset.yaml) from Elasticsearch offer a few interesting features you may also want to integrate before going to production: 97 | 98 | * Different persistence options 99 | * Host-based anti-affinity 100 | * Improved script-based readiness checks 101 | 102 | We haven't seen it in their helm, but if you want high availability of your cluster, use the [allocation awareness](https://www.elastic.co/guide/en/elasticsearch/reference/current/allocation-awareness.html) features to ensure spread of the data across different locations, zones or racks. 103 | 104 | ## Advanced Step: Different Elasticsearch Clusters 105 | 106 | Of course you can decide if the ES Operator should manage one big cluster, or you want to run multiple smaller clusters. This is totally possible. Just make sure they have different cluster names and use different hostnames for cluster discovery through the Kubernetes service. 107 | -------------------------------------------------------------------------------- /docs/RELEASING.md: -------------------------------------------------------------------------------- 1 | # How to release 2 | 3 | ES-Operator is released as docker image to Zalando's public Docker repository `registry.opensource.zalan.do`. Every merge to master generates a new Docker image, you could go with `registry.opensource.zalan.do/poirot/es-operator:latest` for the latest and greatest version. 4 | 5 | When introducing bigger changes, we create tags and write release notes. The release process itself is triggered via the Zalando-internal CI/CD tool. 6 | 7 | ## Steps 8 | 9 | 1. [Draft a new release](https://github.com/zalando-incubator/es-operator/releases/new) in Github, creating a new git tag of type `vX.Y.Z`. Document all changes in this release and link the related issues and PRs accordingly. 10 | 2. Re-trigger the internal CI/CD pipeline, which publishes of the docker image with the version tag `vX.Y.Z` attached. 11 | 12 | -------------------------------------------------------------------------------- /docs/cluster-roles.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: es-operator 5 | rules: 6 | - apiGroups: 7 | - "zalando.org" 8 | resources: 9 | - elasticsearchdatasets 10 | - elasticsearchdatasets/status 11 | - elasticsearchmetricsets 12 | verbs: 13 | - get 14 | - list 15 | - watch 16 | - update 17 | - patch 18 | - apiGroups: 19 | - "zalando.org" 20 | resources: 21 | - elasticsearchmetricsets 22 | verbs: 23 | - create 24 | - apiGroups: 25 | - "" 26 | resources: 27 | - services 28 | verbs: 29 | - get 30 | - list 31 | - watch 32 | - create 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - pods 37 | verbs: 38 | - get 39 | - list 40 | - update 41 | - patch 42 | - delete 43 | - apiGroups: 44 | - "apps" 45 | resources: 46 | - statefulsets 47 | verbs: 48 | - get 49 | - list 50 | - watch 51 | - create 52 | - update 53 | - patch 54 | - delete 55 | - apiGroups: 56 | - policy 57 | resources: 58 | - poddisruptionbudgets 59 | verbs: 60 | - get 61 | - list 62 | - watch 63 | - create 64 | - apiGroups: 65 | - "" 66 | resources: 67 | - events 68 | verbs: 69 | - create 70 | - patch 71 | - update 72 | - apiGroups: 73 | - "" 74 | resources: 75 | - nodes 76 | verbs: 77 | - get 78 | - list 79 | - watch 80 | - apiGroups: 81 | - metrics.k8s.io 82 | resources: 83 | - pods 84 | verbs: 85 | - get 86 | - list 87 | - watch 88 | --- 89 | apiVersion: rbac.authorization.k8s.io/v1 90 | kind: ClusterRoleBinding 91 | metadata: 92 | name: es-operator 93 | roleRef: 94 | apiGroup: rbac.authorization.k8s.io 95 | kind: ClusterRole 96 | name: es-operator 97 | subjects: 98 | - kind: ServiceAccount 99 | name: es-operator 100 | namespace: es-operator-demo 101 | -------------------------------------------------------------------------------- /docs/elasticsearch-cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: es-config 5 | namespace: es-operator-demo 6 | data: 7 | elasticsearch.yml: | 8 | cluster.name: es-cluster 9 | network.host: "0.0.0.0" 10 | bootstrap.memory_lock: false 11 | discovery.seed_hosts: [es-master] 12 | cluster.initial_master_nodes: [es-master-0] 13 | --- 14 | apiVersion: apps/v1 15 | kind: StatefulSet 16 | metadata: 17 | name: es-master 18 | namespace: es-operator-demo 19 | spec: 20 | replicas: 1 21 | serviceName: es-master 22 | selector: 23 | matchLabels: 24 | application: elasticsearch 25 | role: master 26 | template: 27 | metadata: 28 | labels: 29 | application: elasticsearch 30 | role: master 31 | spec: 32 | securityContext: 33 | fsGroup: 1000 34 | initContainers: 35 | - name: init-sysctl 36 | image: busybox 37 | command: 38 | - sysctl 39 | - -w 40 | - vm.max_map_count=262144 41 | resources: 42 | requests: 43 | memory: 50Mi 44 | cpu: 50m 45 | limits: 46 | memory: 50Mi 47 | cpu: 50m 48 | securityContext: 49 | privileged: true 50 | containers: 51 | - name: elasticsearch 52 | resources: 53 | requests: 54 | memory: 600Mi 55 | cpu: 100m 56 | limits: 57 | memory: 600Mi 58 | image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2" 59 | env: 60 | - name: "node.name" 61 | valueFrom: 62 | fieldRef: 63 | fieldPath: metadata.name 64 | - name: "ES_JAVA_OPTS" 65 | value: "-Xmx400m -Xms400m" 66 | - name: node.master 67 | value: "true" 68 | - name: node.data 69 | value: "false" 70 | readinessProbe: 71 | httpGet: 72 | scheme: HTTP 73 | path: /_cluster/health?local=true 74 | port: 9200 75 | initialDelaySeconds: 20 76 | ports: 77 | - containerPort: 9200 78 | name: es-http 79 | - containerPort: 9300 80 | name: es-transport 81 | volumeMounts: 82 | - name: es-data 83 | mountPath: /usr/share/elasticsearch/data 84 | - name: elasticsearch-config 85 | mountPath: /usr/share/elasticsearch/config/elasticsearch.yml 86 | subPath: elasticsearch.yml 87 | volumes: 88 | - name: es-data 89 | emptyDir: {} 90 | - name: elasticsearch-config 91 | configMap: 92 | name: es-config 93 | items: 94 | - key: elasticsearch.yml 95 | path: elasticsearch.yml 96 | --- 97 | apiVersion: v1 98 | kind: Service 99 | metadata: 100 | name: es-http 101 | namespace: es-operator-demo 102 | spec: 103 | selector: 104 | application: elasticsearch 105 | ports: 106 | - name: http 107 | port: 9200 108 | targetPort: 9200 109 | --- 110 | apiVersion: v1 111 | kind: Service 112 | metadata: 113 | name: es-master 114 | namespace: es-operator-demo 115 | spec: 116 | clusterIP: None 117 | publishNotReadyAddresses: true 118 | selector: 119 | application: elasticsearch 120 | role: master 121 | ports: 122 | - name: transport 123 | port: 9300 124 | -------------------------------------------------------------------------------- /docs/elasticsearchdataset-simple.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: zalando.org/v1 2 | kind: ElasticsearchDataSet 3 | metadata: 4 | labels: 5 | application: elasticsearch 6 | role: data 7 | group: simple 8 | name: es-data-simple 9 | namespace: es-operator-demo 10 | spec: 11 | replicas: 1 12 | scaling: 13 | enabled: false 14 | template: 15 | metadata: 16 | labels: 17 | application: elasticsearch 18 | role: data 19 | group: simple 20 | spec: 21 | securityContext: 22 | fsGroup: 1000 23 | containers: 24 | - name: elasticsearch 25 | env: 26 | - name: "node.name" 27 | valueFrom: 28 | fieldRef: 29 | fieldPath: metadata.name 30 | - name: "node.attr.group" 31 | value: "simple" 32 | - name: "node.master" 33 | value: "false" 34 | - name: "node.data" 35 | value: "true" 36 | - name: "ES_JAVA_OPTS" 37 | value: "-Xmx500m -Xms500m" 38 | image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.0.0" 39 | ports: 40 | - containerPort: 9300 41 | name: transport 42 | readinessProbe: 43 | httpGet: 44 | path: /_cat/master 45 | port: 9200 46 | timeoutSeconds: 10 47 | resources: 48 | limits: 49 | cpu: 100m 50 | memory: 800Mi 51 | requests: 52 | cpu: 100m 53 | memory: 800Mi 54 | volumeMounts: 55 | - mountPath: /usr/share/elasticsearch/data 56 | name: data 57 | - name: elasticsearch-config 58 | mountPath: /usr/share/elasticsearch/config/elasticsearch.yml 59 | subPath: elasticsearch.yml 60 | initContainers: 61 | - command: 62 | - sysctl 63 | - -w 64 | - vm.max_map_count=262144 65 | image: busybox:1.30 66 | name: init-sysctl 67 | resources: 68 | limits: 69 | cpu: 50m 70 | memory: 50Mi 71 | requests: 72 | cpu: 50m 73 | memory: 50Mi 74 | securityContext: 75 | runAsUser: 0 76 | privileged: true 77 | volumes: 78 | - name: data 79 | emptyDir: {} 80 | - name: elasticsearch-config 81 | configMap: 82 | name: es-config 83 | items: 84 | - key: elasticsearch.yml 85 | path: elasticsearch.yml 86 | -------------------------------------------------------------------------------- /docs/es-operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: es-operator-demo 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: es-operator 10 | namespace: es-operator-demo 11 | --- 12 | apiVersion: apps/v1 13 | kind: Deployment 14 | metadata: 15 | name: es-operator 16 | namespace: es-operator-demo 17 | labels: 18 | application: es-operator 19 | version: latest 20 | spec: 21 | replicas: 1 22 | strategy: 23 | type: Recreate 24 | selector: 25 | matchLabels: 26 | application: es-operator 27 | template: 28 | metadata: 29 | labels: 30 | application: es-operator 31 | version: latest 32 | spec: 33 | serviceAccountName: es-operator 34 | containers: 35 | - name: es-operator 36 | image: registry.opensource.zalan.do/poirot/es-operator:latest 37 | resources: 38 | limits: 39 | cpu: 20m 40 | memory: 100Mi 41 | requests: 42 | cpu: 20m 43 | memory: 100Mi 44 | -------------------------------------------------------------------------------- /docs/migrating-from-transient-settings.md: -------------------------------------------------------------------------------- 1 | # Migrating away from Transient Cluster Settings 2 | 3 | The [transient cluster settings](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/settings.html#cluster-setting-types) 4 | are 5 | being [deprecated](https://www.elastic.co/guide/en/elasticsearch/reference/7.16/migrating-7.16.html#breaking_716_settings_deprecations) 6 | from ES 7.16.0. The es-operator was relying on the transient cluster settings for operating on the cluster because 7 | transient settings have the highest priority. The es-operator controlled mainly the cluster rebalance and the exclude 8 | ips list for the cluster scaling actions. Moving forward the es-operator will now exclusively operate only edit the 9 | persistent cluster settings. Some teams might still rely on using the transient settings for manual cluster operations 10 | which can inadvertently cause issues with the es-operator updates to the cluster settings causing an inconsistent 11 | cluster state. To avoid this the es-operator is also copying any existing non empty transient settings related to the 12 | cluster rebalance and the exclude ips list into the persistent settings before updating the new values for the 13 | persistent settings. To avoid cluster inconsistencies with the new es-operator, we recommend the below migrations steps 14 | before deploying the new es-operator. 15 | 16 | 1. Follow the 17 | official [Transient settings migration guide](https://www.elastic.co/guide/en/elasticsearch/reference/8.1/transient-settings-migration-guide.html). 18 | 2. Update any custom scripts that are still operating on transient settings. 19 | 3. Deploy the new es-operator. 20 | -------------------------------------------------------------------------------- /docs/zalando.org_elasticsearchmetricsets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.16.1 6 | name: elasticsearchmetricsets.zalando.org 7 | spec: 8 | group: zalando.org 9 | names: 10 | kind: ElasticsearchMetricSet 11 | listKind: ElasticsearchMetricSetList 12 | plural: elasticsearchmetricsets 13 | singular: elasticsearchmetricset 14 | scope: Namespaced 15 | versions: 16 | - name: v1 17 | schema: 18 | openAPIV3Schema: 19 | description: |- 20 | ElasticsearchMetricSet is the metrics holding section of the ElasticsearchDataSet 21 | resource. 22 | properties: 23 | apiVersion: 24 | description: |- 25 | APIVersion defines the versioned schema of this representation of an object. 26 | Servers should convert recognized schemas to the latest internal value, and 27 | may reject unrecognized values. 28 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 29 | type: string 30 | kind: 31 | description: |- 32 | Kind is a string value representing the REST resource this object represents. 33 | Servers may infer this from the endpoint the client submits requests to. 34 | Cannot be updated. 35 | In CamelCase. 36 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 37 | type: string 38 | metadata: 39 | type: object 40 | metrics: 41 | items: 42 | description: |- 43 | ElasticsearchMetric is the single metric sample of the ElasticsearchDataSet 44 | resource. 45 | properties: 46 | timestamp: 47 | format: date-time 48 | type: string 49 | value: 50 | format: int32 51 | type: integer 52 | required: 53 | - timestamp 54 | - value 55 | type: object 56 | type: array 57 | required: 58 | - metrics 59 | type: object 60 | served: true 61 | storage: true 62 | -------------------------------------------------------------------------------- /e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple script for running the e2e test from your local development machine 4 | # against "some" cluster. 5 | # You need to run `kubectl proxy` in a different terminal before running the 6 | # script. 7 | 8 | NAMESPACE="${NAMESPACE:-"es-operator-e2e-$(date +%s)"}" 9 | IMAGE="${IMAGE:-"registry.opensource.zalan.do/poirot/es-operator:latest"}" 10 | SERVICE_ENDPOINT_ES8="${SERVICE_ENDPOINT_ES8:-"http://127.0.0.1:8001/api/v1/namespaces/$NAMESPACE/services/es8-master:9200/proxy"}" 11 | SERVICE_ENDPOINT_ES7="${SERVICE_ENDPOINT_ES7:-"http://127.0.0.1:8001/api/v1/namespaces/$NAMESPACE/services/es7-master:9200/proxy"}" 12 | OPERATOR_ID="${OPERATOR_ID:-"e2e-tests"}" 13 | 14 | # create namespace and resources 15 | kubectl create ns "$NAMESPACE" 16 | kubectl --namespace "$NAMESPACE" apply -f cmd/e2e/account_cdp.yaml 17 | kubectl --namespace "$NAMESPACE" apply -f deploy/e2e/apply/es8-master.yaml 18 | kubectl --namespace "$NAMESPACE" apply -f deploy/e2e/apply/es8-config.yaml 19 | kubectl --namespace "$NAMESPACE" apply -f deploy/e2e/apply/es8-master-service.yaml 20 | kubectl --namespace "$NAMESPACE" apply -f deploy/e2e/apply/es7-master.yaml 21 | kubectl --namespace "$NAMESPACE" apply -f deploy/e2e/apply/es7-config.yaml 22 | kubectl --namespace "$NAMESPACE" apply -f deploy/e2e/apply/es7-master-service.yaml 23 | sed -e "s#{{{NAMESPACE}}}#$NAMESPACE#" \ 24 | -e "s#{{{IMAGE}}}#$IMAGE#" \ 25 | -e "s#{{{OPERATOR_ID}}}#$OPERATOR_ID#" < manifests/es-operator.yaml \ 26 | | kubectl --namespace "$NAMESPACE" apply -f - 27 | 28 | # run e2e tests 29 | ES_SERVICE_ENDPOINT_ES8=$SERVICE_ENDPOINT_ES8 \ 30 | ES_SERVICE_ENDPOINT_ES7=$SERVICE_ENDPOINT_ES7 \ 31 | E2E_NAMESPACE="$NAMESPACE" \ 32 | OPERATOR_ID="$OPERATOR_ID" \ 33 | KUBECONFIG=~/.kube/config go test -v -parallel 64 ./cmd/e2e/... 34 | 35 | kubectl delete ns "$NAMESPACE" 36 | -------------------------------------------------------------------------------- /elasticsearchdataset-vct.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: zalando.org/v1 2 | kind: ElasticsearchDataSet 3 | metadata: 4 | labels: 5 | application: elasticsearch 6 | role: data 7 | group: simple 8 | name: es-data-simple 9 | namespace: es-operator-demo 10 | spec: 11 | #replicas: 5 12 | volumeClaimTemplates: 13 | - metadata: 14 | annotations: 15 | volume.beta.kubernetes.io/storage-class: px-es-data-sc 16 | name: data 17 | spec: 18 | storageClassName: px-es-data-sc 19 | accessModes: 20 | - ReadWriteOnce 21 | resources: 22 | requests: 23 | storage: 10Gi 24 | scaling: 25 | enabled: true 26 | minReplicas: 3 27 | maxReplicas: 99 28 | minIndexReplicas: 2 29 | maxIndexReplicas: 3 30 | minShardsPerNode: 2 31 | maxShardsPerNode: 6 32 | scaleUpCPUBoundary: 50 33 | scaleUpThresholdDurationSeconds: 30 34 | scaleUpCooldownSeconds: 30 35 | scaleDownCPUBoundary: 25 36 | scaleDownThresholdDurationSeconds: 30 37 | scaleDownCooldownSeconds: 30 38 | diskUsagePercentScaledownWatermark: 80 39 | template: 40 | metadata: 41 | labels: 42 | application: elasticsearch 43 | role: data 44 | group: simple 45 | spec: 46 | securityContext: 47 | fsGroup: 1000 48 | containers: 49 | - name: elasticsearch 50 | env: 51 | - name: "node.name" 52 | valueFrom: 53 | fieldRef: 54 | fieldPath: metadata.name 55 | - name: "node.attr.group" 56 | value: "simple" 57 | - name: "node.master" 58 | value: "false" 59 | - name: "node.data" 60 | value: "true" 61 | - name: "ES_JAVA_OPTS" 62 | value: "-Xmx500m -Xms500m" 63 | image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.0.0" 64 | ports: 65 | - containerPort: 9300 66 | name: transport 67 | readinessProbe: 68 | httpGet: 69 | path: /_cat/master 70 | port: 9200 71 | timeoutSeconds: 10 72 | resources: 73 | limits: 74 | cpu: 1500m 75 | memory: 800Mi 76 | requests: 77 | memory: 800Mi 78 | volumeMounts: 79 | - mountPath: /usr/share/elasticsearch/data 80 | name: data 81 | - name: elasticsearch-config 82 | mountPath: /usr/share/elasticsearch/config/elasticsearch.yml 83 | subPath: elasticsearch.yml 84 | 85 | serviceAccountName: operator 86 | volumes: 87 | - name: elasticsearch-config 88 | configMap: 89 | name: es-config 90 | items: 91 | - key: elasticsearch.yml 92 | path: elasticsearch.yml 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zalando-incubator/es-operator 2 | 3 | require ( 4 | github.com/alecthomas/kingpin/v2 v2.4.0 5 | github.com/cenk/backoff v2.2.1+incompatible 6 | github.com/go-resty/resty/v2 v2.15.3 7 | github.com/jarcoal/httpmock v1.3.1 8 | github.com/prometheus/client_golang v1.20.4 9 | github.com/sirupsen/logrus v1.9.3 10 | github.com/stretchr/testify v1.9.0 11 | k8s.io/api v0.31.1 12 | k8s.io/apimachinery v0.31.1 13 | k8s.io/client-go v0.31.1 14 | k8s.io/code-generator v0.31.1 15 | k8s.io/metrics v0.31.1 16 | sigs.k8s.io/controller-tools v0.16.1 17 | sigs.k8s.io/yaml v1.4.0 18 | ) 19 | 20 | require ( 21 | github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 25 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 26 | github.com/fatih/color v1.17.0 // indirect 27 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 28 | github.com/go-logr/logr v1.4.2 // indirect 29 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 30 | github.com/go-openapi/jsonreference v0.20.2 // indirect 31 | github.com/go-openapi/swag v0.22.4 // indirect 32 | github.com/gobuffalo/flect v1.0.2 // indirect 33 | github.com/gogo/protobuf v1.3.2 // indirect 34 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 35 | github.com/golang/protobuf v1.5.4 // indirect 36 | github.com/google/gnostic-models v0.6.8 // indirect 37 | github.com/google/go-cmp v0.6.0 // indirect 38 | github.com/google/gofuzz v1.2.0 // indirect 39 | github.com/google/uuid v1.6.0 // indirect 40 | github.com/imdario/mergo v0.3.7 // indirect 41 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 42 | github.com/josharian/intern v1.0.0 // indirect 43 | github.com/json-iterator/go v1.1.12 // indirect 44 | github.com/klauspost/compress v1.17.9 // indirect 45 | github.com/mailru/easyjson v0.7.7 // indirect 46 | github.com/mattn/go-colorable v0.1.13 // indirect 47 | github.com/mattn/go-isatty v0.0.20 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 53 | github.com/prometheus/client_model v0.6.1 // indirect 54 | github.com/prometheus/common v0.55.0 // indirect 55 | github.com/prometheus/procfs v0.15.1 // indirect 56 | github.com/spf13/cobra v1.8.1 // indirect 57 | github.com/spf13/pflag v1.0.5 // indirect 58 | github.com/x448/float16 v0.8.4 // indirect 59 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 60 | golang.org/x/mod v0.20.0 // indirect 61 | golang.org/x/net v0.29.0 // indirect 62 | golang.org/x/oauth2 v0.21.0 // indirect 63 | golang.org/x/sync v0.8.0 // indirect 64 | golang.org/x/sys v0.25.0 // indirect 65 | golang.org/x/term v0.24.0 // indirect 66 | golang.org/x/text v0.18.0 // indirect 67 | golang.org/x/time v0.6.0 // indirect 68 | golang.org/x/tools v0.24.0 // indirect 69 | google.golang.org/protobuf v1.34.2 // indirect 70 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 71 | gopkg.in/inf.v0 v0.9.1 // indirect 72 | gopkg.in/yaml.v2 v2.4.0 // indirect 73 | gopkg.in/yaml.v3 v3.0.1 // indirect 74 | k8s.io/apiextensions-apiserver v0.31.0 // indirect 75 | k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect 76 | k8s.io/klog/v2 v2.130.1 // indirect 77 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 78 | k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect 79 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 80 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 81 | ) 82 | 83 | replace k8s.io/klog => github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b 84 | 85 | go 1.23 86 | 87 | toolchain go1.23.1 88 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright YEAR The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /hack/crd/trim.go: -------------------------------------------------------------------------------- 1 | // This program removes descriptions from the input CRD yaml to reduce its size. 2 | // 3 | // # Why 4 | // 5 | // When CRD is applied via `kubectl apply -f docs/stackset_crd.yaml` (aka client-side apply) kubectl stores 6 | // CRD content into the kubectl.kubernetes.io/last-applied-configuration annotation which has 7 | // size limit of 262144 bytes. 8 | // If the size of the annotation exceeds the limit, kubectl will fail with the following error: 9 | // 10 | // The CustomResourceDefinition "stacksets.zalando.org" is invalid: metadata.annotations: Too long: must have at most 262144 bytes 11 | // 12 | // See https://github.com/kubernetes/kubectl/issues/712 13 | // 14 | // The CRD contains a lot of descriptions for k8s.io types and controller-gen 15 | // does not allow to skip descriptions per field or per package, 16 | // see https://github.com/kubernetes-sigs/controller-tools/issues/441 17 | // 18 | // # How 19 | // 20 | // It removes descriptions starting at the deepest level of the yaml tree 21 | // until the size of the yaml converted to json is less than the maximum allowed annotation size. 22 | package main 23 | 24 | import ( 25 | "encoding/json" 26 | "io" 27 | "log" 28 | "os" 29 | "sort" 30 | 31 | "sigs.k8s.io/yaml" 32 | ) 33 | 34 | const maxAnnotationSize = 262144 35 | 36 | func must(err error) { 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | 42 | func mustGet[T any](v T, err error) T { 43 | must(err) 44 | return v 45 | } 46 | 47 | type description struct { 48 | depth int 49 | property map[string]any 50 | value string 51 | } 52 | 53 | func main() { 54 | yamlBytes := mustGet(io.ReadAll(os.Stdin)) 55 | 56 | o := make(map[string]any) 57 | must(yaml.Unmarshal(yamlBytes, &o)) 58 | 59 | jsonBytes := mustGet(json.Marshal(o)) 60 | size := len(jsonBytes) 61 | 62 | descriptions := collect(o, 0) 63 | 64 | sort.Slice(descriptions, func(i, j int) bool { 65 | if descriptions[i].depth == descriptions[j].depth { 66 | return len(descriptions[i].value) > len(descriptions[j].value) 67 | } 68 | return descriptions[i].depth > descriptions[j].depth 69 | }) 70 | 71 | for _, d := range descriptions { 72 | if size <= maxAnnotationSize { 73 | break 74 | } 75 | size -= len(d.value) 76 | delete(d.property, "description") 77 | } 78 | 79 | if size > maxAnnotationSize { 80 | log.Fatalf("YAML converted to JSON must be at most %d bytes long but it is %d bytes", maxAnnotationSize, size) 81 | } 82 | 83 | outYaml := mustGet(yaml.Marshal(o)) 84 | mustGet(os.Stdout.Write(outYaml)) 85 | } 86 | 87 | func collect(o any, depth int) (descriptions []description) { 88 | switch o := o.(type) { 89 | case map[string]any: 90 | for key, value := range o { 91 | switch value := value.(type) { 92 | case string: 93 | if key == "description" { 94 | descriptions = append(descriptions, description{depth, o, value}) 95 | } 96 | default: 97 | descriptions = append(descriptions, collect(value, depth+1)...) 98 | } 99 | } 100 | case []any: 101 | for _, item := range o { 102 | descriptions = append(descriptions, collect(item, depth+1)...) 103 | } 104 | } 105 | return descriptions 106 | } 107 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | /* 4 | Copyright 2019 The Kubernetes Authors. 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 19 | package tools 20 | 21 | import _ "k8s.io/code-generator" 22 | import _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 23 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2017 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SRC="github.com" 22 | GOPKG="$SRC/zalando-incubator/es-operator" 23 | CUSTOM_RESOURCE_NAME="zalando.org" 24 | CUSTOM_RESOURCE_VERSION="v1" 25 | 26 | SCRIPT_ROOT="$(dirname "${BASH_SOURCE[0]}")/.." 27 | 28 | OUTPUT_DIR="pkg/client" 29 | OUTPUT_PKG="${GOPKG}/${OUTPUT_DIR}" 30 | APIS_PKG="${GOPKG}/pkg/apis" 31 | GROUPS_WITH_VERSIONS="${CUSTOM_RESOURCE_NAME}:${CUSTOM_RESOURCE_VERSION}" 32 | 33 | echo "Generating deepcopy funcs" 34 | go run k8s.io/code-generator/cmd/deepcopy-gen \ 35 | --output-file zz_generated.deepcopy.go \ 36 | --bounding-dirs "${APIS_PKG}" \ 37 | --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ 38 | "${APIS_PKG}/${CUSTOM_RESOURCE_NAME}/${CUSTOM_RESOURCE_VERSION}" 39 | 40 | echo "Generating clientset for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}" 41 | go run k8s.io/code-generator/cmd/client-gen \ 42 | --clientset-name versioned \ 43 | --input-base "" \ 44 | --input "${APIS_PKG}/${CUSTOM_RESOURCE_NAME}/${CUSTOM_RESOURCE_VERSION}" \ 45 | --output-pkg "${OUTPUT_PKG}/clientset" \ 46 | --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ 47 | --output-dir "${OUTPUT_DIR}/clientset" 48 | 49 | echo "Generating listers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/listers" 50 | go run k8s.io/code-generator/cmd/lister-gen \ 51 | --output-pkg "${OUTPUT_PKG}/listers" \ 52 | --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ 53 | --output-dir "${OUTPUT_DIR}/listers" \ 54 | "${APIS_PKG}/${CUSTOM_RESOURCE_NAME}/${CUSTOM_RESOURCE_VERSION}" 55 | 56 | echo "Generating informers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/informers" 57 | go run k8s.io/code-generator/cmd/informer-gen \ 58 | --versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}/${CLIENTSET_NAME_VERSIONED:-versioned}" \ 59 | --listers-package "${OUTPUT_PKG}/listers" \ 60 | --output-pkg "${OUTPUT_PKG}/informers" \ 61 | --go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ 62 | --output-dir "${OUTPUT_DIR}/informers" \ 63 | "${APIS_PKG}/${CUSTOM_RESOURCE_NAME}/${CUSTOM_RESOURCE_VERSION}" 64 | -------------------------------------------------------------------------------- /label_selector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Labels is a map of labels. 9 | type Labels map[string]string 10 | 11 | func (l Labels) String() string { 12 | labels := make([]string, 0, len(l)) 13 | for k, v := range l { 14 | labels = append(labels, fmt.Sprintf("%s=%s", k, v)) 15 | } 16 | 17 | return strings.Join(labels, ",") 18 | } 19 | 20 | // Set parses a pod selector string and adds it to the list. 21 | func (l Labels) Set(value string) error { 22 | labelsStrs := strings.Split(value, ",") 23 | for _, labelStr := range labelsStrs { 24 | kv := strings.Split(labelStr, "=") 25 | if len(kv) < 1 || len(kv) > 2 { 26 | return fmt.Errorf("invalid pod selector format") 27 | } 28 | 29 | val := "" 30 | if len(kv) == 2 { 31 | val = kv[1] 32 | } 33 | 34 | l[kv[0]] = val 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // IsCumulative always return true because it's allowed to call Set multiple 41 | // times. 42 | func (l Labels) IsCumulative() bool { 43 | return true 44 | } 45 | -------------------------------------------------------------------------------- /label_selector_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestLabelsString(t *testing.T) { 6 | labels := Labels(map[string]string{ 7 | "master": "true", 8 | }) 9 | expected := "master=true" 10 | 11 | if labels.String() != expected { 12 | t.Errorf("expected %s, got %s", expected, labels.String()) 13 | } 14 | 15 | } 16 | 17 | func TestSetLabelsValue(t *testing.T) { 18 | for _, tc := range []struct { 19 | msg string 20 | value string 21 | valid bool 22 | }{ 23 | { 24 | msg: "test valid labels", 25 | value: "master=true,worker=false", 26 | valid: true, 27 | }, 28 | { 29 | msg: "test invalid labels", 30 | value: "master=true=false", 31 | valid: false, 32 | }, 33 | } { 34 | t.Run(tc.msg, func(t *testing.T) { 35 | labels := Labels(map[string]string{}) 36 | err := labels.Set(tc.value) 37 | if err != nil && tc.valid { 38 | t.Errorf("should not fail: %s", err) 39 | } 40 | 41 | if err == nil && !tc.valid { 42 | t.Error("expected failure") 43 | } 44 | }) 45 | } 46 | } 47 | 48 | func TestLabelsIsCumulative(t *testing.T) { 49 | var labels Labels 50 | if !labels.IsCumulative() { 51 | t.Error("expected IsCumulative = true") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/alecthomas/kingpin/v2" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | log "github.com/sirupsen/logrus" 16 | "github.com/zalando-incubator/es-operator/operator" 17 | "github.com/zalando-incubator/es-operator/pkg/clientset" 18 | v1 "k8s.io/api/core/v1" 19 | "k8s.io/client-go/rest" 20 | "k8s.io/client-go/transport" 21 | ) 22 | 23 | const ( 24 | defaultInterval = "10s" 25 | defaultAutoscalerInterval = "30s" 26 | defaultMetricsAddress = ":7979" 27 | defaultClientGoTimeout = 30 * time.Second 28 | defaultClusterDNSZone = "cluster.local." 29 | ) 30 | 31 | var ( 32 | config struct { 33 | Interval time.Duration 34 | AutoscalerInterval time.Duration 35 | APIServer *url.URL 36 | PodSelectors Labels 37 | PriorityNodeSelectors Labels 38 | MetricsAddress string 39 | ClientGoTimeout time.Duration 40 | Debug bool 41 | OperatorID string 42 | Namespace string 43 | ClusterDNSZone string 44 | ElasticsearchEndpoint *url.URL 45 | } 46 | ) 47 | 48 | func main() { 49 | config.PodSelectors = Labels(map[string]string{}) 50 | config.PriorityNodeSelectors = Labels(map[string]string{}) 51 | 52 | kingpin.Flag("debug", "Enable debug logging.").BoolVar(&config.Debug) 53 | kingpin.Flag("interval", "Interval between syncing."). 54 | Default(defaultInterval).DurationVar(&config.Interval) 55 | kingpin.Flag("autoscaler-interval", "Interval between checking if autoscaling is needed."). 56 | Default(defaultAutoscalerInterval).DurationVar(&config.AutoscalerInterval) 57 | kingpin.Flag("apiserver", "API server url.").URLVar(&config.APIServer) 58 | kingpin.Flag("pod-selector", "Operator will manage all pods selected by this label selector. =,+."). 59 | SetValue(&config.PodSelectors) 60 | kingpin.Flag("priority-node-selector", "Specify a label selector for finding nodes with the highest priority. Common use case for this is to priorize nodes that are ready over nodes that are about to be terminated."). 61 | SetValue(&config.PriorityNodeSelectors) 62 | kingpin.Flag("metrics-address", "defines where to serve metrics"). 63 | Default(defaultMetricsAddress).StringVar(&config.MetricsAddress) 64 | kingpin.Flag("client-go-timeout", "Set the timeout used for the Kubernetes client"). 65 | Default(defaultClientGoTimeout.String()).DurationVar(&config.ClientGoTimeout) 66 | kingpin.Flag("operator-id", "ID of the operator used to determine ownership of EDS resources"). 67 | StringVar(&config.OperatorID) 68 | kingpin.Flag("cluster-dns-zone", "The zone used for the cluster internal DNS. Used when generating ES service endpoint"). 69 | Default(defaultClusterDNSZone).StringVar(&config.ClusterDNSZone) 70 | kingpin.Flag("elasticsearch-endpoint", "The Elasticsearch endpoint to use for reaching Elasticsearch API. By default the service endpoint for the EDS is used"). 71 | URLVar(&config.ElasticsearchEndpoint) 72 | kingpin.Flag("namespace", "Limit operator to a certain namespace"). 73 | Default(v1.NamespaceAll).StringVar(&config.Namespace) 74 | 75 | kingpin.Parse() 76 | 77 | if config.Debug { 78 | log.SetLevel(log.DebugLevel) 79 | } 80 | 81 | ctx, cancel := context.WithCancel(context.Background()) 82 | kubeConfig, err := configureKubeConfig(config.APIServer, defaultClientGoTimeout, ctx.Done()) 83 | if err != nil { 84 | log.Fatalf("Failed to setup Kubernetes config: %v", err) 85 | } 86 | 87 | client, err := clientset.NewClientset(kubeConfig) 88 | if err != nil { 89 | log.Fatalf("Failed to setup Kubernetes client: %v", err) 90 | } 91 | 92 | operator := operator.NewElasticsearchOperator( 93 | client, 94 | config.PriorityNodeSelectors, 95 | config.Interval, 96 | config.AutoscalerInterval, 97 | config.OperatorID, 98 | config.Namespace, 99 | config.ClusterDNSZone, 100 | config.ElasticsearchEndpoint, 101 | ) 102 | 103 | go handleSigterm(cancel) 104 | go serveMetrics(config.MetricsAddress) 105 | err = operator.Run(ctx) 106 | if err != nil { 107 | cancel() 108 | log.Fatalf("Failed to run operator: %v", err) 109 | } 110 | } 111 | 112 | // handleSigterm handles SIGTERM signal sent to the process. 113 | func handleSigterm(cancelFunc func()) { 114 | signals := make(chan os.Signal, 1) 115 | signal.Notify(signals, syscall.SIGTERM) 116 | <-signals 117 | log.Info("Received Term signal. Terminating...") 118 | cancelFunc() 119 | } 120 | 121 | // configureKubeConfig configures a kubeconfig. 122 | func configureKubeConfig(apiServerURL *url.URL, timeout time.Duration, stopCh <-chan struct{}) (*rest.Config, error) { 123 | tr := &http.Transport{ 124 | DialContext: (&net.Dialer{ 125 | Timeout: timeout, 126 | KeepAlive: 30 * time.Second, 127 | DualStack: false, // K8s do not work well with IPv6 128 | }).DialContext, 129 | TLSHandshakeTimeout: timeout, 130 | ResponseHeaderTimeout: 10 * time.Second, 131 | MaxIdleConns: 10, 132 | MaxIdleConnsPerHost: 2, 133 | IdleConnTimeout: 20 * time.Second, 134 | } 135 | 136 | // We need this to reliably fade on DNS change, which is right 137 | // now not fixed with IdleConnTimeout in the http.Transport. 138 | // https://github.com/golang/go/issues/23427 139 | go func(d time.Duration) { 140 | for { 141 | select { 142 | case <-time.After(d): 143 | tr.CloseIdleConnections() 144 | case <-stopCh: 145 | return 146 | } 147 | } 148 | }(20 * time.Second) 149 | 150 | if apiServerURL != nil { 151 | return &rest.Config{ 152 | Host: apiServerURL.String(), 153 | Timeout: timeout, 154 | Transport: tr, 155 | QPS: 100.0, 156 | Burst: 500, 157 | }, nil 158 | } 159 | 160 | config, err := rest.InClusterConfig() 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | // patch TLS config 166 | restTransportConfig, err := config.TransportConfig() 167 | if err != nil { 168 | return nil, err 169 | } 170 | restTLSConfig, err := transport.TLSConfigFor(restTransportConfig) 171 | if err != nil { 172 | return nil, err 173 | } 174 | tr.TLSClientConfig = restTLSConfig 175 | 176 | config.Timeout = timeout 177 | config.Transport = tr 178 | config.QPS = 100.0 179 | config.Burst = 500 180 | // disable TLSClientConfig to make the custom Transport work 181 | config.TLSClientConfig = rest.TLSClientConfig{} 182 | return config, nil 183 | } 184 | 185 | // gather go metrics 186 | func serveMetrics(address string) { 187 | http.Handle("/metrics", promhttp.Handler()) 188 | log.Fatal(http.ListenAndServe(address, nil)) 189 | } 190 | -------------------------------------------------------------------------------- /manifests/es-operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: es-operator 5 | labels: 6 | application: es-operator 7 | version: latest 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | application: es-operator 15 | template: 16 | metadata: 17 | labels: 18 | application: es-operator 19 | version: latest 20 | spec: 21 | serviceAccountName: es-operator 22 | containers: 23 | - name: es-operator 24 | image: {{{IMAGE}}} 25 | args: 26 | - --priority-node-selector=lifecycle-status=ready 27 | - --operator-id={{{OPERATOR_ID}}} 28 | - --interval=30s 29 | - --namespace={{{NAMESPACE}}} 30 | - --debug 31 | resources: 32 | limits: 33 | cpu: 50m 34 | memory: 300Mi 35 | requests: 36 | cpu: 50m 37 | memory: 300Mi 38 | -------------------------------------------------------------------------------- /manifests/metrics-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | k8s-app: metrics-server 6 | name: metrics-server 7 | namespace: kube-system 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: ClusterRole 11 | metadata: 12 | labels: 13 | k8s-app: metrics-server 14 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 15 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 16 | rbac.authorization.k8s.io/aggregate-to-view: "true" 17 | name: system:aggregated-metrics-reader 18 | rules: 19 | - apiGroups: 20 | - metrics.k8s.io 21 | resources: 22 | - pods 23 | - nodes 24 | verbs: 25 | - get 26 | - list 27 | - watch 28 | --- 29 | apiVersion: rbac.authorization.k8s.io/v1 30 | kind: ClusterRole 31 | metadata: 32 | labels: 33 | k8s-app: metrics-server 34 | name: system:metrics-server 35 | rules: 36 | - apiGroups: 37 | - "" 38 | resources: 39 | - pods 40 | - nodes 41 | - nodes/stats 42 | - namespaces 43 | - configmaps 44 | verbs: 45 | - get 46 | - list 47 | - watch 48 | --- 49 | apiVersion: rbac.authorization.k8s.io/v1 50 | kind: RoleBinding 51 | metadata: 52 | labels: 53 | k8s-app: metrics-server 54 | name: metrics-server-auth-reader 55 | namespace: kube-system 56 | roleRef: 57 | apiGroup: rbac.authorization.k8s.io 58 | kind: Role 59 | name: extension-apiserver-authentication-reader 60 | subjects: 61 | - kind: ServiceAccount 62 | name: metrics-server 63 | namespace: kube-system 64 | --- 65 | apiVersion: rbac.authorization.k8s.io/v1 66 | kind: ClusterRoleBinding 67 | metadata: 68 | labels: 69 | k8s-app: metrics-server 70 | name: metrics-server:system:auth-delegator 71 | roleRef: 72 | apiGroup: rbac.authorization.k8s.io 73 | kind: ClusterRole 74 | name: system:auth-delegator 75 | subjects: 76 | - kind: ServiceAccount 77 | name: metrics-server 78 | namespace: kube-system 79 | --- 80 | apiVersion: rbac.authorization.k8s.io/v1 81 | kind: ClusterRoleBinding 82 | metadata: 83 | labels: 84 | k8s-app: metrics-server 85 | name: system:metrics-server 86 | roleRef: 87 | apiGroup: rbac.authorization.k8s.io 88 | kind: ClusterRole 89 | name: system:metrics-server 90 | subjects: 91 | - kind: ServiceAccount 92 | name: metrics-server 93 | namespace: kube-system 94 | --- 95 | apiVersion: v1 96 | kind: Service 97 | metadata: 98 | labels: 99 | k8s-app: metrics-server 100 | name: metrics-server 101 | namespace: kube-system 102 | spec: 103 | ports: 104 | - name: https 105 | port: 443 106 | protocol: TCP 107 | targetPort: https 108 | selector: 109 | k8s-app: metrics-server 110 | --- 111 | apiVersion: apps/v1 112 | kind: Deployment 113 | metadata: 114 | labels: 115 | k8s-app: metrics-server 116 | name: metrics-server 117 | namespace: kube-system 118 | spec: 119 | selector: 120 | matchLabels: 121 | k8s-app: metrics-server 122 | strategy: 123 | rollingUpdate: 124 | maxUnavailable: 0 125 | template: 126 | metadata: 127 | labels: 128 | k8s-app: metrics-server 129 | spec: 130 | containers: 131 | - args: 132 | - --cert-dir=/tmp 133 | - --secure-port=4443 134 | - --kubelet-insecure-tls 135 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname 136 | - --kubelet-use-node-status-port 137 | image: k8s.gcr.io/metrics-server/metrics-server:v0.4.2 138 | imagePullPolicy: IfNotPresent 139 | livenessProbe: 140 | failureThreshold: 3 141 | httpGet: 142 | path: /livez 143 | port: https 144 | scheme: HTTPS 145 | periodSeconds: 10 146 | name: metrics-server 147 | ports: 148 | - containerPort: 4443 149 | name: https 150 | protocol: TCP 151 | readinessProbe: 152 | failureThreshold: 3 153 | httpGet: 154 | path: /readyz 155 | port: https 156 | scheme: HTTPS 157 | periodSeconds: 10 158 | securityContext: 159 | readOnlyRootFilesystem: true 160 | runAsNonRoot: true 161 | runAsUser: 1000 162 | volumeMounts: 163 | - mountPath: /tmp 164 | name: tmp-dir 165 | nodeSelector: 166 | kubernetes.io/os: linux 167 | priorityClassName: system-cluster-critical 168 | serviceAccountName: metrics-server 169 | volumes: 170 | - emptyDir: {} 171 | name: tmp-dir 172 | --- 173 | apiVersion: apiregistration.k8s.io/v1 174 | kind: APIService 175 | metadata: 176 | labels: 177 | k8s-app: metrics-server 178 | name: v1beta1.metrics.k8s.io 179 | spec: 180 | group: metrics.k8s.io 181 | groupPriorityMinimum: 100 182 | insecureSkipTLSVerify: true 183 | service: 184 | name: metrics-server 185 | namespace: kube-system 186 | version: v1beta1 187 | versionPriority: 100 188 | -------------------------------------------------------------------------------- /manifests/operator_service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: operator 5 | -------------------------------------------------------------------------------- /manifests/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: es-operator 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: ClusterRole 8 | metadata: 9 | name: es-operator-e2e 10 | rules: 11 | - apiGroups: 12 | - "zalando.org" 13 | resources: 14 | - elasticsearchdatasets 15 | - elasticsearchdatasets/status 16 | - elasticsearchmetricsets 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | - update 22 | - patch 23 | # used by e2e test runner 24 | - create 25 | - update 26 | - patch 27 | - delete 28 | - apiGroups: 29 | - "" 30 | resources: 31 | - services 32 | verbs: 33 | - get 34 | - list 35 | - watch 36 | - create 37 | - apiGroups: 38 | - "" 39 | resources: 40 | - pods 41 | verbs: 42 | - get 43 | - list 44 | - update 45 | - patch 46 | - delete 47 | - apiGroups: 48 | - "apps" 49 | resources: 50 | - statefulsets 51 | verbs: 52 | - get 53 | - list 54 | - watch 55 | - create 56 | - update 57 | - patch 58 | - delete 59 | - apiGroups: 60 | - policy 61 | resources: 62 | - poddisruptionbudgets 63 | verbs: 64 | - get 65 | - list 66 | - watch 67 | - create 68 | - apiGroups: 69 | - "" 70 | resources: 71 | - events 72 | verbs: 73 | - create 74 | - patch 75 | - update 76 | - apiGroups: 77 | - "" 78 | resources: 79 | - nodes 80 | verbs: 81 | - get 82 | - list 83 | - watch 84 | - apiGroups: 85 | - metrics.k8s.io 86 | resources: 87 | - pods 88 | verbs: 89 | - get 90 | - list 91 | - watch 92 | --- 93 | apiVersion: rbac.authorization.k8s.io/v1 94 | kind: ClusterRoleBinding 95 | metadata: 96 | name: es-operator-e2e 97 | roleRef: 98 | apiGroup: rbac.authorization.k8s.io 99 | kind: ClusterRole 100 | name: es-operator-e2e 101 | subjects: 102 | - kind: ServiceAccount 103 | name: es-operator 104 | namespace: "{{{NAMESPACE}}}" 105 | -------------------------------------------------------------------------------- /manifests/sysctl.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: sysctl 5 | spec: 6 | updateStrategy: 7 | type: RollingUpdate 8 | selector: 9 | matchLabels: 10 | application: sysctl 11 | template: 12 | metadata: 13 | labels: 14 | application: sysctl 15 | spec: 16 | containers: 17 | - name: sysctl 18 | image: busybox 19 | stdin: true 20 | command: ["/bin/sh", "-c"] 21 | args: 22 | - "sysctl -w vm.max_map_count=262144 && cat" 23 | resources: 24 | limits: 25 | cpu: 1m 26 | memory: 5Mi 27 | requests: 28 | cpu: 1m 29 | memory: 5Mi 30 | securityContext: 31 | privileged: true 32 | tolerations: 33 | - effect: NoSchedule 34 | operator: Exists 35 | - effect: NoExecute 36 | operator: Exists 37 | -------------------------------------------------------------------------------- /operator/elasticsearch_test.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/zalando-incubator/es-operator/pkg/clientset" 8 | "k8s.io/client-go/kubernetes/fake" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | zv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 13 | v1 "k8s.io/api/core/v1" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/types" 16 | ) 17 | 18 | func TestHasOwnership(t *testing.T) { 19 | eds := &zv1.ElasticsearchDataSet{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Annotations: map[string]string{ 22 | esOperatorAnnotationKey: "my-operator", 23 | }, 24 | }, 25 | } 26 | 27 | operator := &ElasticsearchOperator{ 28 | operatorID: "my-operator", 29 | } 30 | assert.True(t, operator.hasOwnership(eds)) 31 | 32 | eds.Annotations[esOperatorAnnotationKey] = "not-my-operator" 33 | assert.False(t, operator.hasOwnership(eds)) 34 | 35 | delete(eds.Annotations, esOperatorAnnotationKey) 36 | assert.False(t, operator.hasOwnership(eds)) 37 | 38 | operator.operatorID = "" 39 | assert.True(t, operator.hasOwnership(eds)) 40 | } 41 | 42 | func TestGetElasticsearchEndpoint(t *testing.T) { 43 | faker := &clientset.Clientset{ 44 | Interface: fake.NewSimpleClientset(), 45 | } 46 | esOperator := NewElasticsearchOperator(faker, nil, 1*time.Second, 1*time.Second, "", "", "cluster.local.", nil) 47 | 48 | eds := &zv1.ElasticsearchDataSet{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Name: "foo", 51 | Namespace: "bar", 52 | }, 53 | } 54 | 55 | url := esOperator.getElasticsearchEndpoint(eds) 56 | assert.Equal(t, "http://foo.bar.svc.cluster.local.:9200", url.String()) 57 | 58 | customURL := "http://127.0.0.1:8001/api/v1/namespaces/default/services/elasticsearch:9200/proxy" 59 | customEndpoint, err := url.Parse(customURL) 60 | assert.NoError(t, err) 61 | 62 | esOperator = NewElasticsearchOperator(faker, nil, 1*time.Second, 1*time.Second, "", "", ".cluster.local.", customEndpoint) 63 | url = esOperator.getElasticsearchEndpoint(eds) 64 | assert.Equal(t, customURL, url.String()) 65 | } 66 | 67 | func TestGetEmptyElasticSearchDrainingSpec(t *testing.T) { 68 | faker := &clientset.Clientset{ 69 | Interface: fake.NewSimpleClientset(), 70 | } 71 | esOperator := NewElasticsearchOperator(faker, nil, 1*time.Second, 1*time.Second, "", "", "cluster.local.", nil) 72 | 73 | eds := &zv1.ElasticsearchDataSet{ 74 | ObjectMeta: metav1.ObjectMeta{ 75 | Name: "foo", 76 | Namespace: "bar", 77 | }, 78 | } 79 | 80 | config := esOperator.getDrainingConfig(eds) 81 | assert.NotNil(t, config) 82 | assert.Equal(t, config.MaxRetries, 999) 83 | assert.Equal(t, config.MinimumWaitTime, 10*time.Second) 84 | assert.Equal(t, config.MaximumWaitTime, 30*time.Second) 85 | } 86 | 87 | func TestGetNotEmptyElasticSearchDrainingSpec(t *testing.T) { 88 | faker := &clientset.Clientset{ 89 | Interface: fake.NewSimpleClientset(), 90 | } 91 | esOperator := NewElasticsearchOperator(faker, nil, 1*time.Second, 1*time.Second, "", "", "cluster.local.", nil) 92 | 93 | eds := &zv1.ElasticsearchDataSet{ 94 | ObjectMeta: metav1.ObjectMeta{ 95 | Name: "foo", 96 | Namespace: "bar", 97 | }, 98 | Spec: zv1.ElasticsearchDataSetSpec{ 99 | Experimental: &zv1.ExperimentalSpec{ 100 | Draining: &zv1.ElasticsearchDataSetDraining{ 101 | MaxRetries: 7, 102 | MinimumWaitTimeDurationSeconds: 2, 103 | MaximumWaitTimeDurationSeconds: 34, 104 | }, 105 | }, 106 | }, 107 | } 108 | 109 | config := esOperator.getDrainingConfig(eds) 110 | assert.NotNil(t, config) 111 | assert.Equal(t, config.MaxRetries, 7) 112 | assert.Equal(t, config.MinimumWaitTime, 2*time.Second) 113 | assert.Equal(t, config.MaximumWaitTime, 34*time.Second) 114 | } 115 | 116 | func TestGetOwnerUID(t *testing.T) { 117 | objectMeta := metav1.ObjectMeta{ 118 | OwnerReferences: []metav1.OwnerReference{ 119 | { 120 | UID: types.UID("x"), 121 | }, 122 | }, 123 | } 124 | 125 | uid, ok := getOwnerUID(objectMeta) 126 | assert.Equal(t, types.UID("x"), uid) 127 | assert.True(t, ok) 128 | 129 | uid, ok = getOwnerUID(metav1.ObjectMeta{}) 130 | assert.Equal(t, types.UID(""), uid) 131 | assert.False(t, ok) 132 | } 133 | 134 | func TestTemplateInjectLabels(t *testing.T) { 135 | template := v1.PodTemplateSpec{} 136 | labels := map[string]string{"foo": "bar"} 137 | 138 | expectedTemplate := v1.PodTemplateSpec{ 139 | ObjectMeta: metav1.ObjectMeta{ 140 | Labels: labels, 141 | }, 142 | } 143 | 144 | newTemplate := templateInjectLabels(template, labels) 145 | assert.Equal(t, expectedTemplate, newTemplate) 146 | } 147 | 148 | func TestValidateScalingSettings(tt *testing.T) { 149 | for _, tc := range []struct { 150 | msg string 151 | scaling *zv1.ElasticsearchDataSetScaling 152 | err bool 153 | }{ 154 | { 155 | msg: "test simple valid scaling config", 156 | scaling: &zv1.ElasticsearchDataSetScaling{ 157 | Enabled: true, 158 | MinReplicas: 1, 159 | MaxReplicas: 3, 160 | MinIndexReplicas: 0, 161 | MaxIndexReplicas: 2, 162 | MinShardsPerNode: 1, 163 | MaxShardsPerNode: 1, 164 | }, 165 | }, 166 | { 167 | msg: "test min > max replicas", 168 | scaling: &zv1.ElasticsearchDataSetScaling{ 169 | Enabled: true, 170 | MinReplicas: 2, 171 | MaxReplicas: 1, 172 | MinIndexReplicas: 0, 173 | MaxIndexReplicas: 2, 174 | MinShardsPerNode: 1, 175 | MaxShardsPerNode: 1, 176 | }, 177 | err: true, 178 | }, 179 | { 180 | msg: "test min > max indexReplicas", 181 | scaling: &zv1.ElasticsearchDataSetScaling{ 182 | Enabled: true, 183 | MinReplicas: 1, 184 | MaxReplicas: 1, 185 | MinIndexReplicas: 2, 186 | MaxIndexReplicas: 1, 187 | MinShardsPerNode: 1, 188 | MaxShardsPerNode: 1, 189 | }, 190 | err: true, 191 | }, 192 | { 193 | msg: "test min > max shardsPerNode", 194 | scaling: &zv1.ElasticsearchDataSetScaling{ 195 | Enabled: true, 196 | MinReplicas: 1, 197 | MaxReplicas: 1, 198 | MinIndexReplicas: 1, 199 | MaxIndexReplicas: 1, 200 | MinShardsPerNode: 2, 201 | MaxShardsPerNode: 1, 202 | }, 203 | err: true, 204 | }, 205 | { 206 | msg: "test min possible shardsPerNode lower than minShardsPerNode", 207 | scaling: &zv1.ElasticsearchDataSetScaling{ 208 | Enabled: true, 209 | MinReplicas: 1, 210 | MaxReplicas: 2, 211 | MinIndexReplicas: 0, 212 | MaxIndexReplicas: 3, 213 | MinShardsPerNode: 2, 214 | MaxShardsPerNode: 2, 215 | }, 216 | err: true, 217 | }, 218 | { 219 | msg: "test minShardsPerNode > 0 and minReplicas < 1", 220 | scaling: &zv1.ElasticsearchDataSetScaling{ 221 | Enabled: true, 222 | MinReplicas: 0, 223 | MaxReplicas: 2, 224 | MinIndexReplicas: 1, 225 | MaxIndexReplicas: 3, 226 | MinShardsPerNode: 1, 227 | MaxShardsPerNode: 2, 228 | }, 229 | err: true, 230 | }, 231 | { 232 | msg: "scaling disabled", 233 | scaling: &zv1.ElasticsearchDataSetScaling{ 234 | Enabled: false, 235 | }, 236 | }, 237 | } { 238 | tt.Run(tc.msg, func(t *testing.T) { 239 | err := validateScalingSettings(tc.scaling) 240 | if !tc.err { 241 | require.NoError(t, err) 242 | return 243 | } 244 | require.Error(t, err) 245 | }) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /operator/metrics_collector.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "sort" 7 | "time" 8 | 9 | log "github.com/sirupsen/logrus" 10 | v12 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 11 | "github.com/zalando-incubator/es-operator/pkg/clientset" 12 | v1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/metrics/pkg/apis/metrics/v1beta1" 15 | ) 16 | 17 | type ElasticsearchMetricsCollector struct { 18 | logger *log.Entry 19 | kube *clientset.Clientset 20 | es ESResource 21 | } 22 | 23 | func (c *ElasticsearchMetricsCollector) collectMetrics(ctx context.Context) error { 24 | // first, collect metrics for all pods.... 25 | metrics, err := c.kube.MetricsV1Beta1().PodMetricses(c.es.ElasticsearchDataSet.Namespace).List(ctx, metav1.ListOptions{}) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | cpuUsagePercent := getCPUUsagePercent(metrics.Items, c.es.Pods) 31 | 32 | if len(cpuUsagePercent) == 0 { 33 | c.logger.Debug("Didn't have any metrics to collect.") 34 | return nil 35 | } 36 | 37 | // calculate its median 38 | median := calculateMedian(cpuUsagePercent) 39 | 40 | // next, get the configmap and store the sample 41 | return c.storeMedian(ctx, median) 42 | } 43 | 44 | func getCPUUsagePercent(metrics []v1beta1.PodMetrics, pods []v1.Pod) []int32 { 45 | podResources := make(map[string]map[string]v1.ResourceRequirements, len(pods)) 46 | for _, pod := range pods { 47 | containerMap := make(map[string]v1.ResourceRequirements, len(pod.Spec.Containers)) 48 | for _, container := range pod.Spec.Containers { 49 | containerMap[container.Name] = container.Resources 50 | } 51 | podResources[pod.Name] = containerMap 52 | } 53 | 54 | // calculate the max usage/request value for each pod by looking at all 55 | // containers in every pod. 56 | cpuUsagePercent := []int32{} 57 | for _, podMetrics := range metrics { 58 | if containerResources, ok := podResources[podMetrics.Name]; ok { 59 | podMax := int32(0) 60 | for _, container := range podMetrics.Containers { 61 | if resources, ok := containerResources[container.Name]; ok { 62 | requestedCPU := resources.Requests.Cpu().MilliValue() 63 | usageCPU := container.Usage.Cpu().MilliValue() 64 | if requestedCPU > 0 { 65 | usagePcnt := int32(100 * usageCPU / requestedCPU) 66 | if usagePcnt > podMax { 67 | podMax = usagePcnt 68 | } 69 | } 70 | } 71 | } 72 | cpuUsagePercent = append(cpuUsagePercent, podMax) 73 | } 74 | } 75 | return cpuUsagePercent 76 | } 77 | 78 | func (c *ElasticsearchMetricsCollector) storeMedian(ctx context.Context, i int32) error { 79 | currentValue := v12.ElasticsearchMetric{ 80 | Timestamp: metav1.Now(), 81 | Value: i, 82 | } 83 | 84 | if c.es.MetricSet == nil { 85 | c.es.MetricSet = &v12.ElasticsearchMetricSet{ 86 | ObjectMeta: metav1.ObjectMeta{ 87 | Name: c.es.ElasticsearchDataSet.Name, 88 | Namespace: c.es.ElasticsearchDataSet.Namespace, 89 | Labels: c.es.ElasticsearchDataSet.Labels, 90 | OwnerReferences: []metav1.OwnerReference{ 91 | { 92 | APIVersion: c.es.ElasticsearchDataSet.APIVersion, 93 | Kind: c.es.ElasticsearchDataSet.Kind, 94 | Name: c.es.ElasticsearchDataSet.Name, 95 | UID: c.es.ElasticsearchDataSet.UID, 96 | }, 97 | }, 98 | }, 99 | Metrics: []v12.ElasticsearchMetric{currentValue}, 100 | } 101 | _, err := c.kube.ZalandoV1().ElasticsearchMetricSets(c.es.MetricSet.Namespace).Create(ctx, c.es.MetricSet, metav1.CreateOptions{}) 102 | if err != nil { 103 | return err 104 | } 105 | } else { 106 | threshold := time.Duration(math.Max(float64(c.es.ElasticsearchDataSet.Spec.Scaling.ScaleDownThresholdDurationSeconds), float64(c.es.ElasticsearchDataSet.Spec.Scaling.ScaleUpThresholdDurationSeconds))) * time.Second 107 | oldestTimestamp := time.Now().Add(-threshold) 108 | 109 | newMetricsList := make([]v12.ElasticsearchMetric, 0, len(c.es.MetricSet.Metrics)+1) 110 | for _, m := range c.es.MetricSet.Metrics { 111 | if m.Timestamp.Time.Before(oldestTimestamp) { 112 | continue 113 | } 114 | newMetricsList = append(newMetricsList, m) 115 | } 116 | newMetricsList = append(newMetricsList, currentValue) 117 | c.es.MetricSet.Metrics = newMetricsList 118 | 119 | _, err := c.kube.ZalandoV1().ElasticsearchMetricSets(c.es.MetricSet.Namespace).Update(ctx, c.es.MetricSet, metav1.UpdateOptions{}) 120 | if err != nil { 121 | return err 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | func calculateMedian(cpuMetrics []int32) int32 { 128 | sort.Slice(cpuMetrics, func(i, j int) bool { return cpuMetrics[i] < cpuMetrics[j] }) 129 | // in case of even number of samples we now get the lower one. 130 | return cpuMetrics[(len(cpuMetrics)-1)/2] 131 | } 132 | -------------------------------------------------------------------------------- /operator/metrics_collector_test.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | v1 "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/api/resource" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/metrics/pkg/apis/metrics/v1beta1" 12 | ) 13 | 14 | func TestCalculateMedian(t *testing.T) { 15 | assert.Equal(t, int32(4), calculateMedian([]int32{1, 4, 6})) 16 | assert.Equal(t, int32(1), calculateMedian([]int32{1, 4})) 17 | } 18 | 19 | func TestGetCPUUsagePercent(t *testing.T) { 20 | pods := []v1.Pod{ 21 | { 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Name: "pod-x", 24 | }, 25 | Spec: v1.PodSpec{ 26 | Containers: []v1.Container{ 27 | { 28 | Name: "a", 29 | Resources: v1.ResourceRequirements{ 30 | Requests: v1.ResourceList{ 31 | v1.ResourceCPU: resource.MustParse("100m"), 32 | }, 33 | }, 34 | }, 35 | { 36 | Name: "b", 37 | Resources: v1.ResourceRequirements{ 38 | Requests: v1.ResourceList{ 39 | v1.ResourceCPU: resource.MustParse("1000m"), 40 | }, 41 | }, 42 | }, 43 | }, 44 | }, 45 | }, 46 | } 47 | 48 | podMetrics := []v1beta1.PodMetrics{ 49 | { 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: "pod-x", 52 | }, 53 | Containers: []v1beta1.ContainerMetrics{ 54 | { 55 | Name: "a", 56 | Usage: v1.ResourceList{ 57 | v1.ResourceCPU: resource.MustParse("50m"), 58 | }, 59 | }, 60 | { 61 | Name: "b", 62 | Usage: v1.ResourceList{ 63 | v1.ResourceCPU: resource.MustParse("0m"), 64 | }, 65 | }, 66 | }, 67 | }, 68 | { 69 | ObjectMeta: metav1.ObjectMeta{ 70 | Name: "pod-y", 71 | }, 72 | Containers: []v1beta1.ContainerMetrics{ 73 | { 74 | Name: "a", 75 | Usage: v1.ResourceList{ 76 | v1.ResourceCPU: resource.MustParse("50m"), 77 | }, 78 | }, 79 | { 80 | Name: "b", 81 | Usage: v1.ResourceList{ 82 | v1.ResourceCPU: resource.MustParse("0m"), 83 | }, 84 | }, 85 | }, 86 | }, 87 | } 88 | 89 | cpuUsagePercent := getCPUUsagePercent(podMetrics, pods) 90 | require.Len(t, cpuUsagePercent, 1) 91 | require.EqualValues(t, 50, cpuUsagePercent[0]) 92 | } 93 | -------------------------------------------------------------------------------- /operator/null/string.go: -------------------------------------------------------------------------------- 1 | package null 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | // nullBytes is a JSON null literal 11 | var nullBytes = []byte("null") 12 | 13 | // String is a nullable string. It supports SQL and JSON serialization. 14 | // It will marshal to null if null. Blank string input will be considered null. 15 | type String struct { 16 | sql.NullString 17 | } 18 | 19 | // StringFrom creates a new String that will never be blank. 20 | func StringFrom(s string) String { 21 | return NewString(s, true) 22 | } 23 | 24 | // StringFromPtr creates a new String that be null if s is nil. 25 | func StringFromPtr(s *string) String { 26 | if s == nil { 27 | return NewString("", false) 28 | } 29 | return NewString(*s, true) 30 | } 31 | 32 | // ValueOrZero returns the inner value if valid, otherwise zero. 33 | func (s String) ValueOrZero() string { 34 | if !s.Valid { 35 | return "" 36 | } 37 | return s.String 38 | } 39 | 40 | // NewString creates a new String 41 | func NewString(s string, valid bool) String { 42 | return String{ 43 | NullString: sql.NullString{ 44 | String: s, 45 | Valid: valid, 46 | }, 47 | } 48 | } 49 | 50 | // UnmarshalJSON implements json.Unmarshaler. 51 | // It supports string and null input. Blank string input does not produce a null String. 52 | func (s *String) UnmarshalJSON(data []byte) error { 53 | if bytes.Equal(data, nullBytes) { 54 | s.Valid = false 55 | return nil 56 | } 57 | 58 | if err := json.Unmarshal(data, &s.String); err != nil { 59 | return fmt.Errorf("null: couldn't unmarshal JSON: %w", err) 60 | } 61 | 62 | s.Valid = true 63 | return nil 64 | } 65 | 66 | // MarshalJSON implements json.Marshaler. 67 | // It will encode null if this String is null. 68 | func (s String) MarshalJSON() ([]byte, error) { 69 | if !s.Valid { 70 | return []byte("null"), nil 71 | } 72 | return json.Marshal(s.String) 73 | } 74 | 75 | // MarshalText implements encoding.TextMarshaler. 76 | // It will encode a blank string when this String is null. 77 | func (s String) MarshalText() ([]byte, error) { 78 | if !s.Valid { 79 | return []byte{}, nil 80 | } 81 | return []byte(s.String), nil 82 | } 83 | 84 | // UnmarshalText implements encoding.TextUnmarshaler. 85 | // It will unmarshal to a null String if the input is a blank string. 86 | func (s *String) UnmarshalText(text []byte) error { 87 | s.String = string(text) 88 | s.Valid = s.String != "" 89 | return nil 90 | } 91 | 92 | // Equal returns true if both strings have the same value or are both null. 93 | func (s String) Equal(other String) bool { 94 | return s.Valid == other.Valid && (!s.Valid || s.String == other.String) 95 | } 96 | -------------------------------------------------------------------------------- /operator/operator_test.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | zv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 10 | appsv1 "k8s.io/api/apps/v1" 11 | v1 "k8s.io/api/core/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | "k8s.io/apimachinery/pkg/types" 15 | ) 16 | 17 | type mockResource struct { 18 | apiVersion string 19 | kind string 20 | name string 21 | namespace string 22 | uid types.UID 23 | generation int64 24 | labels map[string]string 25 | labelSelector map[string]string 26 | replicas int32 27 | eds *zv1.ElasticsearchDataSet 28 | podTemplateSpec *v1.PodTemplateSpec 29 | volumeClaimTemplates []v1.PersistentVolumeClaim 30 | } 31 | 32 | func (r *mockResource) Name() string { return r.name } 33 | func (r *mockResource) Namespace() string { return r.namespace } 34 | func (r *mockResource) APIVersion() string { return r.apiVersion } 35 | func (r *mockResource) Kind() string { return r.kind } 36 | func (r *mockResource) Labels() map[string]string { return r.labels } 37 | func (r *mockResource) LabelSelector() map[string]string { return r.labelSelector } 38 | func (r *mockResource) Generation() int64 { return r.generation } 39 | func (r *mockResource) UID() types.UID { return r.uid } 40 | func (r *mockResource) Replicas() int32 { return r.replicas } 41 | func (r *mockResource) PodTemplateSpec() *v1.PodTemplateSpec { return r.podTemplateSpec } 42 | func (r *mockResource) VolumeClaimTemplates() []v1.PersistentVolumeClaim { 43 | return r.volumeClaimTemplates 44 | } 45 | func (r *mockResource) Self() runtime.Object { return r.eds } 46 | func (r *mockResource) EnsureResources(ctx context.Context) error { return nil } 47 | func (r *mockResource) UpdateStatus(ctx context.Context, sts *appsv1.StatefulSet) error { return nil } 48 | func (r *mockResource) PreScaleDownHook(ctx context.Context) error { return nil } 49 | func (r *mockResource) OnStableReplicasHook(ctx context.Context) error { return nil } 50 | func (r *mockResource) Drain(ctx context.Context, pod *v1.Pod) error { return nil } 51 | func (r *mockResource) Get(ctx context.Context) (StatefulResource, error) { return r, nil } 52 | 53 | func TestPrioritizePodsForUpdate(t *testing.T) { 54 | updatingPod := v1.Pod{ 55 | ObjectMeta: metav1.ObjectMeta{ 56 | Name: "sts-4", 57 | GenerateName: "sts-", 58 | Annotations: map[string]string{ 59 | operatorPodDrainingAnnotationKey: "", 60 | }, 61 | Labels: map[string]string{ 62 | controllerRevisionHashLabelKey: "", 63 | }, 64 | Namespace: "default", 65 | }, 66 | Spec: v1.PodSpec{ 67 | NodeName: "node1", 68 | }, 69 | } 70 | 71 | stsPod := v1.Pod{ 72 | ObjectMeta: metav1.ObjectMeta{ 73 | Name: "sts-1", 74 | GenerateName: "sts-", 75 | Annotations: map[string]string{}, 76 | Labels: map[string]string{ 77 | controllerRevisionHashLabelKey: "hash", 78 | }, 79 | Namespace: "default", 80 | }, 81 | Spec: v1.PodSpec{ 82 | NodeName: "node1", 83 | }, 84 | } 85 | 86 | stsPod0 := v1.Pod{ 87 | ObjectMeta: metav1.ObjectMeta{ 88 | Name: "sts-0", 89 | GenerateName: "sts-", 90 | Annotations: map[string]string{}, 91 | Labels: map[string]string{ 92 | controllerRevisionHashLabelKey: "hash", 93 | }, 94 | Namespace: "default", 95 | }, 96 | Spec: v1.PodSpec{ 97 | NodeName: "node1", 98 | }, 99 | } 100 | 101 | stsPod2 := v1.Pod{ 102 | ObjectMeta: metav1.ObjectMeta{ 103 | Name: "sts-2", 104 | GenerateName: "sts-", 105 | Annotations: map[string]string{}, 106 | Labels: map[string]string{ 107 | controllerRevisionHashLabelKey: "hash", 108 | }, 109 | Namespace: "default", 110 | }, 111 | Spec: v1.PodSpec{ 112 | NodeName: "node3", 113 | }, 114 | } 115 | 116 | podNoNode := v1.Pod{ 117 | ObjectMeta: metav1.ObjectMeta{ 118 | Name: "sts-2", 119 | GenerateName: "sts-", 120 | Annotations: map[string]string{}, 121 | Labels: map[string]string{ 122 | controllerRevisionHashLabelKey: "hash", 123 | }, 124 | Namespace: "default", 125 | }, 126 | Spec: v1.PodSpec{ 127 | NodeName: "", 128 | }, 129 | } 130 | 131 | podUpToDate := v1.Pod{ 132 | ObjectMeta: metav1.ObjectMeta{ 133 | Name: "sts-3", 134 | GenerateName: "sts-", 135 | Annotations: map[string]string{}, 136 | Labels: map[string]string{ 137 | controllerRevisionHashLabelKey: "hash", 138 | }, 139 | Namespace: "default", 140 | }, 141 | Spec: v1.PodSpec{ 142 | NodeName: "node2", 143 | }, 144 | } 145 | 146 | sr := &mockResource{ 147 | eds: &zv1.ElasticsearchDataSet{ 148 | TypeMeta: metav1.TypeMeta{}, 149 | ObjectMeta: metav1.ObjectMeta{}, 150 | Spec: zv1.ElasticsearchDataSetSpec{}, 151 | Status: zv1.ElasticsearchDataSetStatus{}, 152 | }, 153 | } 154 | 155 | sts := &appsv1.StatefulSet{ 156 | Status: appsv1.StatefulSetStatus{ 157 | UpdateRevision: "hash", 158 | }, 159 | } 160 | 161 | priorityNodes := map[string]v1.Node{ 162 | "node2": {}, 163 | } 164 | 165 | unschedulableNodes := map[string]v1.Node{ 166 | "node3": {}, 167 | } 168 | 169 | pods := []*v1.Pod{&updatingPod} 170 | 171 | sortedPods, err := prioritizePodsForUpdate(pods, sts, sr, priorityNodes, unschedulableNodes) 172 | assert.NoError(t, err) 173 | assert.Len(t, sortedPods, 1) 174 | assert.Equal(t, updatingPod, sortedPods[0]) 175 | 176 | // updating pod should be prioritized over stsPod 177 | pods = []*v1.Pod{&stsPod, &updatingPod} 178 | sortedPods, err = prioritizePodsForUpdate(pods, sts, sr, priorityNodes, unschedulableNodes) 179 | assert.NoError(t, err) 180 | assert.Len(t, sortedPods, 2) 181 | assert.Equal(t, updatingPod, sortedPods[0]) 182 | 183 | // stsPods should be sorted by ordinal number 184 | pods = []*v1.Pod{&stsPod, &stsPod0} 185 | sortedPods, err = prioritizePodsForUpdate(pods, sts, sr, priorityNodes, unschedulableNodes) 186 | assert.NoError(t, err) 187 | assert.Len(t, sortedPods, 2) 188 | assert.Equal(t, stsPod0, sortedPods[0]) 189 | 190 | // pods on unschedulable nodes should get higher priority 191 | pods = []*v1.Pod{&stsPod, &stsPod2} 192 | sortedPods, err = prioritizePodsForUpdate(pods, sts, sr, priorityNodes, unschedulableNodes) 193 | assert.NoError(t, err) 194 | assert.Len(t, sortedPods, 2) 195 | assert.Equal(t, stsPod2, sortedPods[0]) 196 | 197 | // don't prioritize pods not on a node. 198 | pods = []*v1.Pod{&podNoNode} 199 | sortedPods, err = prioritizePodsForUpdate(pods, sts, sr, priorityNodes, unschedulableNodes) 200 | assert.NoError(t, err) 201 | assert.Len(t, sortedPods, 0) 202 | 203 | // don't prioritize pods part of unscaled statefulset 204 | desiredReplicas := int32(1) 205 | currentReplicas := int32(3) 206 | sr = &mockResource{ 207 | replicas: desiredReplicas, 208 | } 209 | 210 | sts = &appsv1.StatefulSet{ 211 | Spec: appsv1.StatefulSetSpec{ 212 | Replicas: ¤tReplicas, 213 | }, 214 | Status: appsv1.StatefulSetStatus{ 215 | UpdateRevision: "hash", 216 | }, 217 | } 218 | 219 | pods = []*v1.Pod{&podUpToDate} 220 | sortedPods, err = prioritizePodsForUpdate(pods, sts, sr, priorityNodes, unschedulableNodes) 221 | assert.NoError(t, err) 222 | assert.Len(t, sortedPods, 0) 223 | } 224 | 225 | func TestSortStatefulSetPods(t *testing.T) { 226 | pods := make([]*v1.Pod, 0, 13) 227 | for _, num := range []int{12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} { 228 | pods = append(pods, &v1.Pod{ 229 | ObjectMeta: metav1.ObjectMeta{ 230 | Name: fmt.Sprintf("sts-%d", num), 231 | GenerateName: "sts-", 232 | }, 233 | }) 234 | } 235 | 236 | sortedPods, err := sortStatefulSetPods(pods) 237 | assert.NoError(t, err) 238 | assert.Len(t, sortedPods, 13) 239 | assert.Equal(t, sortedPods[12].Name, "sts-12") 240 | assert.Equal(t, sortedPods[0].Name, "sts-0") 241 | } 242 | -------------------------------------------------------------------------------- /operator/recorder.go: -------------------------------------------------------------------------------- 1 | package operator 2 | 3 | import ( 4 | clientv1 "k8s.io/api/core/v1" 5 | clientset "k8s.io/client-go/kubernetes" 6 | "k8s.io/client-go/kubernetes/fake" 7 | "k8s.io/client-go/kubernetes/scheme" 8 | v1core "k8s.io/client-go/kubernetes/typed/core/v1" 9 | kube_record "k8s.io/client-go/tools/record" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // CreateEventRecorder creates an event recorder to send custom events to Kubernetes to be recorded for targeted Kubernetes objects 15 | func createEventRecorder(kubeClient clientset.Interface) kube_record.EventRecorder { 16 | eventBroadcaster := kube_record.NewBroadcaster() 17 | eventBroadcaster.StartLogging(logrus.Infof) 18 | if _, isfake := kubeClient.(*fake.Clientset); !isfake { 19 | eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: v1core.New(kubeClient.CoreV1().RESTClient()).Events("")}) 20 | } 21 | return eventBroadcaster.NewRecorder(scheme.Scheme, clientv1.EventSource{Component: "es-operator"}) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/apis/zalando.org/register.go: -------------------------------------------------------------------------------- 1 | package zalando 2 | 3 | const ( 4 | // GroupName is the group name used in this package. 5 | GroupName = "zalando.org" 6 | ) 7 | -------------------------------------------------------------------------------- /pkg/apis/zalando.org/v1/register.go: -------------------------------------------------------------------------------- 1 | // Package v1 contains API Schema definitions for the zalando v1 API group 2 | // +kubebuilder:object:generate=true 3 | // +groupName=zalando.org 4 | package v1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | 11 | "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org" 12 | ) 13 | 14 | var ( 15 | schemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 16 | // AddToScheme applies all the stored functions to the scheme. A non-nil error 17 | // indicates that one function failed and the attempt was abandoned. 18 | AddToScheme = schemeBuilder.AddToScheme 19 | ) 20 | 21 | // SchemeGroupVersion is the group version used to register these objects. 22 | var SchemeGroupVersion = schema.GroupVersion{Group: zalando.GroupName, Version: "v1"} 23 | 24 | // Resource takes an unqualified resource and returns a Group-qualified GroupResource. 25 | func Resource(resource string) schema.GroupResource { 26 | return SchemeGroupVersion.WithResource(resource).GroupResource() 27 | } 28 | 29 | // addKnownTypes adds the set of types defined in this package to the supplied scheme. 30 | func addKnownTypes(scheme *runtime.Scheme) error { 31 | scheme.AddKnownTypes(SchemeGroupVersion, 32 | &ElasticsearchDataSet{}, 33 | &ElasticsearchDataSetList{}, 34 | &ElasticsearchMetricSet{}, 35 | &ElasticsearchMetricSetList{}, 36 | ) 37 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package versioned 20 | 21 | import ( 22 | "fmt" 23 | "net/http" 24 | 25 | zalandov1 "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/typed/zalando.org/v1" 26 | discovery "k8s.io/client-go/discovery" 27 | rest "k8s.io/client-go/rest" 28 | flowcontrol "k8s.io/client-go/util/flowcontrol" 29 | ) 30 | 31 | type Interface interface { 32 | Discovery() discovery.DiscoveryInterface 33 | ZalandoV1() zalandov1.ZalandoV1Interface 34 | } 35 | 36 | // Clientset contains the clients for groups. 37 | type Clientset struct { 38 | *discovery.DiscoveryClient 39 | zalandoV1 *zalandov1.ZalandoV1Client 40 | } 41 | 42 | // ZalandoV1 retrieves the ZalandoV1Client 43 | func (c *Clientset) ZalandoV1() zalandov1.ZalandoV1Interface { 44 | return c.zalandoV1 45 | } 46 | 47 | // Discovery retrieves the DiscoveryClient 48 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 49 | if c == nil { 50 | return nil 51 | } 52 | return c.DiscoveryClient 53 | } 54 | 55 | // NewForConfig creates a new Clientset for the given config. 56 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 57 | // NewForConfig will generate a rate-limiter in configShallowCopy. 58 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 59 | // where httpClient was generated with rest.HTTPClientFor(c). 60 | func NewForConfig(c *rest.Config) (*Clientset, error) { 61 | configShallowCopy := *c 62 | 63 | if configShallowCopy.UserAgent == "" { 64 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() 65 | } 66 | 67 | // share the transport between all clients 68 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return NewForConfigAndClient(&configShallowCopy, httpClient) 74 | } 75 | 76 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 77 | // Note the http client provided takes precedence over the configured transport values. 78 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 79 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 80 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 81 | configShallowCopy := *c 82 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 83 | if configShallowCopy.Burst <= 0 { 84 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 85 | } 86 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 87 | } 88 | 89 | var cs Clientset 90 | var err error 91 | cs.zalandoV1, err = zalandov1.NewForConfigAndClient(&configShallowCopy, httpClient) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return &cs, nil 101 | } 102 | 103 | // NewForConfigOrDie creates a new Clientset for the given config and 104 | // panics if there is an error in the config. 105 | func NewForConfigOrDie(c *rest.Config) *Clientset { 106 | cs, err := NewForConfig(c) 107 | if err != nil { 108 | panic(err) 109 | } 110 | return cs 111 | } 112 | 113 | // New creates a new Clientset for the given RESTClient. 114 | func New(c rest.Interface) *Clientset { 115 | var cs Clientset 116 | cs.zalandoV1 = zalandov1.New(c) 117 | 118 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 119 | return &cs 120 | } 121 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated clientset. 20 | package versioned 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | clientset "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 23 | zalandov1 "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/typed/zalando.org/v1" 24 | fakezalandov1 "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/typed/zalando.org/v1/fake" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/watch" 27 | "k8s.io/client-go/discovery" 28 | fakediscovery "k8s.io/client-go/discovery/fake" 29 | "k8s.io/client-go/testing" 30 | ) 31 | 32 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 33 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 34 | // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement 35 | // for a real clientset and is mostly useful in simple unit tests. 36 | // 37 | // DEPRECATED: NewClientset replaces this with support for field management, which significantly improves 38 | // server side apply testing. NewClientset is only available when apply configurations are generated (e.g. 39 | // via --with-applyconfig). 40 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 41 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 42 | for _, obj := range objects { 43 | if err := o.Add(obj); err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | cs := &Clientset{tracker: o} 49 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 50 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 51 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 52 | gvr := action.GetResource() 53 | ns := action.GetNamespace() 54 | watch, err := o.Watch(gvr, ns) 55 | if err != nil { 56 | return false, nil, err 57 | } 58 | return true, watch, nil 59 | }) 60 | 61 | return cs 62 | } 63 | 64 | // Clientset implements clientset.Interface. Meant to be embedded into a 65 | // struct to get a default implementation. This makes faking out just the method 66 | // you want to test easier. 67 | type Clientset struct { 68 | testing.Fake 69 | discovery *fakediscovery.FakeDiscovery 70 | tracker testing.ObjectTracker 71 | } 72 | 73 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 74 | return c.discovery 75 | } 76 | 77 | func (c *Clientset) Tracker() testing.ObjectTracker { 78 | return c.tracker 79 | } 80 | 81 | var ( 82 | _ clientset.Interface = &Clientset{} 83 | _ testing.FakeClient = &Clientset{} 84 | ) 85 | 86 | // ZalandoV1 retrieves the ZalandoV1Client 87 | func (c *Clientset) ZalandoV1() zalandov1.ZalandoV1Interface { 88 | return &fakezalandov1.FakeZalandoV1{Fake: &c.Fake} 89 | } 90 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | zalandov1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var scheme = runtime.NewScheme() 31 | var codecs = serializer.NewCodecFactory(scheme) 32 | 33 | var localSchemeBuilder = runtime.SchemeBuilder{ 34 | zalandov1.AddToScheme, 35 | } 36 | 37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 38 | // of clientsets, like in: 39 | // 40 | // import ( 41 | // "k8s.io/client-go/kubernetes" 42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 44 | // ) 45 | // 46 | // kclientset, _ := kubernetes.NewForConfig(c) 47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 48 | // 49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 50 | // correctly. 51 | var AddToScheme = localSchemeBuilder.AddToScheme 52 | 53 | func init() { 54 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 55 | utilruntime.Must(AddToScheme(scheme)) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package scheme 20 | 21 | import ( 22 | zalandov1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var Scheme = runtime.NewScheme() 31 | var Codecs = serializer.NewCodecFactory(Scheme) 32 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 33 | var localSchemeBuilder = runtime.SchemeBuilder{ 34 | zalandov1.AddToScheme, 35 | } 36 | 37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 38 | // of clientsets, like in: 39 | // 40 | // import ( 41 | // "k8s.io/client-go/kubernetes" 42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 44 | // ) 45 | // 46 | // kclientset, _ := kubernetes.NewForConfig(c) 47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 48 | // 49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 50 | // correctly. 51 | var AddToScheme = localSchemeBuilder.AddToScheme 52 | 53 | func init() { 54 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 55 | utilruntime.Must(AddToScheme(Scheme)) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/elasticsearchdataset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 25 | scheme "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/scheme" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | gentype "k8s.io/client-go/gentype" 30 | ) 31 | 32 | // ElasticsearchDataSetsGetter has a method to return a ElasticsearchDataSetInterface. 33 | // A group's client should implement this interface. 34 | type ElasticsearchDataSetsGetter interface { 35 | ElasticsearchDataSets(namespace string) ElasticsearchDataSetInterface 36 | } 37 | 38 | // ElasticsearchDataSetInterface has methods to work with ElasticsearchDataSet resources. 39 | type ElasticsearchDataSetInterface interface { 40 | Create(ctx context.Context, elasticsearchDataSet *v1.ElasticsearchDataSet, opts metav1.CreateOptions) (*v1.ElasticsearchDataSet, error) 41 | Update(ctx context.Context, elasticsearchDataSet *v1.ElasticsearchDataSet, opts metav1.UpdateOptions) (*v1.ElasticsearchDataSet, error) 42 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 43 | UpdateStatus(ctx context.Context, elasticsearchDataSet *v1.ElasticsearchDataSet, opts metav1.UpdateOptions) (*v1.ElasticsearchDataSet, error) 44 | Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error 45 | DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error 46 | Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ElasticsearchDataSet, error) 47 | List(ctx context.Context, opts metav1.ListOptions) (*v1.ElasticsearchDataSetList, error) 48 | Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) 49 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ElasticsearchDataSet, err error) 50 | ElasticsearchDataSetExpansion 51 | } 52 | 53 | // elasticsearchDataSets implements ElasticsearchDataSetInterface 54 | type elasticsearchDataSets struct { 55 | *gentype.ClientWithList[*v1.ElasticsearchDataSet, *v1.ElasticsearchDataSetList] 56 | } 57 | 58 | // newElasticsearchDataSets returns a ElasticsearchDataSets 59 | func newElasticsearchDataSets(c *ZalandoV1Client, namespace string) *elasticsearchDataSets { 60 | return &elasticsearchDataSets{ 61 | gentype.NewClientWithList[*v1.ElasticsearchDataSet, *v1.ElasticsearchDataSetList]( 62 | "elasticsearchdatasets", 63 | c.RESTClient(), 64 | scheme.ParameterCodec, 65 | namespace, 66 | func() *v1.ElasticsearchDataSet { return &v1.ElasticsearchDataSet{} }, 67 | func() *v1.ElasticsearchDataSetList { return &v1.ElasticsearchDataSetList{} }), 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/elasticsearchmetricset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 25 | scheme "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/scheme" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | gentype "k8s.io/client-go/gentype" 30 | ) 31 | 32 | // ElasticsearchMetricSetsGetter has a method to return a ElasticsearchMetricSetInterface. 33 | // A group's client should implement this interface. 34 | type ElasticsearchMetricSetsGetter interface { 35 | ElasticsearchMetricSets(namespace string) ElasticsearchMetricSetInterface 36 | } 37 | 38 | // ElasticsearchMetricSetInterface has methods to work with ElasticsearchMetricSet resources. 39 | type ElasticsearchMetricSetInterface interface { 40 | Create(ctx context.Context, elasticsearchMetricSet *v1.ElasticsearchMetricSet, opts metav1.CreateOptions) (*v1.ElasticsearchMetricSet, error) 41 | Update(ctx context.Context, elasticsearchMetricSet *v1.ElasticsearchMetricSet, opts metav1.UpdateOptions) (*v1.ElasticsearchMetricSet, error) 42 | Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error 43 | DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error 44 | Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ElasticsearchMetricSet, error) 45 | List(ctx context.Context, opts metav1.ListOptions) (*v1.ElasticsearchMetricSetList, error) 46 | Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) 47 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ElasticsearchMetricSet, err error) 48 | ElasticsearchMetricSetExpansion 49 | } 50 | 51 | // elasticsearchMetricSets implements ElasticsearchMetricSetInterface 52 | type elasticsearchMetricSets struct { 53 | *gentype.ClientWithList[*v1.ElasticsearchMetricSet, *v1.ElasticsearchMetricSetList] 54 | } 55 | 56 | // newElasticsearchMetricSets returns a ElasticsearchMetricSets 57 | func newElasticsearchMetricSets(c *ZalandoV1Client, namespace string) *elasticsearchMetricSets { 58 | return &elasticsearchMetricSets{ 59 | gentype.NewClientWithList[*v1.ElasticsearchMetricSet, *v1.ElasticsearchMetricSetList]( 60 | "elasticsearchmetricsets", 61 | c.RESTClient(), 62 | scheme.ParameterCodec, 63 | namespace, 64 | func() *v1.ElasticsearchMetricSet { return &v1.ElasticsearchMetricSet{} }, 65 | func() *v1.ElasticsearchMetricSetList { return &v1.ElasticsearchMetricSetList{} }), 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/fake/fake_elasticsearchdataset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | labels "k8s.io/apimachinery/pkg/labels" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | testing "k8s.io/client-go/testing" 30 | ) 31 | 32 | // FakeElasticsearchDataSets implements ElasticsearchDataSetInterface 33 | type FakeElasticsearchDataSets struct { 34 | Fake *FakeZalandoV1 35 | ns string 36 | } 37 | 38 | var elasticsearchdatasetsResource = v1.SchemeGroupVersion.WithResource("elasticsearchdatasets") 39 | 40 | var elasticsearchdatasetsKind = v1.SchemeGroupVersion.WithKind("ElasticsearchDataSet") 41 | 42 | // Get takes name of the elasticsearchDataSet, and returns the corresponding elasticsearchDataSet object, and an error if there is any. 43 | func (c *FakeElasticsearchDataSets) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.ElasticsearchDataSet, err error) { 44 | emptyResult := &v1.ElasticsearchDataSet{} 45 | obj, err := c.Fake. 46 | Invokes(testing.NewGetActionWithOptions(elasticsearchdatasetsResource, c.ns, name, options), emptyResult) 47 | 48 | if obj == nil { 49 | return emptyResult, err 50 | } 51 | return obj.(*v1.ElasticsearchDataSet), err 52 | } 53 | 54 | // List takes label and field selectors, and returns the list of ElasticsearchDataSets that match those selectors. 55 | func (c *FakeElasticsearchDataSets) List(ctx context.Context, opts metav1.ListOptions) (result *v1.ElasticsearchDataSetList, err error) { 56 | emptyResult := &v1.ElasticsearchDataSetList{} 57 | obj, err := c.Fake. 58 | Invokes(testing.NewListActionWithOptions(elasticsearchdatasetsResource, elasticsearchdatasetsKind, c.ns, opts), emptyResult) 59 | 60 | if obj == nil { 61 | return emptyResult, err 62 | } 63 | 64 | label, _, _ := testing.ExtractFromListOptions(opts) 65 | if label == nil { 66 | label = labels.Everything() 67 | } 68 | list := &v1.ElasticsearchDataSetList{ListMeta: obj.(*v1.ElasticsearchDataSetList).ListMeta} 69 | for _, item := range obj.(*v1.ElasticsearchDataSetList).Items { 70 | if label.Matches(labels.Set(item.Labels)) { 71 | list.Items = append(list.Items, item) 72 | } 73 | } 74 | return list, err 75 | } 76 | 77 | // Watch returns a watch.Interface that watches the requested elasticsearchDataSets. 78 | func (c *FakeElasticsearchDataSets) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 79 | return c.Fake. 80 | InvokesWatch(testing.NewWatchActionWithOptions(elasticsearchdatasetsResource, c.ns, opts)) 81 | 82 | } 83 | 84 | // Create takes the representation of a elasticsearchDataSet and creates it. Returns the server's representation of the elasticsearchDataSet, and an error, if there is any. 85 | func (c *FakeElasticsearchDataSets) Create(ctx context.Context, elasticsearchDataSet *v1.ElasticsearchDataSet, opts metav1.CreateOptions) (result *v1.ElasticsearchDataSet, err error) { 86 | emptyResult := &v1.ElasticsearchDataSet{} 87 | obj, err := c.Fake. 88 | Invokes(testing.NewCreateActionWithOptions(elasticsearchdatasetsResource, c.ns, elasticsearchDataSet, opts), emptyResult) 89 | 90 | if obj == nil { 91 | return emptyResult, err 92 | } 93 | return obj.(*v1.ElasticsearchDataSet), err 94 | } 95 | 96 | // Update takes the representation of a elasticsearchDataSet and updates it. Returns the server's representation of the elasticsearchDataSet, and an error, if there is any. 97 | func (c *FakeElasticsearchDataSets) Update(ctx context.Context, elasticsearchDataSet *v1.ElasticsearchDataSet, opts metav1.UpdateOptions) (result *v1.ElasticsearchDataSet, err error) { 98 | emptyResult := &v1.ElasticsearchDataSet{} 99 | obj, err := c.Fake. 100 | Invokes(testing.NewUpdateActionWithOptions(elasticsearchdatasetsResource, c.ns, elasticsearchDataSet, opts), emptyResult) 101 | 102 | if obj == nil { 103 | return emptyResult, err 104 | } 105 | return obj.(*v1.ElasticsearchDataSet), err 106 | } 107 | 108 | // UpdateStatus was generated because the type contains a Status member. 109 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 110 | func (c *FakeElasticsearchDataSets) UpdateStatus(ctx context.Context, elasticsearchDataSet *v1.ElasticsearchDataSet, opts metav1.UpdateOptions) (result *v1.ElasticsearchDataSet, err error) { 111 | emptyResult := &v1.ElasticsearchDataSet{} 112 | obj, err := c.Fake. 113 | Invokes(testing.NewUpdateSubresourceActionWithOptions(elasticsearchdatasetsResource, "status", c.ns, elasticsearchDataSet, opts), emptyResult) 114 | 115 | if obj == nil { 116 | return emptyResult, err 117 | } 118 | return obj.(*v1.ElasticsearchDataSet), err 119 | } 120 | 121 | // Delete takes name of the elasticsearchDataSet and deletes it. Returns an error if one occurs. 122 | func (c *FakeElasticsearchDataSets) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 123 | _, err := c.Fake. 124 | Invokes(testing.NewDeleteActionWithOptions(elasticsearchdatasetsResource, c.ns, name, opts), &v1.ElasticsearchDataSet{}) 125 | 126 | return err 127 | } 128 | 129 | // DeleteCollection deletes a collection of objects. 130 | func (c *FakeElasticsearchDataSets) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 131 | action := testing.NewDeleteCollectionActionWithOptions(elasticsearchdatasetsResource, c.ns, opts, listOpts) 132 | 133 | _, err := c.Fake.Invokes(action, &v1.ElasticsearchDataSetList{}) 134 | return err 135 | } 136 | 137 | // Patch applies the patch and returns the patched elasticsearchDataSet. 138 | func (c *FakeElasticsearchDataSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ElasticsearchDataSet, err error) { 139 | emptyResult := &v1.ElasticsearchDataSet{} 140 | obj, err := c.Fake. 141 | Invokes(testing.NewPatchSubresourceActionWithOptions(elasticsearchdatasetsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) 142 | 143 | if obj == nil { 144 | return emptyResult, err 145 | } 146 | return obj.(*v1.ElasticsearchDataSet), err 147 | } 148 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/fake/fake_elasticsearchmetricset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | "context" 23 | 24 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | labels "k8s.io/apimachinery/pkg/labels" 27 | types "k8s.io/apimachinery/pkg/types" 28 | watch "k8s.io/apimachinery/pkg/watch" 29 | testing "k8s.io/client-go/testing" 30 | ) 31 | 32 | // FakeElasticsearchMetricSets implements ElasticsearchMetricSetInterface 33 | type FakeElasticsearchMetricSets struct { 34 | Fake *FakeZalandoV1 35 | ns string 36 | } 37 | 38 | var elasticsearchmetricsetsResource = v1.SchemeGroupVersion.WithResource("elasticsearchmetricsets") 39 | 40 | var elasticsearchmetricsetsKind = v1.SchemeGroupVersion.WithKind("ElasticsearchMetricSet") 41 | 42 | // Get takes name of the elasticsearchMetricSet, and returns the corresponding elasticsearchMetricSet object, and an error if there is any. 43 | func (c *FakeElasticsearchMetricSets) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.ElasticsearchMetricSet, err error) { 44 | emptyResult := &v1.ElasticsearchMetricSet{} 45 | obj, err := c.Fake. 46 | Invokes(testing.NewGetActionWithOptions(elasticsearchmetricsetsResource, c.ns, name, options), emptyResult) 47 | 48 | if obj == nil { 49 | return emptyResult, err 50 | } 51 | return obj.(*v1.ElasticsearchMetricSet), err 52 | } 53 | 54 | // List takes label and field selectors, and returns the list of ElasticsearchMetricSets that match those selectors. 55 | func (c *FakeElasticsearchMetricSets) List(ctx context.Context, opts metav1.ListOptions) (result *v1.ElasticsearchMetricSetList, err error) { 56 | emptyResult := &v1.ElasticsearchMetricSetList{} 57 | obj, err := c.Fake. 58 | Invokes(testing.NewListActionWithOptions(elasticsearchmetricsetsResource, elasticsearchmetricsetsKind, c.ns, opts), emptyResult) 59 | 60 | if obj == nil { 61 | return emptyResult, err 62 | } 63 | 64 | label, _, _ := testing.ExtractFromListOptions(opts) 65 | if label == nil { 66 | label = labels.Everything() 67 | } 68 | list := &v1.ElasticsearchMetricSetList{ListMeta: obj.(*v1.ElasticsearchMetricSetList).ListMeta} 69 | for _, item := range obj.(*v1.ElasticsearchMetricSetList).Items { 70 | if label.Matches(labels.Set(item.Labels)) { 71 | list.Items = append(list.Items, item) 72 | } 73 | } 74 | return list, err 75 | } 76 | 77 | // Watch returns a watch.Interface that watches the requested elasticsearchMetricSets. 78 | func (c *FakeElasticsearchMetricSets) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 79 | return c.Fake. 80 | InvokesWatch(testing.NewWatchActionWithOptions(elasticsearchmetricsetsResource, c.ns, opts)) 81 | 82 | } 83 | 84 | // Create takes the representation of a elasticsearchMetricSet and creates it. Returns the server's representation of the elasticsearchMetricSet, and an error, if there is any. 85 | func (c *FakeElasticsearchMetricSets) Create(ctx context.Context, elasticsearchMetricSet *v1.ElasticsearchMetricSet, opts metav1.CreateOptions) (result *v1.ElasticsearchMetricSet, err error) { 86 | emptyResult := &v1.ElasticsearchMetricSet{} 87 | obj, err := c.Fake. 88 | Invokes(testing.NewCreateActionWithOptions(elasticsearchmetricsetsResource, c.ns, elasticsearchMetricSet, opts), emptyResult) 89 | 90 | if obj == nil { 91 | return emptyResult, err 92 | } 93 | return obj.(*v1.ElasticsearchMetricSet), err 94 | } 95 | 96 | // Update takes the representation of a elasticsearchMetricSet and updates it. Returns the server's representation of the elasticsearchMetricSet, and an error, if there is any. 97 | func (c *FakeElasticsearchMetricSets) Update(ctx context.Context, elasticsearchMetricSet *v1.ElasticsearchMetricSet, opts metav1.UpdateOptions) (result *v1.ElasticsearchMetricSet, err error) { 98 | emptyResult := &v1.ElasticsearchMetricSet{} 99 | obj, err := c.Fake. 100 | Invokes(testing.NewUpdateActionWithOptions(elasticsearchmetricsetsResource, c.ns, elasticsearchMetricSet, opts), emptyResult) 101 | 102 | if obj == nil { 103 | return emptyResult, err 104 | } 105 | return obj.(*v1.ElasticsearchMetricSet), err 106 | } 107 | 108 | // Delete takes name of the elasticsearchMetricSet and deletes it. Returns an error if one occurs. 109 | func (c *FakeElasticsearchMetricSets) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { 110 | _, err := c.Fake. 111 | Invokes(testing.NewDeleteActionWithOptions(elasticsearchmetricsetsResource, c.ns, name, opts), &v1.ElasticsearchMetricSet{}) 112 | 113 | return err 114 | } 115 | 116 | // DeleteCollection deletes a collection of objects. 117 | func (c *FakeElasticsearchMetricSets) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { 118 | action := testing.NewDeleteCollectionActionWithOptions(elasticsearchmetricsetsResource, c.ns, opts, listOpts) 119 | 120 | _, err := c.Fake.Invokes(action, &v1.ElasticsearchMetricSetList{}) 121 | return err 122 | } 123 | 124 | // Patch applies the patch and returns the patched elasticsearchMetricSet. 125 | func (c *FakeElasticsearchMetricSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ElasticsearchMetricSet, err error) { 126 | emptyResult := &v1.ElasticsearchMetricSet{} 127 | obj, err := c.Fake. 128 | Invokes(testing.NewPatchSubresourceActionWithOptions(elasticsearchmetricsetsResource, c.ns, name, pt, data, opts, subresources...), emptyResult) 129 | 130 | if obj == nil { 131 | return emptyResult, err 132 | } 133 | return obj.(*v1.ElasticsearchMetricSet), err 134 | } 135 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/fake/fake_zalando.org_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | v1 "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/typed/zalando.org/v1" 23 | rest "k8s.io/client-go/rest" 24 | testing "k8s.io/client-go/testing" 25 | ) 26 | 27 | type FakeZalandoV1 struct { 28 | *testing.Fake 29 | } 30 | 31 | func (c *FakeZalandoV1) ElasticsearchDataSets(namespace string) v1.ElasticsearchDataSetInterface { 32 | return &FakeElasticsearchDataSets{c, namespace} 33 | } 34 | 35 | func (c *FakeZalandoV1) ElasticsearchMetricSets(namespace string) v1.ElasticsearchMetricSetInterface { 36 | return &FakeElasticsearchMetricSets{c, namespace} 37 | } 38 | 39 | // RESTClient returns a RESTClient that is used to communicate 40 | // with API server by this client implementation. 41 | func (c *FakeZalandoV1) RESTClient() rest.Interface { 42 | var ret *rest.RESTClient 43 | return ret 44 | } 45 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | type ElasticsearchDataSetExpansion interface{} 22 | 23 | type ElasticsearchMetricSetExpansion interface{} 24 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/zalando.org/v1/zalando.org_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "net/http" 23 | 24 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 25 | "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/scheme" 26 | rest "k8s.io/client-go/rest" 27 | ) 28 | 29 | type ZalandoV1Interface interface { 30 | RESTClient() rest.Interface 31 | ElasticsearchDataSetsGetter 32 | ElasticsearchMetricSetsGetter 33 | } 34 | 35 | // ZalandoV1Client is used to interact with features provided by the zalando.org group. 36 | type ZalandoV1Client struct { 37 | restClient rest.Interface 38 | } 39 | 40 | func (c *ZalandoV1Client) ElasticsearchDataSets(namespace string) ElasticsearchDataSetInterface { 41 | return newElasticsearchDataSets(c, namespace) 42 | } 43 | 44 | func (c *ZalandoV1Client) ElasticsearchMetricSets(namespace string) ElasticsearchMetricSetInterface { 45 | return newElasticsearchMetricSets(c, namespace) 46 | } 47 | 48 | // NewForConfig creates a new ZalandoV1Client for the given config. 49 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 50 | // where httpClient was generated with rest.HTTPClientFor(c). 51 | func NewForConfig(c *rest.Config) (*ZalandoV1Client, error) { 52 | config := *c 53 | if err := setConfigDefaults(&config); err != nil { 54 | return nil, err 55 | } 56 | httpClient, err := rest.HTTPClientFor(&config) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return NewForConfigAndClient(&config, httpClient) 61 | } 62 | 63 | // NewForConfigAndClient creates a new ZalandoV1Client for the given config and http client. 64 | // Note the http client provided takes precedence over the configured transport values. 65 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ZalandoV1Client, error) { 66 | config := *c 67 | if err := setConfigDefaults(&config); err != nil { 68 | return nil, err 69 | } 70 | client, err := rest.RESTClientForConfigAndClient(&config, h) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return &ZalandoV1Client{client}, nil 75 | } 76 | 77 | // NewForConfigOrDie creates a new ZalandoV1Client for the given config and 78 | // panics if there is an error in the config. 79 | func NewForConfigOrDie(c *rest.Config) *ZalandoV1Client { 80 | client, err := NewForConfig(c) 81 | if err != nil { 82 | panic(err) 83 | } 84 | return client 85 | } 86 | 87 | // New creates a new ZalandoV1Client for the given RESTClient. 88 | func New(c rest.Interface) *ZalandoV1Client { 89 | return &ZalandoV1Client{c} 90 | } 91 | 92 | func setConfigDefaults(config *rest.Config) error { 93 | gv := v1.SchemeGroupVersion 94 | config.GroupVersion = &gv 95 | config.APIPath = "/apis" 96 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 97 | 98 | if config.UserAgent == "" { 99 | config.UserAgent = rest.DefaultKubernetesUserAgent() 100 | } 101 | 102 | return nil 103 | } 104 | 105 | // RESTClient returns a RESTClient that is used to communicate 106 | // with API server by this client implementation. 107 | func (c *ZalandoV1Client) RESTClient() rest.Interface { 108 | if c == nil { 109 | return nil 110 | } 111 | return c.restClient 112 | } 113 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/factory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package externalversions 20 | 21 | import ( 22 | reflect "reflect" 23 | sync "sync" 24 | time "time" 25 | 26 | versioned "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 27 | internalinterfaces "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/internalinterfaces" 28 | zalandoorg "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/zalando.org" 29 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | runtime "k8s.io/apimachinery/pkg/runtime" 31 | schema "k8s.io/apimachinery/pkg/runtime/schema" 32 | cache "k8s.io/client-go/tools/cache" 33 | ) 34 | 35 | // SharedInformerOption defines the functional option type for SharedInformerFactory. 36 | type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory 37 | 38 | type sharedInformerFactory struct { 39 | client versioned.Interface 40 | namespace string 41 | tweakListOptions internalinterfaces.TweakListOptionsFunc 42 | lock sync.Mutex 43 | defaultResync time.Duration 44 | customResync map[reflect.Type]time.Duration 45 | transform cache.TransformFunc 46 | 47 | informers map[reflect.Type]cache.SharedIndexInformer 48 | // startedInformers is used for tracking which informers have been started. 49 | // This allows Start() to be called multiple times safely. 50 | startedInformers map[reflect.Type]bool 51 | // wg tracks how many goroutines were started. 52 | wg sync.WaitGroup 53 | // shuttingDown is true when Shutdown has been called. It may still be running 54 | // because it needs to wait for goroutines. 55 | shuttingDown bool 56 | } 57 | 58 | // WithCustomResyncConfig sets a custom resync period for the specified informer types. 59 | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { 60 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 61 | for k, v := range resyncConfig { 62 | factory.customResync[reflect.TypeOf(k)] = v 63 | } 64 | return factory 65 | } 66 | } 67 | 68 | // WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. 69 | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { 70 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 71 | factory.tweakListOptions = tweakListOptions 72 | return factory 73 | } 74 | } 75 | 76 | // WithNamespace limits the SharedInformerFactory to the specified namespace. 77 | func WithNamespace(namespace string) SharedInformerOption { 78 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 79 | factory.namespace = namespace 80 | return factory 81 | } 82 | } 83 | 84 | // WithTransform sets a transform on all informers. 85 | func WithTransform(transform cache.TransformFunc) SharedInformerOption { 86 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 87 | factory.transform = transform 88 | return factory 89 | } 90 | } 91 | 92 | // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. 93 | func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { 94 | return NewSharedInformerFactoryWithOptions(client, defaultResync) 95 | } 96 | 97 | // NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. 98 | // Listers obtained via this SharedInformerFactory will be subject to the same filters 99 | // as specified here. 100 | // Deprecated: Please use NewSharedInformerFactoryWithOptions instead 101 | func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { 102 | return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) 103 | } 104 | 105 | // NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. 106 | func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { 107 | factory := &sharedInformerFactory{ 108 | client: client, 109 | namespace: v1.NamespaceAll, 110 | defaultResync: defaultResync, 111 | informers: make(map[reflect.Type]cache.SharedIndexInformer), 112 | startedInformers: make(map[reflect.Type]bool), 113 | customResync: make(map[reflect.Type]time.Duration), 114 | } 115 | 116 | // Apply all options 117 | for _, opt := range options { 118 | factory = opt(factory) 119 | } 120 | 121 | return factory 122 | } 123 | 124 | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { 125 | f.lock.Lock() 126 | defer f.lock.Unlock() 127 | 128 | if f.shuttingDown { 129 | return 130 | } 131 | 132 | for informerType, informer := range f.informers { 133 | if !f.startedInformers[informerType] { 134 | f.wg.Add(1) 135 | // We need a new variable in each loop iteration, 136 | // otherwise the goroutine would use the loop variable 137 | // and that keeps changing. 138 | informer := informer 139 | go func() { 140 | defer f.wg.Done() 141 | informer.Run(stopCh) 142 | }() 143 | f.startedInformers[informerType] = true 144 | } 145 | } 146 | } 147 | 148 | func (f *sharedInformerFactory) Shutdown() { 149 | f.lock.Lock() 150 | f.shuttingDown = true 151 | f.lock.Unlock() 152 | 153 | // Will return immediately if there is nothing to wait for. 154 | f.wg.Wait() 155 | } 156 | 157 | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { 158 | informers := func() map[reflect.Type]cache.SharedIndexInformer { 159 | f.lock.Lock() 160 | defer f.lock.Unlock() 161 | 162 | informers := map[reflect.Type]cache.SharedIndexInformer{} 163 | for informerType, informer := range f.informers { 164 | if f.startedInformers[informerType] { 165 | informers[informerType] = informer 166 | } 167 | } 168 | return informers 169 | }() 170 | 171 | res := map[reflect.Type]bool{} 172 | for informType, informer := range informers { 173 | res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) 174 | } 175 | return res 176 | } 177 | 178 | // InformerFor returns the SharedIndexInformer for obj using an internal 179 | // client. 180 | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { 181 | f.lock.Lock() 182 | defer f.lock.Unlock() 183 | 184 | informerType := reflect.TypeOf(obj) 185 | informer, exists := f.informers[informerType] 186 | if exists { 187 | return informer 188 | } 189 | 190 | resyncPeriod, exists := f.customResync[informerType] 191 | if !exists { 192 | resyncPeriod = f.defaultResync 193 | } 194 | 195 | informer = newFunc(f.client, resyncPeriod) 196 | informer.SetTransform(f.transform) 197 | f.informers[informerType] = informer 198 | 199 | return informer 200 | } 201 | 202 | // SharedInformerFactory provides shared informers for resources in all known 203 | // API group versions. 204 | // 205 | // It is typically used like this: 206 | // 207 | // ctx, cancel := context.Background() 208 | // defer cancel() 209 | // factory := NewSharedInformerFactory(client, resyncPeriod) 210 | // defer factory.WaitForStop() // Returns immediately if nothing was started. 211 | // genericInformer := factory.ForResource(resource) 212 | // typedInformer := factory.SomeAPIGroup().V1().SomeType() 213 | // factory.Start(ctx.Done()) // Start processing these informers. 214 | // synced := factory.WaitForCacheSync(ctx.Done()) 215 | // for v, ok := range synced { 216 | // if !ok { 217 | // fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) 218 | // return 219 | // } 220 | // } 221 | // 222 | // // Creating informers can also be created after Start, but then 223 | // // Start must be called again: 224 | // anotherGenericInformer := factory.ForResource(resource) 225 | // factory.Start(ctx.Done()) 226 | type SharedInformerFactory interface { 227 | internalinterfaces.SharedInformerFactory 228 | 229 | // Start initializes all requested informers. They are handled in goroutines 230 | // which run until the stop channel gets closed. 231 | // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. 232 | Start(stopCh <-chan struct{}) 233 | 234 | // Shutdown marks a factory as shutting down. At that point no new 235 | // informers can be started anymore and Start will return without 236 | // doing anything. 237 | // 238 | // In addition, Shutdown blocks until all goroutines have terminated. For that 239 | // to happen, the close channel(s) that they were started with must be closed, 240 | // either before Shutdown gets called or while it is waiting. 241 | // 242 | // Shutdown may be called multiple times, even concurrently. All such calls will 243 | // block until all goroutines have terminated. 244 | Shutdown() 245 | 246 | // WaitForCacheSync blocks until all started informers' caches were synced 247 | // or the stop channel gets closed. 248 | WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool 249 | 250 | // ForResource gives generic access to a shared informer of the matching type. 251 | ForResource(resource schema.GroupVersionResource) (GenericInformer, error) 252 | 253 | // InformerFor returns the SharedIndexInformer for obj using an internal 254 | // client. 255 | InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer 256 | 257 | Zalando() zalandoorg.Interface 258 | } 259 | 260 | func (f *sharedInformerFactory) Zalando() zalandoorg.Interface { 261 | return zalandoorg.New(f, f.namespace, f.tweakListOptions) 262 | } 263 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package externalversions 20 | 21 | import ( 22 | "fmt" 23 | 24 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | cache "k8s.io/client-go/tools/cache" 27 | ) 28 | 29 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 30 | // sharedInformers based on type 31 | type GenericInformer interface { 32 | Informer() cache.SharedIndexInformer 33 | Lister() cache.GenericLister 34 | } 35 | 36 | type genericInformer struct { 37 | informer cache.SharedIndexInformer 38 | resource schema.GroupResource 39 | } 40 | 41 | // Informer returns the SharedIndexInformer. 42 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 43 | return f.informer 44 | } 45 | 46 | // Lister returns the GenericLister. 47 | func (f *genericInformer) Lister() cache.GenericLister { 48 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 49 | } 50 | 51 | // ForResource gives generic access to a shared informer of the matching type 52 | // TODO extend this to unknown resources with a client pool 53 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 54 | switch resource { 55 | // Group=zalando.org, Version=v1 56 | case v1.SchemeGroupVersion.WithResource("elasticsearchdatasets"): 57 | return &genericInformer{resource: resource.GroupResource(), informer: f.Zalando().V1().ElasticsearchDataSets().Informer()}, nil 58 | case v1.SchemeGroupVersion.WithResource("elasticsearchmetricsets"): 59 | return &genericInformer{resource: resource.GroupResource(), informer: f.Zalando().V1().ElasticsearchMetricSets().Informer()}, nil 60 | 61 | } 62 | 63 | return nil, fmt.Errorf("no informer found for %v", resource) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package internalinterfaces 20 | 21 | import ( 22 | time "time" 23 | 24 | versioned "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | cache "k8s.io/client-go/tools/cache" 28 | ) 29 | 30 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 31 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 32 | 33 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 34 | type SharedInformerFactory interface { 35 | Start(stopCh <-chan struct{}) 36 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 37 | } 38 | 39 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 40 | type TweakListOptionsFunc func(*v1.ListOptions) 41 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/zalando.org/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package zalando 20 | 21 | import ( 22 | internalinterfaces "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/internalinterfaces" 23 | v1 "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/zalando.org/v1" 24 | ) 25 | 26 | // Interface provides access to each of this group's versions. 27 | type Interface interface { 28 | // V1 provides access to shared informers for resources in V1. 29 | V1() v1.Interface 30 | } 31 | 32 | type group struct { 33 | factory internalinterfaces.SharedInformerFactory 34 | namespace string 35 | tweakListOptions internalinterfaces.TweakListOptionsFunc 36 | } 37 | 38 | // New returns a new Interface. 39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 41 | } 42 | 43 | // V1 returns a new v1.Interface. 44 | func (g *group) V1() v1.Interface { 45 | return v1.New(g.factory, g.namespace, g.tweakListOptions) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/zalando.org/v1/elasticsearchdataset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | time "time" 24 | 25 | zalandoorgv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 26 | versioned "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 27 | internalinterfaces "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/internalinterfaces" 28 | v1 "github.com/zalando-incubator/es-operator/pkg/client/listers/zalando.org/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | runtime "k8s.io/apimachinery/pkg/runtime" 31 | watch "k8s.io/apimachinery/pkg/watch" 32 | cache "k8s.io/client-go/tools/cache" 33 | ) 34 | 35 | // ElasticsearchDataSetInformer provides access to a shared informer and lister for 36 | // ElasticsearchDataSets. 37 | type ElasticsearchDataSetInformer interface { 38 | Informer() cache.SharedIndexInformer 39 | Lister() v1.ElasticsearchDataSetLister 40 | } 41 | 42 | type elasticsearchDataSetInformer struct { 43 | factory internalinterfaces.SharedInformerFactory 44 | tweakListOptions internalinterfaces.TweakListOptionsFunc 45 | namespace string 46 | } 47 | 48 | // NewElasticsearchDataSetInformer constructs a new informer for ElasticsearchDataSet type. 49 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 50 | // one. This reduces memory footprint and number of connections to the server. 51 | func NewElasticsearchDataSetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 52 | return NewFilteredElasticsearchDataSetInformer(client, namespace, resyncPeriod, indexers, nil) 53 | } 54 | 55 | // NewFilteredElasticsearchDataSetInformer constructs a new informer for ElasticsearchDataSet type. 56 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 57 | // one. This reduces memory footprint and number of connections to the server. 58 | func NewFilteredElasticsearchDataSetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 59 | return cache.NewSharedIndexInformer( 60 | &cache.ListWatch{ 61 | ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 62 | if tweakListOptions != nil { 63 | tweakListOptions(&options) 64 | } 65 | return client.ZalandoV1().ElasticsearchDataSets(namespace).List(context.TODO(), options) 66 | }, 67 | WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 68 | if tweakListOptions != nil { 69 | tweakListOptions(&options) 70 | } 71 | return client.ZalandoV1().ElasticsearchDataSets(namespace).Watch(context.TODO(), options) 72 | }, 73 | }, 74 | &zalandoorgv1.ElasticsearchDataSet{}, 75 | resyncPeriod, 76 | indexers, 77 | ) 78 | } 79 | 80 | func (f *elasticsearchDataSetInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 81 | return NewFilteredElasticsearchDataSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 82 | } 83 | 84 | func (f *elasticsearchDataSetInformer) Informer() cache.SharedIndexInformer { 85 | return f.factory.InformerFor(&zalandoorgv1.ElasticsearchDataSet{}, f.defaultInformer) 86 | } 87 | 88 | func (f *elasticsearchDataSetInformer) Lister() v1.ElasticsearchDataSetLister { 89 | return v1.NewElasticsearchDataSetLister(f.Informer().GetIndexer()) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/zalando.org/v1/elasticsearchmetricset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | "context" 23 | time "time" 24 | 25 | zalandoorgv1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 26 | versioned "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 27 | internalinterfaces "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/internalinterfaces" 28 | v1 "github.com/zalando-incubator/es-operator/pkg/client/listers/zalando.org/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | runtime "k8s.io/apimachinery/pkg/runtime" 31 | watch "k8s.io/apimachinery/pkg/watch" 32 | cache "k8s.io/client-go/tools/cache" 33 | ) 34 | 35 | // ElasticsearchMetricSetInformer provides access to a shared informer and lister for 36 | // ElasticsearchMetricSets. 37 | type ElasticsearchMetricSetInformer interface { 38 | Informer() cache.SharedIndexInformer 39 | Lister() v1.ElasticsearchMetricSetLister 40 | } 41 | 42 | type elasticsearchMetricSetInformer struct { 43 | factory internalinterfaces.SharedInformerFactory 44 | tweakListOptions internalinterfaces.TweakListOptionsFunc 45 | namespace string 46 | } 47 | 48 | // NewElasticsearchMetricSetInformer constructs a new informer for ElasticsearchMetricSet type. 49 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 50 | // one. This reduces memory footprint and number of connections to the server. 51 | func NewElasticsearchMetricSetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 52 | return NewFilteredElasticsearchMetricSetInformer(client, namespace, resyncPeriod, indexers, nil) 53 | } 54 | 55 | // NewFilteredElasticsearchMetricSetInformer constructs a new informer for ElasticsearchMetricSet type. 56 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 57 | // one. This reduces memory footprint and number of connections to the server. 58 | func NewFilteredElasticsearchMetricSetInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 59 | return cache.NewSharedIndexInformer( 60 | &cache.ListWatch{ 61 | ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 62 | if tweakListOptions != nil { 63 | tweakListOptions(&options) 64 | } 65 | return client.ZalandoV1().ElasticsearchMetricSets(namespace).List(context.TODO(), options) 66 | }, 67 | WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 68 | if tweakListOptions != nil { 69 | tweakListOptions(&options) 70 | } 71 | return client.ZalandoV1().ElasticsearchMetricSets(namespace).Watch(context.TODO(), options) 72 | }, 73 | }, 74 | &zalandoorgv1.ElasticsearchMetricSet{}, 75 | resyncPeriod, 76 | indexers, 77 | ) 78 | } 79 | 80 | func (f *elasticsearchMetricSetInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 81 | return NewFilteredElasticsearchMetricSetInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 82 | } 83 | 84 | func (f *elasticsearchMetricSetInformer) Informer() cache.SharedIndexInformer { 85 | return f.factory.InformerFor(&zalandoorgv1.ElasticsearchMetricSet{}, f.defaultInformer) 86 | } 87 | 88 | func (f *elasticsearchMetricSetInformer) Lister() v1.ElasticsearchMetricSetLister { 89 | return v1.NewElasticsearchMetricSetLister(f.Informer().GetIndexer()) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/zalando.org/v1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | internalinterfaces "github.com/zalando-incubator/es-operator/pkg/client/informers/externalversions/internalinterfaces" 23 | ) 24 | 25 | // Interface provides access to all the informers in this group version. 26 | type Interface interface { 27 | // ElasticsearchDataSets returns a ElasticsearchDataSetInformer. 28 | ElasticsearchDataSets() ElasticsearchDataSetInformer 29 | // ElasticsearchMetricSets returns a ElasticsearchMetricSetInformer. 30 | ElasticsearchMetricSets() ElasticsearchMetricSetInformer 31 | } 32 | 33 | type version struct { 34 | factory internalinterfaces.SharedInformerFactory 35 | namespace string 36 | tweakListOptions internalinterfaces.TweakListOptionsFunc 37 | } 38 | 39 | // New returns a new Interface. 40 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 41 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 42 | } 43 | 44 | // ElasticsearchDataSets returns a ElasticsearchDataSetInformer. 45 | func (v *version) ElasticsearchDataSets() ElasticsearchDataSetInformer { 46 | return &elasticsearchDataSetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 47 | } 48 | 49 | // ElasticsearchMetricSets returns a ElasticsearchMetricSetInformer. 50 | func (v *version) ElasticsearchMetricSets() ElasticsearchMetricSetInformer { 51 | return &elasticsearchMetricSetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 52 | } 53 | -------------------------------------------------------------------------------- /pkg/client/listers/zalando.org/v1/elasticsearchdataset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 23 | "k8s.io/apimachinery/pkg/labels" 24 | "k8s.io/client-go/listers" 25 | "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // ElasticsearchDataSetLister helps list ElasticsearchDataSets. 29 | // All objects returned here must be treated as read-only. 30 | type ElasticsearchDataSetLister interface { 31 | // List lists all ElasticsearchDataSets in the indexer. 32 | // Objects returned here must be treated as read-only. 33 | List(selector labels.Selector) (ret []*v1.ElasticsearchDataSet, err error) 34 | // ElasticsearchDataSets returns an object that can list and get ElasticsearchDataSets. 35 | ElasticsearchDataSets(namespace string) ElasticsearchDataSetNamespaceLister 36 | ElasticsearchDataSetListerExpansion 37 | } 38 | 39 | // elasticsearchDataSetLister implements the ElasticsearchDataSetLister interface. 40 | type elasticsearchDataSetLister struct { 41 | listers.ResourceIndexer[*v1.ElasticsearchDataSet] 42 | } 43 | 44 | // NewElasticsearchDataSetLister returns a new ElasticsearchDataSetLister. 45 | func NewElasticsearchDataSetLister(indexer cache.Indexer) ElasticsearchDataSetLister { 46 | return &elasticsearchDataSetLister{listers.New[*v1.ElasticsearchDataSet](indexer, v1.Resource("elasticsearchdataset"))} 47 | } 48 | 49 | // ElasticsearchDataSets returns an object that can list and get ElasticsearchDataSets. 50 | func (s *elasticsearchDataSetLister) ElasticsearchDataSets(namespace string) ElasticsearchDataSetNamespaceLister { 51 | return elasticsearchDataSetNamespaceLister{listers.NewNamespaced[*v1.ElasticsearchDataSet](s.ResourceIndexer, namespace)} 52 | } 53 | 54 | // ElasticsearchDataSetNamespaceLister helps list and get ElasticsearchDataSets. 55 | // All objects returned here must be treated as read-only. 56 | type ElasticsearchDataSetNamespaceLister interface { 57 | // List lists all ElasticsearchDataSets in the indexer for a given namespace. 58 | // Objects returned here must be treated as read-only. 59 | List(selector labels.Selector) (ret []*v1.ElasticsearchDataSet, err error) 60 | // Get retrieves the ElasticsearchDataSet from the indexer for a given namespace and name. 61 | // Objects returned here must be treated as read-only. 62 | Get(name string) (*v1.ElasticsearchDataSet, error) 63 | ElasticsearchDataSetNamespaceListerExpansion 64 | } 65 | 66 | // elasticsearchDataSetNamespaceLister implements the ElasticsearchDataSetNamespaceLister 67 | // interface. 68 | type elasticsearchDataSetNamespaceLister struct { 69 | listers.ResourceIndexer[*v1.ElasticsearchDataSet] 70 | } 71 | -------------------------------------------------------------------------------- /pkg/client/listers/zalando.org/v1/elasticsearchmetricset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/zalando-incubator/es-operator/pkg/apis/zalando.org/v1" 23 | "k8s.io/apimachinery/pkg/labels" 24 | "k8s.io/client-go/listers" 25 | "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // ElasticsearchMetricSetLister helps list ElasticsearchMetricSets. 29 | // All objects returned here must be treated as read-only. 30 | type ElasticsearchMetricSetLister interface { 31 | // List lists all ElasticsearchMetricSets in the indexer. 32 | // Objects returned here must be treated as read-only. 33 | List(selector labels.Selector) (ret []*v1.ElasticsearchMetricSet, err error) 34 | // ElasticsearchMetricSets returns an object that can list and get ElasticsearchMetricSets. 35 | ElasticsearchMetricSets(namespace string) ElasticsearchMetricSetNamespaceLister 36 | ElasticsearchMetricSetListerExpansion 37 | } 38 | 39 | // elasticsearchMetricSetLister implements the ElasticsearchMetricSetLister interface. 40 | type elasticsearchMetricSetLister struct { 41 | listers.ResourceIndexer[*v1.ElasticsearchMetricSet] 42 | } 43 | 44 | // NewElasticsearchMetricSetLister returns a new ElasticsearchMetricSetLister. 45 | func NewElasticsearchMetricSetLister(indexer cache.Indexer) ElasticsearchMetricSetLister { 46 | return &elasticsearchMetricSetLister{listers.New[*v1.ElasticsearchMetricSet](indexer, v1.Resource("elasticsearchmetricset"))} 47 | } 48 | 49 | // ElasticsearchMetricSets returns an object that can list and get ElasticsearchMetricSets. 50 | func (s *elasticsearchMetricSetLister) ElasticsearchMetricSets(namespace string) ElasticsearchMetricSetNamespaceLister { 51 | return elasticsearchMetricSetNamespaceLister{listers.NewNamespaced[*v1.ElasticsearchMetricSet](s.ResourceIndexer, namespace)} 52 | } 53 | 54 | // ElasticsearchMetricSetNamespaceLister helps list and get ElasticsearchMetricSets. 55 | // All objects returned here must be treated as read-only. 56 | type ElasticsearchMetricSetNamespaceLister interface { 57 | // List lists all ElasticsearchMetricSets in the indexer for a given namespace. 58 | // Objects returned here must be treated as read-only. 59 | List(selector labels.Selector) (ret []*v1.ElasticsearchMetricSet, err error) 60 | // Get retrieves the ElasticsearchMetricSet from the indexer for a given namespace and name. 61 | // Objects returned here must be treated as read-only. 62 | Get(name string) (*v1.ElasticsearchMetricSet, error) 63 | ElasticsearchMetricSetNamespaceListerExpansion 64 | } 65 | 66 | // elasticsearchMetricSetNamespaceLister implements the ElasticsearchMetricSetNamespaceLister 67 | // interface. 68 | type elasticsearchMetricSetNamespaceLister struct { 69 | listers.ResourceIndexer[*v1.ElasticsearchMetricSet] 70 | } 71 | -------------------------------------------------------------------------------- /pkg/client/listers/zalando.org/v1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | // ElasticsearchDataSetListerExpansion allows custom methods to be added to 22 | // ElasticsearchDataSetLister. 23 | type ElasticsearchDataSetListerExpansion interface{} 24 | 25 | // ElasticsearchDataSetNamespaceListerExpansion allows custom methods to be added to 26 | // ElasticsearchDataSetNamespaceLister. 27 | type ElasticsearchDataSetNamespaceListerExpansion interface{} 28 | 29 | // ElasticsearchMetricSetListerExpansion allows custom methods to be added to 30 | // ElasticsearchMetricSetLister. 31 | type ElasticsearchMetricSetListerExpansion interface{} 32 | 33 | // ElasticsearchMetricSetNamespaceListerExpansion allows custom methods to be added to 34 | // ElasticsearchMetricSetNamespaceLister. 35 | type ElasticsearchMetricSetNamespaceListerExpansion interface{} 36 | -------------------------------------------------------------------------------- /pkg/clientset/clientset.go: -------------------------------------------------------------------------------- 1 | package clientset 2 | 3 | import ( 4 | "fmt" 5 | 6 | clientset "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned" 7 | zalandov1 "github.com/zalando-incubator/es-operator/pkg/client/clientset/versioned/typed/zalando.org/v1" 8 | "k8s.io/client-go/kubernetes" 9 | "k8s.io/client-go/rest" 10 | metrics "k8s.io/metrics/pkg/client/clientset/versioned" 11 | "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" 12 | ) 13 | 14 | type ZInterface = clientset.Interface 15 | 16 | type Clientset struct { 17 | kubernetes.Interface 18 | zInterface clientset.Interface 19 | mInterface metrics.Interface 20 | } 21 | 22 | func (c *Clientset) ZalandoV1() zalandov1.ZalandoV1Interface { 23 | return c.zInterface.ZalandoV1() 24 | } 25 | 26 | func (c *Clientset) MetricsV1Beta1() v1beta1.MetricsV1beta1Interface { 27 | return c.mInterface.MetricsV1beta1() 28 | } 29 | 30 | func NewClientset(kubeConfig *rest.Config) (*Clientset, error) { 31 | client, err := kubernetes.NewForConfig(kubeConfig) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed to setup Kubernetes client: %v", err) 34 | } 35 | 36 | zClient, err := clientset.NewForConfig(kubeConfig) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to setup Kubernetes CRD client: %v", err) 39 | } 40 | 41 | mClient, err := metrics.NewForConfig(kubeConfig) 42 | if err != nil { 43 | return nil, fmt.Errorf("failed to setup Kubernetes metrics client: %v", err) 44 | } 45 | 46 | return &Clientset{ 47 | Interface: client, 48 | zInterface: zClient, 49 | mInterface: mClient, 50 | }, nil 51 | } 52 | --------------------------------------------------------------------------------