Skip to content

Commit

Permalink
Add Go operator (#7)
Browse files Browse the repository at this point in the history
This is the 1st version of the operator written in Go. Using these operator, users can interact with the cluster. The operator consists of CustomResource, represented by CustomResourceDefinition and controller loop, also called reconciliation loop. Coordinators and data instances are created as StatefulSets. Each coordinator and data instance have two services attached to it, ClusterIP and NodePort. ClusterIP is used for internal communication between instances and NodePort for external access to the Bolt server. Controller tests are deleted since they weren't used. Setup job runs as Deployment. License and organization name should be user-provided in the form of Kubernetes Secret.
as51340 authored Aug 14, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 5046a2b commit 5effd3e
Showing 37 changed files with 2,875 additions and 155 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
# Ignore build and test binaries.
bin/
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
as51340
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -12,3 +12,5 @@ bin
*.swp
*.swo
*~

./manager
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "helm-charts"]
path = helm-charts
url = https://github.com/memgraph/helm-charts.git
url = https://github.com/memgraph/helm-charts.git
branch = main
40 changes: 40 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
run:
deadline: 5m
allow-parallel-runners: true

issues:
# don't skip warning about doc comments
# don't exclude the default set of lint
exclude-use-default: false
# restore some of the defaults
# (fill in the rest as needed)
exclude-rules:
- path: "api/*"
linters:
- lll
- path: "internal/*"
linters:
- dupl
- lll
linters:
disable-all: true
enable:
- dupl
- errcheck
- exportloopref
- goconst
- gocyclo
- gofmt
- goimports
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- prealloc
- staticcheck
- typecheck
- unconvert
- unparam
- unused
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -10,3 +10,9 @@ repos:
- id: mixed-line-ending
- id: check-merge-conflict
- id: detect-private-key

- repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-rc.1
hooks:
- id: go-mod-tidy
- id: go-fmt
36 changes: 31 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
# Build the manager binary
FROM quay.io/operator-framework/helm-operator:v1.35.0
FROM golang:1.22 AS builder
ARG TARGETOS
ARG TARGETARCH

ENV HOME=/opt/helm
COPY watches.yaml ${HOME}/watches.yaml
COPY helm-charts ${HOME}/helm-charts
WORKDIR ${HOME}
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/controller/ internal/controller/

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532

ENTRYPOINT ["/manager"]
215 changes: 143 additions & 72 deletions Makefile
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
VERSION ?= 0.0.3
VERSION ?= 0.0.4

# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
@@ -28,7 +28,7 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
# This variable is used to construct full image tags for bundle and catalog images.
#
# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
# my.domain/kubernetes-operator-bundle:$VERSION and my.domain/kubernetes-operator-catalog:$VERSION.
# com/kubernetes-operator-bundle:$VERSION and com/kubernetes-operator-catalog:$VERSION.
IMAGE_TAG_BASE ?= memgraph/kubernetes-operator

# BUNDLE_IMG defines the image:tag used for the bundle.
@@ -52,15 +52,35 @@ OPERATOR_SDK_VERSION ?= v1.35.0

# Image URL to use all building/pushing image targets
IMG ?= $(IMAGE_TAG_BASE):$(VERSION)
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.28.3

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif

# CONTAINER_TOOL defines the container tool to be used for building images.
# Be aware that the target commands are only tested with Docker which is
# scaffolded by default. However, you might want to replace it to use other
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker

# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec

.PHONY: all
all: docker-build
all: build

##@ General

# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk commands is responsible for reading the
# target descriptions by '##'. The awk command is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
@@ -73,87 +93,146 @@ all: docker-build
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ Development

.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration and CustomResourceDefinition objects.
$(CONTROLLER_GEN) crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases

.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

.PHONY: fmt
fmt: ## Run go fmt against code.
go fmt ./...

.PHONY: vet
vet: ## Run go vet against code.
go vet ./...

.PHONY: test
test: manifests generate fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out

# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
test-e2e:
go test ./test/e2e/ -v -ginkgo.v

GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
GOLANGCI_LINT_VERSION ?= v1.54.2
golangci-lint:
@[ -f $(GOLANGCI_LINT) ] || { \
set -e ;\
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\
}

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter & yamllint
$(GOLANGCI_LINT) run

.PHONY: lint-fix
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
$(GOLANGCI_LINT) run --fix

##@ Build

.PHONY: build
build: manifests generate fmt vet ## Build manager binary.
go build -o bin/manager cmd/main.go

.PHONY: run
run: helm-operator ## Run against the configured Kubernetes cluster in ~/.kube/config
$(HELM_OPERATOR) run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go

# If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: ## Build docker image with the manager.
docker build -t ${IMG} .
$(CONTAINER_TOOL) build -t ${IMG} .

.PHONY: docker-push
docker-push: ## Push docker image with the manager.
docker push ${IMG}
$(CONTAINER_TOOL) push ${IMG}

# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/
# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=<myregistry/image:<tag>> than the export will fail)
# To properly provided solutions that supports more than one platform you should use this option.
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
.PHONY: docker-buildx
docker-buildx: ## Build and push docker image for the manager for cross-platform support
- docker buildx create --name project-v3-builder
docker buildx use project-v3-builder
- docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile .
- docker buildx rm project-v3-builder
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
- $(CONTAINER_TOOL) buildx create --name project-v3-builder
$(CONTAINER_TOOL) buildx use project-v3-builder
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
- $(CONTAINER_TOOL) buildx rm project-v3-builder
rm Dockerfile.cross

##@ Deployment

ifndef ignore-not-found
ignore-not-found = false
endif

.PHONY: install
install: kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl apply -f -
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f -

.PHONY: uninstall
uninstall: kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl delete -f -
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -

.PHONY: deploy
deploy: kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image memgraph-kubernetes-operator=${IMG}
$(KUSTOMIZE) build config/default | kubectl apply -f -
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -

.PHONY: undeploy
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/default | kubectl delete -f -
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -

OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
ARCH := $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')
##@ Build Dependencies

.PHONY: kustomize
KUSTOMIZE = $(shell pwd)/bin/kustomize
kustomize: ## Download kustomize locally if necessary.
ifeq (,$(wildcard $(KUSTOMIZE)))
ifeq (,$(shell which kustomize 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(KUSTOMIZE)) ;\
curl -sSLo - https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v5.2.1/kustomize_v5.2.1_$(OS)_$(ARCH).tar.gz | \
tar xzf - -C bin/ ;\
}
else
KUSTOMIZE = $(shell which kustomize)
endif
endif
## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)

