diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index ec51f73e0..0e49e7fff 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -84,7 +84,7 @@ jobs: echo "This is not a pull request. Skipping..." exit 0 fi - make tidy update-generated synchronize-v2alpha1-with-v1 generate-internal sync fmt yamlfmt + make tidy update-generated synchronize-v2alpha1-with-v1 generate-internal sync fmt yamlfmt license git checkout -- go.sum # ignore changes in go.sum if [ ! -z "$(git status --porcelain)" ]; then echo "There are uncommited changes!" diff --git a/CHANGELOG.md b/CHANGELOG.md index b7d9770dd..bc318d1d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A) - (Feature) ArangoRoute CRD +- (Feature) ArangoRoute Operator ## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23) - (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries diff --git a/Makefile b/Makefile index b82382f3a..6e9106f98 100644 --- a/Makefile +++ b/Makefile @@ -777,7 +777,7 @@ tools-min: update-vendor .PHONY: tools tools: tools-min @echo ">> Fetching gci" - @GOBIN=$(GOPATH)/bin go install github.com/daixiang0/gci@v0.3.0 + @GOBIN=$(GOPATH)/bin go install github.com/daixiang0/gci@v0.13.4 @echo ">> Fetching yamlfmt" @GOBIN=$(GOPATH)/bin go install github.com/google/yamlfmt/cmd/yamlfmt@v0.10.0 @echo ">> Downloading protobuf compiler..." diff --git a/README.md b/README.md index 634034fa6..ddcded77c 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ Flags: --kubernetes.max-batch-size int Size of batch during objects read (default 256) --kubernetes.qps float32 Number of queries per second for k8s API (default 15) --log.format string Set log format. Allowed values: 'pretty', 'JSON'. If empty, default format is used (default "pretty") - --log.level stringArray Set log levels in format or =. Possible loggers: action, agency, api-server, assertion, backup-operator, chaos-monkey, crd, deployment, deployment-ci, deployment-reconcile, deployment-replication, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, http, inspector, integrations, k8s-client, monitor, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, pod_compare, root, root-event-recorder, server, server-authentication (default [info]) + --log.level stringArray Set log levels in format or =. Possible loggers: action, agency, api-server, assertion, backup-operator, chaos-monkey, crd, deployment, deployment-ci, deployment-reconcile, deployment-replication, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, http, inspector, integrations, k8s-client, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, pod_compare, root, root-event-recorder, server, server-authentication (default [info]) --log.sampling If true, operator will try to minimize duplication of logging events (default true) --memory-limit uint Define memory limit for hard shutdown and the dump of goroutines. Used for testing --metrics.excluded-prefixes stringArray List of the excluded metrics prefixes @@ -192,6 +192,7 @@ Flags: --operator.deployment Enable to run the ArangoDeployment operator --operator.deployment-replication Enable to run the ArangoDeploymentReplication operator --operator.ml Enable to run the ArangoML operator + --operator.networking Enable to run the Networking operator --operator.reconciliation.retry.count int Count of retries during Object Update operations in the Reconciliation loop (default 25) --operator.reconciliation.retry.delay duration Delay between Object Update operations in the Reconciliation loop (default 1s) --operator.storage Enable to run the ArangoLocalStorage operator diff --git a/chart/kube-arangodb-arm64/templates/deployment.yaml b/chart/kube-arangodb-arm64/templates/deployment.yaml index 51b35d9a7..49958189d 100644 --- a/chart/kube-arangodb-arm64/templates/deployment.yaml +++ b/chart/kube-arangodb-arm64/templates/deployment.yaml @@ -117,6 +117,9 @@ spec: {{ if .Values.operator.features.analytics }} - --operator.analytics {{- end }} +{{ if .Values.operator.features.networking }} + - --operator.networking +{{- end }} {{ if .Values.operator.features.k8sToK8sClusterSync }} - --operator.k2k-cluster-sync {{- end }} diff --git a/chart/kube-arangodb-arm64/templates/networking-operator/cluster-role-binding.yaml b/chart/kube-arangodb-arm64/templates/networking-operator/cluster-role-binding.yaml new file mode 100644 index 000000000..ece410ff9 --- /dev/null +++ b/chart/kube-arangodb-arm64/templates/networking-operator/cluster-role-binding.yaml @@ -0,0 +1,26 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-arm64/templates/networking-operator/cluster-role.yaml b/chart/kube-arangodb-arm64/templates/networking-operator/cluster-role.yaml new file mode 100644 index 000000000..45840ac01 --- /dev/null +++ b/chart/kube-arangodb-arm64/templates/networking-operator/cluster-role.yaml @@ -0,0 +1,22 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch"] + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-arm64/templates/networking-operator/role-binding.yaml b/chart/kube-arangodb-arm64/templates/networking-operator/role-binding.yaml new file mode 100644 index 000000000..29802d1d8 --- /dev/null +++ b/chart/kube-arangodb-arm64/templates/networking-operator/role-binding.yaml @@ -0,0 +1,25 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "kube-arangodb.rbac" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-arm64/templates/networking-operator/role.yaml b/chart/kube-arangodb-arm64/templates/networking-operator/role.yaml new file mode 100644 index 000000000..38339a312 --- /dev/null +++ b/chart/kube-arangodb-arm64/templates/networking-operator/role.yaml @@ -0,0 +1,68 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: + - "ml.arangodb.com" + resources: + - "arangomlextensions" + - "arangomlextensions/status" + - "arangomlbatchjobs" + - "arangomlbatchjobs/status" + - "arangomlcronjobs" + - "arangomlcronjobs/status" + - "arangomlstorages" + - "arangomlstorages/status" + verbs: + - "*" + - apiGroups: + - "scheduler.arangodb.com" + resources: + - "arangoprofiles" + - "arangoprofiles/status" + verbs: + - "*" + - apiGroups: + - "database.arangodb.com" + resources: + - "arangodeployments" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "rbac.authorization.k8s.io" + resources: + - "roles" + - "rolebindings" + verbs: ["*"] + - apiGroups: + - "batch" + resources: + - "cronjobs" + - "jobs" + verbs: ["*"] + - apiGroups: ["apps"] + resources: + - "statefulsets" + verbs: ["*"] + - apiGroups: [""] + resources: + - "pods" + - "secrets" + - "services" + - "serviceaccounts" + verbs: ["*"] +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise-arm64/templates/deployment.yaml b/chart/kube-arangodb-enterprise-arm64/templates/deployment.yaml index 51b35d9a7..49958189d 100644 --- a/chart/kube-arangodb-enterprise-arm64/templates/deployment.yaml +++ b/chart/kube-arangodb-enterprise-arm64/templates/deployment.yaml @@ -117,6 +117,9 @@ spec: {{ if .Values.operator.features.analytics }} - --operator.analytics {{- end }} +{{ if .Values.operator.features.networking }} + - --operator.networking +{{- end }} {{ if .Values.operator.features.k8sToK8sClusterSync }} - --operator.k2k-cluster-sync {{- end }} diff --git a/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/cluster-role-binding.yaml b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/cluster-role-binding.yaml new file mode 100644 index 000000000..ece410ff9 --- /dev/null +++ b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/cluster-role-binding.yaml @@ -0,0 +1,26 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/cluster-role.yaml b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/cluster-role.yaml new file mode 100644 index 000000000..45840ac01 --- /dev/null +++ b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/cluster-role.yaml @@ -0,0 +1,22 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch"] + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/role-binding.yaml b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/role-binding.yaml new file mode 100644 index 000000000..29802d1d8 --- /dev/null +++ b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/role-binding.yaml @@ -0,0 +1,25 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "kube-arangodb.rbac" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/role.yaml b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/role.yaml new file mode 100644 index 000000000..38339a312 --- /dev/null +++ b/chart/kube-arangodb-enterprise-arm64/templates/networking-operator/role.yaml @@ -0,0 +1,68 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: + - "ml.arangodb.com" + resources: + - "arangomlextensions" + - "arangomlextensions/status" + - "arangomlbatchjobs" + - "arangomlbatchjobs/status" + - "arangomlcronjobs" + - "arangomlcronjobs/status" + - "arangomlstorages" + - "arangomlstorages/status" + verbs: + - "*" + - apiGroups: + - "scheduler.arangodb.com" + resources: + - "arangoprofiles" + - "arangoprofiles/status" + verbs: + - "*" + - apiGroups: + - "database.arangodb.com" + resources: + - "arangodeployments" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "rbac.authorization.k8s.io" + resources: + - "roles" + - "rolebindings" + verbs: ["*"] + - apiGroups: + - "batch" + resources: + - "cronjobs" + - "jobs" + verbs: ["*"] + - apiGroups: ["apps"] + resources: + - "statefulsets" + verbs: ["*"] + - apiGroups: [""] + resources: + - "pods" + - "secrets" + - "services" + - "serviceaccounts" + verbs: ["*"] +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise/templates/deployment.yaml b/chart/kube-arangodb-enterprise/templates/deployment.yaml index 51b35d9a7..49958189d 100644 --- a/chart/kube-arangodb-enterprise/templates/deployment.yaml +++ b/chart/kube-arangodb-enterprise/templates/deployment.yaml @@ -117,6 +117,9 @@ spec: {{ if .Values.operator.features.analytics }} - --operator.analytics {{- end }} +{{ if .Values.operator.features.networking }} + - --operator.networking +{{- end }} {{ if .Values.operator.features.k8sToK8sClusterSync }} - --operator.k2k-cluster-sync {{- end }} diff --git a/chart/kube-arangodb-enterprise/templates/networking-operator/cluster-role-binding.yaml b/chart/kube-arangodb-enterprise/templates/networking-operator/cluster-role-binding.yaml new file mode 100644 index 000000000..ece410ff9 --- /dev/null +++ b/chart/kube-arangodb-enterprise/templates/networking-operator/cluster-role-binding.yaml @@ -0,0 +1,26 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise/templates/networking-operator/cluster-role.yaml b/chart/kube-arangodb-enterprise/templates/networking-operator/cluster-role.yaml new file mode 100644 index 000000000..45840ac01 --- /dev/null +++ b/chart/kube-arangodb-enterprise/templates/networking-operator/cluster-role.yaml @@ -0,0 +1,22 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch"] + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise/templates/networking-operator/role-binding.yaml b/chart/kube-arangodb-enterprise/templates/networking-operator/role-binding.yaml new file mode 100644 index 000000000..29802d1d8 --- /dev/null +++ b/chart/kube-arangodb-enterprise/templates/networking-operator/role-binding.yaml @@ -0,0 +1,25 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "kube-arangodb.rbac" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb-enterprise/templates/networking-operator/role.yaml b/chart/kube-arangodb-enterprise/templates/networking-operator/role.yaml new file mode 100644 index 000000000..38339a312 --- /dev/null +++ b/chart/kube-arangodb-enterprise/templates/networking-operator/role.yaml @@ -0,0 +1,68 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: + - "ml.arangodb.com" + resources: + - "arangomlextensions" + - "arangomlextensions/status" + - "arangomlbatchjobs" + - "arangomlbatchjobs/status" + - "arangomlcronjobs" + - "arangomlcronjobs/status" + - "arangomlstorages" + - "arangomlstorages/status" + verbs: + - "*" + - apiGroups: + - "scheduler.arangodb.com" + resources: + - "arangoprofiles" + - "arangoprofiles/status" + verbs: + - "*" + - apiGroups: + - "database.arangodb.com" + resources: + - "arangodeployments" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "rbac.authorization.k8s.io" + resources: + - "roles" + - "rolebindings" + verbs: ["*"] + - apiGroups: + - "batch" + resources: + - "cronjobs" + - "jobs" + verbs: ["*"] + - apiGroups: ["apps"] + resources: + - "statefulsets" + verbs: ["*"] + - apiGroups: [""] + resources: + - "pods" + - "secrets" + - "services" + - "serviceaccounts" + verbs: ["*"] +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb/templates/deployment.yaml b/chart/kube-arangodb/templates/deployment.yaml index 51b35d9a7..49958189d 100644 --- a/chart/kube-arangodb/templates/deployment.yaml +++ b/chart/kube-arangodb/templates/deployment.yaml @@ -117,6 +117,9 @@ spec: {{ if .Values.operator.features.analytics }} - --operator.analytics {{- end }} +{{ if .Values.operator.features.networking }} + - --operator.networking +{{- end }} {{ if .Values.operator.features.k8sToK8sClusterSync }} - --operator.k2k-cluster-sync {{- end }} diff --git a/chart/kube-arangodb/templates/networking-operator/cluster-role-binding.yaml b/chart/kube-arangodb/templates/networking-operator/cluster-role-binding.yaml new file mode 100644 index 000000000..ece410ff9 --- /dev/null +++ b/chart/kube-arangodb/templates/networking-operator/cluster-role-binding.yaml @@ -0,0 +1,26 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb/templates/networking-operator/cluster-role.yaml b/chart/kube-arangodb/templates/networking-operator/cluster-role.yaml new file mode 100644 index 000000000..45840ac01 --- /dev/null +++ b/chart/kube-arangodb/templates/networking-operator/cluster-role.yaml @@ -0,0 +1,22 @@ +{{ if .Values.rbac.enabled -}} +{{ if not (eq .Values.operator.scope "namespaced") -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kube-arangodb.rbac-cluster" . }}-networking + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch"] + +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb/templates/networking-operator/role-binding.yaml b/chart/kube-arangodb/templates/networking-operator/role-binding.yaml new file mode 100644 index 000000000..29802d1d8 --- /dev/null +++ b/chart/kube-arangodb/templates/networking-operator/role-binding.yaml @@ -0,0 +1,25 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "kube-arangodb.rbac" . }}-networking +subjects: + - kind: ServiceAccount + name: {{ template "kube-arangodb.operatorName" . }} + namespace: {{ .Release.Namespace }} + +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb/templates/networking-operator/role.yaml b/chart/kube-arangodb/templates/networking-operator/role.yaml new file mode 100644 index 000000000..38339a312 --- /dev/null +++ b/chart/kube-arangodb/templates/networking-operator/role.yaml @@ -0,0 +1,68 @@ +{{ if .Values.rbac.enabled -}} +{{ if .Values.operator.features.networking -}} + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "kube-arangodb.rbac" . }}-networking + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "kube-arangodb.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + release: {{ .Release.Name }} +rules: + - apiGroups: + - "ml.arangodb.com" + resources: + - "arangomlextensions" + - "arangomlextensions/status" + - "arangomlbatchjobs" + - "arangomlbatchjobs/status" + - "arangomlcronjobs" + - "arangomlcronjobs/status" + - "arangomlstorages" + - "arangomlstorages/status" + verbs: + - "*" + - apiGroups: + - "scheduler.arangodb.com" + resources: + - "arangoprofiles" + - "arangoprofiles/status" + verbs: + - "*" + - apiGroups: + - "database.arangodb.com" + resources: + - "arangodeployments" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "rbac.authorization.k8s.io" + resources: + - "roles" + - "rolebindings" + verbs: ["*"] + - apiGroups: + - "batch" + resources: + - "cronjobs" + - "jobs" + verbs: ["*"] + - apiGroups: ["apps"] + resources: + - "statefulsets" + verbs: ["*"] + - apiGroups: [""] + resources: + - "pods" + - "secrets" + - "services" + - "serviceaccounts" + verbs: ["*"] +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/kube-arangodb/values.yaml b/chart/kube-arangodb/values.yaml index 0ec2b19b7..98ac2e00f 100644 --- a/chart/kube-arangodb/values.yaml +++ b/chart/kube-arangodb/values.yaml @@ -34,6 +34,7 @@ operator: k8sToK8sClusterSync: false ml: false analytics: false + networking: true tolerations: [] rbac: enabled: true diff --git a/cmd/cmd.go b/cmd/cmd.go index b557e5e12..bafe078fa 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -119,6 +119,7 @@ var ( enableApps bool // Run apps operator enableML bool // Run ml operator enableAnalytics bool // Run analytics operator + enableNetworking bool // Run networking operator versionOnly bool // Run only version endpoint, explicitly disabled with other enableK2KClusterSync bool // Run k2kClusterSync operator @@ -184,6 +185,7 @@ var ( appsProbe probe.ReadyProbe mlProbe probe.ReadyProbe analyticsProbe probe.ReadyProbe + networkingProbe probe.ReadyProbe k2KClusterSyncProbe probe.ReadyProbe ) @@ -210,6 +212,7 @@ func init() { f.BoolVar(&operatorOptions.enableApps, "operator.apps", false, "Enable to run the ArangoApps operator") f.BoolVar(&operatorOptions.enableML, "operator.ml", false, "Enable to run the ArangoML operator") f.BoolVar(&operatorOptions.enableAnalytics, "operator.analytics", false, "Enable to run the Analytics operator") + f.BoolVar(&operatorOptions.enableNetworking, "operator.networking", false, "Enable to run the Networking operator") f.BoolVar(&operatorOptions.enableK2KClusterSync, "operator.k2k-cluster-sync", false, "Enable to run the ListSimple operator") f.MarkDeprecated("operator.k2k-cluster-sync", "Enabled within deployment operator") f.BoolVar(&operatorOptions.versionOnly, "operator.version", false, "Enable only version endpoint in Operator") @@ -345,16 +348,17 @@ func executeMain(cmd *cobra.Command, args []string) { // Check operating mode if !operatorOptions.enableDeployment && !operatorOptions.enableDeploymentReplication && !operatorOptions.enableStorage && - !operatorOptions.enableBackup && !operatorOptions.enableApps && !operatorOptions.enableK2KClusterSync && !operatorOptions.enableML && !operatorOptions.enableAnalytics { + !operatorOptions.enableBackup && !operatorOptions.enableApps && !operatorOptions.enableK2KClusterSync && + !operatorOptions.enableML && !operatorOptions.enableAnalytics && !operatorOptions.enableNetworking { if !operatorOptions.versionOnly { if version.GetVersionV1().IsEnterprise() { - logger.Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml, --operator.analytics or any combination of these") + logger.Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml, --operator.analytics, --operator.networking or any combination of these") } else { - logger.Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync or any combination of these") + logger.Fatal("Turn on --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.networking or any combination of these") } } } else if operatorOptions.versionOnly { - logger.Fatal("Options --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml, --operator.analytics cannot be enabled together with --operator.version") + logger.Fatal("Options --operator.deployment, --operator.deployment-replication, --operator.storage, --operator.backup, --operator.apps, --operator.k2k-cluster-sync, --operator.ml, --operator.analytics, --operator.networking cannot be enabled together with --operator.version") } else if !version.GetVersionV1().IsEnterprise() { if operatorOptions.enableML || operatorOptions.enableAnalytics { logger.Fatal("Options --operator.ml, --operator.analytics can be enabled only on the Enterprise Operator") @@ -492,6 +496,10 @@ func executeMain(cmd *cobra.Command, args []string) { Enabled: cfg.EnableAnalytics, Probe: &analyticsProbe, }, + Networking: server.OperatorDependency{ + Enabled: cfg.EnableNetworking, + Probe: &analyticsProbe, + }, ClusterSync: server.OperatorDependency{ Enabled: cfg.EnableK2KClusterSync, Probe: &k2KClusterSyncProbe, @@ -576,6 +584,7 @@ func newOperatorConfigAndDeps(id, namespace, name string) (operator.Config, oper EnableApps: operatorOptions.enableApps, EnableML: operatorOptions.enableML, EnableAnalytics: operatorOptions.enableAnalytics, + EnableNetworking: operatorOptions.enableNetworking, EnableK2KClusterSync: operatorOptions.enableK2KClusterSync, AllowChaos: chaosOptions.allowed, ScalingIntegrationEnabled: operatorOptions.scalingIntegrationEnabled, @@ -596,6 +605,7 @@ func newOperatorConfigAndDeps(id, namespace, name string) (operator.Config, oper AppsProbe: &appsProbe, MlProbe: &mlProbe, AnalyticsProbe: &analyticsProbe, + NetworkingProbe: &networkingProbe, K2KClusterSyncProbe: &k2KClusterSyncProbe, } diff --git a/docs/api/ArangoRoute.V1Alpha1.md b/docs/api/ArangoRoute.V1Alpha1.md index f0e148992..63e46c1b4 100644 --- a/docs/api/ArangoRoute.V1Alpha1.md +++ b/docs/api/ArangoRoute.V1Alpha1.md @@ -12,7 +12,7 @@ title: ArangoRoute V1Alpha1 Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec.go#L27) -Deployment specifies the ArangoDeployment object name +DeploymentName specifies the ArangoDeployment object name *** @@ -85,7 +85,53 @@ Path specifies the Path route ### .status.conditions -Type: `api.Conditions` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status.go#L28) +Type: `api.Conditions` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status.go#L31) Conditions specific to the entire extension +*** + +### .status.deployment.checksum + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L61) + +UID keeps the information about object Checksum + +*** + +### .status.deployment.name + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L52) + +Name of the object + +*** + +### .status.deployment.namespace + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L55) + +Namespace of the object. Should default to the namespace of the parent object + +*** + +### .status.deployment.uid + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L58) + +UID keeps the information about object UID + +*** + +### .status.targets\[int\].tls.insecure + +Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target_tls.go#L27) + +Insecure allows Insecure traffic + +*** + +### .status.targets\[int\].url + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L34) + diff --git a/internal/generators/generator_pkg_operatorv2_handlers.go.tmpl b/internal/generators/generator_pkg_operatorv2_handlers.go.tmpl new file mode 100644 index 000000000..2ac7759ea --- /dev/null +++ b/internal/generators/generator_pkg_operatorv2_handlers.go.tmpl @@ -0,0 +1,49 @@ +package operator + +import ( + "context" + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP{{ .id }}Func{{ .templateVars }} func(ctx context.Context {{- if .inputVars }}, {{ .inputVars }}{{ end }}) (bool, error) + +type HandleP{{ .id }}ConditionFunc{{ .templateVars }} func(ctx context.Context {{- if .inputVars }}, {{ .inputVars }}{{ end }}) (*Condition, bool, error) + +type HandleP{{ .id }}ConditionExtract{{ .templateVars }} func(ctx context.Context {{- if .inputVars }}, {{ .inputVars }}{{ end }}) *api.ConditionList + +func HandleP{{ .id }}{{ .templateVars }}(ctx context.Context{{- if .inputVars }}, {{ .inputVars }}{{ end }}, handler ...HandleP{{ .id }}Func{{ .templateInputVars }}) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx{{- if .cleanRefs }}, {{ .cleanRefs }}{{ end }}) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP{{ .id }}WithStop{{ .templateVars }}(ctx context.Context{{- if .inputVars }}, {{ .inputVars }}{{ end }}, handler ...HandleP{{ .id }}Func{{ .templateInputVars }}) (bool, error) { + changed, err := HandleP{{ .id }}{{ .templateInputVars }}(ctx {{- if .cleanRefs }}, {{ .cleanRefs }}{{ end }}, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP{{ .id }}WithCondition{{ .templateVars }}(ctx context.Context, conditions *api.ConditionList, condition api.ConditionType{{- if .inputVars }}, {{ .inputVars }}{{ end }}, handler ...HandleP{{ .id }}Func{{ .templateInputVars }}) (bool, error) { + changed, err := HandleP{{ .id }}{{ .templateInputVars }}(ctx{{- if .cleanRefs }}, {{ .cleanRefs }}{{ end }}, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP{{ .id }}Condition{{ .templateVars }}(extract HandleP{{ .id }}ConditionExtract{{ .templateInputVars }}, condition api.ConditionType, handler HandleP{{ .id }}ConditionFunc{{ .templateInputVars }}) HandleP{{ .id }}Func{{ .templateInputVars }} { + return func(ctx context.Context {{- if .inputVars }}, {{ .inputVars }}{{ end }}) (bool, error) { + c, changed, err := handler(ctx{{- if .cleanRefs }}, {{ .cleanRefs }}{{ end }}) + return WithConditionChange(extract(ctx{{- if .cleanRefs }}, {{ .cleanRefs }}{{ end }}), condition, c, changed, err) + } +} diff --git a/internal/generators/generator_test.go b/internal/generators/generator_test.go new file mode 100644 index 000000000..22476f3ce --- /dev/null +++ b/internal/generators/generator_test.go @@ -0,0 +1,90 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package generators + +import ( + _ "embed" + "fmt" + "os" + "path" + "testing" + "text/template" + + "github.com/stretchr/testify/require" + + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/strings" +) + +//go:embed generator_pkg_operatorv2_handlers.go.tmpl +var generatorPKGOperatorV2Handlers []byte + +func Test_Generate_PKG_OperatorV2_Handlers(t *testing.T) { + root := os.Getenv("ROOT") + require.NotEmpty(t, root) + + i, err := template.New("metrics").Parse(string(generatorPKGOperatorV2Handlers)) + require.NoError(t, err) + + for id := 0; id < 10; id++ { + t.Run(fmt.Sprintf("%d", id), func(t *testing.T) { + out, err := os.OpenFile(path.Join(root, "pkg/operatorV2", fmt.Sprintf("handler_p%d.generated.go", id)), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + require.NoError(t, err) + + var params []string + + for z := 0; z < id; z++ { + params = append(params, fmt.Sprintf("P%d", z+1)) + } + + cleanVars := strings.Join(params, ", ") + + cleanRefs := strings.Join(util.FormatList(params, func(a string) string { + return strings.ToLower(a) + }), ", ") + + templateVars := strings.Join(params, ", ") + templateInputVars := strings.Join(params, ", ") + inputVars := strings.Join(util.FormatList(params, func(a string) string { + return fmt.Sprintf("%s %s", strings.ToLower(a), a) + }), ", ") + + if templateVars != "" { + templateVars = fmt.Sprintf("[%s any]", templateVars) + } + + if templateInputVars != "" { + templateInputVars = fmt.Sprintf("[%s]", templateInputVars) + } + + require.NoError(t, i.Execute(out, map[string]interface{}{ + "id": id, + "templateVars": templateVars, + "templateInputVars": templateInputVars, + "inputVars": inputVars, + "cleanVars": cleanVars, + "cleanRefs": cleanRefs, + })) + + require.NoError(t, out.Close()) + }) + } +} diff --git a/pkg/apis/networking/v1alpha1/conditions.go b/pkg/apis/networking/v1alpha1/conditions.go new file mode 100644 index 000000000..12866c304 --- /dev/null +++ b/pkg/apis/networking/v1alpha1/conditions.go @@ -0,0 +1,30 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1alpha1 + +import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + +const ( + ReadyCondition api.ConditionType = "Ready" + DeploymentFoundCondition api.ConditionType = "DeploymentFound" + DestinationValidCondition api.ConditionType = "DestinationValid" + SpecValidCondition api.ConditionType = "SpecValid" +) diff --git a/pkg/apis/networking/v1alpha1/route_spec.go b/pkg/apis/networking/v1alpha1/route_spec.go index d98caf3ca..a51f5eba2 100644 --- a/pkg/apis/networking/v1alpha1/route_spec.go +++ b/pkg/apis/networking/v1alpha1/route_spec.go @@ -23,8 +23,8 @@ package v1alpha1 import shared "github.com/arangodb/kube-arangodb/pkg/apis/shared" type ArangoRouteSpec struct { - // Deployment specifies the ArangoDeployment object name - Deployment string `json:"deployment,omitempty"` + // DeploymentName specifies the ArangoDeployment object name + DeploymentName *string `json:"deployment,omitempty"` // Destination defines the route destination Destination *ArangoRouteSpecDestination `json:"destination,omitempty"` @@ -33,15 +33,30 @@ type ArangoRouteSpec struct { Route *ArangoRouteSpecRoute `json:"route,omitempty"` } +func (s *ArangoRouteSpec) GetDestination() *ArangoRouteSpecDestination { + if s == nil || s.Destination == nil { + return nil + } + + return s.Destination +} + +func (s *ArangoRouteSpec) GetRoute() *ArangoRouteSpecRoute { + if s == nil || s.Route == nil { + return nil + } + return s.Route +} + func (s *ArangoRouteSpec) Validate() error { if s == nil { s = &ArangoRouteSpec{} } if err := shared.WithErrors(shared.PrefixResourceErrors("spec", - shared.PrefixResourceError("deployment", shared.ValidateResourceName(s.Deployment)), + shared.PrefixResourceErrors("deployment", shared.ValidateResourceNamePointer(s.DeploymentName)), shared.ValidateRequiredInterfacePath("destination", s.Destination), - shared.ValidateRequiredInterfacePath("route", s.Route), + shared.ValidateOptionalInterfacePath("route", s.Route), )); err != nil { return err } diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination.go b/pkg/apis/networking/v1alpha1/route_spec_destination.go index e491bea21..c8dbb6a74 100644 --- a/pkg/apis/networking/v1alpha1/route_spec_destination.go +++ b/pkg/apis/networking/v1alpha1/route_spec_destination.go @@ -33,15 +33,39 @@ type ArangoRouteSpecDestination struct { TLS *ArangoRouteSpecDestinationTLS `json:"tls,omitempty"` } -func (s *ArangoRouteSpecDestination) Validate() error { - if s == nil { - s = &ArangoRouteSpecDestination{} +func (s *ArangoRouteSpecDestination) GetService() *ArangoRouteSpecDestinationService { + if s == nil || s.Service == nil { + return nil + } + + return s.Service +} + +func (s *ArangoRouteSpecDestination) GetSchema() *ArangoRouteSpecDestinationSchema { + if s == nil || s.Schema == nil { + return nil + } + + return s.Schema +} + +func (s *ArangoRouteSpecDestination) GetTLS() *ArangoRouteSpecDestinationTLS { + if s == nil || s.TLS == nil { + return nil + } + + return s.TLS +} + +func (a *ArangoRouteSpecDestination) Validate() error { + if a == nil { + a = &ArangoRouteSpecDestination{} } if err := shared.WithErrors( - shared.ValidateOptionalInterfacePath("service", s.Service), - shared.ValidateOptionalInterfacePath("schema", s.Schema), - shared.ValidateOptionalInterfacePath("tls", s.TLS), + shared.ValidateOptionalInterfacePath("service", a.Service), + shared.ValidateOptionalInterfacePath("schema", a.Schema), + shared.ValidateOptionalInterfacePath("tls", a.TLS), ); err != nil { return err } diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination_service.go b/pkg/apis/networking/v1alpha1/route_spec_destination_service.go index af55e3edc..9b2c0d9f4 100644 --- a/pkg/apis/networking/v1alpha1/route_spec_destination_service.go +++ b/pkg/apis/networking/v1alpha1/route_spec_destination_service.go @@ -36,12 +36,22 @@ type ArangoRouteSpecDestinationService struct { Port *intstr.IntOrString `json:"port,omitempty"` } -func (s *ArangoRouteSpecDestinationService) Validate() error { - if s == nil { - s = &ArangoRouteSpecDestinationService{} +func (a *ArangoRouteSpecDestinationService) GetPort() *intstr.IntOrString { + if a == nil || a.Port == nil { + return nil } - if err := shared.WithErrors(s.Object.Validate()); err != nil { + return a.Port +} + +func (a *ArangoRouteSpecDestinationService) Validate() error { + if a == nil { + a = &ArangoRouteSpecDestinationService{} + } + + if err := shared.WithErrors(a.Object.Validate(), shared.ValidateRequiredPath("port", a.Port, func(i intstr.IntOrString) error { + return nil + })); err != nil { return err } diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination_tls.go b/pkg/apis/networking/v1alpha1/route_spec_destination_tls.go index e5fdb3449..e5b2750f5 100644 --- a/pkg/apis/networking/v1alpha1/route_spec_destination_tls.go +++ b/pkg/apis/networking/v1alpha1/route_spec_destination_tls.go @@ -25,6 +25,14 @@ type ArangoRouteSpecDestinationTLS struct { Insecure *bool `json:"insecure,omitempty"` } -func (s *ArangoRouteSpecDestinationTLS) Validate() error { +func (a *ArangoRouteSpecDestinationTLS) GetInsecure() bool { + if a == nil || a.Insecure == nil { + return false + } + + return true +} + +func (a *ArangoRouteSpecDestinationTLS) Validate() error { return nil } diff --git a/pkg/apis/networking/v1alpha1/route_spec_route.go b/pkg/apis/networking/v1alpha1/route_spec_route.go index 018396094..9537b6abd 100644 --- a/pkg/apis/networking/v1alpha1/route_spec_route.go +++ b/pkg/apis/networking/v1alpha1/route_spec_route.go @@ -29,21 +29,21 @@ type ArangoRouteSpecRoute struct { Path *string `json:"path,omitempty"` } -func (s *ArangoRouteSpecRoute) GetPath() string { - if s == nil && s.Path == nil { +func (a *ArangoRouteSpecRoute) GetPath() string { + if a == nil || a.Path == nil { return "/" } - return *s.Path + return *a.Path } -func (s *ArangoRouteSpecRoute) Validate() error { - if s == nil { - s = &ArangoRouteSpecRoute{} +func (a *ArangoRouteSpecRoute) Validate() error { + if a == nil { + a = &ArangoRouteSpecRoute{} } if err := shared.WithErrors( - shared.PrefixResourceError("path", shared.ValidateAPIPath(s.GetPath())), + shared.PrefixResourceError("path", shared.ValidateAPIPath(a.GetPath())), ); err != nil { return err } diff --git a/pkg/apis/networking/v1alpha1/route_status.go b/pkg/apis/networking/v1alpha1/route_status.go index 514db9e44..8829c5e11 100644 --- a/pkg/apis/networking/v1alpha1/route_status.go +++ b/pkg/apis/networking/v1alpha1/route_status.go @@ -20,10 +20,19 @@ package v1alpha1 -import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +import ( + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" +) type ArangoRouteStatus struct { // Conditions specific to the entire extension // +doc/type: api.Conditions Conditions api.ConditionList `json:"conditions,omitempty"` + + // Deployment keeps the ArangoDeployment reference + Deployment *sharedApi.Object `json:"deployment,omitempty"` + + // Targets keeps the target details + Targets ArangoRouteStatusTargets `json:"targets,omitempty"` } diff --git a/pkg/apis/networking/v1alpha1/route_status_target.go b/pkg/apis/networking/v1alpha1/route_status_target.go new file mode 100644 index 000000000..bd4b9152a --- /dev/null +++ b/pkg/apis/networking/v1alpha1/route_status_target.go @@ -0,0 +1,44 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1alpha1 + +import "github.com/arangodb/kube-arangodb/pkg/util" + +type ArangoRouteStatusTargets []ArangoRouteStatusTarget + +func (a ArangoRouteStatusTargets) Hash() string { + return util.SHA256FromExtract(func(t ArangoRouteStatusTarget) string { + return t.Hash() + }, a...) +} + +type ArangoRouteStatusTarget struct { + Url string `json:"url,omitempty"` + + TLS ArangoRouteStatusTargetTLS `json:"tls,omitempty"` +} + +func (a *ArangoRouteStatusTarget) Hash() string { + if a == nil { + return "" + } + return util.SHA256FromStringArray(a.Url, a.TLS.Hash()) +} diff --git a/pkg/apis/networking/v1alpha1/route_status_target_tls.go b/pkg/apis/networking/v1alpha1/route_status_target_tls.go new file mode 100644 index 000000000..c813655c1 --- /dev/null +++ b/pkg/apis/networking/v1alpha1/route_status_target_tls.go @@ -0,0 +1,36 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1alpha1 + +import "github.com/arangodb/kube-arangodb/pkg/util" + +type ArangoRouteStatusTargetTLS struct { + // Insecure allows Insecure traffic + Insecure bool `json:"insecure"` +} + +func (a *ArangoRouteStatusTargetTLS) Hash() string { + if a == nil { + return "" + } + + return util.SHA256FromStringArray(util.BoolSwitch(a.Insecure, "true", "false")) +} diff --git a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go index 6b2e00ac8..1dfa0daa4 100644 --- a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go @@ -96,6 +96,11 @@ func (in *ArangoRouteList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArangoRouteSpec) DeepCopyInto(out *ArangoRouteSpec) { *out = *in + if in.DeploymentName != nil { + in, out := &in.DeploymentName, &out.DeploymentName + *out = new(string) + **out = **in + } if in.Destination != nil { in, out := &in.Destination, &out.Destination *out = new(ArangoRouteSpecDestination) @@ -228,6 +233,16 @@ func (in *ArangoRouteStatus) DeepCopyInto(out *ArangoRouteStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = new(v1.Object) + (*in).DeepCopyInto(*out) + } + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make(ArangoRouteStatusTargets, len(*in)) + copy(*out, *in) + } return } @@ -240,3 +255,56 @@ func (in *ArangoRouteStatus) DeepCopy() *ArangoRouteStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArangoRouteStatusTarget) DeepCopyInto(out *ArangoRouteStatusTarget) { + *out = *in + out.TLS = in.TLS + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTarget. +func (in *ArangoRouteStatusTarget) DeepCopy() *ArangoRouteStatusTarget { + if in == nil { + return nil + } + out := new(ArangoRouteStatusTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArangoRouteStatusTargetTLS) DeepCopyInto(out *ArangoRouteStatusTargetTLS) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTargetTLS. +func (in *ArangoRouteStatusTargetTLS) DeepCopy() *ArangoRouteStatusTargetTLS { + if in == nil { + return nil + } + out := new(ArangoRouteStatusTargetTLS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ArangoRouteStatusTargets) DeepCopyInto(out *ArangoRouteStatusTargets) { + { + in := &in + *out = make(ArangoRouteStatusTargets, len(*in)) + copy(*out, *in) + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteStatusTargets. +func (in ArangoRouteStatusTargets) DeepCopy() ArangoRouteStatusTargets { + if in == nil { + return nil + } + out := new(ArangoRouteStatusTargets) + in.DeepCopyInto(out) + return *out +} diff --git a/pkg/apis/shared/validate.go b/pkg/apis/shared/validate.go index e415d4fff..47e059533 100644 --- a/pkg/apis/shared/validate.go +++ b/pkg/apis/shared/validate.go @@ -22,6 +22,7 @@ package shared import ( "fmt" + "reflect" "regexp" "github.com/google/uuid" @@ -57,6 +58,15 @@ func ValidateResourceName(name string) error { return errors.WithStack(errors.Errorf("Name '%s' is not a valid resource name", name)) } +// ValidateResourceNamePointer validates a kubernetes resource name. +// If not valid, an error is returned. +func ValidateResourceNamePointer(name *string) error { + if name == nil { + return errors.WithStack(errors.Errorf("Name is nil")) + } + return ValidateResourceName(*name) +} + // ValidateOptionalResourceName validates a kubernetes resource name. // If not empty and not valid, an error is returned. func ValidateOptionalResourceName(name string) error { @@ -98,7 +108,7 @@ func ValidatePullPolicy(in core.PullPolicy) error { return errors.Errorf("Unknown pull policy: '%s'", string(in)) } -func Validate[T interface{}](in T) error { +func Validate[T any](in T) error { res, _ := validate(in) return res } @@ -107,6 +117,9 @@ func validate(in any) (error, bool) { if in == nil { return nil, false } + if reflect.ValueOf(in).IsZero() { + return nil, false + } if v, ok := in.(ValidateInterface); ok { return v.Validate(), true } @@ -114,7 +127,7 @@ func validate(in any) (error, bool) { } // ValidateOptional Validates object if is not nil -func ValidateOptional[T interface{}](in *T, validator func(T) error) error { +func ValidateOptional[T any](in *T, validator func(T) error) error { if in != nil { return validator(*in) } @@ -122,6 +135,11 @@ func ValidateOptional[T interface{}](in *T, validator func(T) error) error { return nil } +// ValidateOptionalPath Validates object if is not nil +func ValidateOptionalPath[T any](path string, in *T, validator func(T) error) error { + return PrefixResourceErrors(path, ValidateOptional(in, validator)) +} + // ValidateOptionalInterface Validates object if is not nil func ValidateOptionalInterface[T ValidateInterface](in T) error { res, _ := validate(in) @@ -130,11 +148,11 @@ func ValidateOptionalInterface[T ValidateInterface](in T) error { // ValidateOptionalInterfacePath Validates object if is not nil with path func ValidateOptionalInterfacePath[T ValidateInterface](path string, in T) error { - return PrefixResourceError(path, ValidateOptionalInterface(in)) + return PrefixResourceErrors(path, ValidateOptionalInterface(in)) } // ValidateRequired Validates object and required not nil value -func ValidateRequired[T interface{}](in *T, validator func(T) error) error { +func ValidateRequired[T any](in *T, validator func(T) error) error { if in != nil { return validator(*in) } @@ -142,6 +160,11 @@ func ValidateRequired[T interface{}](in *T, validator func(T) error) error { return errors.Errorf("should be not nil") } +// ValidateRequiredPath Validates object and required not nil value +func ValidateRequiredPath[T any](path string, in *T, validator func(T) error) error { + return PrefixResourceErrors(path, ValidateRequired(in, validator)) +} + // ValidateRequiredInterface Validates object if is not nil func ValidateRequiredInterface[T ValidateInterface](in T) error { res, ok := validate(in) @@ -153,11 +176,11 @@ func ValidateRequiredInterface[T ValidateInterface](in T) error { // ValidateRequiredInterfacePath Validates object if is not nil with path func ValidateRequiredInterfacePath[T ValidateInterface](path string, in T) error { - return PrefixResourceError(path, ValidateRequiredInterface(in)) + return PrefixResourceErrors(path, ValidateRequiredInterface(in)) } // ValidateList validates all elements on the list -func ValidateList[T interface{}](in []T, validator func(T) error) error { +func ValidateList[T any](in []T, validator func(T) error) error { errors := make([]error, len(in)) for id := range in { diff --git a/pkg/crd/crds/networking-route.schema.generated.yaml b/pkg/crd/crds/networking-route.schema.generated.yaml index ee0df1501..796f3be0a 100644 --- a/pkg/crd/crds/networking-route.schema.generated.yaml +++ b/pkg/crd/crds/networking-route.schema.generated.yaml @@ -4,7 +4,7 @@ v1alpha1: spec: properties: deployment: - description: Deployment specifies the ArangoDeployment object name + description: DeploymentName specifies the ArangoDeployment object name type: string destination: description: Destination defines the route destination diff --git a/pkg/handlers/networking/route/handler.go b/pkg/handlers/networking/route/handler.go new file mode 100644 index 000000000..fc5ea603b --- /dev/null +++ b/pkg/handlers/networking/route/handler.go @@ -0,0 +1,123 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "context" + + apiErrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" + arangoClientSet "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned" + "github.com/arangodb/kube-arangodb/pkg/logging" + operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/event" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" + "github.com/arangodb/kube-arangodb/pkg/util" +) + +var logger = logging.Global().RegisterAndGetLogger("networking-route-operator", logging.Info) + +type handler struct { + client arangoClientSet.Interface + kubeClient kubernetes.Interface + + eventRecorder event.RecorderInstance + + operator operator.Operator +} + +func (h *handler) Name() string { + return Kind() +} + +func (h *handler) Handle(ctx context.Context, item operation.Item) error { + // Get Backup object. It also covers NotFound case + + object, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.client.NetworkingV1alpha1().ArangoRoutes(item.Namespace).Get, item.Name, meta.GetOptions{}) + if err != nil { + if apiErrors.IsNotFound(err) { + return nil + } + + return err + } + + status := object.Status.DeepCopy() + + changed, reconcileErr := operator.HandleP3WithStop(ctx, item, object, status, h.handle) + if reconcileErr != nil && !operator.IsReconcile(reconcileErr) { + logger.Err(reconcileErr).Warn("Fail for %s %s/%s", + item.Kind, + item.Namespace, + item.Name) + + return reconcileErr + } + + if !changed { + return reconcileErr + } + + logger.Debug("Updating %s %s/%s", + item.Kind, + item.Namespace, + item.Name) + + if _, err := operator.WithNetworkingArangoRouteUpdateStatusInterfaceRetry(context.Background(), h.client.NetworkingV1alpha1().ArangoRoutes(object.GetNamespace()), object, *status, meta.UpdateOptions{}); err != nil { + return err + } + + return reconcileErr +} + +func (h *handler) handle(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus) (bool, error) { + return operator.HandleP3WithCondition(ctx, &status.Conditions, networkingApi.ReadyCondition, item, extension, status, h.HandleSpecValidity, h.HandleArangoDeployment) +} + +func (h *handler) HandleSpecValidity(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus) (bool, error) { + if err := extension.Spec.Validate(); err != nil { + // We have received an error in the spec! + + logger.Err(err).Warn("Invalid Spec on %s", item.String()) + + if status.Conditions.Update(networkingApi.SpecValidCondition, false, "Spec is invalid", "Spec is invalid") { + return true, operator.Stop("Invalid spec") + } + return false, operator.Stop("Invalid spec") + } + + if status.Conditions.Update(networkingApi.SpecValidCondition, true, "Spec is valid", "Spec is valid") { + return true, operator.Reconcile("Conditions updated") + } + + return false, nil +} + +func (h *handler) CanBeHandled(item operation.Item) bool { + return item.Group == Group() && + item.Version == Version() && + item.Kind == Kind() +} + +func (h *handler) init() {} diff --git a/pkg/handlers/networking/route/handler_deployment.go b/pkg/handlers/networking/route/handler_deployment.go new file mode 100644 index 000000000..23e16b7fa --- /dev/null +++ b/pkg/handlers/networking/route/handler_deployment.go @@ -0,0 +1,82 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "context" + + apiErrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" + operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" + "github.com/arangodb/kube-arangodb/pkg/util" +) + +func (h *handler) HandleArangoDeployment(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus) (bool, error) { + var name = util.WithDefault(extension.Spec.DeploymentName) + + if status.Deployment != nil { + name = status.Deployment.GetName() + } + + deployment, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.client.DatabaseV1().ArangoDeployments(item.Namespace).Get, name, meta.GetOptions{}) + if err != nil { + if apiErrors.IsNotFound(err) { + // Condition for Found should be set to false + if util.Or( + status.Conditions.Update(networkingApi.DeploymentFoundCondition, false, "ArangoDeployment not found", "ArangoDeployment not found"), + ) { + return true, operator.Reconcile("Conditions updated") + } + return false, nil + } + + return false, err + } + + if status.Deployment == nil { + status.Deployment = util.NewType(sharedApi.NewObject(deployment)) + return true, operator.Reconcile("Deployment saved") + } else if !status.Deployment.Equals(deployment) { + if util.Or( + status.Conditions.Update(networkingApi.DeploymentFoundCondition, false, "ArangoDeployment changed", "ArangoDeployment changed"), + ) { + return true, operator.Reconcile("Conditions updated") + } + + return false, operator.Stop("ArangoDeployment Changed") + } + + // Condition for Found should be set to true + + if status.Conditions.Update(networkingApi.DeploymentFoundCondition, true, "ArangoDeployment found", "ArangoDeployment found") { + return true, operator.Reconcile("Conditions updated") + } + + return operator.HandleP4(ctx, item, extension, status, deployment, + operator.HandleP4Condition(func(_ context.Context, _ operation.Item, _ *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, _ *api.ArangoDeployment) *api.ConditionList { + return &status.Conditions + }, networkingApi.DestinationValidCondition, h.HandleArangoDestinationWithTargets), h.HandleDestinationRequired) +} diff --git a/pkg/handlers/networking/route/handler_deployment_test.go b/pkg/handlers/networking/route/handler_deployment_test.go new file mode 100644 index 000000000..53ae7eff5 --- /dev/null +++ b/pkg/handlers/networking/route/handler_deployment_test.go @@ -0,0 +1,145 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/tests" +) + +func Test_Handler_Deployment(t *testing.T) { + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition)) + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DeploymentFoundCondition)) +} + +func Test_Handler_MissingDeployment(t *testing.T) { + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment-missing") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition)) + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DeploymentFoundCondition)) +} + +func Test_Handler_Deployment_Changed(t *testing.T) { + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DeploymentFoundCondition)) + + deployment.UID = uuid.NewUUID() + + tests.UpdateObjects(t, handler.kubeClient, handler.client, &deployment) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition)) + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DeploymentFoundCondition)) +} diff --git a/pkg/handlers/networking/route/handler_destination.go b/pkg/handlers/networking/route/handler_destination.go new file mode 100644 index 000000000..0d646a859 --- /dev/null +++ b/pkg/handlers/networking/route/handler_destination.go @@ -0,0 +1,171 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "context" + "fmt" + + core "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" + operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" + "github.com/arangodb/kube-arangodb/pkg/util" +) + +func (h *handler) HandleArangoDestination(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, _ *api.ArangoDeployment) (*operator.Condition, bool, error) { + if dest := extension.Spec.GetDestination(); dest != nil { + if svc := dest.GetService(); svc != nil { + port := svc.Port + + if port == nil { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: "Missing Port definition", + }, false, nil + } + + s, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Services(svc.GetNamespace(extension)).Get, svc.GetName(), meta.GetOptions{}) + if err != nil { + if api.IsNotFound(err) { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: fmt.Sprintf("Service `%s/%s` Not found", svc.GetNamespace(extension), svc.GetName()), + }, false, nil + } + + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: fmt.Sprintf("Unknown error for service `%s/%s`: %s", svc.GetNamespace(extension), svc.GetName(), err.Error()), + }, false, nil + } + + if !svc.Equals(s) { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: fmt.Sprintf("Service `%s/%s` Changed", svc.GetNamespace(extension), svc.GetName()), + }, false, nil + } + + var destPort int32 + + if port.Type == intstr.Int { + p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool { + return v.Port == port.IntVal + }) + if !ok { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: fmt.Sprintf("Port `%d` not defined on Service `%s/%s`", port.IntVal, svc.GetNamespace(extension), svc.GetName()), + }, false, nil + } + + destPort = p.Port + } else if port.Type == intstr.String && port.StrVal != "" { + p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool { + return v.Name == port.StrVal + }) + if !ok { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: fmt.Sprintf("Port `%s` not defined on Service `%s/%s`", port.StrVal, svc.GetNamespace(extension), svc.GetName()), + }, false, nil + } + + destPort = p.Port + } else { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: "Unknown Port definition", + }, false, nil + } + + if destPort == -1 { + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: fmt.Sprintf("Unable to discover port on Service `%s/%s`", svc.GetNamespace(extension), svc.GetName()), + }, false, nil + } + + var targets = networkingApi.ArangoRouteStatusTargets{ + networkingApi.ArangoRouteStatusTarget{ + Url: fmt.Sprintf("%s://%s.%s.svc:%d%s", dest.GetSchema().String(), s.GetName(), s.GetNamespace(), destPort, extension.Spec.GetRoute().GetPath()), + TLS: networkingApi.ArangoRouteStatusTargetTLS{ + Insecure: extension.Spec.Destination.GetTLS().GetInsecure(), + }, + }, + } + + if status.Targets.Hash() == targets.Hash() { + return &operator.Condition{ + Status: true, + Reason: "Destination Found", + Message: "Destination Found", + Hash: targets.Hash(), + }, false, nil + } + + status.Targets = targets + return &operator.Condition{ + Status: true, + Reason: "Destination Found", + Message: "Destination Found", + Hash: targets.Hash(), + }, true, nil + } + } + + return &operator.Condition{ + Status: false, + Reason: "Destination Not Found", + Message: "Destination Not Found", + }, false, nil +} + +func (h *handler) HandleArangoDestinationWithTargets(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, depl *api.ArangoDeployment) (*operator.Condition, bool, error) { + c, changed, err := h.HandleArangoDestination(ctx, item, extension, status, depl) + if c == nil && !c.Status && status.Targets != nil { + status.Targets = nil + changed = true + } + + return c, changed, err +} + +func (h *handler) HandleDestinationRequired(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, _ *api.ArangoDeployment) (bool, error) { + if !status.Conditions.IsTrue(networkingApi.DestinationValidCondition) { + return false, operator.Stop("Destination is not ready") + } + + return false, nil +} diff --git a/pkg/handlers/networking/route/handler_destination_test.go b/pkg/handlers/networking/route/handler_destination_test.go new file mode 100644 index 000000000..81ff36452 --- /dev/null +++ b/pkg/handlers/networking/route/handler_destination_test.go @@ -0,0 +1,422 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "testing" + + "github.com/stretchr/testify/require" + core "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/tests" +) + +func Test_Handler_Destination_Service_Missing(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Not Found") + require.EqualValues(t, c.Message, "Unknown error for service `fake/deployment`: services \"deployment\" not found") +} + +func Test_Handler_Destination_Service_Valid(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10244, + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Hash, extension.Status.Targets.Hash()) +} + +func Test_Handler_Destination_Service_ValidName(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromString("test")), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10241, + Name: "test1", + }, + { + Port: 10244, + Name: "test", + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Hash, extension.Status.Targets.Hash()) +} + +func Test_Handler_Destination_Service_WrongPort(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10245, + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Not Found") + require.EqualValues(t, c.Message, "Port `10244` not defined on Service `fake/deployment`") +} + +func Test_Handler_Destination_Service_WrongPortName(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromString("test")), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10245, + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Not Found") + require.EqualValues(t, c.Message, "Port `test` not defined on Service `fake/deployment`") +} + +func Test_Handler_Destination_Service_Insecure_Default(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10244, + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Testcense + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Hash, extension.Status.Targets.Hash()) + + require.Len(t, extension.Status.Targets, 1) + require.False(t, extension.Status.Targets[0].TLS.Insecure) +} + +func Test_Handler_Destination_Service_Insecure_Nil(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + TLS: &networkingApi.ArangoRouteSpecDestinationTLS{ + Insecure: nil, + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10244, + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Hash, extension.Status.Targets.Hash()) + + require.Len(t, extension.Status.Targets, 1) + require.False(t, extension.Status.Targets[0].TLS.Insecure) +} + +func Test_Handler_Destination_Service_Insecure_Override(t *testing.T) { + // Setup + handler := newFakeHandler() + + // Arrange + extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test", + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.DeploymentName = util.NewType("deployment") + }, + func(t *testing.T, obj *networkingApi.ArangoRoute) { + obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{ + Service: &networkingApi.ArangoRouteSpecDestinationService{ + Object: &sharedApi.Object{ + Name: "deployment", + }, + Port: util.NewType(intstr.FromInt32(10244)), + }, + TLS: &networkingApi.ArangoRouteSpecDestinationTLS{ + Insecure: util.NewType(true), + }, + } + }) + deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment") + svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) { + obj.Spec.Ports = []core.ServicePort{ + { + Port: 10244, + }, + } + }) + + refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc) + + // Test + require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension))) + + // Refresh + refresh(t) + + // Assert + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition)) + require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition)) + + c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) + require.True(t, ok) + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Reason, "Destination Found") + require.EqualValues(t, c.Hash, extension.Status.Targets.Hash()) + + require.Len(t, extension.Status.Targets, 1) + require.True(t, extension.Status.Targets[0].TLS.Insecure) +} diff --git a/pkg/handlers/networking/route/handler_test.go b/pkg/handlers/networking/route/handler_test.go new file mode 100644 index 000000000..fe0e331a5 --- /dev/null +++ b/pkg/handlers/networking/route/handler_test.go @@ -0,0 +1,59 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "testing" + + "github.com/stretchr/testify/require" + apiErrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" + "github.com/arangodb/kube-arangodb/pkg/util/tests" +) + +func Test_ObjectNotFound(t *testing.T) { + // Arrange + handler := newFakeHandler() + + i := newItem(operation.Add, "test", "test") + + actions := map[operation.Operation]bool{ + operation.Add: false, + operation.Update: false, + operation.Delete: false, + } + + // Act + for op, shouldFail := range actions { + t.Run(string(op), func(t *testing.T) { + err := tests.Handle(handler, i) + + // Assert + if shouldFail { + require.Error(t, err) + require.True(t, apiErrors.IsNotFound(err)) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/handlers/networking/route/local.go b/pkg/handlers/networking/route/local.go new file mode 100644 index 000000000..b11b076ff --- /dev/null +++ b/pkg/handlers/networking/route/local.go @@ -0,0 +1,38 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "github.com/arangodb/kube-arangodb/pkg/apis/networking" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" +) + +func Kind() string { + return networking.ArangoRouteResourceKind +} + +func Group() string { + return networkingApi.SchemeGroupVersion.Group +} + +func Version() string { + return networkingApi.SchemeGroupVersion.Version +} diff --git a/pkg/handlers/networking/route/register.go b/pkg/handlers/networking/route/register.go new file mode 100644 index 000000000..e8b79752f --- /dev/null +++ b/pkg/handlers/networking/route/register.go @@ -0,0 +1,60 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + + arangoClientSet "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned" + arangoInformer "github.com/arangodb/kube-arangodb/pkg/generated/informers/externalversions" + operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/event" +) + +// RegisterInformer into operator +func RegisterInformer(operator operator.Operator, recorder event.Recorder, client arangoClientSet.Interface, + kubeClient kubernetes.Interface, informer arangoInformer.SharedInformerFactory, kubeInformer informers.SharedInformerFactory) error { + + if err := operator.RegisterInformer(informer.Networking().V1alpha1().ArangoRoutes().Informer(), + Group(), + Version(), + Kind()); err != nil { + return err + } + + h := &handler{ + client: client, + kubeClient: kubeClient, + + eventRecorder: recorder.NewInstance(Group(), Version(), Kind()), + + operator: operator, + } + + h.init() + + if err := operator.RegisterHandler(h); err != nil { + return err + } + + return nil +} diff --git a/pkg/handlers/networking/route/suite_test.go b/pkg/handlers/networking/route/suite_test.go new file mode 100644 index 000000000..dc222725c --- /dev/null +++ b/pkg/handlers/networking/route/suite_test.go @@ -0,0 +1,61 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package route + +import ( + "k8s.io/client-go/kubernetes/fake" + + "github.com/arangodb/kube-arangodb/pkg/apis/analytics" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" + fakeClientSet "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned/fake" + operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/event" + "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" +) + +func newFakeHandler() *handler { + f := fakeClientSet.NewSimpleClientset() + k := fake.NewSimpleClientset() + + h := &handler{ + client: f, + kubeClient: k, + eventRecorder: event.NewEventRecorder("mock", k).NewInstance(Group(), Version(), Kind()), + operator: operator.NewOperator("mock", "mock", "mock"), + } + + h.init() + + return h +} + +func newItem(o operation.Operation, namespace, name string) operation.Item { + return operation.Item{ + Group: networkingApi.SchemeGroupVersion.Group, + Version: networkingApi.SchemeGroupVersion.Version, + Kind: analytics.GraphAnalyticsEngineResourceKind, + + Operation: o, + + Namespace: namespace, + Name: name, + } +} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index c83515751..586f3bcd1 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -37,6 +37,7 @@ import ( backupdef "github.com/arangodb/kube-arangodb/pkg/apis/backup" depldef "github.com/arangodb/kube-arangodb/pkg/apis/deployment" deplapi "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/arangodb/kube-arangodb/pkg/apis/networking" repldef "github.com/arangodb/kube-arangodb/pkg/apis/replication" replapi "github.com/arangodb/kube-arangodb/pkg/apis/replication/v1" lsapi "github.com/arangodb/kube-arangodb/pkg/apis/storage/v1alpha" @@ -45,6 +46,7 @@ import ( arangoInformer "github.com/arangodb/kube-arangodb/pkg/generated/informers/externalversions" "github.com/arangodb/kube-arangodb/pkg/handlers/backup" "github.com/arangodb/kube-arangodb/pkg/handlers/job" + "github.com/arangodb/kube-arangodb/pkg/handlers/networking/route" "github.com/arangodb/kube-arangodb/pkg/handlers/policy" "github.com/arangodb/kube-arangodb/pkg/logging" "github.com/arangodb/kube-arangodb/pkg/operator/scope" @@ -68,10 +70,11 @@ var logger = logging.Global().RegisterAndGetLogger("operator", logging.Info) type operatorV2type string const ( - backupOperator operatorV2type = "backup" - mlOperator operatorV2type = "ml" - analyticsOperator operatorV2type = "analytics" - appsOperator operatorV2type = "apps" + backupOperator operatorV2type = "backup" + mlOperator operatorV2type = "ml" + analyticsOperator operatorV2type = "analytics" + networkingOperator operatorV2type = "networking" + appsOperator operatorV2type = "apps" ) type Event struct { @@ -102,6 +105,7 @@ type Config struct { EnableStorage bool EnableML bool EnableAnalytics bool + EnableNetworking bool EnableBackup bool EnableApps bool EnableK2KClusterSync bool @@ -124,6 +128,7 @@ type Dependencies struct { BackupProbe *probe.ReadyProbe MlProbe *probe.ReadyProbe AnalyticsProbe *probe.ReadyProbe + NetworkingProbe *probe.ReadyProbe AppsProbe *probe.ReadyProbe K2KClusterSyncProbe *probe.ReadyProbe } @@ -192,6 +197,13 @@ func (o *Operator) Run() { go o.runWithoutLeaderElection("arango-analytics-operator", constants.AnalyticsLabelRole, o.onStartAnalytics, o.Dependencies.AnalyticsProbe) } } + if o.Config.EnableNetworking { + if !o.Config.SingleMode { + go o.runLeaderElection("arango-networking-operator", constants.NetworkingLabelRole, o.onStartNetworking, o.Dependencies.NetworkingProbe) + } else { + go o.runWithoutLeaderElection("arango-networking-operator", constants.NetworkingLabelRole, o.onStartNetworking, o.Dependencies.NetworkingProbe) + } + } if o.Config.EnableK2KClusterSync { // Nothing to do o.log.Warn("K2K Cluster sync is permanently disabled") @@ -262,6 +274,11 @@ func (o *Operator) onStartApps(stop <-chan struct{}) { o.onStartOperatorV2(appsOperator, stop) } +// onStartNetworking starts the operator and run till given channel is closed. +func (o *Operator) onStartNetworking(stop <-chan struct{}) { + o.onStartOperatorV2(networkingOperator, stop) +} + // onStartOperatorV2 run the operatorV2 type func (o *Operator) onStartOperatorV2(operatorType operatorV2type, stop <-chan struct{}) { operatorName := fmt.Sprintf("arangodb-%s-operator", operatorType) @@ -290,6 +307,9 @@ func (o *Operator) onStartOperatorV2(operatorType operatorV2type, stop <-chan st case analyticsOperator: o.onStartOperatorV2Analytics(operator, eventRecorder, o.Client.Arango(), o.Client.Kubernetes(), arangoInformer, kubeInformer) o.Dependencies.AnalyticsProbe.SetReady() + case networkingOperator: + o.onStartOperatorV2Networking(operator, eventRecorder, o.Client.Arango(), o.Client.Kubernetes(), arangoInformer, kubeInformer) + o.Dependencies.NetworkingProbe.SetReady() } if err := operator.RegisterStarter(arangoInformer); err != nil { @@ -321,6 +341,18 @@ func (o *Operator) onStartOperatorV2Apps(operator operatorV2.Operator, recorder } } +func (o *Operator) onStartOperatorV2Networking(operator operatorV2.Operator, recorder event.Recorder, client arangoClientSet.Interface, kubeClient kubernetes.Interface, informer arangoInformer.SharedInformerFactory, kubeInformer informers.SharedInformerFactory) { + checkFn := func() error { + _, err := o.Client.Arango().NetworkingV1alpha1().ArangoRoutes(o.Namespace).List(context.Background(), meta.ListOptions{}) + return err + } + o.waitForCRD(networking.ArangoRouteCRDName, checkFn) + + if err := route.RegisterInformer(operator, recorder, client, kubeClient, informer, kubeInformer); err != nil { + panic(err) + } +} + func (o *Operator) onStartOperatorV2Backup(operator operatorV2.Operator, recorder event.Recorder, client arangoClientSet.Interface, kubeClient kubernetes.Interface, informer arangoInformer.SharedInformerFactory) { checkFn := func() error { _, err := o.Client.Arango().BackupV1().ArangoBackups(o.Namespace).List(context.Background(), meta.ListOptions{}) diff --git a/pkg/operatorV2/handle.go b/pkg/operatorV2/handle.go index 17b27f214..5e0be594f 100644 --- a/pkg/operatorV2/handle.go +++ b/pkg/operatorV2/handle.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,19 +21,26 @@ package operator import ( - "context" - api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" ) +type Condition struct { + Status bool + Reason string + Message string + Hash string +} + func WithCondition(conditions *api.ConditionList, condition api.ConditionType, changed bool, err error) (bool, error) { + var hash string + if changed || err != nil { // Condition should be false - if conditions.Update(condition, false, "Not ready", "Not ready") { + if conditions.UpdateWithHash(condition, false, "Not ready", "Not ready", hash) { changed = true } } else { - if conditions.Update(condition, true, "Ready", "Ready") { + if conditions.UpdateWithHash(condition, true, "Ready", "Ready", hash) { changed = true } } @@ -47,234 +54,26 @@ func WithCondition(conditions *api.ConditionList, condition api.ConditionType, c return changed, err } -type HandleP0Func func(ctx context.Context) (bool, error) - -type HandleP1Func[P1 interface{}] func(ctx context.Context, p1 P1) (bool, error) - -type HandleP2Func[P1, P2 interface{}] func(ctx context.Context, p1 P1, p2 P2) (bool, error) - -type HandleP3Func[P1, P2, P3 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3) (bool, error) - -type HandleP4Func[P1, P2, P3, P4 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4) (bool, error) - -type HandleP5Func[P1, P2, P3, P4, P5 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5) (bool, error) - -type HandleP6Func[P1, P2, P3, P4, P5, P6 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6) (bool, error) - -type HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9) (bool, error) - -func HandleP0(ctx context.Context, handler ...HandleP0Func) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx) - if changed { - isChanged = true +func WithConditionChange(conditions *api.ConditionList, condition api.ConditionType, c *Condition, changed bool, err error) (bool, error) { + if c == nil { + if conditions.Remove(condition) { + changed = true } - - if err != nil { - return isChanged, err + } else { + if conditions.UpdateWithHash(condition, c.Status, c.Reason, c.Message, c.Hash) { + changed = true } } - return isChanged, nil -} - -func HandleP1[P1 interface{}](ctx context.Context, p1 P1, handler ...HandleP1Func[P1]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1) + if err == nil || IsStop(err) { if changed { - isChanged = true - } - - if err != nil { - return isChanged, err + err = Reconcile("Condition changed") } } - return isChanged, nil -} - -func HandleP1WithStop[P1 interface{}](ctx context.Context, p1 P1, handler ...HandleP1Func[P1]) (bool, error) { - changed, err := HandleP1[P1](ctx, p1, handler...) - if IsStop(err) { - return changed, nil + if err == nil && changed { + err = Reconcile("Condition changed") } return changed, err } - -func HandleP2[P1, P2 interface{}](ctx context.Context, p1 P1, p2 P2, handler ...HandleP2Func[P1, P2]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1, p2) - if changed { - isChanged = true - } - - if err != nil { - return isChanged, err - } - } - - return isChanged, nil -} - -func HandleP2WithStop[P1, P2 interface{}](ctx context.Context, p1 P1, p2 P2, handler ...HandleP2Func[P1, P2]) (bool, error) { - changed, err := HandleP2[P1, P2](ctx, p1, p2, handler...) - if IsStop(err) { - return changed, nil - } - - return changed, err -} - -func HandleP3[P1, P2, P3 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, handler ...HandleP3Func[P1, P2, P3]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1, p2, p3) - if changed { - isChanged = true - } - - if err != nil { - return isChanged, err - } - } - - return isChanged, nil -} - -func HandleP3WithStop[P1, P2, P3 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, handler ...HandleP3Func[P1, P2, P3]) (bool, error) { - changed, err := HandleP3[P1, P2, P3](ctx, p1, p2, p3, handler...) - if IsStop(err) { - return changed, nil - } - - return changed, err -} - -func HandleP3WithCondition[P1, P2, P3 interface{}](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, handler ...HandleP3Func[P1, P2, P3]) (bool, error) { - changed, err := HandleP3[P1, P2, P3](ctx, p1, p2, p3, handler...) - return WithCondition(conditions, condition, changed, err) -} - -func HandleP4[P1, P2, P3, P4 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, handler ...HandleP4Func[P1, P2, P3, P4]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1, p2, p3, p4) - if changed { - isChanged = true - } - - if err != nil { - return isChanged, err - } - } - - return isChanged, nil -} - -func HandleP4WithStop[P1, P2, P3, P4 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, handler ...HandleP4Func[P1, P2, P3, P4]) (bool, error) { - changed, err := HandleP4[P1, P2, P3, P4](ctx, p1, p2, p3, p4, handler...) - if IsStop(err) { - return changed, nil - } - - return changed, err -} - -func HandleP4WithCondition[P1, P2, P3, P4 interface{}](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, handler ...HandleP4Func[P1, P2, P3, P4]) (bool, error) { - changed, err := HandleP4[P1, P2, P3, P4](ctx, p1, p2, p3, p4, handler...) - return WithCondition(conditions, condition, changed, err) -} - -func HandleP5[P1, P2, P3, P4, P5 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, handler ...HandleP5Func[P1, P2, P3, P4, P5]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1, p2, p3, p4, p5) - if changed { - isChanged = true - } - - if err != nil { - return isChanged, err - } - } - - return isChanged, nil -} - -func HandleP5WithStop[P1, P2, P3, P4, P5 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, handler ...HandleP5Func[P1, P2, P3, P4, P5]) (bool, error) { - changed, err := HandleP5[P1, P2, P3, P4, P5](ctx, p1, p2, p3, p4, p5, handler...) - if IsStop(err) { - return changed, nil - } - - return changed, err -} - -func HandleP5WithCondition[P1, P2, P3, P4, P5 interface{}](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, handler ...HandleP5Func[P1, P2, P3, P4, P5]) (bool, error) { - changed, err := HandleP5[P1, P2, P3, P4, P5](ctx, p1, p2, p3, p4, p5, handler...) - return WithCondition(conditions, condition, changed, err) -} - -func HandleP6[P1, P2, P3, P4, P5, P6 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1, p2, p3, p4, p5, p6) - if changed { - isChanged = true - } - - if err != nil { - return isChanged, err - } - } - - return isChanged, nil -} - -func HandleP6WithStop[P1, P2, P3, P4, P5, P6 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { - changed, err := HandleP6[P1, P2, P3, P4, P5, P6](ctx, p1, p2, p3, p4, p5, p6, handler...) - if IsStop(err) { - return changed, nil - } - - return changed, err -} - -func HandleP6WithCondition[P1, P2, P3, P4, P5, P6 interface{}](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { - changed, err := HandleP6[P1, P2, P3, P4, P5, P6](ctx, p1, p2, p3, p4, p5, p6, handler...) - return WithCondition(conditions, condition, changed, err) -} - -func HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { - isChanged := false - for _, h := range handler { - changed, err := h(ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9) - if changed { - isChanged = true - } - - if err != nil { - return isChanged, err - } - } - - return isChanged, nil -} - -func HandleP9WithStop[P1, P2, P3, P4, P5, P6, P7, P8, P9 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { - changed, err := HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9](ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9, handler...) - if IsStop(err) { - return changed, nil - } - - return changed, err -} - -func HandleP9WithCondition[P1, P2, P3, P4, P5, P6, P7, P8, P9 interface{}](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { - changed, err := HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9](ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9, handler...) - return WithCondition(conditions, condition, changed, err) -} diff --git a/pkg/operatorV2/handler_p0.generated.go b/pkg/operatorV2/handler_p0.generated.go new file mode 100644 index 000000000..f29852614 --- /dev/null +++ b/pkg/operatorV2/handler_p0.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP0Func func(ctx context.Context) (bool, error) + +type HandleP0ConditionFunc func(ctx context.Context) (*Condition, bool, error) + +type HandleP0ConditionExtract func(ctx context.Context) *api.ConditionList + +func HandleP0(ctx context.Context, handler ...HandleP0Func) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP0WithStop(ctx context.Context, handler ...HandleP0Func) (bool, error) { + changed, err := HandleP0(ctx, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP0WithCondition(ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, handler ...HandleP0Func) (bool, error) { + changed, err := HandleP0(ctx, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP0Condition(extract HandleP0ConditionExtract, condition api.ConditionType, handler HandleP0ConditionFunc) HandleP0Func { + return func(ctx context.Context) (bool, error) { + c, changed, err := handler(ctx) + return WithConditionChange(extract(ctx), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p1.generated.go b/pkg/operatorV2/handler_p1.generated.go new file mode 100644 index 000000000..381d562d2 --- /dev/null +++ b/pkg/operatorV2/handler_p1.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP1Func[P1 any] func(ctx context.Context, p1 P1) (bool, error) + +type HandleP1ConditionFunc[P1 any] func(ctx context.Context, p1 P1) (*Condition, bool, error) + +type HandleP1ConditionExtract[P1 any] func(ctx context.Context, p1 P1) *api.ConditionList + +func HandleP1[P1 any](ctx context.Context, p1 P1, handler ...HandleP1Func[P1]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP1WithStop[P1 any](ctx context.Context, p1 P1, handler ...HandleP1Func[P1]) (bool, error) { + changed, err := HandleP1[P1](ctx, p1, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP1WithCondition[P1 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, handler ...HandleP1Func[P1]) (bool, error) { + changed, err := HandleP1[P1](ctx, p1, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP1Condition[P1 any](extract HandleP1ConditionExtract[P1], condition api.ConditionType, handler HandleP1ConditionFunc[P1]) HandleP1Func[P1] { + return func(ctx context.Context, p1 P1) (bool, error) { + c, changed, err := handler(ctx, p1) + return WithConditionChange(extract(ctx, p1), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p2.generated.go b/pkg/operatorV2/handler_p2.generated.go new file mode 100644 index 000000000..5176b8cab --- /dev/null +++ b/pkg/operatorV2/handler_p2.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP2Func[P1, P2 any] func(ctx context.Context, p1 P1, p2 P2) (bool, error) + +type HandleP2ConditionFunc[P1, P2 any] func(ctx context.Context, p1 P1, p2 P2) (*Condition, bool, error) + +type HandleP2ConditionExtract[P1, P2 any] func(ctx context.Context, p1 P1, p2 P2) *api.ConditionList + +func HandleP2[P1, P2 any](ctx context.Context, p1 P1, p2 P2, handler ...HandleP2Func[P1, P2]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP2WithStop[P1, P2 any](ctx context.Context, p1 P1, p2 P2, handler ...HandleP2Func[P1, P2]) (bool, error) { + changed, err := HandleP2[P1, P2](ctx, p1, p2, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP2WithCondition[P1, P2 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, handler ...HandleP2Func[P1, P2]) (bool, error) { + changed, err := HandleP2[P1, P2](ctx, p1, p2, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP2Condition[P1, P2 any](extract HandleP2ConditionExtract[P1, P2], condition api.ConditionType, handler HandleP2ConditionFunc[P1, P2]) HandleP2Func[P1, P2] { + return func(ctx context.Context, p1 P1, p2 P2) (bool, error) { + c, changed, err := handler(ctx, p1, p2) + return WithConditionChange(extract(ctx, p1, p2), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p3.generated.go b/pkg/operatorV2/handler_p3.generated.go new file mode 100644 index 000000000..31b05f6b2 --- /dev/null +++ b/pkg/operatorV2/handler_p3.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP3Func[P1, P2, P3 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3) (bool, error) + +type HandleP3ConditionFunc[P1, P2, P3 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3) (*Condition, bool, error) + +type HandleP3ConditionExtract[P1, P2, P3 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3) *api.ConditionList + +func HandleP3[P1, P2, P3 any](ctx context.Context, p1 P1, p2 P2, p3 P3, handler ...HandleP3Func[P1, P2, P3]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP3WithStop[P1, P2, P3 any](ctx context.Context, p1 P1, p2 P2, p3 P3, handler ...HandleP3Func[P1, P2, P3]) (bool, error) { + changed, err := HandleP3[P1, P2, P3](ctx, p1, p2, p3, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP3WithCondition[P1, P2, P3 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, handler ...HandleP3Func[P1, P2, P3]) (bool, error) { + changed, err := HandleP3[P1, P2, P3](ctx, p1, p2, p3, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP3Condition[P1, P2, P3 any](extract HandleP3ConditionExtract[P1, P2, P3], condition api.ConditionType, handler HandleP3ConditionFunc[P1, P2, P3]) HandleP3Func[P1, P2, P3] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3) + return WithConditionChange(extract(ctx, p1, p2, p3), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p4.generated.go b/pkg/operatorV2/handler_p4.generated.go new file mode 100644 index 000000000..b1249dcec --- /dev/null +++ b/pkg/operatorV2/handler_p4.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP4Func[P1, P2, P3, P4 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4) (bool, error) + +type HandleP4ConditionFunc[P1, P2, P3, P4 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4) (*Condition, bool, error) + +type HandleP4ConditionExtract[P1, P2, P3, P4 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4) *api.ConditionList + +func HandleP4[P1, P2, P3, P4 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, handler ...HandleP4Func[P1, P2, P3, P4]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP4WithStop[P1, P2, P3, P4 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, handler ...HandleP4Func[P1, P2, P3, P4]) (bool, error) { + changed, err := HandleP4[P1, P2, P3, P4](ctx, p1, p2, p3, p4, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP4WithCondition[P1, P2, P3, P4 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, handler ...HandleP4Func[P1, P2, P3, P4]) (bool, error) { + changed, err := HandleP4[P1, P2, P3, P4](ctx, p1, p2, p3, p4, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP4Condition[P1, P2, P3, P4 any](extract HandleP4ConditionExtract[P1, P2, P3, P4], condition api.ConditionType, handler HandleP4ConditionFunc[P1, P2, P3, P4]) HandleP4Func[P1, P2, P3, P4] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3, p4) + return WithConditionChange(extract(ctx, p1, p2, p3, p4), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p5.generated.go b/pkg/operatorV2/handler_p5.generated.go new file mode 100644 index 000000000..b8803f296 --- /dev/null +++ b/pkg/operatorV2/handler_p5.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP5Func[P1, P2, P3, P4, P5 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5) (bool, error) + +type HandleP5ConditionFunc[P1, P2, P3, P4, P5 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5) (*Condition, bool, error) + +type HandleP5ConditionExtract[P1, P2, P3, P4, P5 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5) *api.ConditionList + +func HandleP5[P1, P2, P3, P4, P5 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, handler ...HandleP5Func[P1, P2, P3, P4, P5]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4, p5) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP5WithStop[P1, P2, P3, P4, P5 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, handler ...HandleP5Func[P1, P2, P3, P4, P5]) (bool, error) { + changed, err := HandleP5[P1, P2, P3, P4, P5](ctx, p1, p2, p3, p4, p5, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP5WithCondition[P1, P2, P3, P4, P5 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, handler ...HandleP5Func[P1, P2, P3, P4, P5]) (bool, error) { + changed, err := HandleP5[P1, P2, P3, P4, P5](ctx, p1, p2, p3, p4, p5, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP5Condition[P1, P2, P3, P4, P5 any](extract HandleP5ConditionExtract[P1, P2, P3, P4, P5], condition api.ConditionType, handler HandleP5ConditionFunc[P1, P2, P3, P4, P5]) HandleP5Func[P1, P2, P3, P4, P5] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3, p4, p5) + return WithConditionChange(extract(ctx, p1, p2, p3, p4, p5), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p6.generated.go b/pkg/operatorV2/handler_p6.generated.go new file mode 100644 index 000000000..5811317f2 --- /dev/null +++ b/pkg/operatorV2/handler_p6.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP6Func[P1, P2, P3, P4, P5, P6 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6) (bool, error) + +type HandleP6ConditionFunc[P1, P2, P3, P4, P5, P6 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6) (*Condition, bool, error) + +type HandleP6ConditionExtract[P1, P2, P3, P4, P5, P6 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6) *api.ConditionList + +func HandleP6[P1, P2, P3, P4, P5, P6 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4, p5, p6) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP6WithStop[P1, P2, P3, P4, P5, P6 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { + changed, err := HandleP6[P1, P2, P3, P4, P5, P6](ctx, p1, p2, p3, p4, p5, p6, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP6WithCondition[P1, P2, P3, P4, P5, P6 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { + changed, err := HandleP6[P1, P2, P3, P4, P5, P6](ctx, p1, p2, p3, p4, p5, p6, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP6Condition[P1, P2, P3, P4, P5, P6 any](extract HandleP6ConditionExtract[P1, P2, P3, P4, P5, P6], condition api.ConditionType, handler HandleP6ConditionFunc[P1, P2, P3, P4, P5, P6]) HandleP6Func[P1, P2, P3, P4, P5, P6] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3, p4, p5, p6) + return WithConditionChange(extract(ctx, p1, p2, p3, p4, p5, p6), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p7.generated.go b/pkg/operatorV2/handler_p7.generated.go new file mode 100644 index 000000000..edd4fe844 --- /dev/null +++ b/pkg/operatorV2/handler_p7.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP7Func[P1, P2, P3, P4, P5, P6, P7 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7) (bool, error) + +type HandleP7ConditionFunc[P1, P2, P3, P4, P5, P6, P7 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7) (*Condition, bool, error) + +type HandleP7ConditionExtract[P1, P2, P3, P4, P5, P6, P7 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7) *api.ConditionList + +func HandleP7[P1, P2, P3, P4, P5, P6, P7 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, handler ...HandleP7Func[P1, P2, P3, P4, P5, P6, P7]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4, p5, p6, p7) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP7WithStop[P1, P2, P3, P4, P5, P6, P7 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, handler ...HandleP7Func[P1, P2, P3, P4, P5, P6, P7]) (bool, error) { + changed, err := HandleP7[P1, P2, P3, P4, P5, P6, P7](ctx, p1, p2, p3, p4, p5, p6, p7, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP7WithCondition[P1, P2, P3, P4, P5, P6, P7 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, handler ...HandleP7Func[P1, P2, P3, P4, P5, P6, P7]) (bool, error) { + changed, err := HandleP7[P1, P2, P3, P4, P5, P6, P7](ctx, p1, p2, p3, p4, p5, p6, p7, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP7Condition[P1, P2, P3, P4, P5, P6, P7 any](extract HandleP7ConditionExtract[P1, P2, P3, P4, P5, P6, P7], condition api.ConditionType, handler HandleP7ConditionFunc[P1, P2, P3, P4, P5, P6, P7]) HandleP7Func[P1, P2, P3, P4, P5, P6, P7] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3, p4, p5, p6, p7) + return WithConditionChange(extract(ctx, p1, p2, p3, p4, p5, p6, p7), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p8.generated.go b/pkg/operatorV2/handler_p8.generated.go new file mode 100644 index 000000000..72c210d3e --- /dev/null +++ b/pkg/operatorV2/handler_p8.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP8Func[P1, P2, P3, P4, P5, P6, P7, P8 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8) (bool, error) + +type HandleP8ConditionFunc[P1, P2, P3, P4, P5, P6, P7, P8 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8) (*Condition, bool, error) + +type HandleP8ConditionExtract[P1, P2, P3, P4, P5, P6, P7, P8 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8) *api.ConditionList + +func HandleP8[P1, P2, P3, P4, P5, P6, P7, P8 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, handler ...HandleP8Func[P1, P2, P3, P4, P5, P6, P7, P8]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4, p5, p6, p7, p8) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP8WithStop[P1, P2, P3, P4, P5, P6, P7, P8 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, handler ...HandleP8Func[P1, P2, P3, P4, P5, P6, P7, P8]) (bool, error) { + changed, err := HandleP8[P1, P2, P3, P4, P5, P6, P7, P8](ctx, p1, p2, p3, p4, p5, p6, p7, p8, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP8WithCondition[P1, P2, P3, P4, P5, P6, P7, P8 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, handler ...HandleP8Func[P1, P2, P3, P4, P5, P6, P7, P8]) (bool, error) { + changed, err := HandleP8[P1, P2, P3, P4, P5, P6, P7, P8](ctx, p1, p2, p3, p4, p5, p6, p7, p8, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP8Condition[P1, P2, P3, P4, P5, P6, P7, P8 any](extract HandleP8ConditionExtract[P1, P2, P3, P4, P5, P6, P7, P8], condition api.ConditionType, handler HandleP8ConditionFunc[P1, P2, P3, P4, P5, P6, P7, P8]) HandleP8Func[P1, P2, P3, P4, P5, P6, P7, P8] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3, p4, p5, p6, p7, p8) + return WithConditionChange(extract(ctx, p1, p2, p3, p4, p5, p6, p7, p8), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/handler_p9.generated.go b/pkg/operatorV2/handler_p9.generated.go new file mode 100644 index 000000000..d21ed0f0e --- /dev/null +++ b/pkg/operatorV2/handler_p9.generated.go @@ -0,0 +1,70 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "context" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" +) + +type HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9) (bool, error) + +type HandleP9ConditionFunc[P1, P2, P3, P4, P5, P6, P7, P8, P9 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9) (*Condition, bool, error) + +type HandleP9ConditionExtract[P1, P2, P3, P4, P5, P6, P7, P8, P9 any] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9) *api.ConditionList + +func HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP9WithStop[P1, P2, P3, P4, P5, P6, P7, P8, P9 any](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { + changed, err := HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9](ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP9WithCondition[P1, P2, P3, P4, P5, P6, P7, P8, P9 any](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { + changed, err := HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9](ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9, handler...) + return WithCondition(conditions, condition, changed, err) +} + +func HandleP9Condition[P1, P2, P3, P4, P5, P6, P7, P8, P9 any](extract HandleP9ConditionExtract[P1, P2, P3, P4, P5, P6, P7, P8, P9], condition api.ConditionType, handler HandleP9ConditionFunc[P1, P2, P3, P4, P5, P6, P7, P8, P9]) HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9] { + return func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9) (bool, error) { + c, changed, err := handler(ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9) + return WithConditionChange(extract(ctx, p1, p2, p3, p4, p5, p6, p7, p8, p9), condition, c, changed, err) + } +} diff --git a/pkg/operatorV2/update_wraps.go b/pkg/operatorV2/update_wraps.go index a936e1e41..acae0b86f 100644 --- a/pkg/operatorV2/update_wraps.go +++ b/pkg/operatorV2/update_wraps.go @@ -29,6 +29,7 @@ import ( backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1" mlApiv1alpha1 "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1" mlApi "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1beta1" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" ) func WithArangoBackupUpdateStatusInterfaceRetry(ctx context.Context, client UpdateStatusInterface[backupApi.ArangoBackupStatus, *backupApi.ArangoBackup], obj *backupApi.ArangoBackup, status backupApi.ArangoBackupStatus, opts meta.UpdateOptions) (*backupApi.ArangoBackup, error) { @@ -51,6 +52,10 @@ func WithAnalyticsGAEUpdateStatusInterfaceRetry(ctx context.Context, client Upda return WithUpdateStatusInterfaceRetry[analyticsApi.GraphAnalyticsEngineStatus, *analyticsApi.GraphAnalyticsEngine](ctx, client, obj, status, opts) } +func WithNetworkingArangoRouteUpdateStatusInterfaceRetry(ctx context.Context, client UpdateStatusInterface[networkingApi.ArangoRouteStatus, *networkingApi.ArangoRoute], obj *networkingApi.ArangoRoute, status networkingApi.ArangoRouteStatus, opts meta.UpdateOptions) (*networkingApi.ArangoRoute, error) { + return WithUpdateStatusInterfaceRetry[networkingApi.ArangoRouteStatus, *networkingApi.ArangoRoute](ctx, client, obj, status, opts) +} + func WithArangoStorageUpdateStatusInterfaceRetry(ctx context.Context, client UpdateStatusInterface[mlApi.ArangoMLStorageStatus, *mlApi.ArangoMLStorage], obj *mlApi.ArangoMLStorage, status mlApi.ArangoMLStorageStatus, opts meta.UpdateOptions) (*mlApi.ArangoMLStorage, error) { return WithUpdateStatusInterfaceRetry[mlApi.ArangoMLStorageStatus, *mlApi.ArangoMLStorage](ctx, client, obj, status, opts) } diff --git a/pkg/server/server.go b/pkg/server/server.go index 0bf9ccac1..940cff618 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -70,6 +70,7 @@ type Dependencies struct { Apps OperatorDependency ML OperatorDependency Analytics OperatorDependency + Networking OperatorDependency ClusterSync OperatorDependency Operators Operators Secrets typedCore.SecretInterface @@ -194,6 +195,10 @@ func NewServer(cli typedCore.CoreV1Interface, cfg Config, deps Dependencies) (*S r.GET("/ready/analytics", gin.WrapF(deps.Analytics.Probe.ReadyHandler)) readyProbes = append(readyProbes, deps.Analytics.Probe) } + if deps.Networking.Enabled { + r.GET("/ready/networking", gin.WrapF(deps.Networking.Probe.ReadyHandler)) + readyProbes = append(readyProbes, deps.Networking.Probe) + } r.GET("/ready", gin.WrapF(ready(readyProbes...))) r.GET("/metrics", gin.WrapF(metrics.Handler())) r.POST("/login", s.auth.handleLogin) diff --git a/pkg/util/checksum.go b/pkg/util/checksum.go index 907516926..41028698e 100644 --- a/pkg/util/checksum.go +++ b/pkg/util/checksum.go @@ -26,8 +26,28 @@ import ( "fmt" "k8s.io/apimachinery/pkg/util/json" + + "github.com/arangodb/kube-arangodb/pkg/util/strings" ) +type Hash interface { + Hash() string +} + +func SHA256FromExtract[T any](extract func(T) string, obj ...T) string { + return SHA256FromStringArray(strings.Join(FormatList(obj, extract), "|")) +} + +func SHA256FromHashArray[T Hash](data []T) string { + return SHA256FromExtract(func(t T) string { + return t.Hash() + }, data...) +} + +func SHA256FromStringArray(data ...string) string { + return SHA256FromString(strings.Join(data, "|")) +} + func SHA256FromString(data string) string { return SHA256([]byte(data)) } diff --git a/pkg/util/constants/constants.go b/pkg/util/constants/constants.go index 245d3ec05..9b603a1f8 100644 --- a/pkg/util/constants/constants.go +++ b/pkg/util/constants/constants.go @@ -73,6 +73,7 @@ const ( BackupLabelRole = "backup/role" MLLabelRole = "ml/role" AnalyticsLabelRole = "analytics/role" + NetworkingLabelRole = "networking/role" AppsLabelRole = "apps/role" ClusterSyncLabelRole = "clustersync/role" LabelRole = "role" diff --git a/pkg/util/dict.go b/pkg/util/dict.go index c356b246a..bf2c775bd 100644 --- a/pkg/util/dict.go +++ b/pkg/util/dict.go @@ -21,6 +21,7 @@ package util import ( + "maps" "reflect" "sort" ) @@ -50,7 +51,7 @@ func CopyFullMap[K comparable, V any](src map[K]V) map[K]V { r := map[K]V{} - CopyMap(r, src) + maps.Copy(r, src) return r } @@ -73,13 +74,6 @@ func MergeMaps[K comparable, V any](override bool, maps ...map[K]V) map[K]V { return r } -func CopyMap[K comparable, V any](dst, src map[K]V) { - // TODO: replace with maps.Copy when switching to go1.21 - for k, v := range src { - dst[k] = v - } -} - func IterateSorted[V any](m map[string]V, cb func(string, V)) { for _, k := range SortKeys(m) { cb(k, m[k]) diff --git a/pkg/util/list.go b/pkg/util/list.go index 208cd8462..d58b2c6dc 100644 --- a/pkg/util/list.go +++ b/pkg/util/list.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ package util import "sort" -type List[T any] []T +type List[T comparable] []T func (l List[T]) Filter(fn func(T) bool) List[T] { if l == nil { @@ -57,6 +57,16 @@ func (l List[T]) Sort(fn func(T, T) bool) List[T] { return clone } +func PickFromList[V any](in []V, q func(v V) bool) (V, bool) { + for _, v := range in { + if q(v) { + return v, true + } + } + + return Default[V](), false +} + func MapList[T, V comparable](in List[T], fn func(T) V) List[V] { if in == nil { return nil @@ -67,3 +77,27 @@ func MapList[T, V comparable](in List[T], fn func(T) V) List[V] { } return result } + +func FormatList[A, B any](in []A, format func(A) B) []B { + var r = make([]B, len(in)) + + for id := range in { + r[id] = format(in[id]) + } + + return r +} + +func FormatListErr[A, B any](in []A, format func(A) (B, error)) ([]B, error) { + var r = make([]B, len(in)) + + for id := range in { + if o, err := format(in[id]); err != nil { + return nil, err + } else { + r[id] = o + } + } + + return r, nil +} diff --git a/pkg/util/tests/kubernetes.go b/pkg/util/tests/kubernetes.go index fe5e30498..066ea95b8 100644 --- a/pkg/util/tests/kubernetes.go +++ b/pkg/util/tests/kubernetes.go @@ -45,6 +45,8 @@ import ( "github.com/arangodb/kube-arangodb/pkg/apis/ml" mlApiv1alpha1 "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1" mlApi "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1beta1" + "github.com/arangodb/kube-arangodb/pkg/apis/networking" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" "github.com/arangodb/kube-arangodb/pkg/apis/scheduler" schedulerApiv1alpha1 "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1" schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1" @@ -243,6 +245,12 @@ func CreateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe vl := *v _, err := arango.AnalyticsV1alpha1().GraphAnalyticsEngines(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) require.NoError(t, err) + case **networkingApi.ArangoRoute: + require.NotNil(t, v) + + vl := *v + _, err := arango.NetworkingV1alpha1().ArangoRoutes(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) + require.NoError(t, err) default: require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) } @@ -397,6 +405,12 @@ func UpdateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe vl := *v _, err := arango.AnalyticsV1alpha1().GraphAnalyticsEngines(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{}) require.NoError(t, err) + case **networkingApi.ArangoRoute: + require.NotNil(t, v) + + vl := *v + _, err := arango.NetworkingV1alpha1().ArangoRoutes(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{}) + require.NoError(t, err) default: require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) } @@ -525,6 +539,11 @@ func DeleteObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe vl := *v require.NoError(t, arango.AnalyticsV1alpha1().GraphAnalyticsEngines(vl.GetNamespace()).Delete(context.Background(), vl.GetName(), meta.DeleteOptions{})) + case **networkingApi.ArangoRoute: + require.NotNil(t, v) + + vl := *v + require.NoError(t, arango.NetworkingV1alpha1().ArangoRoutes(vl.GetNamespace()).Delete(context.Background(), vl.GetName(), meta.DeleteOptions{})) default: require.Fail(t, fmt.Sprintf("Unable to delete object: %s", reflect.TypeOf(v).String())) } @@ -882,6 +901,21 @@ func RefreshObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientS } else { *v = vn } + case **networkingApi.ArangoRoute: + require.NotNil(t, v) + + vl := *v + + vn, err := arango.NetworkingV1alpha1().ArangoRoutes(vl.GetNamespace()).Get(context.Background(), vl.GetName(), meta.GetOptions{}) + if err != nil { + if kerrors.IsNotFound(err) { + *v = nil + } else { + require.NoError(t, err) + } + } else { + *v = vn + } default: require.Fail(t, fmt.Sprintf("Unable to get object: %s", reflect.TypeOf(v).String())) } @@ -1054,6 +1088,14 @@ func SetMetaBasedOnType(t *testing.T, object meta.Object) { analytics.GraphAnalyticsEngineResourcePlural, object.GetNamespace(), object.GetName())) + case *networkingApi.ArangoRoute: + v.Kind = networking.ArangoRouteResourceKind + v.APIVersion = networkingApi.SchemeGroupVersion.String() + v.SetSelfLink(fmt.Sprintf("/api/%s/%s/%s/%s", + networkingApi.SchemeGroupVersion.String(), + networking.ArangoRouteResourcePlural, + object.GetNamespace(), + object.GetName())) default: require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) } @@ -1230,6 +1272,12 @@ func GVK(t *testing.T, object meta.Object) schema.GroupVersionKind { Version: analyticsApi.ArangoAnalyticsVersion, Kind: analytics.GraphAnalyticsEngineResourceKind, } + case *networkingApi.ArangoRoute: + return schema.GroupVersionKind{ + Group: networking.ArangoNetworkingGroupName, + Version: networkingApi.ArangoNetworkingVersion, + Kind: networking.ArangoRouteResourceKind, + } default: require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String())) return schema.GroupVersionKind{} diff --git a/pkg/util/tests/kubernetes_test.go b/pkg/util/tests/kubernetes_test.go index d5e2716ce..57061255d 100644 --- a/pkg/util/tests/kubernetes_test.go +++ b/pkg/util/tests/kubernetes_test.go @@ -36,6 +36,7 @@ import ( api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" mlApiv1alpha1 "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1" mlApi "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1beta1" + networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1" schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1" "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" "github.com/arangodb/kube-arangodb/pkg/util/kclient" @@ -87,4 +88,5 @@ func Test_NewMetaObject(t *testing.T) { NewMetaObjectRun[*mlApiv1alpha1.ArangoMLCronJob](t) NewMetaObjectRun[*schedulerApi.ArangoProfile](t) NewMetaObjectRun[*analyticsApi.GraphAnalyticsEngine](t) + NewMetaObjectRun[*networkingApi.ArangoRoute](t) }