1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] Webhooks (#1781)

This commit is contained in:
Adam Janikowski 2024-12-13 10:13:58 +01:00 committed by GitHub
parent 3993a0c40f
commit 58154dbf5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 4372 additions and 463 deletions

View file

@ -129,6 +129,8 @@ linters-settings:
pkg: k8s.io/api/batch/v1
- alias: core
pkg: k8s.io/api/core/v1
- alias: admission
pkg: k8s.io/api/admission/v1
- alias: policy
pkg: k8s.io/api/policy/v1
- alias: storage

View file

@ -33,6 +33,7 @@
- (Feature) (Scheduler) Shutdown Integration
- (Feature) CertManager Integration
- (Feature) (Networking) Gateway Options sync
- (Feature) Webhooks
## [1.2.43](https://github.com/arangodb/kube-arangodb/tree/1.2.43) (2024-10-14)
- (Feature) ArangoRoute CRD

View file

@ -195,7 +195,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 <level> or <logger>=<level>. 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, generic-parent-operator, helm, http, inspector, integration-config-v1, integration-envoy-auth-v3, integration-scheduler-v2, integration-storage-v2, integrations, k8s-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, platform-chart-operator, platform-pod-shutdown, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, server, server-authentication (default [info])
--log.level stringArray Set log levels in format <level> or <logger>=<level>. 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, generic-parent-operator, helm, http, inspector, integration-config-v1, integration-envoy-auth-v3, integration-scheduler-v2, integration-storage-v2, integrations, k8s-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, platform-chart-operator, platform-pod-shutdown, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, server, server-authentication, webhook (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

View file

@ -191,6 +191,67 @@ spec:
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{ if .Values.webhooks.enabled }}
- name: webhooks
imagePullPolicy: {{ .Values.operator.imagePullPolicy }}
image: {{ .Values.operator.image }}
args:
- webhook
{{- if .Values.certificate.enabled }}
- --ssl.secret.name={{ template "kube-arangodb.operatorName" . }}-webhook-cert
- --ssl.secret.namespace={{ .Release.Namespace }}
{{- end -}}
{{- if .Values.webhooks.args }}
{{- range .Values.webhooks.args }}
- {{ . | quote }}
{{- end }}
{{- end }}
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_CONTAINER_NAME
value: "webhooks"
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- name: webhooks
containerPort: 8828
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- 'ALL'
{{- if .Values.webhooks.resources }}
resources:
{{ toYaml .Values.webhooks.resources | indent 22 }}
{{- end }}
{{- if not .Values.webhooks.debug }}
livenessProbe:
httpGet:
path: /health
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{- end }}
tolerations:
- key: "node.kubernetes.io/unreachable"

View file

@ -0,0 +1,31 @@
{{ if .Values.webhooks.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
namespace: {{ .Release.Namespace }}
{{- if .Values.operator.annotations }}
annotations:
{{ toYaml .Values.operator.annotations | indent 8 }}
{{- end }}
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 }}
spec:
ports:
- name: webhooks
port: 443
protocol: TCP
targetPort: webhooks
selector:
app.kubernetes.io/name: {{ template "kube-arangodb.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
type: ClusterIP
{{- end }}

View file

@ -0,0 +1,24 @@
{{ if .Values.certificate.enabled -}}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
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 }}
spec:
secretName: {{ template "kube-arangodb.operatorName" . }}-webhook-cert
duration: {{ .Values.certificate.cert.duration }}
issuerRef:
name: {{ template "kube-arangodb.operatorName" . }}
dnsNames:
- {{ template "kube-arangodb.operatorName" . }}-webhook
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}.svc
{{- end }}

View file

@ -0,0 +1,42 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks:
- name: "pods.policies.scheduler.arangodb.com"
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- {{ .Release.Namespace }}
objectSelector:
matchExpressions:
- key: profiles.arangodb.com/deployment
operator: Exists
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: {{ .Release.Namespace }}
name: {{ template "kube-arangodb.operatorName" . }}-webhook
path: /webhook/core/v1/pods/policies/mutate
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
{{- end }}

View file

@ -0,0 +1,17 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks: []
{{- end }}

View file

@ -44,6 +44,16 @@ rbac:
acs: true
at: true
debug: false
webhooks:
enabled: false
args: []
resources:
limits:
cpu: 1
memory: 128Mi
requests:
cpu: 250m
memory: 128Mi
certificate:
enabled: false
ca:

View file

@ -191,6 +191,67 @@ spec:
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{ if .Values.webhooks.enabled }}
- name: webhooks
imagePullPolicy: {{ .Values.operator.imagePullPolicy }}
image: {{ .Values.operator.image }}
args:
- webhook
{{- if .Values.certificate.enabled }}
- --ssl.secret.name={{ template "kube-arangodb.operatorName" . }}-webhook-cert
- --ssl.secret.namespace={{ .Release.Namespace }}
{{- end -}}
{{- if .Values.webhooks.args }}
{{- range .Values.webhooks.args }}
- {{ . | quote }}
{{- end }}
{{- end }}
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_CONTAINER_NAME
value: "webhooks"
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- name: webhooks
containerPort: 8828
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- 'ALL'
{{- if .Values.webhooks.resources }}
resources:
{{ toYaml .Values.webhooks.resources | indent 22 }}
{{- end }}
{{- if not .Values.webhooks.debug }}
livenessProbe:
httpGet:
path: /health
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{- end }}
tolerations:
- key: "node.kubernetes.io/unreachable"

View file

@ -0,0 +1,31 @@
{{ if .Values.webhooks.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
namespace: {{ .Release.Namespace }}
{{- if .Values.operator.annotations }}
annotations:
{{ toYaml .Values.operator.annotations | indent 8 }}
{{- end }}
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 }}
spec:
ports:
- name: webhooks
port: 443
protocol: TCP
targetPort: webhooks
selector:
app.kubernetes.io/name: {{ template "kube-arangodb.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
type: ClusterIP
{{- end }}

View file

@ -0,0 +1,24 @@
{{ if .Values.certificate.enabled -}}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
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 }}
spec:
secretName: {{ template "kube-arangodb.operatorName" . }}-webhook-cert
duration: {{ .Values.certificate.cert.duration }}
issuerRef:
name: {{ template "kube-arangodb.operatorName" . }}
dnsNames:
- {{ template "kube-arangodb.operatorName" . }}-webhook
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}.svc
{{- end }}

View file

@ -0,0 +1,42 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks:
- name: "pods.policies.scheduler.arangodb.com"
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- {{ .Release.Namespace }}
objectSelector:
matchExpressions:
- key: profiles.arangodb.com/deployment
operator: Exists
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: {{ .Release.Namespace }}
name: {{ template "kube-arangodb.operatorName" . }}-webhook
path: /webhook/core/v1/pods/policies/mutate
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
{{- end }}

View file

@ -0,0 +1,17 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks: []
{{- end }}

View file

@ -44,6 +44,16 @@ rbac:
acs: true
at: true
debug: false
webhooks:
enabled: false
args: []
resources:
limits:
cpu: 1
memory: 128Mi
requests:
cpu: 250m
memory: 128Mi
certificate:
enabled: false
ca:

View file

@ -191,6 +191,67 @@ spec:
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{ if .Values.webhooks.enabled }}
- name: webhooks
imagePullPolicy: {{ .Values.operator.imagePullPolicy }}
image: {{ .Values.operator.image }}
args:
- webhook
{{- if .Values.certificate.enabled }}
- --ssl.secret.name={{ template "kube-arangodb.operatorName" . }}-webhook-cert
- --ssl.secret.namespace={{ .Release.Namespace }}
{{- end -}}
{{- if .Values.webhooks.args }}
{{- range .Values.webhooks.args }}
- {{ . | quote }}
{{- end }}
{{- end }}
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_CONTAINER_NAME
value: "webhooks"
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- name: webhooks
containerPort: 8828
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- 'ALL'
{{- if .Values.webhooks.resources }}
resources:
{{ toYaml .Values.webhooks.resources | indent 22 }}
{{- end }}
{{- if not .Values.webhooks.debug }}
livenessProbe:
httpGet:
path: /health
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{- end }}
tolerations:
- key: "node.kubernetes.io/unreachable"

View file

@ -0,0 +1,31 @@
{{ if .Values.webhooks.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
namespace: {{ .Release.Namespace }}
{{- if .Values.operator.annotations }}
annotations:
{{ toYaml .Values.operator.annotations | indent 8 }}
{{- end }}
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 }}
spec:
ports:
- name: webhooks
port: 443
protocol: TCP
targetPort: webhooks
selector:
app.kubernetes.io/name: {{ template "kube-arangodb.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
type: ClusterIP
{{- end }}

View file

@ -0,0 +1,24 @@
{{ if .Values.certificate.enabled -}}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
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 }}
spec:
secretName: {{ template "kube-arangodb.operatorName" . }}-webhook-cert
duration: {{ .Values.certificate.cert.duration }}
issuerRef:
name: {{ template "kube-arangodb.operatorName" . }}
dnsNames:
- {{ template "kube-arangodb.operatorName" . }}-webhook
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}.svc
{{- end }}

View file

@ -0,0 +1,42 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks:
- name: "pods.policies.scheduler.arangodb.com"
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- {{ .Release.Namespace }}
objectSelector:
matchExpressions:
- key: profiles.arangodb.com/deployment
operator: Exists
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: {{ .Release.Namespace }}
name: {{ template "kube-arangodb.operatorName" . }}-webhook
path: /webhook/core/v1/pods/policies/mutate
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
{{- end }}

View file

@ -0,0 +1,17 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks: []
{{- end }}

View file

@ -44,6 +44,16 @@ rbac:
acs: true
at: true
debug: false
webhooks:
enabled: false
args: []
resources:
limits:
cpu: 1
memory: 128Mi
requests:
cpu: 250m
memory: 128Mi
certificate:
enabled: false
ca:

View file