.PHONY: helm-operator
HELM_OPERATOR = $(shell pwd)/bin/helm-operator
helm-operator: ## Download helm-operator locally if necessary, preferring the $(pwd)/bin path over global if both exist.
ifeq (,$(wildcard $(HELM_OPERATOR)))
ifeq (,$(shell which helm-operator 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(HELM_OPERATOR)) ;\
curl -sSLo $(HELM_OPERATOR) https://github.com/operator-framework/operator-sdk/releases/download/v1.35.0/helm-operator_$(OS)_$(ARCH) ;\
chmod +x $(HELM_OPERATOR) ;\
}
else
HELM_OPERATOR = $(shell which helm-operator)
endif
endif
## Tool Binaries
KUBECTL ?= kubectl
KUSTOMIZE ?= $(LOCALBIN)/kustomize
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest

## Tool Versions
KUSTOMIZE_VERSION ?= v5.2.1
CONTROLLER_TOOLS_VERSION ?= v0.15.0

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
$(KUSTOMIZE): $(LOCALBIN)
@if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \
echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \
rm -rf $(LOCALBIN)/kustomize; \
fi
test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION)

.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten.
$(CONTROLLER_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)

.PHONY: envtest
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest

.PHONY: operator-sdk
OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk
@@ -163,7 +242,8 @@ ifeq (, $(shell which operator-sdk 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(OPERATOR_SDK)) ;\
curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS)_$(ARCH) ;\
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\
chmod +x $(OPERATOR_SDK) ;\
}
else
@@ -172,9 +252,9 @@ endif
endif

.PHONY: bundle
bundle: kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
$(OPERATOR_SDK) generate kustomize manifests -q
cd config/manager && $(KUSTOMIZE) edit set image memgraph-kubernetes-operator=$(IMG)
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS)
$(OPERATOR_SDK) bundle validate ./bundle

@@ -194,7 +274,8 @@ ifeq (,$(shell which opm 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(OPM)) ;\
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$(OS)-$(ARCH)-opm ;\
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\
chmod +x $(OPM) ;\
}
else
@@ -225,13 +306,3 @@ catalog-build: opm ## Build a catalog image.
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)

HELMIFY ?= helmify

.PHONY: helmify
helmify: $(HELMIFY) ## Download helmify locally if necessary.
$(HELMIFY): $(LOCALBIN)
test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest

helm: config/manifests kustomize helmify
$(KUSTOMIZE) build config/default | $(HELMIFY)
5 changes: 4 additions & 1 deletion PROJECT
Original file line number Diff line number Diff line change
@@ -4,17 +4,20 @@
# More info: https://book.kubebuilder.io/reference/project-config.html
domain: com
layout:
- helm.sdk.operatorframework.io/v1
- go.kubebuilder.io/v4
plugins:
manifests.sdk.operatorframework.io/v2: {}
scorecard.sdk.operatorframework.io/v2: {}
projectName: kubernetes-operator
repo: github.com/memgraph/kubernetes-operator
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
domain: com
group: memgraph
kind: MemgraphHA
path: github.com/memgraph/kubernetes-operator/api/v1
version: v1
version: "3"
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -25,3 +25,97 @@ Check our [Documentation](/docs) to start using our Kubernetes operator.
## License

Please check the [LICENSE](LICENSE) file


# new stuff from go

# kubernetes-operator
// TODO(user): Add simple overview of use/purpose

## Description
// TODO(user): An in-depth paragraph about your project and overview of use

## Getting Started

### Prerequisites
- go version v1.20.0+
- docker version 17.03+.
- kubectl version v1.11.3+.
- Access to a Kubernetes v1.11.3+ cluster.

### To Deploy on the cluster
**Build and push your image to the location specified by `IMG`:**

```sh
make docker-build docker-push IMG=<some-registry>/kubernetes-operator:tag
```

**NOTE:** This image ought to be published in the personal registry you specified.
And it is required to have access to pull the image from the working environment.
Make sure you have the proper permission to the registry if the above commands don’t work.

**Install the CRDs into the cluster:**

```sh
make install
```

**Deploy the Manager to the cluster with the image specified by `IMG`:**

```sh
make deploy IMG=<some-registry>/kubernetes-operator:tag
```

> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
privileges or be logged in as admin.

**Create instances of your solution**
You can apply the samples (examples) from the config/sample:

```sh
kubectl apply -k config/samples/
```

>**NOTE**: Ensure that the samples has default values to test it out.
### To Uninstall
**Delete the instances (CRs) from the cluster:**

```sh
kubectl delete -k config/samples/
```

**Delete the APIs(CRDs) from the cluster:**

```sh
make uninstall
```

**UnDeploy the controller from the cluster:**

```sh
make undeploy
```

## Contributing
// TODO(user): Add detailed information on how you would like others to contribute to this project

**NOTE:** Run `make help` for more information on all potential `make` targets

More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)

## License

Copyright 2024 Memgraph Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
39 changes: 39 additions & 0 deletions api/v1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package v1 contains API Schema definitions for the memgraph v1 API group
// +kubebuilder:object:generate=true
// +groupName=memgraph.com
package v1

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "memgraph.com", Version: "v1"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

/* AddToScheme adds the types in this group-version to the given scheme.
Scheme is an abstraction used in the API Machinery to create a mapping between Go
structures and Group-Version-Kinds.
*/
AddToScheme = SchemeBuilder.AddToScheme
)
132 changes: 132 additions & 0 deletions api/v1/memgraphha_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
In some way this file simulates types.go from k8s.io/api/apps/v1 to define new resources
we are using.
*/

package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// MemgraphHASpec defines the desired state of MemgraphHA
type MemgraphHASpec struct {
Coordinators []Coordinator `json:"coordinators"`
Data []DataItem `json:"data"`
Memgraph MemgraphConfig `json:"memgraph"`
}

type Coordinator struct {
ID string `json:"id"`
BoltPort int `json:"boltPort"`
ManagementPort int `json:"managementPort"`
CoordinatorPort int `json:"coordinatorPort"`
Args []string `json:"args"`
}

type DataItem struct {
ID string `json:"id"`
BoltPort int `json:"boltPort"`
ManagementPort int `json:"managementPort"`
ReplicationPort int `json:"replicationPort"`
Args []string `json:"args"`
}

type MemgraphConfig struct {
Data MemgraphDataConfig `json:"data"`
Coordinators MemgraphCoordinatorsConfig `json:"coordinators"`
Env map[string]string `json:"env"`
Image ImageConfig `json:"image"`
Probes MemgraphProbesConfig `json:"probes"`
}

type MemgraphDataConfig struct {
VolumeClaim VolumeClaimConfig `json:"volumeClaim"`
}

type MemgraphCoordinatorsConfig struct {
VolumeClaim VolumeClaimConfig `json:"volumeClaim"`
}

type VolumeClaimConfig struct {
StoragePVCClassName string `json:"storagePVCClassName"`
StoragePVC bool `json:"storagePVC"`
StoragePVCSize string `json:"storagePVCSize"`
LogPVCClassName string `json:"logPVCClassName"`
LogPVC bool `json:"logPVC"`
LogPVCSize string `json:"logPVCSize"`
}

type ImageConfig struct {
PullPolicy string `json:"pullPolicy"`
Repository string `json:"repository"`
Tag string `json:"tag"`
}

type MemgraphProbesConfig struct {
Liveness ProbeConfig `json:"liveness"`
Readiness ProbeConfig `json:"readiness"`
Startup ProbeConfig `json:"startup"`
}

// ProbeConfig configures individual probes
type ProbeConfig struct {
InitialDelaySeconds int `json:"initialDelaySeconds"`
PeriodSeconds int `json:"periodSeconds"`
FailureThreshold int `json:"failureThreshold,omitempty"`
}

// MemgraphHAStatus defines the observed state of MemgraphHA
type MemgraphHAStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// MemgraphHA is the Schema for the memgraphhas API
/*
Every Kind needs to have two structures: metav1.TypeMeta and metav1.ObjectMeta.
TypeMeta structure contains information about the GVK of the Kind.
ObjectMeta contains metadata for the Kind.
*/
type MemgraphHA struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec MemgraphHASpec `json:"spec,omitempty"`
Status MemgraphHAStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// MemgraphHAList contains a list of MemgraphHA
type MemgraphHAList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []MemgraphHA `json:"items"`
}

func init() {
SchemeBuilder.Register(&MemgraphHA{}, &MemgraphHAList{})
}
298 changes: 298 additions & 0 deletions api/v1/zz_generated.deepcopy.go
131 changes: 131 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"crypto/tls"
"flag"
"os"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"

"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

memgraphv1 "github.com/memgraph/kubernetes-operator/api/v1"
"github.com/memgraph/kubernetes-operator/internal/controller"
//+kubebuilder:scaffold:imports
)

var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)

func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))

utilruntime.Must(memgraphv1.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}

func main() {
var metricsAddr string
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&secureMetrics, "metrics-secure", false,
"If set the metrics endpoint is served securely")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()

ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancelation and
// Rapid Reset CVEs. For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
// - https://github.com/advisories/GHSA-4374-p667-p6c8
disableHTTP2 := func(c *tls.Config) {
setupLog.Info("disabling http/2")
c.NextProtos = []string{"http/1.1"}
}

tlsOpts := []func(*tls.Config){}
if !enableHTTP2 {
tlsOpts = append(tlsOpts, disableHTTP2)
}

webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
})

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
TLSOpts: tlsOpts,
},
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}

if err = (&controller.MemgraphHAReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MemgraphHA")
os.Exit(1)
}
//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
210 changes: 199 additions & 11 deletions config/crd/bases/memgraph.com_memgraphhas.yaml
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.15.0
name: memgraphhas.memgraph.com
spec:
group: memgraph.com
@@ -15,28 +17,214 @@ spec:
- name: v1
schema:
openAPIV3Schema:
description: MemgraphHA is the Schema for the memgraphhas API
description: |-
MemgraphHA is the Schema for the memgraphhas API
Every Kind needs to have two structures: metav1.TypeMeta and metav1.ObjectMeta.
TypeMeta structure contains information about the GVK of the Kind.
ObjectMeta contains metadata for the Kind.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: Spec defines the desired state of MemgraphHA
description: MemgraphHASpec defines the desired state of MemgraphHA
properties:
coordinators:
items:
properties:
args:
items:
type: string
type: array
boltPort:
type: integer
coordinatorPort:
type: integer
id:
type: string
managementPort:
type: integer
required:
- args
- boltPort
- coordinatorPort
- id
- managementPort
type: object
type: array
data:
items:
properties:
args:
items:
type: string
type: array
boltPort:
type: integer
id:
type: string
managementPort:
type: integer
replicationPort:
type: integer
required:
- args
- boltPort
- id
- managementPort
- replicationPort
type: object
type: array
memgraph:
properties:
coordinators:
properties:
volumeClaim:
properties:
logPVC:
type: boolean
logPVCClassName:
type: string
logPVCSize:
type: string
storagePVC:
type: boolean
storagePVCClassName:
type: string
storagePVCSize:
type: string
required:
- logPVC
- logPVCClassName
- logPVCSize
- storagePVC
- storagePVCClassName
- storagePVCSize
type: object
required:
- volumeClaim
type: object
data:
properties:
volumeClaim:
properties:
logPVC:
type: boolean
logPVCClassName:
type: string
logPVCSize:
type: string
storagePVC:
type: boolean
storagePVCClassName:
type: string
storagePVCSize:
type: string
required:
- logPVC
- logPVCClassName
- logPVCSize
- storagePVC
- storagePVCClassName
- storagePVCSize
type: object
required:
- volumeClaim
type: object
env:
additionalProperties:
type: string
type: object
image:
properties:
pullPolicy:
type: string
repository:
type: string
tag:
type: string
required:
- pullPolicy
- repository
- tag
type: object
probes:
properties:
liveness:
description: ProbeConfig configures individual probes
properties:
failureThreshold:
type: integer
initialDelaySeconds:
type: integer
periodSeconds:
type: integer
required:
- initialDelaySeconds
- periodSeconds
type: object
readiness:
description: ProbeConfig configures individual probes
properties:
failureThreshold:
type: integer
initialDelaySeconds:
type: integer
periodSeconds:
type: integer
required:
- initialDelaySeconds
- periodSeconds
type: object
startup:
description: ProbeConfig configures individual probes
properties:
failureThreshold:
type: integer
initialDelaySeconds:
type: integer
periodSeconds:
type: integer
required:
- initialDelaySeconds
- periodSeconds
type: object
required:
- liveness
- readiness
- startup
type: object
required:
- coordinators
- data
- env
- image
- probes
type: object
required:
- coordinators
- data
- memgraph
type: object
x-kubernetes-preserve-unknown-fields: true
status:
description: Status defines the observed state of MemgraphHA
description: MemgraphHAStatus defines the observed state of MemgraphHA
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
17 changes: 17 additions & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -4,3 +4,20 @@
resources:
- bases/memgraph.com_memgraphhas.yaml
#+kubebuilder:scaffold:crdkustomizeresource