@ -191,6 +191,67 @@ spec:
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{ if .Values.webhooks.enabled }}
- name: webhooks
imagePullPolicy: {{ .Values.operator.imagePullPolicy }}
image: {{ .Values.operator.image }}
args:
- webhook
{{- if .Values.certificate.enabled }}
- --ssl.secret.name={{ template "kube-arangodb.operatorName" . }}-webhook-cert
- --ssl.secret.namespace={{ .Release.Namespace }}
{{- end -}}
{{- if .Values.webhooks.args }}
{{- range .Values.webhooks.args }}
- {{ . | quote }}
{{- end }}
{{- end }}
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_CONTAINER_NAME
value: "webhooks"
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- name: webhooks
containerPort: 8828
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- 'ALL'
{{- if .Values.webhooks.resources }}
resources:
{{ toYaml .Values.webhooks.resources | indent 22 }}
{{- end }}
{{- if not .Values.webhooks.debug }}
livenessProbe:
httpGet:
path: /health
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8828
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
{{- end }}
{{- end }}
tolerations:
- key: "node.kubernetes.io/unreachable"

View file

@ -0,0 +1,31 @@
{{ if .Values.webhooks.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
namespace: {{ .Release.Namespace }}
{{- if .Values.operator.annotations }}
annotations:
{{ toYaml .Values.operator.annotations | indent 8 }}
{{- end }}
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 }}
spec:
ports:
- name: webhooks
port: 443
protocol: TCP
targetPort: webhooks
selector:
app.kubernetes.io/name: {{ template "kube-arangodb.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
release: {{ .Release.Name }}
type: ClusterIP
{{- end }}

View file

@ -0,0 +1,24 @@
{{ if .Values.certificate.enabled -}}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ template "kube-arangodb.operatorName" . }}-webhook
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 }}
spec:
secretName: {{ template "kube-arangodb.operatorName" . }}-webhook-cert
duration: {{ .Values.certificate.cert.duration }}
issuerRef:
name: {{ template "kube-arangodb.operatorName" . }}
dnsNames:
- {{ template "kube-arangodb.operatorName" . }}-webhook
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}
- {{ template "kube-arangodb.operatorName" . }}-webhook.{{ .Release.Namespace }}.svc
{{- end }}

View file

@ -0,0 +1,42 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks:
- name: "pods.policies.scheduler.arangodb.com"
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- {{ .Release.Namespace }}
objectSelector:
matchExpressions:
- key: profiles.arangodb.com/deployment
operator: Exists
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: {{ .Release.Namespace }}
name: {{ template "kube-arangodb.operatorName" . }}-webhook
path: /webhook/core/v1/pods/policies/mutate
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
{{- end }}

View file

@ -0,0 +1,17 @@
{{ if .Values.webhooks.enabled }}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "{{ template "kube-arangodb.operatorName" . }}.{{ .Release.Namespace }}.operator.arangodb.com"
annotations:
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kube-arangodb.operatorName" . }}-ca"
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 }}
webhooks: []
{{- end }}

View file

@ -45,6 +45,16 @@ rbac:
acs: true
at: true
debug: false
webhooks:
enabled: false
args: []
resources:
limits:
cpu: 1
memory: 128Mi
requests:
cpu: 250m
memory: 128Mi
certificate:
enabled: false
ca:

View file