patches:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
#- path: patches/webhook_in_memgraphhas.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch

# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- path: patches/cainjection_in_memgraphhas.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch

# [WEBHOOK] To enable webhook, uncomment the following section
# the following config is for teaching kustomize how to do kustomization for CRDs.

#configurations:
#- kustomizeconfig.yaml
19 changes: 19 additions & 0 deletions config/crd/kustomizeconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# This file is for teaching kustomize how to substitute name and namespace reference in CRD
nameReference:
- kind: Service
version: v1
fieldSpecs:
- kind: CustomResourceDefinition
version: v1
group: apiextensions.k8s.io
path: spec/conversion/webhook/clientConfig/service/name

namespace:
- kind: CustomResourceDefinition
version: v1
group: apiextensions.k8s.io
path: spec/conversion/webhook/clientConfig/service/namespace
create: false

varReference:
- path: metadata/annotations
7 changes: 5 additions & 2 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
resources:
- namespace.yaml
- manager.yaml
- namespace.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: memgraph/kubernetes-operator
newTag: 0.0.4
- name: memgraph-kubernetes-operator
newName: memgraph/kubernetes-operator
newTag: 0.0.3
newTag: 0.0.4
2 changes: 1 addition & 1 deletion config/manager/manager.yaml
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ spec:
runAsNonRoot: true
containers:
- args:
image: memgraph/kubernetes-operator:0.0.3 # TODO: (andi) Try to specify this in a single place, currently used by kustomization.yaml and Makefile
image: memgraph/kubernetes-operator:0.0.4
name: manager
securityContext:
readOnlyRootFilesystem: true
76 changes: 40 additions & 36 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
@@ -1,42 +1,52 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: memgraph-kubernetes-operator
rules:
##
## Base operator rules
##
# We need to get namespaces so the operator can read namespaces to ensure they exist
- apiGroups:
- ""
resources:
- namespaces
- pods
- services
- configmaps
- secrets
verbs:
- create
- delete
- get
# We need to manage Helm release secrets
- list
- patch
- update
- watch
- apiGroups:
- ""
- apps
resources:
- secrets
- statefulsets
verbs:
- "*"
# We need to create events on CRs about things happening during reconciliation
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
- batch
resources:
- events
- jobs
verbs:
- create

##
## Rules for memgraph.com/v1, Kind: MemgraphHA
##
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- memgraph.com
resources:
- memgraphhas
- memgraphhas/status
- memgraphhas/finalizers
verbs:
- create
- delete
@@ -45,23 +55,17 @@ rules:
- patch
- update
- watch
- verbs:
- "*"
apiGroups:
- "apps"
resources:
- "statefulsets"
- verbs:
- "*"
apiGroups:
- "batch"
- apiGroups:
- memgraph.com
resources:
- "jobs"
- verbs:
- "*"
apiGroups:
- ""
- memgraphhas/finalizers
verbs:
- update
- apiGroups:
- memgraph.com
resources:
- "services"

#+kubebuilder:scaffold:rules
- memgraphhas/status
verbs:
- get
- patch
- update
15 changes: 7 additions & 8 deletions config/samples/memgraph_v1_ha.yaml
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ spec:
- --log-level=TRACE
- --coordinator-hostname=memgraph-coordinator-1.default.svc.cluster.local
- --log-file=/var/log/memgraph/memgraph.log
- --nuraft-log-file=/var/log/memgraph/memgraph.log

- id: "2"
boltPort: 7687
@@ -28,13 +27,13 @@ spec:
- --experimental-enabled=high-availability
- --coordinator-id=2
- --coordinator-port=12000

- --management-port=10000
- --bolt-port=7687
- --also-log-to-stderr
- --log-level=TRACE
- --coordinator-hostname=memgraph-coordinator-2.default.svc.cluster.local
- --log-file=/var/log/memgraph/memgraph.log
- --nuraft-log-file=/var/log/memgraph/memgraph.log

- id: "3"
boltPort: 7687
@@ -50,7 +49,6 @@ spec:
- --log-level=TRACE
- --coordinator-hostname=memgraph-coordinator-3.default.svc.cluster.local
- --log-file=/var/log/memgraph/memgraph.log
- --nuraft-log-file=/var/log/memgraph/memgraph.log


data:
@@ -81,12 +79,12 @@ spec:
memgraph:
data:
volumeClaim:
storagePVCClassName: ""
storagePVC: true
storagePVCSize: 1Gi
logPVCClassName: ""
logPVC: true
logPVCSize: 256Mi
storagePVCClassName: ""
storagePVC: true
storagePVCSize: 1Gi
coordinators:
volumeClaim:
logPVCClassName: ""
@@ -96,13 +94,13 @@ spec:
storagePVC: true
storagePVCSize: 1Gi

env:
env: # This can be removed I think
MEMGRAPH_ENTERPRISE_LICENSE: "${MEMGRAPH_ENTERPRISE_LICENSE}"
MEMGRAPH_ORGANIZATION_NAME: "${MEMGRAPH_ORGANIZATION_NAME}"
image:
pullPolicy: IfNotPresent
repository: memgraph/memgraph
tag: 2.18.1
tag: 2.18.1 # I think we should read this value in controller code.
probes:
liveness:
initialDelaySeconds: 30
@@ -111,5 +109,6 @@ spec:
initialDelaySeconds: 5
periodSeconds: 5
startup:
initialDelaySeconds: 5
failureThreshold: 30
periodSeconds: 10
23 changes: 12 additions & 11 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ git clone --recurse-submodules git@github.com:memgraph/kubernetes-operator.git
## Install K8 resources

```bash
kubectl apply -k config/default
make deploy
```

This command will use operator's image from Memgraph's DockerHub and create all necessary Kubernetes resources for running an operator.
@@ -28,6 +28,7 @@ kubectl get clusterrolebindings -n memgraph-operator-system
kubectl get clusterroles -n memgraph-operator-system
kubectl get deployments -n memgraph-operator-system
kubectl get pods -n memgraph-operator-system
kubectl get services -n memgraph-operator-system
```

CustomResourceDefinition `memgraphhas.memgraph.com`, whose job is to monitor CustomResource `MemgraphHA`, will also get created and you can verify
@@ -39,19 +40,18 @@ kubectl get crds -A

## Start Memgraph High Availability Cluster

We already provide sample cluster in `config/samples/memgraph_v1_ha.yaml`. You only need to set your license information by setting
environment variables `MEMGRAPH_ORGANIZATION_NAME` and `MEMGRAPH_ENTERPRISE_LICENSE` in your local environment with:
We already provide sample cluster in `config/samples/memgraph_v1_ha.yaml`. You only need to set your license information by
creating a Kubernetes Secret containing licensing info. You can do this in a following way:

```bash
export MEMGRAPH_ORGANIZATION_NAME="<YOUR_ORGANIZATION_NAME>"
export MEMGRAPH_ENTERPRISE_LICENSE="<MEMGRAPH_ENTERPRISE_LICENSE>"
kubectl create secret generic memgraph-secrets \
--from-literal=MEMGRAPH_ENTERPRISE_LICENSE="<YOUR_LICENSE_INFO>" \
--from-literal=MEMGRAPH_ORGANIZATION_NAME="<YOUR_ORGANIZATION_NAME>"
```

Start Memgraph HA cluster with `envsubst < config/samples/memgraph_v1_ha.yaml | kubectl apply -f -`. (The `envsubst command` is a part of the `gettext` package.)
Instead of using `envsubst` command, you can directly set environment variables in `config/samples/memgraph_v1_ha.yaml`.
Start Memgraph HA cluster with `kubectl apply -f config/samples/memgraph_v1_ha.yaml`.


After ~40s, you should be able to see instances in the output of `kubectl get pods -A`:
After approx. 60s, you should be able to see instances in the output of `kubectl get pods -A`.


You can now find URL of any coordinator instances by running e.g `minikube service list` and connect to see the state of the cluster by running
@@ -62,7 +62,8 @@ You can now find URL of any coordinator instances by running e.g `minikube servi
## Clear resources

```bash
kubectl delete -f config/samples/memgraph_v1_ha.yaml
kubectl delete -f config/samples/memgraph_v1_ha.yaml # For deleting cluster
kubectl delete pvc --all # Or leave them if you want to use persistent storage
kubectl delete -k config/default
kubectl delete secret memgraph-secrets
make undeploy
```
72 changes: 72 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module github.com/memgraph/kubernetes-operator

go 1.22.0

toolchain go1.22.5

require (
github.com/go-logr/logr v1.4.2
github.com/onsi/ginkgo/v2 v2.19.0
github.com/onsi/gomega v1.33.1
k8s.io/api v0.30.3
k8s.io/apimachinery v0.30.3
k8s.io/client-go v0.30.3
sigs.k8s.io/controller-runtime v0.18.4
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.30.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
175 changes: 175 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8 h1:ssNFCCVmib/GQSzx3uCWyfMgOamLGWuGqlMS77Y1m3Y=
github.com/google/pprof v0.0.0-20240722153945-304e4f0156b8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ=
k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04=
k8s.io/apiextensions-apiserver v0.30.3 h1:oChu5li2vsZHx2IvnGP3ah8Nj3KyqG3kRSaKmijhB9U=
k8s.io/apiextensions-apiserver v0.30.3/go.mod h1:uhXxYDkMAvl6CJw4lrDN4CPbONkF3+XL9cacCT44kV4=
k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc=
k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k=
k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f h1:2sXuKesAYbRHxL3aE2PN6zX/gcJr22cjrsej+W784Tc=
k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw=
sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
15 changes: 15 additions & 0 deletions hack/boilerplate.go.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Copyright 2024 Memgraph Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
23 changes: 23 additions & 0 deletions internal/controller/memgraphha_constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

var boltPort int = 7687
var coordinatorPort int = 12000
var mgmtPort int = 10000
var replicationPort int = 20000
var image string = "memgraph/memgraph:2.18.1"
152 changes: 152 additions & 0 deletions internal/controller/memgraphha_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"

"k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"

memgraphv1 "github.com/memgraph/kubernetes-operator/api/v1"
)

//+kubebuilder:rbac:groups=memgraph.com,resources=memgraphhas,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=memgraph.com,resources=memgraphhas/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=memgraph.com,resources=memgraphhas/finalizers,verbs=update

// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile
func (r *MemgraphHAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)

memgraphha := &memgraphv1.MemgraphHA{}
err := r.Get(ctx, req.NamespacedName, memgraphha)
if err != nil {
if errors.IsNotFound(err) {
logger.Info("MemgraphHA resource not found. Ignoring since object must be deleted.")
return ctrl.Result{}, nil
}
logger.Error(err, "Failed to get MemgraphHA")
return ctrl.Result{}, err
}

logger.Info("Started reconciliation MemgrahHA")

for coordId := 1; coordId <= 3; coordId++ {
// ClusterIP
coordClusterIPStatus, coordClusterIPErr := r.reconcileCoordClusterIPService(ctx, memgraphha, &logger, coordId)
if coordClusterIPErr != nil {
logger.Info("Error returned when reconciling ClusterIP Returning empty Result with error.", "coordId", coordId)
return ctrl.Result{}, coordClusterIPErr
}

if coordClusterIPStatus == true {
logger.Info("ClusterIP has been created. Returning Result with the request for requeing with error set to nil.", "coordId", coordId)
return ctrl.Result{Requeue: true}, nil
}

// NodePort
coordNodePortStatus, coordNodePortErr := r.reconcileCoordNodePortService(ctx, memgraphha, &logger, coordId)
if coordNodePortErr != nil {
logger.Info("Error returned when reconciling NodePort. Returning empty Result with error.", "coordId", coordId)
return ctrl.Result{}, coordNodePortErr
}

if coordNodePortStatus == true {
logger.Info("NodePort has been created. Returning Result with the request for requeing with error set to nil.", "coordId", coordId)
return ctrl.Result{Requeue: true}, nil
}

// Coordinator
coordStatus, coordErr := r.reconcileCoordinator(ctx, memgraphha, &logger, coordId)
if coordErr != nil {
logger.Info("Error returned when reconciling coordinator. Returning empty Result with error.", "coordId", coordId)
return ctrl.Result{}, coordErr
}

if coordStatus == true {
logger.Info("Coordinator has been created. Returning Result with the request for requeing with error set to nil.", "coordId", coordId)
return ctrl.Result{Requeue: true}, nil
}
}