@ -84,9 +84,6 @@ const (
)
var (
logger = logging.Global().RegisterAndGetLogger("root", logging.Info)
eventRecorder = logging.Global().RegisterAndGetLogger("root-event-recorder", logging.Info)
cmdMain = cobra.Command{
Use: "arangodb_operator",
Run: executeMain,

View file

@ -22,17 +22,16 @@ package cmd
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/arangodb/kube-arangodb/pkg/exporter"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
)
var (
@ -67,23 +66,14 @@ func init() {
func cmdExporterCheck(cmd *cobra.Command, args []string) {
if err := cmdExporterCheckE(); err != nil {
log.Error().Err(err).Msgf("Fatal")
logger.Err(err).Error("Fatal")
os.Exit(1)
}
}
func onSigterm(f func()) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
defer f()
<-sigs
}()
}
func cmdExporterCheckE() error {
ctx := util.CreateSignalContext(context.Background())
if len(exporterInput.endpoints) < 1 {
return errors.Errorf("Requires at least one ArangoDB Endpoint to be present")
}
@ -117,26 +107,34 @@ func cmdExporterCheckE() error {
return string(data), nil
}, false, 15*time.Second)
go mon.UpdateMonitorStatus(util.CreateSignalContext(context.Background()))
go mon.UpdateMonitorStatus(ctx)
exporter := exporter.NewExporter(exporterInput.listenAddress, "/metrics", p)
if exporterInput.keyfile != "" {
if e, err := exporter.WithKeyfile(exporterInput.keyfile); err != nil {
return err
} else {
if r, err := e.Start(); err != nil {
return err
} else {
onSigterm(r.Stop)
return r.Wait()
server, err := operatorHTTP.NewServer(ctx,
operatorHTTP.DefaultHTTPServerSettings,
operatorHTTP.WithServeMux(func(in *http.ServeMux) {
in.Handle("/metrics", p)
}, func(in *http.ServeMux) {
in.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head><title>ArangoDB Exporter</title></head>
<body>
<h1>ArangoDB Exporter</h1>
<p><a href='/metrics'>Metrics</a></p>
</body>
</html>`))
})
}),
operatorHTTP.WithTLSConfigFetcherGen(func() util.TLSConfigFetcher {
if exporterInput.keyfile != "" {
return util.NewKeyfileTLSConfig(exporterInput.keyfile)
}
}
} else {
if r, err := exporter.Start(); err != nil {
return err
} else {
onSigterm(r.Stop)
return r.Wait()
}
return nil
}),
)
if err != nil {
return err
}
return server.StartAddr(ctx, exporterInput.listenAddress)
}

28
cmd/logger.go Normal file
View file

@ -0,0 +1,28 @@
//
// 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 cmd
import "github.com/arangodb/kube-arangodb/pkg/logging"
var (
logger = logging.Global().RegisterAndGetLogger("root", logging.Info)
eventRecorder = logging.Global().RegisterAndGetLogger("root-event-recorder", logging.Info)
)

113
cmd/webhook.go Normal file
View file

@ -0,0 +1,113 @@
//
// 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 cmd
import (
"context"
goHttp "net/http"
"os"
"github.com/spf13/cobra"
"github.com/arangodb/kube-arangodb/pkg/handlers/scheduler"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/http"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
"github.com/arangodb/kube-arangodb/pkg/webhook"
)
var (
cmdWebhook = &cobra.Command{
Use: "webhook",
Run: cmdWebhookCheck,
}
webhookInput struct {
listenAddress string
secretName, secretNamespace string
}
)
func init() {
f := cmdWebhook.PersistentFlags()
f.StringVar(&webhookInput.listenAddress, "server.address", "0.0.0.0:8828", "Address the webhook will listen on (IP:port)")
f.StringVar(&webhookInput.secretName, "ssl.secret.name", "", "Secret Name containing TLS certificate used for the metrics server")
f.StringVar(&webhookInput.secretNamespace, "ssl.secret.namespace", os.Getenv(constants.EnvOperatorPodNamespace), "Secret Name containing TLS certificate used for the metrics server")
cmdMain.AddCommand(cmdWebhook)
}
func cmdWebhookCheck(cmd *cobra.Command, args []string) {
if err := cmdWebhookCheckE(); err != nil {
logger.Err(err).Error("Fatal")
os.Exit(1)
}
}
func cmdWebhookCheckE() error {
ctx := util.CreateSignalContext(context.Background())
client, ok := kclient.GetDefaultFactory().Client()
if !ok {
return errors.Errorf("Unable to get client")
}
var admissions webhook.Admissions
admissions = append(admissions, scheduler.WebhookAdmissions(client)...)
server, err := webhookServer(ctx, client, admissions...)
if err != nil {
return err
}
logger.Str("addr", webhookInput.listenAddress).Info("Starting Webhook Server")
return server.StartAddr(ctx, webhookInput.listenAddress)
}
func webhookServer(ctx context.Context, client kclient.Client, admissions ...webhook.Admission) (http.Server, error) {
return http.NewServer(ctx,
http.DefaultHTTPServerSettings,
http.WithTLSConfigFetcherGen(func() util.TLSConfigFetcher {
if webhookInput.secretName != "" && webhookInput.secretNamespace != "" {
return util.NewSecretTLSConfig(client.Kubernetes().CoreV1().Secrets(webhookInput.secretNamespace), webhookInput.secretName)
}
return util.NewSelfSignedTLSConfig("operator")
}),
http.WithServeMux(
func(in *goHttp.ServeMux) {
in.HandleFunc("/ready", func(writer goHttp.ResponseWriter, request *goHttp.Request) {
writer.WriteHeader(goHttp.StatusOK)
})
in.HandleFunc("/health", func(writer goHttp.ResponseWriter, request *goHttp.Request) {
writer.WriteHeader(goHttp.StatusOK)
})
},
webhook.Admissions(admissions).Register(),
),
)
}

View file

@ -406,5 +406,8 @@ Links:
### .spec.template.priority
Type: `integer` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.43/pkg/apis/scheduler/v1beta1/profile_template.go#L32)</sup>
Type: `integer` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.43/pkg/apis/scheduler/v1beta1/profile_template.go#L34)</sup>
Priority defines Priority of the Profile. Higher value means Profile will get applied first.
If Priority across Profiles is same, Profiles are also sorted by name.

View file

@ -24,6 +24,7 @@ Available Commands:
storage
task
version
webhook
Flags:
--action.PVCResize.concurrency int Define limit of concurrent PVC Resizes on the cluster (default 32)
@ -80,7 +81,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 <level> or <logger>=<level>. 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, generic-parent-operator, helm, http, inspector, integration-config-v1, integration-envoy-auth-v3, integration-scheduler-v2, integration-storage-v2, integrations, k8s-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, platform-chart-operator, platform-pod-shutdown, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, server, server-authentication (default [info])
--log.level stringArray Set log levels in format <level> or <logger>=<level>. 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, generic-parent-operator, helm, http, inspector, integration-config-v1, integration-envoy-auth-v3, integration-scheduler-v2, integration-storage-v2, integrations, k8s-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, platform-chart-operator, platform-pod-shutdown, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, server, server-authentication, webhook (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

View file

@ -8,7 +8,7 @@ parent: How to ...
1) Create a kubernetes [Secret](https://kubernetes.io/docs/tasks/configmap-secret/managing-secret-using-kubectl/) with root password:
```bash
kubectl create secret generic arango-root-pwd --from-literal=password=<paste_your_password_here>
kubectl create secret generic arango-root-pwd --from-literal=password=<paste_your_password_here> --from-literal=username=root
```
1) Then specify the newly created secret in the ArangoDeploymentSpec:

View file

@ -29,6 +29,20 @@ metadata:
profiles.arangodb.com/deployment: << deployment name >>
```
### Webhooks
When Webhook support is enabled Integration Sidecar is supported in Kubernetes Pod resources.
To inject integration sidecar for specific deployment label needs to be defined:
```yaml
apiVersion: v1
kind: Pod
metadata:
labels:
profiles.arangodb.com/deployment: << deployment name >>
```
### Integrations
To enable integration in specific version, labels needs to be added:

1752
go.sum

File diff suppressed because it is too large Load diff

View file

@ -798,117 +798,115 @@ var file_integrations_storage_v2_definition_storage_proto_rawDesc = []byte{
0x0a, 0x30, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x08, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x1a, 0x1f, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x23, 0x0a,
0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12,
0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61,
0x74, 0x68, 0x22, 0x71, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61,
0x74, 0x68, 0x12, 0x31, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1d, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x56, 0x32, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x68, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x56, 0x32, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04,
0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65,
0x12, 0x3d, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x22,
0x3e, 0x0a, 0x14, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x69, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x22,
0x17, 0x0a, 0x15, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x69, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x49, 0x0a, 0x1a, 0x53, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x56, 0x32, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70,
0x61, 0x74, 0x68, 0x22, 0x33, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32,
0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x60, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x56, 0x32, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e,
0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04,
0x70, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x50, 0x0a, 0x1c, 0x53, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79,
0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73,
0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x22, 0x49, 0x0a, 0x1a,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x48, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61,
0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64,
0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74,
0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x50, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x74, 0x6f, 0x12, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x1f, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x23, 0x0a, 0x0d,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a,
0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
0x68, 0x22, 0x6f, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4f, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68,
0x12, 0x30, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x56, 0x32, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e,
0x66, 0x6f, 0x22, 0x68, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x3d, 0x0a,
0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0b, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x22, 0x3e, 0x0a, 0x14,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x88, 0x01,
0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x22, 0x17, 0x0a, 0x15,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x48, 0x0a, 0x1a, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x56, 0x32, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22,
0x33, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x52, 0x65, 0x61, 0x64,
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14,
0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63,
0x68, 0x75, 0x6e, 0x6b, 0x22, 0x5f, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56,
0x32, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x50, 0x0a, 0x1c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x56, 0x32, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x63,
0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63,
0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x22, 0x48, 0x0a, 0x1a, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x56, 0x32, 0x48, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49,
0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x4b, 0x0a, 0x1c, 0x53, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68,
0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x1f, 0x0a, 0x1d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x56, 0x32, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70,
0x61, 0x74, 0x68, 0x22, 0x4f, 0x0a, 0x1c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32,
0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x05, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x32, 0xad, 0x04, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x56, 0x32, 0x12, 0x47, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x1e, 0x2e, 0x73, 0x68, 0x75,
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x49,
0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x68, 0x75,
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x49,
0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x0a, 0x52,
0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x24, 0x2e, 0x73, 0x68, 0x75, 0x74,
0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x52, 0x65,
0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x25, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x56, 0x32, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5e, 0x0a, 0x0b, 0x57, 0x72, 0x69, 0x74,
0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x25, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x57, 0x72, 0x69, 0x74,
0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26,
0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74,
0x68, 0x22, 0x4f, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x48, 0x65,
0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x30, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x56, 0x32, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e,
0x66, 0x6f, 0x22, 0x4a, 0x0a, 0x1c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x56, 0x32, 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x1f,
0x0a, 0x1d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x49, 0x0a, 0x1b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4c, 0x69, 0x73, 0x74,
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a,
0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32,
0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x4e, 0x0a, 0x1c, 0x53, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x32, 0xa1, 0x04, 0x0a, 0x09, 0x53,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x12, 0x45, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74,
0x12, 0x1d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1e, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x56, 0x32, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x59, 0x0a, 0x0a, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x23, 0x2e,
0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56,
0x32, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x24, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f,
0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5c, 0x0a, 0x0b, 0x57, 0x72,
0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x24, 0x2e, 0x73, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x57, 0x72, 0x69,
0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x25, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x56, 0x32, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x59, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x64,
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x24, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77,
0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x48, 0x65, 0x61, 0x64, 0x4f,
0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x73,
0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56,
0x32, 0x48, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x12, 0x26, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73, 0x68,
0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x73, 0x68, 0x75,
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4c,
0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x30, 0x01, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x61, 0x6e, 0x67, 0x6f, 0x64, 0x62, 0x2f, 0x6b, 0x75, 0x62, 0x65,
0x2d, 0x61, 0x72, 0x61, 0x6e, 0x67, 0x6f, 0x64, 0x62, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x76,
0x32, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x57, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x64,
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x23, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x48, 0x65, 0x61, 0x64, 0x4f, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x48,
0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63,
0x74, 0x12, 0x25, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x5c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12,
0x24, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x56, 0x32, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x56, 0x32, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x42, 0x46,
0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x61,
0x6e, 0x67, 0x6f, 0x64, 0x62, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x2d, 0x61, 0x72, 0x61, 0x6e, 0x67,
0x6f, 0x64, 0x62, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x64, 0x65, 0x66, 0x69,
0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -925,46 +923,46 @@ func file_integrations_storage_v2_definition_storage_proto_rawDescGZIP() []byte
var file_integrations_storage_v2_definition_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_integrations_storage_v2_definition_storage_proto_goTypes = []interface{}{
(*StorageV2Path)(nil), // 0: shutdown.StorageV2Path
(*StorageV2Object)(nil), // 1: shutdown.StorageV2Object
(*StorageV2ObjectInfo)(nil), // 2: shutdown.StorageV2ObjectInfo
(*StorageV2InitRequest)(nil), // 3: shutdown.StorageV2InitRequest
(*StorageV2InitResponse)(nil), // 4: shutdown.StorageV2InitResponse
(*StorageV2ReadObjectRequest)(nil), // 5: shutdown.StorageV2ReadObjectRequest
(*StorageV2ReadObjectResponse)(nil), // 6: shutdown.StorageV2ReadObjectResponse
(*StorageV2WriteObjectRequest)(nil), // 7: shutdown.StorageV2WriteObjectRequest
(*StorageV2WriteObjectResponse)(nil), // 8: shutdown.StorageV2WriteObjectResponse
(*StorageV2HeadObjectRequest)(nil), // 9: shutdown.StorageV2HeadObjectRequest
(*StorageV2HeadObjectResponse)(nil), // 10: shutdown.StorageV2HeadObjectResponse
(*StorageV2DeleteObjectRequest)(nil), // 11: shutdown.StorageV2DeleteObjectRequest
(*StorageV2DeleteObjectResponse)(nil), // 12: shutdown.StorageV2DeleteObjectResponse
(*StorageV2ListObjectsRequest)(nil), // 13: shutdown.StorageV2ListObjectsRequest
(*StorageV2ListObjectsResponse)(nil), // 14: shutdown.StorageV2ListObjectsResponse
(*StorageV2Path)(nil), // 0: storage.StorageV2Path
(*StorageV2Object)(nil), // 1: storage.StorageV2Object
(*StorageV2ObjectInfo)(nil), // 2: storage.StorageV2ObjectInfo
(*StorageV2InitRequest)(nil), // 3: storage.StorageV2InitRequest
(*StorageV2InitResponse)(nil), // 4: storage.StorageV2InitResponse
(*StorageV2ReadObjectRequest)(nil), // 5: storage.StorageV2ReadObjectRequest
(*StorageV2ReadObjectResponse)(nil), // 6: storage.StorageV2ReadObjectResponse
(*StorageV2WriteObjectRequest)(nil), // 7: storage.StorageV2WriteObjectRequest
(*StorageV2WriteObjectResponse)(nil), // 8: storage.StorageV2WriteObjectResponse
(*StorageV2HeadObjectRequest)(nil), // 9: storage.StorageV2HeadObjectRequest
(*StorageV2HeadObjectResponse)(nil), // 10: storage.StorageV2HeadObjectResponse
(*StorageV2DeleteObjectRequest)(nil), // 11: storage.StorageV2DeleteObjectRequest
(*StorageV2DeleteObjectResponse)(nil), // 12: storage.StorageV2DeleteObjectResponse
(*StorageV2ListObjectsRequest)(nil), // 13: storage.StorageV2ListObjectsRequest
(*StorageV2ListObjectsResponse)(nil), // 14: storage.StorageV2ListObjectsResponse
(*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
}
var file_integrations_storage_v2_definition_storage_proto_depIdxs = []int32{
0, // 0: shutdown.StorageV2Object.path:type_name -> shutdown.StorageV2Path
2, // 1: shutdown.StorageV2Object.info:type_name -> shutdown.StorageV2ObjectInfo
15, // 2: shutdown.StorageV2ObjectInfo.last_updated:type_name -> google.protobuf.Timestamp
0, // 3: shutdown.StorageV2ReadObjectRequest.path:type_name -> shutdown.StorageV2Path
0, // 4: shutdown.StorageV2WriteObjectRequest.path:type_name -> shutdown.StorageV2Path
0, // 5: shutdown.StorageV2HeadObjectRequest.path:type_name -> shutdown.StorageV2Path
2, // 6: shutdown.StorageV2HeadObjectResponse.info:type_name -> shutdown.StorageV2ObjectInfo
0, // 7: shutdown.StorageV2DeleteObjectRequest.path:type_name -> shutdown.StorageV2Path
0, // 8: shutdown.StorageV2ListObjectsRequest.path:type_name -> shutdown.StorageV2Path
1, // 9: shutdown.StorageV2ListObjectsResponse.files:type_name -> shutdown.StorageV2Object
3, // 10: shutdown.StorageV2.Init:input_type -> shutdown.StorageV2InitRequest
5, // 11: shutdown.StorageV2.ReadObject:input_type -> shutdown.StorageV2ReadObjectRequest
7, // 12: shutdown.StorageV2.WriteObject:input_type -> shutdown.StorageV2WriteObjectRequest
9, // 13: shutdown.StorageV2.HeadObject:input_type -> shutdown.StorageV2HeadObjectRequest
11, // 14: shutdown.StorageV2.DeleteObject:input_type -> shutdown.StorageV2DeleteObjectRequest
13, // 15: shutdown.StorageV2.ListObjects:input_type -> shutdown.StorageV2ListObjectsRequest
4, // 16: shutdown.StorageV2.Init:output_type -> shutdown.StorageV2InitResponse
6, // 17: shutdown.StorageV2.ReadObject:output_type -> shutdown.StorageV2ReadObjectResponse
8, // 18: shutdown.StorageV2.WriteObject:output_type -> shutdown.StorageV2WriteObjectResponse
10, // 19: shutdown.StorageV2.HeadObject:output_type -> shutdown.StorageV2HeadObjectResponse
12, // 20: shutdown.StorageV2.DeleteObject:output_type -> shutdown.StorageV2DeleteObjectResponse
14, // 21: shutdown.StorageV2.ListObjects:output_type -> shutdown.StorageV2ListObjectsResponse
0, // 0: storage.StorageV2Object.path:type_name -> storage.StorageV2Path
2, // 1: storage.StorageV2Object.info:type_name -> storage.StorageV2ObjectInfo
15, // 2: storage.StorageV2ObjectInfo.last_updated:type_name -> google.protobuf.Timestamp
0, // 3: storage.StorageV2ReadObjectRequest.path:type_name -> storage.StorageV2Path
0, // 4: storage.StorageV2WriteObjectRequest.path:type_name -> storage.StorageV2Path
0, // 5: storage.StorageV2HeadObjectRequest.path:type_name -> storage.StorageV2Path
2, // 6: storage.StorageV2HeadObjectResponse.info:type_name -> storage.StorageV2ObjectInfo
0, // 7: storage.StorageV2DeleteObjectRequest.path:type_name -> storage.StorageV2Path
0, // 8: storage.StorageV2ListObjectsRequest.path:type_name -> storage.StorageV2Path
1, // 9: storage.StorageV2ListObjectsResponse.files:type_name -> storage.StorageV2Object
3, // 10: storage.StorageV2.Init:input_type -> storage.StorageV2InitRequest
5, // 11: storage.StorageV2.ReadObject:input_type -> storage.StorageV2ReadObjectRequest
7, // 12: storage.StorageV2.WriteObject:input_type -> storage.StorageV2WriteObjectRequest
9, // 13: storage.StorageV2.HeadObject:input_type -> storage.StorageV2HeadObjectRequest
11, // 14: storage.StorageV2.DeleteObject:input_type -> storage.StorageV2DeleteObjectRequest
13, // 15: storage.StorageV2.ListObjects:input_type -> storage.StorageV2ListObjectsRequest
4, // 16: storage.StorageV2.Init:output_type -> storage.StorageV2InitResponse
6, // 17: storage.StorageV2.ReadObject:output_type -> storage.StorageV2ReadObjectResponse
8, // 18: storage.StorageV2.WriteObject:output_type -> storage.StorageV2WriteObjectResponse
10, // 19: storage.StorageV2.HeadObject:output_type -> storage.StorageV2HeadObjectResponse
12, // 20: storage.StorageV2.DeleteObject:output_type -> storage.StorageV2DeleteObjectResponse
14, // 21: storage.StorageV2.ListObjects:output_type -> storage.StorageV2ListObjectsResponse
16, // [16:22] is the sub-list for method output_type
10, // [10:16] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name

View file

@ -20,7 +20,7 @@
syntax = "proto3";
package shutdown;
package storage;
import "google/protobuf/timestamp.proto";

View file

@ -46,7 +46,7 @@ func NewStorageV2Client(cc grpc.ClientConnInterface) StorageV2Client {
func (c *storageV2Client) Init(ctx context.Context, in *StorageV2InitRequest, opts ...grpc.CallOption) (*StorageV2InitResponse, error) {
out := new(StorageV2InitResponse)
err := c.cc.Invoke(ctx, "/shutdown.StorageV2/Init", in, out, opts...)
err := c.cc.Invoke(ctx, "/storage.StorageV2/Init", in, out, opts...)
if err != nil {
return nil, err
}
@ -54,7 +54,7 @@ func (c *storageV2Client) Init(ctx context.Context, in *StorageV2InitRequest, op
}
func (c *storageV2Client) ReadObject(ctx context.Context, in *StorageV2ReadObjectRequest, opts ...grpc.CallOption) (StorageV2_ReadObjectClient, error) {
stream, err := c.cc.NewStream(ctx, &StorageV2_ServiceDesc.Streams[0], "/shutdown.StorageV2/ReadObject", opts...)
stream, err := c.cc.NewStream(ctx, &StorageV2_ServiceDesc.Streams[0], "/storage.StorageV2/ReadObject", opts...)
if err != nil {
return nil, err
}
@ -86,7 +86,7 @@ func (x *storageV2ReadObjectClient) Recv() (*StorageV2ReadObjectResponse, error)
}
func (c *storageV2Client) WriteObject(ctx context.Context, opts ...grpc.CallOption) (StorageV2_WriteObjectClient, error) {
stream, err := c.cc.NewStream(ctx, &StorageV2_ServiceDesc.Streams[1], "/shutdown.StorageV2/WriteObject", opts...)
stream, err := c.cc.NewStream(ctx, &StorageV2_ServiceDesc.Streams[1], "/storage.StorageV2/WriteObject", opts...)
if err != nil {
return nil, err
}
@ -121,7 +121,7 @@ func (x *storageV2WriteObjectClient) CloseAndRecv() (*StorageV2WriteObjectRespon
func (c *storageV2Client) HeadObject(ctx context.Context, in *StorageV2HeadObjectRequest, opts ...grpc.CallOption) (*StorageV2HeadObjectResponse, error) {
out := new(StorageV2HeadObjectResponse)
err := c.cc.Invoke(ctx, "/shutdown.StorageV2/HeadObject", in, out, opts...)
err := c.cc.Invoke(ctx, "/storage.StorageV2/HeadObject", in, out, opts...)
if err != nil {
return nil, err
}
@ -130,7 +130,7 @@ func (c *storageV2Client) HeadObject(ctx context.Context, in *StorageV2HeadObjec
func (c *storageV2Client) DeleteObject(ctx context.Context, in *StorageV2DeleteObjectRequest, opts ...grpc.CallOption) (*StorageV2DeleteObjectResponse, error) {
out := new(StorageV2DeleteObjectResponse)
err := c.cc.Invoke(ctx, "/shutdown.StorageV2/DeleteObject", in, out, opts...)
err := c.cc.Invoke(ctx, "/storage.StorageV2/DeleteObject", in, out, opts...)
if err != nil {
return nil, err
}
@ -138,7 +138,7 @@ func (c *storageV2Client) DeleteObject(ctx context.Context, in *StorageV2DeleteO
}
func (c *storageV2Client) ListObjects(ctx context.Context, in *StorageV2ListObjectsRequest, opts ...grpc.CallOption) (StorageV2_ListObjectsClient, error) {
stream, err := c.cc.NewStream(ctx, &StorageV2_ServiceDesc.Streams[2], "/shutdown.StorageV2/ListObjects", opts...)
stream, err := c.cc.NewStream(ctx, &StorageV2_ServiceDesc.Streams[2], "/storage.StorageV2/ListObjects", opts...)
if err != nil {
return nil, err
}
@ -233,7 +233,7 @@ func _StorageV2_Init_Handler(srv interface{}, ctx context.Context, dec func(inte
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/shutdown.StorageV2/Init",
FullMethod: "/storage.StorageV2/Init",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageV2Server).Init(ctx, req.(*StorageV2InitRequest))
@ -298,7 +298,7 @@ func _StorageV2_HeadObject_Handler(srv interface{}, ctx context.Context, dec fun
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/shutdown.StorageV2/HeadObject",
FullMethod: "/storage.StorageV2/HeadObject",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageV2Server).HeadObject(ctx, req.(*StorageV2HeadObjectRequest))
@ -316,7 +316,7 @@ func _StorageV2_DeleteObject_Handler(srv interface{}, ctx context.Context, dec f
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/shutdown.StorageV2/DeleteObject",
FullMethod: "/storage.StorageV2/DeleteObject",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageV2Server).DeleteObject(ctx, req.(*StorageV2DeleteObjectRequest))
@ -349,7 +349,7 @@ func (x *storageV2ListObjectsServer) Send(m *StorageV2ListObjectsResponse) error
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var StorageV2_ServiceDesc = grpc.ServiceDesc{
ServiceName: "shutdown.StorageV2",
ServiceName: "storage.StorageV2",
HandlerType: (*StorageV2Server)(nil),
Methods: []grpc.MethodDesc{
{

5
pkg/api/api.go generated
View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-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,6 +21,7 @@
package api
import (
"context"
"net"
"net/http"
"time"
@ -73,7 +74,7 @@ func NewServer(cli typedCore.CoreV1Interface, cfg ServerConfig) (*Server, error)
return nil, err
}
tlsConfig, err := prepareTLSConfig(cli, cfg)
tlsConfig, err := prepareTLSConfig(cli, cfg).Eval(context.Background())
if err != nil {
return nil, err
}

63
pkg/api/tls.go generated
View file

@ -21,70 +21,15 @@
package api
import (
"context"
"crypto/tls"
"time"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
typedCore "k8s.io/client-go/kubernetes/typed/core/v1"
"github.com/arangodb-helper/go-certificates"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util"
)
func prepareTLSConfig(cli typedCore.CoreV1Interface, cfg ServerConfig) (*tls.Config, error) {
cert, key, err := loadOrSelfSignCertificate(cli, cfg)
if err != nil {
return nil, errors.WithStack(err)
}
tlsConfig, err := createTLSConfig(cert, key)
if err != nil {
return nil, errors.WithStack(err)
}
return tlsConfig, nil
}
// loadOrSelfSignCertificate loads TLS certificate from secret or creates a new one
func loadOrSelfSignCertificate(cli typedCore.CoreV1Interface, cfg ServerConfig) (string, string, error) {
func prepareTLSConfig(cli typedCore.CoreV1Interface, cfg ServerConfig) util.TLSConfigFetcher {
if cfg.TLSSecretName != "" {
// Load TLS certificate from secret
s, err := cli.Secrets(cfg.Namespace).Get(context.Background(), cfg.TLSSecretName, meta.GetOptions{})
if err != nil {
return "", "", err
}
certBytes, found := s.Data[core.TLSCertKey]
if !found {
return "", "", errors.Errorf("No %s found in secret %s", core.TLSCertKey, cfg.TLSSecretName)
}
keyBytes, found := s.Data[core.TLSPrivateKeyKey]
if !found {
return "", "", errors.Errorf("No %s found in secret %s", core.TLSPrivateKeyKey, cfg.TLSSecretName)
}
return string(certBytes), string(keyBytes), nil
return util.NewSecretTLSConfig(cli.Secrets(cfg.Namespace), cfg.TLSSecretName)
}
// Secret not specified, create our own TLS certificate
options := certificates.CreateCertificateOptions{
CommonName: cfg.ServerName,
Hosts: append([]string{cfg.ServerName}, cfg.ServerAltNames...),
ValidFrom: time.Now(),
ValidFor: time.Hour * 24 * 365 * 10,
IsCA: false,
ECDSACurve: "P256",
}
return certificates.CreateCertificate(options, nil)
}
// createTLSConfig creates a TLS config based on given config
func createTLSConfig(cert, key string) (*tls.Config, error) {
var result *tls.Config
c, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
return nil, errors.WithStack(err)
}
result = &tls.Config{
Certificates: []tls.Certificate{c},
}
return result, nil
return util.NewSelfSignedTLSConfig(cfg.ServerName, cfg.ServerAltNames...)
}

View file

@ -28,8 +28,10 @@ import (
)
type ProfileContainerTemplate struct {
// Containers applies values per container
Containers schedulerContainerApi.Containers `json:"containers,omitempty"`
// All applies generic values to all Containers
All *schedulerContainerApi.Generic `json:"all,omitempty"`
}

View file

@ -29,10 +29,14 @@ import (
)
type ProfileTemplate struct {
// Priority defines Priority of the Profile. Higher value means Profile will get applied first.
// If Priority across Profiles is same, Profiles are also sorted by name.
Priority *int `json:"priority,omitempty"`
// Pod Template
Pod *schedulerPodApi.Pod `json:"pod,omitempty"`
// Container Template
Container *ProfileContainerTemplate `json:"container,omitempty"`
}

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-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.
@ -60,6 +60,8 @@ func (p Path) MarshalJSON() ([]byte, error) {
return json.Marshal(v)
}
type Items []Item
type Item struct {
Op Operation `json:"op"`
Path Path `json:"path"`

View file

@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-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.
@ -20,13 +20,6 @@
package exporter
import (
"net/http"
"time"
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
)
type Authentication func() (string, error)
// CreateArangodJwtAuthorizationHeader calculates a JWT authorization header, for authorization
@ -35,27 +28,3 @@ type Authentication func() (string, error)
func CreateArangodJwtAuthorizationHeader(jwt string) (string, error) {
return "bearer " + jwt, nil
}
func NewExporter(endpoint string, url string, handler http.Handler) operatorHTTP.PlainServer {
s := http.NewServeMux()
s.Handle(url, handler)
s.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head><title>ArangoDB Exporter</title></head>
<body>
<h1>ArangoDB Exporter</h1>
<p><a href='/metrics'>Metrics</a></p>
</body>
</html>`))
})
return operatorHTTP.NewServer(&http.Server{
Addr: endpoint,
ReadTimeout: time.Second * 30,
ReadHeaderTimeout: time.Second * 15,
WriteTimeout: time.Second * 30,
Handler: s,
})
}

View file

@ -0,0 +1,43 @@
//
// 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 scheduler
import (
core "k8s.io/api/core/v1"
"github.com/arangodb/kube-arangodb/pkg/handlers/scheduler/webhooks/policies"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/constants"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
"github.com/arangodb/kube-arangodb/pkg/webhook"
)
func WebhookAdmissions(client kclient.Client) webhook.Admissions {
return webhook.Admissions{
webhook.NewAdmissionHandler[*core.Pod](
"policies",
constants.PodGroup,
constants.PodVersionV1,
constants.PodKind,
constants.PodResource,
policies.NewPoliciesPodHandler(client),
),
}
}

View file

@ -0,0 +1,137 @@
//
// 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 policies
import (
"context"
"strings"
admission "k8s.io/api/admission/v1"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/scheduler"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
kerrors "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
"github.com/arangodb/kube-arangodb/pkg/webhook"
)
func NewPoliciesPodHandler(client kclient.Client) webhook.Handler[*core.Pod] {
return handler{
client: client,
}
}
var _ webhook.MutationHandler[*core.Pod] = handler{}
type handler struct {
client kclient.Client
}
func (h handler) CanHandle(ctx context.Context, log logging.Logger, t webhook.AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) bool {
if request == nil {
return false
}
if request.Operation != admission.Create {
return false
}
if new == nil {
return false
}
_, ok := new.GetLabels()[constants.ProfilesDeployment]
return ok
}
func (h handler) Mutate(ctx context.Context, log logging.Logger, t webhook.AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) (webhook.MutationResponse, error) {
if !h.CanHandle(ctx, log, t, request, old, new) {
return webhook.MutationResponse{}, errors.Errorf("Object cannot be handled")
}
labels := new.GetLabels()
v := labels[constants.ProfilesDeployment]
depl, err := h.client.Arango().DatabaseV1().ArangoDeployments(request.Namespace).Get(ctx, v, meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
return webhook.MutationResponse{
ValidationResponse: webhook.NewValidationResponse(false, "ArangoDeployment %s/%s not found", request.Namespace, v),
}, nil
}
return webhook.MutationResponse{
ValidationResponse: webhook.NewValidationResponse(false, "Unable to get ArangoDeployment %s/%s: %s", request.Namespace, v, err.Error()),
}, nil
}
profiles := util.FilterList(util.FormatList(strings.Split(labels[constants.ProfilesList], ","), func(s string) string {
return strings.TrimSpace(s)
}), func(s string) bool {
return s != ""
})
calculatedProfiles, profilesChecksum, err := scheduler.Profiles(ctx, h.client.Arango().SchedulerV1beta1().ArangoProfiles(depl.GetNamespace()), labels, profiles...)
if err != nil {
return webhook.MutationResponse{
ValidationResponse: webhook.NewValidationResponse(false, "Unable to get ArangoProfiles: %s", err.Error()),
}, nil
}
var template core.PodTemplateSpec
template.Labels = new.GetLabels()
template.Annotations = new.GetAnnotations()
new.Spec.DeepCopyInto(&template.Spec)
if template.Annotations == nil {
template.Annotations = map[string]string{}
}
template.Annotations[constants.ProfilesAnnotationApplied] = "true"
template.Annotations[constants.ProfilesAnnotationChecksum] = profilesChecksum
template.Annotations[constants.ProfilesAnnotationProfiles] = strings.Join(util.FormatList(calculatedProfiles, func(a util.KV[string, schedulerApi.ProfileAcceptedTemplate]) string {
return a.K
}), ",")
if err := schedulerApi.ProfileTemplates(util.FormatList(calculatedProfiles, func(a util.KV[string, schedulerApi.ProfileAcceptedTemplate]) *schedulerApi.ProfileTemplate {
return a.V.Template
})).RenderOnTemplate(&template); err != nil {
return webhook.MutationResponse{
ValidationResponse: webhook.NewValidationResponse(false, "Unable to get apply ArangoProfiles: %s", err.Error()),
}, nil
}
return webhook.MutationResponse{
ValidationResponse: webhook.ValidationResponse{Allowed: true},
Patch: []patch.Item{
patch.ItemReplace(patch.NewPath("metadata", "labels"), template.Labels),
patch.ItemReplace(patch.NewPath("metadata", "annotations"), template.Annotations),
patch.ItemReplace(patch.NewPath("spec"), template.Spec),
},
}, nil
}

40
pkg/logging/http.go Normal file
View file

@ -0,0 +1,40 @@
//
// 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 logging
import (
"net/http"
"github.com/rs/zerolog"
)
func HTTPRequestWrap(request *http.Request) Wrap {
return func(in *zerolog.Event) *zerolog.Event {
if request == nil {
return in
}
in = in.Str("method", request.Method)
in = in.Str("url", request.RequestURI)
return in
}
}

View file

@ -29,13 +29,10 @@ import (
"github.com/gin-gonic/gin"
"github.com/jessevdk/go-assets"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
typedCore "k8s.io/client-go/kubernetes/typed/core/v1"
"github.com/arangodb-helper/go-certificates"
"github.com/arangodb/kube-arangodb/dashboard"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
"github.com/arangodb/kube-arangodb/pkg/util/metrics"
@ -108,40 +105,16 @@ func NewServer(cli typedCore.CoreV1Interface, cfg Config, deps Dependencies) (*S
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
var cert, key string
var fetcher util.TLSConfigFetcher
if cfg.TLSSecretName != "" && cfg.TLSSecretNamespace != "" {
serverLogger.Str("addr", cfg.Address).Str("secret", cfg.TLSSecretName).Str("secret-namespace", cfg.TLSSecretNamespace).Info("Using existing TLS Certificate")
s, err := cli.Secrets(cfg.TLSSecretNamespace).Get(context.Background(), cfg.TLSSecretName, meta.GetOptions{})
if err != nil {
return nil, errors.WithStack(err)
}
certBytes, found := s.Data[core.TLSCertKey]
if !found {
return nil, errors.WithStack(errors.Errorf("No %s found in secret %s", core.TLSCertKey, cfg.TLSSecretName))
}
keyBytes, found := s.Data[core.TLSPrivateKeyKey]
if !found {
return nil, errors.WithStack(errors.Errorf("No %s found in secret %s", core.TLSPrivateKeyKey, cfg.TLSSecretName))
}
cert = string(certBytes)
key = string(keyBytes)
fetcher = util.NewSecretTLSConfig(cli.Secrets(cfg.TLSSecretNamespace), cfg.TLSSecretName)
} else {
serverLogger.Str("addr", cfg.Address).Info("Using SelfSigned TLS Certificate")
options := certificates.CreateCertificateOptions{
CommonName: cfg.PodName,
Hosts: []string{cfg.PodName, cfg.PodIP},
ValidFrom: time.Now(),
ValidFor: time.Hour * 24 * 365 * 10,
IsCA: false,
ECDSACurve: "P256",
}
var err error
cert, key, err = certificates.CreateCertificate(options, nil)
if err != nil {
return nil, errors.WithStack(err)
}
fetcher = util.NewSelfSignedTLSConfig(cfg.PodName, cfg.PodIP)
}
tlsConfig, err := createTLSConfig(cert, key)
tlsConfig, err := fetcher.Eval(context.Background())
if err != nil {
return nil, errors.WithStack(err)
}
@ -256,19 +229,6 @@ func (s *Server) Run() error {
return nil
}
// createTLSConfig creates a TLS config based on given config
func createTLSConfig(cert, key string) (*tls.Config, error) {
var result *tls.Config
c, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
return nil, errors.WithStack(err)
}
result = &tls.Config{
Certificates: []tls.Certificate{c},
}
return result, nil
}
func ready(probes ...*probe.ReadyProbe) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
for _, probe := range probes {

View file

@ -26,6 +26,11 @@ const ProfileGroup = "profiles.arangodb.com"
const ProfilesDeployment = ProfileGroup + "/deployment"
const ProfilesIntegrationPrefix = "integration." + ProfileGroup
const ProfilesList = ProfileGroup + "/profiles"
const ProfilesAnnotationApplied = ProfileGroup + "/applied"
const ProfilesAnnotationChecksum = ProfileGroup + "/checksum"
const ProfilesAnnotationProfiles = ProfileGroup + "/profiles"
const (
ProfilesIntegrationAuthn = "authn"

76
pkg/util/http/error.go Normal file
View file

@ -0,0 +1,76 @@
//
// 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 http
import (
"encoding/json"
"errors"
"fmt"
)
func WrapError(code int, err error) error {
if err == nil {
return nil
}
return NewError(code, err.Error())
}
func NewError(code int, format string, args ...any) error {
return Error{
Code: code,
Message: fmt.Sprintf(format, args...),
}
}
func IsError(err error) (Error, bool) {
if err == nil {
return Error{}, false
}
var v Error
if errors.As(err, &v) {
return v, true
}
return Error{}, false
}
type Error struct {
Code int
Message string
}
func (e Error) Error() string {
return fmt.Sprintf("HTTP Error (%d): %s", e.Code, e.Message)
}
func (e Error) JSON() []byte {
data, err := json.Marshal(map[string]any{
"Code": e.Code,
"Message": e.Message,
})
if err != nil {
return nil
}
return data
}

View file

@ -21,140 +21,147 @@
package http
import (
"context"
"crypto/tls"
"net"
"net/http"
"sync"
"github.com/arangodb-helper/go-certificates"
"time"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
func NewServer(server *http.Server) PlainServer {
return &plainServer{server: server}
func DefaultHTTPServerSettings(in *http.Server, _ context.Context) error {
in.ReadTimeout = time.Second * 30
in.ReadHeaderTimeout = time.Second * 15
in.WriteTimeout = time.Second * 30
in.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
return nil
}
type ServerRunner interface {
Stop()
Wait() error
func WithTLSConfigFetcherGen(gen func() util.TLSConfigFetcher) util.ModEP1[http.Server, context.Context] {
return WithTLSConfigFetcher(gen())
}
type PlainServer interface {
Server
WithSSL(key, cert string) (Server, error)
WithKeyfile(keyfile string) (Server, error)
func WithTLSConfigFetcher(fetcher util.TLSConfigFetcher) util.ModEP1[http.Server, context.Context] {
return func(in *http.Server, p1 context.Context) error {
v, err := fetcher.Eval(p1)
if err != nil {
return err
}
in.TLSConfig = v
return nil
}
}
func WithServeMux(mods ...util.Mod[http.ServeMux]) util.ModEP1[http.Server, context.Context] {
return func(in *http.Server, p1 context.Context) error {
mux := http.NewServeMux()
util.ApplyMods(mux, mods...)
in.Handler = mux
return nil
}
}
func NewServer(ctx context.Context, mods ...util.ModEP1[http.Server, context.Context]) (Server, error) {
var sv http.Server
if err := util.ApplyModsEP1(&sv, ctx, mods...); err != nil {
return nil, err
}
return &server{
server: &sv,
}, nil
}
type Server interface {
Start() (ServerRunner, error)
AsyncAddr(ctx context.Context, addr string) func() error
Async(ctx context.Context, ln net.Listener) func() error
StartAddr(ctx context.Context, addr string) error
Start(ctx context.Context, ln net.Listener) error
}
var _ Server = &tlsServer{}
type tlsServer struct {
type server struct {
server *http.Server
}
func (t *tlsServer) Start() (ServerRunner, error) {
i := serverRunner{
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
func (s *server) AsyncAddr(ctx context.Context, addr string) func() error {
var err error
go i.run(t.server, func(s *http.Server) error {
return s.ListenAndServeTLS("", "")
})
done := make(chan any)
return &i, nil
}
var _ PlainServer = &plainServer{}
type plainServer struct {
server *http.Server
}
func (p *plainServer) WithKeyfile(keyfile string) (Server, error) {
certificate, err := certificates.LoadKeyFile(keyfile)
if err != nil {
return nil, err
}
s := p.server
s.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{certificate},
}
return &tlsServer{server: s}, nil
}
func (p *plainServer) Start() (ServerRunner, error) {
i := serverRunner{
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
go i.run(p.server, func(s *http.Server) error {
return s.ListenAndServe()
})
return &i, nil
}
func (p *plainServer) WithSSL(key, cert string) (Server, error) {
certificate, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, err
}
s := p.server
s.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{certificate},
}
return &tlsServer{server: s}, nil
}
var _ ServerRunner = &serverRunner{}
type serverRunner struct {
lock sync.Mutex
stopCh chan struct{}
doneCh chan struct{}
err error
}
func (s *serverRunner) run(server *http.Server, f func(s *http.Server) error) {
go func() {
defer close(s.doneCh)
if err := f(server); err != nil {
defer close(done)
err = s.StartAddr(ctx, addr)
}()
return func() error {
<-done
return err
}
}
func (s *server) Async(ctx context.Context, ln net.Listener) func() error {
var err error
done := make(chan any)
go func() {
defer close(done)
err = s.Start(ctx, ln)
}()
return func() error {
<-done
return err
}
}
func (s *server) StartAddr(ctx context.Context, addr string) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return s.Start(ctx, ln)
}
func (s *server) Start(ctx context.Context, ln net.Listener) error {
go func() {
<-ctx.Done()
if err := s.server.Close(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
s.err = err
logger.Err(err).Warn("Unable to close server")
}
}
}()
<-s.stopCh
server.Close()
<-s.doneCh
}
func (s *serverRunner) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
select {
case <-s.stopCh:
return
default:
close(s.stopCh)
if s.server.TLSConfig == nil {
if err := s.server.Serve(ln); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
return err
}
}
} else {
if err := s.server.ServeTLS(ln, "", ""); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
return err
}
}
}
}
func (s *serverRunner) Wait() error {
<-s.doneCh
return s.err
return nil
}

View file

@ -122,6 +122,18 @@ func FormatList[A, B any](in []A, format func(A) B) []B {
return r
}
func FilterList[A any](in []A, filter func(A) bool) []A {
r := make([]A, 0, len(in))
for _, el := range in {
if filter(el) {
r = append(r, el)
}
}
return r
}
func ContainsList[A comparable](in []A, item A) bool {
for _, el := range in {
if el == item {

View file

@ -22,16 +22,6 @@ package util
func emptyMod[T any](_ *T) {}
func WithMods[T any](mods ...Mod[T]) Mod[T] {
return func(in *T) {
for _, m := range mods {
if m != nil {
m(in)
}
}
}
}
type Mod[T any] func(in *T)
func (m Mod[T]) Optional() Mod[T] {
@ -47,3 +37,51 @@ func ApplyMods[T any](in *T, mods ...Mod[T]) {
mod(in)
}
}
func emptyModE[T any](_ *T) error {
return nil
}
type ModE[T any] func(in *T) error
func (m ModE[T]) Optional() ModE[T] {
if m == nil {
return emptyModE[T]
}
return m
}
func ApplyModsE[T any](in *T, mods ...ModE[T]) error {
for _, mod := range mods {
if err := mod(in); err != nil {
return err
}
}
return nil
}
func emptyModEP1[T, P1 any](_ *T, _ P1) error {
return nil
}
type ModEP1[T, P1 any] func(in *T, p1 P1) error
func (m ModEP1[T, P1]) Optional() ModEP1[T, P1] {
if m == nil {
return emptyModEP1[T, P1]
}
return m
}
func ApplyModsEP1[T, P1 any](in *T, p1 P1, mods ...ModEP1[T, P1]) error {
for _, mod := range mods {
if err := mod(in, p1); err != nil {
return err
}
}
return nil
}

View file

@ -22,6 +22,8 @@ package util
import (
"reflect"
"github.com/pkg/errors"
)
// NewPointer returns a reference to a copy of the pointer value
@ -135,6 +137,42 @@ func InitType[T interface{}](in *T) *T {
return &q
}
func DeepType[T any]() (T, error) {
var z T
if err := InitDeepType(&z); err != nil {
return Default[T](), err
}
return z, nil
}
func InitDeepType(in any) error {
return initDeepType(reflect.ValueOf(in))
}
func initDeepType(v reflect.Value) error {
switch v.Kind() {
case reflect.Pointer:
if !v.Elem().CanSet() {
return errors.Errorf("Unable to set interface")
}
switch v.Elem().Kind() {
case reflect.Pointer:
nv := reflect.New(v.Type().Elem().Elem())
if err := initDeepType(nv); err != nil {
return err
}
v.Elem().Set(nv)
default:
v.Elem().Set(reflect.New(v.Elem().Type()).Elem())
}
}
return nil
}
type ConditionalFunction[T interface{}] func() (T, bool)
type ConditionalP1Function[T, P1 interface{}] func(p1 P1) (T, bool)

58
pkg/util/tests/http.go Normal file
View file

@ -0,0 +1,58 @@
//
// 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 tests
import (
"context"
"fmt"
"net"
goHttp "net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/http"
)
func NewHTTPServer(ctx context.Context, t *testing.T, mods ...util.ModEP1[goHttp.Server, context.Context]) string {
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
pr, ok := ln.Addr().(*net.TCPAddr)
require.True(t, ok)
addr, port := pr.IP.String(), pr.Port
server, err := http.NewServer(ctx, mods...)
require.NoError(t, err)
closer := server.Async(ctx, ln)
WaitForTCPPort(addr, port).WithContextTimeoutT(t, ctx, 10*time.Second, 125*time.Millisecond)
go func() {
require.NoError(t, closer())
}()
return fmt.Sprintf("%s:%d", addr, port)
}

View file

@ -55,6 +55,7 @@ import (
arangoClientSet "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
@ -1506,13 +1507,8 @@ func NewMetaObjectInDefaultNamespace[T meta.Object](t *testing.T, name string, m
}
func NewMetaObject[T meta.Object](t *testing.T, namespace, name string, mods ...MetaObjectMod[T]) T {
var obj T
if objT := reflect.TypeOf(obj); objT.Kind() == reflect.Pointer {
newObj := reflect.New(objT.Elem())
reflect.ValueOf(&obj).Elem().Set(newObj)
}
obj, err := util.DeepType[T]()
require.NoError(t, err)
if IsNamespaced(obj) {
obj.SetNamespace(namespace)

42
pkg/util/tests/port.go Normal file
View file

@ -0,0 +1,42 @@
//
// 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 tests
import (
"fmt"
"net"
"time"
)
func WaitForTCPPort(addr string, port int) Timeout {
return func() error {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", addr, port), time.Second)
if err != nil {
return nil
}
if err := conn.Close(); err != nil {
return nil
}
return Interrupt()
}
}

View file

@ -21,6 +21,7 @@
package tests
import (
"context"
"testing"
"time"
@ -83,6 +84,36 @@ func (t Timeout) WithTimeout(timeout, interval time.Duration) error {
}
}
func (t Timeout) WithContextTimeout(ctx context.Context, timeout, interval time.Duration) error {
timeoutT := time.NewTimer(timeout)
defer timeoutT.Stop()
intervalT := time.NewTicker(interval)
defer intervalT.Stop()
for {
select {
case <-ctx.Done():
return errors.Errorf("ContextCancelled!")
case <-timeoutT.C:
return errors.Errorf("Timeouted!")
case <-intervalT.C:
if err := t(); err != nil {
var interrupt interrupt
if errors.As(err, &interrupt) {
return nil
}
return err
}
}
}
}
func (t Timeout) WithTimeoutT(z *testing.T, timeout, interval time.Duration) {
require.NoError(z, t.WithTimeout(timeout, interval))
}
func (t Timeout) WithContextTimeoutT(z *testing.T, ctx context.Context, timeout, interval time.Duration) {
require.NoError(z, t.WithContextTimeout(ctx, timeout, interval))
}

121
pkg/util/tls.go Normal file
View file

@ -0,0 +1,121 @@
//
// 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 util
import (
"context"
"crypto/tls"
"time"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb-helper/go-certificates"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/generic"
)
type TLSConfigFetcher func(ctx context.Context) (*tls.Config, error)
func (t TLSConfigFetcher) Eval(ctx context.Context) (*tls.Config, error) {
if t == nil {
return EmptyTLSConfig(ctx)
}
return t(ctx)
}
func EmptyTLSConfig(ctx context.Context) (*tls.Config, error) {
return nil, nil
}
func NewSelfSignedTLSConfig(cn string, names ...string) TLSConfigFetcher {
return func(ctx context.Context) (*tls.Config, error) {
options := certificates.CreateCertificateOptions{
CommonName: cn,
Hosts: append([]string{cn}, names...),
ValidFrom: time.Now(),
ValidFor: time.Hour * 24 * 365 * 10,
IsCA: false,
ECDSACurve: "P256",
}
cert, priv, err := certificates.CreateCertificate(options, nil)
if err != nil {
return nil, err
}
var result *tls.Config
c, err := tls.X509KeyPair([]byte(cert), []byte(priv))
if err != nil {
return nil, errors.WithStack(err)
}
result = &tls.Config{
Certificates: []tls.Certificate{c},
}
return result, nil
}
}
func NewSecretTLSConfig(client generic.GetInterface[*core.Secret], name string) TLSConfigFetcher {
return func(ctx context.Context) (*tls.Config, error) {
nctx, cancel := globals.GetGlobals().Timeouts().Kubernetes().WithTimeout(ctx)
defer cancel()
s, err := client.Get(nctx, name, meta.GetOptions{})
if err != nil {
return nil, err
}
certBytes, found := s.Data[core.TLSCertKey]
if !found {
return nil, errors.Errorf("No %s found in secret %s", core.TLSCertKey, name)
}
keyBytes, found := s.Data[core.TLSPrivateKeyKey]
if !found {
return nil, errors.Errorf("No %s found in secret %s", core.TLSPrivateKeyKey, name)
}
var result *tls.Config
c, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return nil, errors.WithStack(err)
}
result = &tls.Config{
Certificates: []tls.Certificate{c},
}
return result, nil
}
}
func NewKeyfileTLSConfig(keyfile string) TLSConfigFetcher {
return func(ctx context.Context) (*tls.Config, error) {
certificate, err := certificates.LoadKeyFile(keyfile)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{certificate},
}, nil
}
}

329
pkg/webhook/admission.go Normal file
View file

@ -0,0 +1,329 @@
//
// 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 webhook
import (
"context"
"encoding/json"
"fmt"
goHttp "net/http"
"reflect"
"time"
"github.com/pkg/errors"
admission "k8s.io/api/admission/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/http"
"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
)
func NewAdmissionHandler[T meta.Object](name, group, version, kind, resource string, handlers ...Handler[T]) Admission {
return admissionImpl[T]{
name: name,
group: group,
version: version,
kind: kind,
resource: resource,
handlers: handlers,
}
}
type AdmissionRequestType int
const (
AdmissionRequestValidate AdmissionRequestType = iota
AdmissionRequestMutate
)
type Admissions []Admission
func (a Admissions) Register() util.Mod[goHttp.ServeMux] {
return func(in *goHttp.ServeMux) {
for _, handler := range a {
log := logger.Str("name", handler.Name())
log.Info("Registering handler")
if endpoint := fmt.Sprintf("/webhook/%s/%s/validate", gvsAsPath(handler.Resource()), handler.Name()); endpoint != "" {
log.Str("endpoint", endpoint).Info("Registered Validate handler")
in.HandleFunc(endpoint, handler.Validate)
}
if endpoint := fmt.Sprintf("/webhook/%s/%s/mutate", gvsAsPath(handler.Resource()), handler.Name()); endpoint != "" {
log.Str("endpoint", endpoint).Info("Registered Mutate handler")
in.HandleFunc(endpoint, handler.Mutate)
}
}
}
}
func gvsAsPath(in meta.GroupVersionResource) string {
if in.Group == "" {
return fmt.Sprintf("core/%s/%s", in.Version, in.Resource)
}
return fmt.Sprintf("%s/%s/%s", in.Group, in.Version, in.Resource)
}
type Admission interface {
Name() string
Kind() meta.GroupVersionKind
Resource() meta.GroupVersionResource
Validate(goHttp.ResponseWriter, *goHttp.Request)
Mutate(goHttp.ResponseWriter, *goHttp.Request)
}
type admissionImpl[T meta.Object] struct {
name, group, version, kind, resource string
handlers []Handler[T]
}
func (a admissionImpl[T]) Name() string {
return a.name
}
func (a admissionImpl[T]) Kind() meta.GroupVersionKind {
return meta.GroupVersionKind{
Group: a.group,
Version: a.version,
Kind: a.kind,
}
}
func (a admissionImpl[T]) Resource() meta.GroupVersionResource {
return meta.GroupVersionResource{
Group: a.group,
Version: a.version,
Resource: a.resource,
}
}
func (a admissionImpl[T]) Validate(writer goHttp.ResponseWriter, request *goHttp.Request) {
a.request(AdmissionRequestValidate, writer, request)
}
func (a admissionImpl[T]) Mutate(writer goHttp.ResponseWriter, request *goHttp.Request) {
a.request(AdmissionRequestMutate, writer, request)
}
func (a admissionImpl[T]) request(t AdmissionRequestType, writer goHttp.ResponseWriter, request *goHttp.Request) {
log := logger.Wrap(logging.HTTPRequestWrap(request))
log.Info("Request Received")
timeout := time.Second
if request.URL.Query().Has("timeout") {
if v, err := time.ParseDuration(request.URL.Query().Get("timeout")); err == nil {
if v > 500*time.Millisecond {
timeout = v - 200*time.Millisecond
} else {
timeout = v
}
}
}
ctx, c := context.WithTimeout(shutdown.Context(), timeout)
defer c()
code, data := a.requestWriterJSON(ctx, log, t, request)
writer.WriteHeader(code)
if len(data) > 0 {
if _, err := util.WriteAll(writer, data); err != nil {
log.Err(err).Warn("Unable to send response")
}
}
}
func (a admissionImpl[T]) requestWriterJSON(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *goHttp.Request) (int, []byte) {
code, obj, err := a.requestWriter(ctx, log, t, request)
if err != nil {
if herr, ok := http.IsError(err); ok {
return herr.Code, herr.JSON()
}
log.Err(err).Warn("Unexpected Error")
return goHttp.StatusInternalServerError, nil
}
if reflect.ValueOf(obj).IsZero() {
return code, nil
}
data, err := json.Marshal(obj)
if err != nil {
log.Err(err).Warn("Unable to marshal response")
return goHttp.StatusInternalServerError, nil
}
return code, data
}
func (a admissionImpl[T]) requestWriter(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *goHttp.Request) (int, any, error) {
switch t {
case AdmissionRequestValidate, AdmissionRequestMutate:
default:
log.Warn("Invalid AdmissionRequestType")
return 0, nil, http.NewError(goHttp.StatusBadRequest, "Invalid AdmissionRequestType")
}
if request.Method != goHttp.MethodPost {
return 0, nil, http.NewError(goHttp.StatusMethodNotAllowed, "Method '%s' not allowed, expected '%s'", request.Method, goHttp.MethodPost)
}
var req admission.AdmissionReview
if err := json.NewDecoder(request.Body).Decode(&req); err != nil {
return 0, nil, http.WrapError(goHttp.StatusBadRequest, err)
}
resp := a.admissionHandle(ctx, log, t, req.Request)
return 200, admission.AdmissionReview{
TypeMeta: req.TypeMeta,
Response: resp,
}, nil
}
func (a admissionImpl[T]) admissionHandle(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest) *admission.AdmissionResponse {
if request == nil {
return &admission.AdmissionResponse{
Allowed: false,
}
}
if request.Kind != a.Kind() {
return &admission.AdmissionResponse{
UID: request.UID,
Allowed: false,
Result: &meta.Status{
Message: fmt.Sprintf("Invalid Kind. Got '%s', expected '%s'", request.Kind.String(), a.Kind().String()),
},
}
}
resp, err := a.admissionHandleE(ctx, log, t, request)
if err != nil {
return &admission.AdmissionResponse{
UID: request.UID,
Allowed: false,
Result: &meta.Status{
Message: fmt.Sprintf("Unexpected error: %s", err.Error()),
},
}
}
if resp == nil {
return &admission.AdmissionResponse{
UID: request.UID,
Allowed: false,
Result: &meta.Status{
Message: "Missing Response element",
},
}
}
resp.UID = request.UID
return resp
}
func (a admissionImpl[T]) admissionHandleE(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest) (*admission.AdmissionResponse, error) {
old, err := a.evaluateObject(request.OldObject.Raw)
if err != nil {
return nil, errors.Wrapf(err, "Unable to parse old object")
}
new, err := a.evaluateObject(request.Object.Raw)
if err != nil {
return nil, errors.Wrapf(err, "Unable to parse new object")
}
switch t {
case AdmissionRequestValidate:
for _, handler := range a.handlers {
if handler.CanHandle(ctx, log, t, request, old, new) {
if v, ok := handler.(ValidationHandler[T]); ok {
result, err := v.Validate(ctx, log, t, request, old, new)
if err != nil {
return nil, err
}
return result.AsResponse()
}
return ValidationResponse{
Allowed: false,
Message: "Request not handled",
}.AsResponse()
}
}
return &admission.AdmissionResponse{Allowed: true}, nil
case AdmissionRequestMutate:
for _, handler := range a.handlers {
if handler.CanHandle(ctx, log, t, request, old, new) {
if v, ok := handler.(MutationHandler[T]); ok {
result, err := v.Mutate(ctx, log, t, request, old, new)
if err != nil {
return nil, err
}
return result.AsResponse()
}
return ValidationResponse{
Allowed: false,
Message: "Request not handled",
}.AsResponse()
}
}
return &admission.AdmissionResponse{Allowed: true}, nil
default:
return &admission.AdmissionResponse{
Allowed: false,
}, nil
}
}
func (a admissionImpl[T]) evaluateObject(data []byte) (T, error) {
if len(data) == 0 {
return util.Default[T](), nil
}
obj, err := util.DeepType[T]()
if err != nil {
return util.Default[T](), err
}
if err := json.Unmarshal(data, &obj); err != nil {
return util.Default[T](), err
}
return obj, nil
}

52
pkg/webhook/handler.go Normal file
View file

@ -0,0 +1,52 @@
//
// 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 webhook
import (
"context"
admission "k8s.io/api/admission/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/logging"
)
type CanHandleFunc[T meta.Object] func(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new T) bool
type MutateFunc[T meta.Object] func(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new T) (MutationResponse, error)
type ValidateFunc[T meta.Object] func(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new T) (ValidationResponse, error)
type Handler[T meta.Object] interface {
CanHandle(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new T) bool
}
type MutationHandler[T meta.Object] interface {
Handler[T]
Mutate(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new T) (MutationResponse, error)
}
type ValidationHandler[T meta.Object] interface {
Handler[T]
Validate(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new T) (ValidationResponse, error)
}

25
pkg/webhook/logger.go Normal file
View file

@ -0,0 +1,25 @@
//
// 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 webhook
import "github.com/arangodb/kube-arangodb/pkg/logging"
var logger = logging.Global().RegisterAndGetLogger("webhook", logging.Info)

89
pkg/webhook/responses.go Normal file
View file

@ -0,0 +1,89 @@
//
// 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 webhook
import (
"encoding/json"
"fmt"
admission "k8s.io/api/admission/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/util"
)
func NewValidationResponse(allowed bool, msg string, args ...any) ValidationResponse {
return ValidationResponse{
Allowed: allowed,
Message: fmt.Sprintf(msg, args...),
}
}
type ValidationResponse struct {
Allowed bool
Message string
Warnings []string
}
func (v ValidationResponse) AsResponse() (*admission.AdmissionResponse, error) {
if v.Allowed {
return &admission.AdmissionResponse{
Allowed: true,
Warnings: v.Warnings,
}, nil
}
return &admission.AdmissionResponse{
Allowed: false,
Warnings: v.Warnings,
Result: &meta.Status{
Message: v.Message,
},
}, nil
}
type MutationResponse struct {
ValidationResponse
Patch patch.Items
}
func (v MutationResponse) AsResponse() (*admission.AdmissionResponse, error) {
resp, err := v.ValidationResponse.AsResponse()
if err != nil {
return nil, err
}
if len(v.Patch) == 0 {
return resp, nil
}
q, err := json.Marshal(v.Patch)
if err != nil {
return nil, err
}
resp.Patch = q
resp.PatchType = util.NewType(admission.PatchTypeJSONPatch)
return resp, nil
}

View file

@ -0,0 +1,111 @@
//
// 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 webhook
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
admission "k8s.io/api/admission/v1"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/logging"
)
func newPodAdmissionRequest(t *testing.T, name, namespace string, op admission.Operation, old, new *core.Pod) *admission.AdmissionRequest {
req := &admission.AdmissionRequest{
UID: "",
Kind: meta.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "Pod",
},
Resource: meta.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "pods",
},
Name: name,
Namespace: namespace,
Operation: op,
}
if old != nil {
data, err := json.Marshal(old)
require.NoError(t, err)
req.OldObject.Raw = data
}
if new != nil {
data, err := json.Marshal(new)
require.NoError(t, err)
req.Object.Raw = data
}
return req
}
func newPodAdmission(name string, handlers ...Handler[*core.Pod]) Admission {
return NewAdmissionHandler[*core.Pod](name, "", "v1", "Pod", "pods", handlers...)
}
func newPodHandler(can CanHandleFunc[*core.Pod],
mutate MutateFunc[*core.Pod],
validate ValidateFunc[*core.Pod]) Handler[*core.Pod] {
return podHandler{
can: can,
mutate: mutate,
validate: validate,
}
}
type podHandler struct {
can CanHandleFunc[*core.Pod]
mutate MutateFunc[*core.Pod]
validate ValidateFunc[*core.Pod]
}
func (p podHandler) Validate(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) (ValidationResponse, error) {
if p.validate == nil {
return ValidationResponse{}, nil
}
return p.validate(ctx, log, t, request, old, new)
}
func (p podHandler) Mutate(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) (MutationResponse, error) {
if p.mutate == nil {
return MutationResponse{}, nil
}
return p.mutate(ctx, log, t, request, old, new)
}
func (p podHandler) CanHandle(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) bool {
if p.can == nil {
return false
}
return p.can(ctx, log, t, request, old, new)
}

129
pkg/webhook/suite_test.go Normal file
View file

@ -0,0 +1,129 @@
//
// 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 webhook
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
goHttp "net/http"
"testing"
"github.com/stretchr/testify/require"
admission "k8s.io/api/admission/v1"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/http"
"github.com/arangodb/kube-arangodb/pkg/util/tests"
)
func Test(t *testing.T) {
ctx, c := context.WithCancel(context.Background())
defer c()
addr := startHTTPServer(t, ctx, newPodAdmission("test", newPodHandler(func(ctx context.Context, log logging.Logger, t AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) bool {
return true
}, nil, func(ctx context.Context, log logging.Logger, at AdmissionRequestType, request *admission.AdmissionRequest, old, new *core.Pod) (ValidationResponse, error) {
require.Nil(t, old)
require.NotNil(t, new)
require.EqualValues(t, AdmissionRequestValidate, at)
return ValidationResponse{
Allowed: true,
}, nil
})))
resp := requestPod(t, addr, "test", AdmissionRequestValidate,
newPodAdmissionRequest(t, "test", tests.FakeNamespace, admission.Create, nil, &core.Pod{
ObjectMeta: meta.ObjectMeta{
Name: "test",
Namespace: tests.FakeNamespace,
},
}),
)
require.NotNil(t, resp)
require.True(t, resp.Allowed)
}
func requestPod(t *testing.T, addr string, name string, mode AdmissionRequestType, req *admission.AdmissionRequest) *admission.AdmissionResponse {
return request(t, addr, meta.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "pods",
}, name, mode, req)
}
func request(t *testing.T, addr string, gvs meta.GroupVersionResource, name string, mode AdmissionRequestType, request *admission.AdmissionRequest) *admission.AdmissionResponse {
if request == nil {
request = &admission.AdmissionRequest{}
}
request.UID = uuid.NewUUID()
data, err := json.Marshal(admission.AdmissionReview{
Request: request,
})
require.NoError(t, err)
var c = goHttp.Client{
Transport: &goHttp.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
url := fmt.Sprintf("https://%s/webhook/%s/%s/%s", addr, gvsAsPath(gvs), name, util.BoolSwitch(mode == AdmissionRequestValidate, "validate", "mutate"))
t.Logf("Request send to: %s", url)
resp, err := c.Post(url, "application/json", bytes.NewReader(data))
require.NoError(t, err)
require.EqualValues(t, 200, resp.StatusCode)
var rv admission.AdmissionReview
require.NoError(t, json.NewDecoder(resp.Body).Decode(&rv))
require.NoError(t, resp.Body.Close())
require.NotNil(t, rv.Response)
require.EqualValues(t, request.UID, rv.Response.UID)
return rv.Response
}
func startHTTPServer(t *testing.T, ctx context.Context, admissions ...Admission) string {
return tests.NewHTTPServer(ctx, t,
http.DefaultHTTPServerSettings,
http.WithTLSConfigFetcher(util.NewSelfSignedTLSConfig("localhost", "127.0.0.1")),
http.WithServeMux(
Admissions(admissions).Register(),
),
)
}