logger.Info("Reconciliation of coordinators finished without actions needed.")

for dataInstanceId := 0; dataInstanceId <= 1; dataInstanceId++ {
// ClusterIP
dataInstanceClusterIPStatus, dataInstanceClusterIPErr := r.reconcileDataInstanceClusterIPService(ctx, memgraphha, &logger, dataInstanceId)
if dataInstanceClusterIPErr != nil {
logger.Info("Error returned when reconciling ClusterIP. Returning empty Result with error.", "dataInstanceId", dataInstanceId)
return ctrl.Result{}, dataInstanceClusterIPErr
}

if dataInstanceClusterIPStatus == true {
logger.Info("ClusterIP has been created. Returning Result with the request for requeing with error set to nil.", "dataInstanceId", dataInstanceId)
return ctrl.Result{Requeue: true}, nil
}

// NodePort
dataInstanceNodePortStatus, dataInstanceNodePortErr := r.reconcileDataInstanceNodePortService(ctx, memgraphha, &logger, dataInstanceId)
if dataInstanceNodePortErr != nil {
logger.Info("Error returned when reconciling NodePort. Returning empty Result with error.", "dataInstanceId", dataInstanceId)
return ctrl.Result{}, dataInstanceNodePortErr
}

if dataInstanceNodePortStatus == true {
logger.Info("NodePort has been created. Returning Result with the request for requeing with error set to nil.", "dataInstanceId", dataInstanceId)
return ctrl.Result{Requeue: true}, nil
}

// Data instance
dataInstancesStatus, dataInstancesErr := r.reconcileDataInstance(ctx, memgraphha, &logger, dataInstanceId)
if dataInstancesErr != nil {
logger.Info("Error returned when reconciling data instance. Returning empty Result with error.", "dataInstanceId", dataInstanceId)
return ctrl.Result{}, dataInstancesErr
}

if dataInstancesStatus == true {
logger.Info("Data instance has been created. Returning Result with the request for requeing with error=nil.", "dataInstanceId", dataInstanceId)
return ctrl.Result{Requeue: true}, nil
}
}

logger.Info("Reconciliation of data instances finished without actions needed.")

setupJobStatus, setupJobErr := r.reconcileSetupJob(ctx, memgraphha, &logger)
if setupJobErr != nil {
logger.Info("Error returned when reconciling coordinator. Returning empty Result with error.")
return ctrl.Result{}, setupJobErr
}

// Since it is currently the last step, we don't need to requeue
if setupJobStatus == true {
logger.Info("SetupJob has been created.")
}

logger.Info("Reconciliation of MemgraphHA finished.")
// The resource doesn't need to be reconciled anymore
return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *MemgraphHAReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&memgraphv1.MemgraphHA{}).
Complete(r)
}
236 changes: 236 additions & 0 deletions internal/controller/memgraphha_coord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
copyright 2024 memgraph ltd.
licensed under the apache license, version 2.0 (the "license");
you may not use this file except in compliance with the license.
you may obtain a copy of the license at
http://www.apache.org/licenses/license-2.0
unless required by applicable law or agreed to in writing, software
distributed under the license is distributed on an "as is" basis,
without warranties or conditions of any kind, either express or implied.
see the license for the specific language governing permissions and
limitations under the license.
*/

package controller

import (
"context"
"fmt"

"github.com/go-logr/logr"
memgraphv1 "github.com/memgraph/kubernetes-operator/api/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
)

/*
Returns bool, error tuple. If error exists, the caller should return with error and status will always be set to true.
If there is no error, we must look at bool status which when true will say that the coordinator was createdand we need to requeue
or that nothing was done and we can continue with the next step of reconciliation.
*/
func (r *MemgraphHAReconciler) reconcileCoordinator(ctx context.Context, memgraphha *memgraphv1.MemgraphHA, logger *logr.Logger, coordId int) (bool, error) {
name := fmt.Sprintf("memgraph-coordinator-%d", coordId)
logger.Info("Started reconciling", "StatefulSet", name)
coordStatefulSet := &appsv1.StatefulSet{}
err := r.Get(ctx, types.NamespacedName{Name: name, Namespace: memgraphha.Namespace}, coordStatefulSet)

if err == nil {
logger.Info("StatefulSet already exists.", "StatefulSet", name)
return false, nil
}

if errors.IsNotFound(err) {
coord := r.createStatefulSetForCoord(memgraphha, coordId)
logger.Info("Creating a new StatefulSet", "StatefulSet.Namespace", coord.Namespace, "StatefulSet.Name", coord.Name)
err := r.Create(ctx, coord)
if err != nil {
logger.Error(err, "Failed to create new StatefulSet", "StatefulSet.Namespace", coord.Namespace, "StatefulSet.Name", coord.Name)
return true, err
}
logger.Info("StatefulSet is created.", "StatefulSet", name)
return true, nil
}

logger.Error(err, "Failed to fetch StatefulSet", "StatefulSet", name)
return true, err
}

func (r *MemgraphHAReconciler) createStatefulSetForCoord(memgraphha *memgraphv1.MemgraphHA, coordId int) *appsv1.StatefulSet {
coordName := fmt.Sprintf("memgraph-coordinator-%d", coordId)
serviceName := coordName // service has the same name as the coordinator
labels := createCoordLabels(coordName)
replicas := int32(1)
containerName := "memgraph-coordinator"
args := []string{
fmt.Sprintf("--coordinator-id=%d", coordId),
fmt.Sprintf("--coordinator-port=%d", coordinatorPort),
fmt.Sprintf("--management-port=%d", mgmtPort),
fmt.Sprintf("--bolt-port=%d", boltPort),
fmt.Sprintf("--coordinator-hostname=%s.default.svc.cluster.local", coordName),
"--experimental-enabled=high-availability",
"--also-log-to-stderr",
"--log-level=TRACE",
"--log-file=/var/log/memgraph/memgraph.log",
}
volumeLibName := fmt.Sprintf("%s-lib-storage", coordName)
volumeLibSize := "1Gi"
volumeLogName := fmt.Sprintf("%s-log-storage", coordName)
volumeLogSize := "256Mi"
initContainerName := "init"
initContainerCommand := []string{
"/bin/sh",
"-c",
}
initContainerArgs := []string{"chown -R memgraph:memgraph /var/log; chown -R memgraph:memgraph /var/lib"}
initContainerPrivileged := true
initContainerReadOnlyRootFilesystem := false
initContainerRunAsNonRoot := false
initContainerRunAsUser := int64(0)

coord := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: coordName,
Namespace: memgraphha.Namespace,
},
Spec: appsv1.StatefulSetSpec{
ServiceName: serviceName,
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: initContainerName,
Image: image,
VolumeMounts: []corev1.VolumeMount{
{
Name: volumeLibName,
MountPath: "/var/lib/memgraph",
},
{
Name: volumeLogName,
MountPath: "/var/log/memgraph",
},
},
Command: initContainerCommand,
Args: initContainerArgs,
SecurityContext: &corev1.SecurityContext{
Privileged: &initContainerPrivileged,
ReadOnlyRootFilesystem: &initContainerReadOnlyRootFilesystem,
RunAsNonRoot: &initContainerRunAsNonRoot,
RunAsUser: &initContainerRunAsUser,
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"all"},
Add: []corev1.Capability{"CHOWN"},
},
},
},
},

Containers: []corev1.Container{{
Name: containerName,
Image: image,
ImagePullPolicy: corev1.PullAlways, // set to PullIfNotPresent when testing with local image
Ports: []corev1.ContainerPort{
{
ContainerPort: int32(boltPort),
Name: "bolt",
},
{
ContainerPort: int32(mgmtPort),
Name: "management",
},
{
ContainerPort: int32(coordinatorPort),
Name: "coordinator",
},
},
Args: args,
Env: []corev1.EnvVar{
{
Name: "MEMGRAPH_ENTERPRISE_LICENSE",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "memgraph-secrets",
},
Key: "MEMGRAPH_ENTERPRISE_LICENSE",
},
},
},
{
Name: "MEMGRAPH_ORGANIZATION_NAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "memgraph-secrets",
},
Key: "MEMGRAPH_ORGANIZATION_NAME",
},
},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: volumeLibName,
MountPath: "/var/lib/memgraph",
},
{
Name: volumeLogName,
MountPath: "/var/log/memgraph",
},
},
}},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: volumeLibName,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(volumeLibSize),
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: volumeLogName,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(volumeLogSize),
},
},
},
},
},
},
}

ctrl.SetControllerReference(memgraphha, coord, r.Scheme)
return coord
}

func createCoordLabels(coordName string) map[string]string {
return map[string]string{"app": coordName}
}
156 changes: 156 additions & 0 deletions internal/controller/memgraphha_coord_services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/go-logr/logr"
memgraphv1 "github.com/memgraph/kubernetes-operator/api/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
)

func (r *MemgraphHAReconciler) reconcileCoordNodePortService(ctx context.Context, memgraphha *memgraphv1.MemgraphHA, logger *logr.Logger, coordId int) (bool, error) {
serviceName := fmt.Sprintf("memgraph-coordinator-%d-external", coordId)
logger.Info("Started reconciling NodePort service", "NodePort", serviceName)

coordNodePortService := &corev1.Service{}
err := r.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: memgraphha.Namespace}, coordNodePortService)

if err == nil {
logger.Info("NodePort already exists.", "NodePort", serviceName)
return false, nil
}

if errors.IsNotFound(err) {
nodePort := r.createCoordNodePort(memgraphha, coordId)
logger.Info("Creating a new NodePort", "NodePort.Namespace", nodePort.Namespace, "NodePort.Name", nodePort.Name)
err := r.Create(ctx, nodePort)
if err != nil {
logger.Error(err, "Failed to create new NodePort", "NodePort.Namespace", nodePort.Namespace, "NodePort.Name", nodePort.Name)
return true, err
}
logger.Info("NodePort is created.", "NodePort", serviceName)
return true, nil
}

logger.Error(err, "Failed to fetch NodePort", "NodePort", serviceName)
return true, err

}

func (r *MemgraphHAReconciler) createCoordNodePort(memgraphha *memgraphv1.MemgraphHA, coordId int) *corev1.Service {
serviceName := fmt.Sprintf("memgraph-coordinator-%d-external", coordId)
coordName := fmt.Sprintf("memgraph-coordinator-%d", coordId)

coordNodePort := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: memgraphha.Namespace,
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeNodePort,
Selector: createCoordLabels(coordName),
Ports: []corev1.ServicePort{
{
Name: "bolt",
Protocol: corev1.ProtocolTCP,
Port: int32(boltPort),
TargetPort: intstr.FromInt(boltPort),
},
},
},
}

ctrl.SetControllerReference(memgraphha, coordNodePort, r.Scheme)
return coordNodePort
}

func (r *MemgraphHAReconciler) reconcileCoordClusterIPService(ctx context.Context, memgraphha *memgraphv1.MemgraphHA, logger *logr.Logger, coordId int) (bool, error) {
serviceName := fmt.Sprintf("memgraph-coordinator-%d", coordId)
logger.Info("Started reconciling ClusterIP service", "ClusterIP", serviceName)

coordClusterIPService := &corev1.Service{}
err := r.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: memgraphha.Namespace}, coordClusterIPService)

if err == nil {
logger.Info("ClusterIP already exists.", "ClusterIP", serviceName)
return false, nil
}

if errors.IsNotFound(err) {
clusterIP := r.createCoordClusterIP(memgraphha, coordId)
logger.Info("Creating a new ClusterIP", "ClusterIP.Namespace", clusterIP.Namespace, "ClusterIP.Name", clusterIP.Name)
err := r.Create(ctx, clusterIP)
if err != nil {
logger.Error(err, "Failed to create new ClusterIP", "ClusterIP.Namespace", clusterIP.Namespace, "ClusterIP.Name", clusterIP.Name)
return true, err
}
logger.Info("ClusterIP is created.", "ClusterIP", serviceName)
return true, nil
}

logger.Error(err, "Failed to fetch ClusterIP", "ClusterIP", serviceName)
return true, err

}

func (r *MemgraphHAReconciler) createCoordClusterIP(memgraphha *memgraphv1.MemgraphHA, coordId int) *corev1.Service {
serviceName := fmt.Sprintf("memgraph-coordinator-%d", coordId)
coordName := serviceName

coordClusterIP := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: memgraphha.Namespace,
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
Selector: createCoordLabels(coordName),
Ports: []corev1.ServicePort{
{
Name: "bolt",
Protocol: corev1.ProtocolTCP,
Port: int32(boltPort),
TargetPort: intstr.FromInt(boltPort),
},
{
Name: "coordinator",
Protocol: corev1.ProtocolTCP,
Port: int32(coordinatorPort),
TargetPort: intstr.FromInt(coordinatorPort),
},
{
Name: "management",
Protocol: corev1.ProtocolTCP,
Port: int32(mgmtPort),
TargetPort: intstr.FromInt(mgmtPort),
},
},
},
}

ctrl.SetControllerReference(memgraphha, coordClusterIP, r.Scheme)
return coordClusterIP
}
229 changes: 229 additions & 0 deletions internal/controller/memgraphha_data_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
Copyright 2024 Memgraph Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"
"fmt"

"github.com/go-logr/logr"
memgraphv1 "github.com/memgraph/kubernetes-operator/api/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
)

func (r *MemgraphHAReconciler) reconcileDataInstance(ctx context.Context, memgraphha *memgraphv1.MemgraphHA, logger *logr.Logger, dataInstanceId int) (bool, error) {
name := fmt.Sprintf("memgraph-data-%d", dataInstanceId)
logger.Info("Started reconciling", "StatefulSet", name)
dataInstanceStatefulSet := &appsv1.StatefulSet{}
err := r.Get(ctx, types.NamespacedName{Name: name, Namespace: memgraphha.Namespace}, dataInstanceStatefulSet)

if err == nil {
logger.Info("StatefulSet already exists.", "StatefulSet", name)
return false, nil
}

if errors.IsNotFound(err) {
dataInstance := r.createStatefulSetForDataInstance(memgraphha, dataInstanceId)
logger.Info("Creating a new StatefulSet", "StatefulSet.Namespace", dataInstance.Namespace, "StatefulSet.Name", dataInstance.Name)
err := r.Create(ctx, dataInstance)
if err != nil {
logger.Error(err, "Failed to create new StatefulSet", "StatefulSet.Namespace", dataInstance.Namespace, "StatefulSet.Name", dataInstance.Name)
return true, err
}
logger.Info("StatefulSet is created.", "StatefulSet", name)
return true, nil
}

logger.Error(err, "Failed to fetch StatefulSet", "StatefulSet", name)
return true, err

}

func (r *MemgraphHAReconciler) createStatefulSetForDataInstance(memgraphha *memgraphv1.MemgraphHA, dataInstanceId int) *appsv1.StatefulSet {
dataInstanceName := fmt.Sprintf("memgraph-data-%d", dataInstanceId)
serviceName := dataInstanceName
labels := createDataInstanceLabels(dataInstanceName)
replicas := int32(1)
containerName := "memgraph-data"
args := []string{
fmt.Sprintf("--management-port=%d", mgmtPort),
fmt.Sprintf("--bolt-port=%d", boltPort),
"--experimental-enabled=high-availability",
"--also-log-to-stderr",
"--log-level=TRACE",
"--log-file=/var/log/memgraph/memgraph.log",
}
volumeLibName := fmt.Sprintf("%s-lib-storage", dataInstanceName)
volumeLibSize := "1Gi"
volumeLogName := fmt.Sprintf("%s-log-storage", dataInstanceName)
volumeLogSize := "256Mi"
initContainerName := "init"
initContainerCommand := []string{
"/bin/sh",
"-c",
}
initContainerArgs := []string{"chown -R memgraph:memgraph /var/log; chown -R memgraph:memgraph /var/lib"}
initContainerPrivileged := true
initContainerReadOnlyRootFilesystem := false
initContainerRunAsNonRoot := false
initContainerRunAsUser := int64(0)

data := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: dataInstanceName,
Namespace: memgraphha.Namespace,
},
Spec: appsv1.StatefulSetSpec{
ServiceName: serviceName,
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: initContainerName,
Image: image,
VolumeMounts: []corev1.VolumeMount{
{
Name: volumeLibName,
MountPath: "/var/lib/memgraph",
},
{
Name: volumeLogName,
MountPath: "/var/log/memgraph",
},
},
Command: initContainerCommand,
Args: initContainerArgs,
SecurityContext: &corev1.SecurityContext{
Privileged: &initContainerPrivileged,
ReadOnlyRootFilesystem: &initContainerReadOnlyRootFilesystem,
RunAsNonRoot: &initContainerRunAsNonRoot,
RunAsUser: &initContainerRunAsUser,
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"all"},
Add: []corev1.Capability{"CHOWN"},
},
},
},
},

Containers: []corev1.Container{{
Name: containerName,
Image: image,
ImagePullPolicy: corev1.PullAlways, // set to PullIfNotPresent when testing with local image
Ports: []corev1.ContainerPort{
{
ContainerPort: int32(boltPort),
Name: "bolt",
},
{
ContainerPort: int32(mgmtPort),
Name: "management",
},
{
ContainerPort: int32(replicationPort),
Name: "replication",
},
},
Args: args,
Env: []corev1.EnvVar{
{
Name: "MEMGRAPH_ENTERPRISE_LICENSE",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "memgraph-secrets",
},
Key: "MEMGRAPH_ENTERPRISE_LICENSE",
},
},
},
{
Name: "MEMGRAPH_ORGANIZATION_NAME",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "memgraph-secrets",
},
Key: "MEMGRAPH_ORGANIZATION_NAME",
},
},
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: volumeLibName,
MountPath: "/var/lib/memgraph",
},
{
Name: volumeLogName,
MountPath: "/var/log/memgraph",
},
},
}},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: volumeLibName,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(volumeLibSize),
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: volumeLogName,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(volumeLogSize),
},
},
},
},
},
},
}

ctrl.SetControllerReference(memgraphha, data, r.Scheme)
return data
}

func createDataInstanceLabels(dataInstanceName string) map[string]string {
return map[string]string{"app": dataInstanceName}
}
Loading

0 comments on commit 5effd3e

Please sign in to comment.