diff --git a/.ci/clusters/global_backend_config.yaml b/.ci/clusters/global_backend_config.yaml new file mode 100644 index 000000000..f55d06dc1 --- /dev/null +++ b/.ci/clusters/global_backend_config.yaml @@ -0,0 +1,8 @@ +apiVersion: compute.functionmesh.io/v1alpha1 +kind: BackendConfig +metadata: + name: global-backend-config +spec: + env: + global1: globalvalue1 + shared1: fromglobal \ No newline at end of file diff --git a/.ci/helm.sh b/.ci/helm.sh index 931744862..56dde3d3d 100644 --- a/.ci/helm.sh +++ b/.ci/helm.sh @@ -596,3 +596,16 @@ function ci::verify_log_topic_with_auth() { fi return 1 } + +function ci::verify_env() { + pod="$1-function-0" + key=$2 + expect=$3 + result=$(kubectl exec -n ${NAMESPACE} ${pod} -- env | grep "${key}") + echo "$result" + echo "$expect" + if [[ "$result" = "$expect" ]]; then + return 0 + fi + return 1 +} \ No newline at end of file diff --git a/.ci/tests/integration/cases/crypto-function/manifests.yaml b/.ci/tests/integration/cases/crypto-function/manifests.yaml index cf57b4404..fe4f0fd19 100644 --- a/.ci/tests/integration/cases/crypto-function/manifests.yaml +++ b/.ci/tests/integration/cases/crypto-function/manifests.yaml @@ -9,7 +9,6 @@ spec: forwardSourceMessageProperty: true maxPendingAsyncRequests: 1000 replicas: 1 - maxReplicas: 5 logTopic: persistent://public/default/logging-function-logs input: topics: diff --git a/.ci/tests/integration/cases/global-and-namespaced-config/manifests.yaml b/.ci/tests/integration/cases/global-and-namespaced-config/manifests.yaml new file mode 100644 index 000000000..109b0ce8d --- /dev/null +++ b/.ci/tests/integration/cases/global-and-namespaced-config/manifests.yaml @@ -0,0 +1,77 @@ +apiVersion: compute.functionmesh.io/v1alpha1 +kind: Function +metadata: + name: function-sample-env + namespace: default +spec: + image: streamnative/pulsar-functions-java-sample:2.9.2.23 + className: org.apache.pulsar.functions.api.examples.ExclamationFunction + forwardSourceMessageProperty: true + maxPendingAsyncRequests: 1000 + replicas: 1 + maxReplicas: 5 + logTopic: persistent://public/default/logging-function-logs + input: + topics: + - persistent://public/default/input-java-topic + typeClassName: java.lang.String + output: + topic: persistent://public/default/output-java-topic + typeClassName: java.lang.String + resources: + requests: + cpu: 50m + memory: 1G + limits: + memory: 1.1G + # each secret will be loaded ad an env variable from the `path` secret with the `key` in that secret in the name of `name` + secretsMap: + "name": + path: "test-secret" + key: "username" + "pwd": + path: "test-secret" + key: "password" + pulsar: + pulsarConfig: "test-pulsar" + tlsConfig: + enabled: false + allowInsecure: false + hostnameVerification: true + certSecretName: sn-platform-tls-broker + certSecretKey: "" + #authConfig: "test-auth" + java: + jar: /pulsar/examples/api-examples.jar + # to be delete & use admission hook + clusterName: test + autoAck: true +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-pulsar +data: + webServiceURL: http://sn-platform-pulsar-broker.default.svc.cluster.local:8080 + brokerServiceURL: pulsar://sn-platform-pulsar-broker.default.svc.cluster.local:6650 +#--- +#apiVersion: v1 +#kind: ConfigMap +#metadata: +# name: test-auth +#data: +# clientAuthenticationPlugin: "abc" +# clientAuthenticationParameters: "xyz" +# tlsTrustCertsFilePath: "uvw" +# useTls: "true" +# tlsAllowInsecureConnection: "false" +# tlsHostnameVerificationEnable: "true" +--- +apiVersion: v1 +data: + username: YWRtaW4= + password: MWYyZDFlMmU2N2Rm +kind: Secret +metadata: + name: test-secret +type: Opaque \ No newline at end of file diff --git a/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config-kube-system.yaml b/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config-kube-system.yaml new file mode 100644 index 000000000..4398bd104 --- /dev/null +++ b/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config-kube-system.yaml @@ -0,0 +1,9 @@ +apiVersion: compute.functionmesh.io/v1alpha1 +kind: BackendConfig +metadata: + name: backend-config + namespace: kube-system +spec: + env: + namespaced1: namespacedvalue1 + shared1: fromnamespace diff --git a/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config.yaml b/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config.yaml new file mode 100644 index 000000000..6ef4131ab --- /dev/null +++ b/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config.yaml @@ -0,0 +1,9 @@ +apiVersion: compute.functionmesh.io/v1alpha1 +kind: BackendConfig +metadata: + name: backend-config + namespace: default +spec: + env: + namespaced1: namespacedvalue1 + shared1: fromnamespace \ No newline at end of file diff --git a/.ci/tests/integration/cases/global-and-namespaced-config/verify.sh b/.ci/tests/integration/cases/global-and-namespaced-config/verify.sh new file mode 100644 index 000000000..d9347e980 --- /dev/null +++ b/.ci/tests/integration/cases/global-and-namespaced-config/verify.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -e + +E2E_DIR=$(dirname "$0") +BASE_DIR=$(cd "${E2E_DIR}"/../../../../..;pwd) +PULSAR_NAMESPACE=${PULSAR_NAMESPACE:-"default"} +PULSAR_RELEASE_NAME=${PULSAR_RELEASE_NAME:-"sn-platform"} +E2E_KUBECONFIG=${E2E_KUBECONFIG:-"/tmp/e2e-k8s.config"} + +source "${BASE_DIR}"/.ci/helm.sh + +if [ ! "$KUBECONFIG" ]; then + export KUBECONFIG=${E2E_KUBECONFIG} +fi + +manifests_file="${BASE_DIR}"/.ci/tests/integration/cases/global-and-namespaced-config/manifests.yaml +mesh_config_file="${BASE_DIR}"/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config.yaml +mesh_config_file_in_kube_system="${BASE_DIR}"/.ci/tests/integration/cases/global-and-namespaced-config/mesh-config-kube-system.yaml +global_mesh_config_file="${BASE_DIR}"/.ci/clusters/global_backend_config.yaml + + +kubectl apply -f "${mesh_config_file}" > /dev/null 2>&1 +kubectl apply -f "${manifests_file}" > /dev/null 2>&1 + +verify_fm_result=$(ci::verify_function_mesh function-sample-env 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_fm_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" global1 global1=globalvalue1 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${mesh_config_file}" > /dev/null 2>&1 + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" namespaced1 namespaced1=namespacedvalue1 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${mesh_config_file}" > /dev/null 2>&1 + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +# if global and namespaced config has same key, the value from namespace should be used +verify_env_result=$(ci::verify_env "function-sample-env" shared1 shared1=fromnamespace 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${mesh_config_file}" > /dev/null 2>&1 + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +# delete the namespaced config, the function should be reconciled without namespaced env injected +kubectl delete -f "${mesh_config_file}" > /dev/null 2>&1 +sleep 30 + +verify_fm_result=$(ci::verify_function_mesh function-sample-env 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_fm_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" global1 global1=globalvalue1 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" shared1 shared1=fromglobal 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" namespaced1 "" 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +# delete the global config, the function should be reconciled without global env injected +kubectl delete -f "${global_mesh_config_file}" -n $FUNCTION_MESH_NAMESPACE > /dev/null 2>&1 || true +sleep 30 + +verify_fm_result=$(ci::verify_function_mesh function-sample-env 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_fm_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" global1 "" 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_env_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +# config created in an another namespace should not affect functions in other namespaces +kubectl apply -f "${mesh_config_file_in_kube_system}" > /dev/null 2>&1 +sleep 30 + +verify_fm_result=$(ci::verify_function_mesh function-sample-env 2>&1) +if [ $? -ne 0 ]; then + echo "$verify_fm_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +verify_env_result=$(ci::verify_env "function-sample-env" namespaced1 "" 2>&1) +if [ $? -eq 0 ]; then + echo "e2e-test: ok" | yq eval - +else + echo "$verify_env_result" + kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true + exit 1 +fi + +kubectl delete -f "${manifests_file}" > /dev/null 2>&1 || true diff --git a/.ci/tests/integration/e2e_with_tls.yaml b/.ci/tests/integration/e2e_with_tls.yaml index 0f2a36e59..4e470681e 100644 --- a/.ci/tests/integration/e2e_with_tls.yaml +++ b/.ci/tests/integration/e2e_with_tls.yaml @@ -87,12 +87,17 @@ setup: image="function-mesh-operator:latest" IMG=${image} make docker-build-skip-test kind load docker-image ${image} - helm install ${FUNCTION_MESH_RELEASE_NAME} -n ${FUNCTION_MESH_NAMESPACE} --set operatorImage=${image} --create-namespace charts/function-mesh-operator + helm install ${FUNCTION_MESH_RELEASE_NAME} -n ${FUNCTION_MESH_NAMESPACE} --set operatorImage=${image} --set controllerManager.globalBackendConfig=global-backend-config --set controllerManager.globalBackendConfigNamespace=${FUNCTION_MESH_NAMESPACE} --set controllerManager.namespacedBackendConfig=backend-config --create-namespace charts/function-mesh-operator wait: - namespace: function-mesh resource: pod label-selector: app.kubernetes.io/name=function-mesh-operator for: condition=Ready + + - name: apply global env config map + command: | + kubectl create -n ${FUNCTION_MESH_NAMESPACE} -f .ci/clusters/global_backend_config.yaml + timeout: 60m cleanup: @@ -124,3 +129,5 @@ verify: expected: expected.data.yaml - query: bash .ci/tests/integration/cases/crypto-function/verify.sh expected: expected.data.yaml + - query: timeout 5m bash .ci/tests/integration/cases/global-and-namespaced-config/verify.sh + expected: expected.data.yaml diff --git a/.github/workflows/olm-verify.yml b/.github/workflows/olm-verify.yml index 31f052e29..3283f6975 100644 --- a/.github/workflows/olm-verify.yml +++ b/.github/workflows/olm-verify.yml @@ -27,10 +27,10 @@ jobs: - name: checkout uses: actions/checkout@v2 - - name: Set up GO 1.20.4 + - name: Set up GO 1.21.8 uses: actions/setup-go@v1 with: - go-version: 1.20.4 + go-version: 1.21.8 id: go - name: InstallKubebuilder diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index a43c02d0c..22581d92d 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -23,10 +23,10 @@ jobs: repository: ${{github.event.pull_request.head.repo.full_name}} ref: ${{ github.event.pull_request.head.sha }} - - name: Set up GO 1.20.4 + - name: Set up GO 1.21.8 uses: actions/setup-go@v1 with: - go-version: 1.20.4 + go-version: 1.21.8 id: go - name: InstallKubebuilder diff --git a/Makefile b/Makefile index 6f25aef0a..5d271d6a7 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ test-ginkgo: generate fmt vet manifests envtest .PHONY: envtest envtest: - test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@v0.0.0-20240320141353-395cfc7486e6 # Build manager binary manager: generate fmt vet diff --git a/api/compute/v1alpha1/backendconfig_types.go b/api/compute/v1alpha1/backendconfig_types.go new file mode 100644 index 000000000..a1c21d18d --- /dev/null +++ b/api/compute/v1alpha1/backendconfig_types.go @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// BackendConfigSpec defines the desired state of BackendConfig +// +kubebuilder:validation:Optional +type BackendConfigSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + Name string `json:"name,omitempty"` + + // +kubebuilder:validation:Optional + Env map[string]string `json:"env,omitempty"` +} + +// BackendConfigStatus defines the observed state of BackendConfig +type BackendConfigStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector + +// BackendConfig is the Schema of the global configs for all functions, sinks and sources +// +kubebuilder:pruning:PreserveUnknownFields +type BackendConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackendConfigSpec `json:"spec,omitempty"` + Status BackendConfigStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackendConfigList contains a list of BackendConfig +type BackendConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BackendConfig `json:"items"` +} + +func init() { + SchemeBuilder.Register(&BackendConfig{}, &BackendConfigList{}) +} diff --git a/api/compute/v1alpha1/function_types.go b/api/compute/v1alpha1/function_types.go index ff1a83c70..ae4f74a5d 100644 --- a/api/compute/v1alpha1/function_types.go +++ b/api/compute/v1alpha1/function_types.go @@ -119,10 +119,12 @@ type FunctionSpec struct { type FunctionStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - Conditions map[Component]ResourceCondition `json:"conditions"` - Replicas int32 `json:"replicas"` - Selector string `json:"selector"` - ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Conditions map[Component]ResourceCondition `json:"conditions"` + Replicas int32 `json:"replicas"` + Selector string `json:"selector"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + GlobalBackendConfigRevision string `json:"globalBackendConfigRevision,omitempty"` + NamespacedBackendConfigRevision string `json:"namespacedBackendConfigRevision,omitempty"` } // +genclient diff --git a/api/compute/v1alpha1/sink_types.go b/api/compute/v1alpha1/sink_types.go index e60b2d0ce..91a1fa43c 100644 --- a/api/compute/v1alpha1/sink_types.go +++ b/api/compute/v1alpha1/sink_types.go @@ -109,10 +109,12 @@ type SinkSpec struct { type SinkStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - Conditions map[Component]ResourceCondition `json:"conditions"` - Replicas int32 `json:"replicas"` - Selector string `json:"selector"` - ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Conditions map[Component]ResourceCondition `json:"conditions"` + Replicas int32 `json:"replicas"` + Selector string `json:"selector"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + GlobalBackendConfigRevision string `json:"globalBackendConfigRevision,omitempty"` + NamespacedBackendConfigRevision string `json:"namespacedBackendConfigRevision,omitempty"` } // +genclient diff --git a/api/compute/v1alpha1/source_types.go b/api/compute/v1alpha1/source_types.go index b61297e7f..0293f4429 100644 --- a/api/compute/v1alpha1/source_types.go +++ b/api/compute/v1alpha1/source_types.go @@ -114,10 +114,12 @@ type BatchSourceConfig struct { type SourceStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - Conditions map[Component]ResourceCondition `json:"conditions"` - Replicas int32 `json:"replicas"` - Selector string `json:"selector"` - ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Conditions map[Component]ResourceCondition `json:"conditions"` + Replicas int32 `json:"replicas"` + Selector string `json:"selector"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + GlobalBackendConfigRevision string `json:"globalBackendConfigRevision,omitempty"` + NamespacedBackendConfigRevision string `json:"namespacedBackendConfigRevision,omitempty"` } // +genclient diff --git a/api/compute/v1alpha1/zz_generated.deepcopy.go b/api/compute/v1alpha1/zz_generated.deepcopy.go index a52e7c9ec..c8909120f 100644 --- a/api/compute/v1alpha1/zz_generated.deepcopy.go +++ b/api/compute/v1alpha1/zz_generated.deepcopy.go @@ -55,6 +55,102 @@ func (in *AuthConfig) DeepCopy() *AuthConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendConfig) DeepCopyInto(out *BackendConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendConfig. +func (in *BackendConfig) DeepCopy() *BackendConfig { + if in == nil { + return nil + } + out := new(BackendConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackendConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendConfigList) DeepCopyInto(out *BackendConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BackendConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendConfigList. +func (in *BackendConfigList) DeepCopy() *BackendConfigList { + if in == nil { + return nil + } + out := new(BackendConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackendConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendConfigSpec) DeepCopyInto(out *BackendConfigSpec) { + *out = *in + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendConfigSpec. +func (in *BackendConfigSpec) DeepCopy() *BackendConfigSpec { + if in == nil { + return nil + } + out := new(BackendConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendConfigStatus) DeepCopyInto(out *BackendConfigStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendConfigStatus. +func (in *BackendConfigStatus) DeepCopy() *BackendConfigStatus { + if in == nil { + return nil + } + out := new(BackendConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BatchSourceConfig) DeepCopyInto(out *BatchSourceConfig) { *out = *in diff --git a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-backendconfigs.yaml b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-backendconfigs.yaml new file mode 100644 index 000000000..888cdbde4 --- /dev/null +++ b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-backendconfigs.yaml @@ -0,0 +1,70 @@ +{{- if .Values.admissionWebhook.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- if eq .Values.admissionWebhook.certificate.provider "cert-manager" }} + {{- include "function-mesh-operator.certManager.annotation" . | nindent 4 -}} + {{- end }} + controller-gen.kubebuilder.io/version: v0.9.2 + name: backendconfigs.compute.functionmesh.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + {{- if eq .Values.admissionWebhook.certificate.provider "custom" }} + {{- $caSecret := (lookup "v1" "Secret" .Values.admissionWebhook.secretsWebhookNamespace (include "function-mesh-operator.certificate.caSecret" .)) -}} + {{- if $caSecret }} + {{- $caCert := (b64dec (get $caSecret.data "tls.crt")) -}} + {{ printf (include "function-mesh-operator.caBundle" .) (b64enc $caCert) | nindent 8 }} + {{- end }} + {{- end }} + service: + name: {{ include "function-mesh-operator.webhook.service" . }} + namespace: {{ .Release.Namespace }} + path: /convert + port: 443 + conversionReviewVersions: + - v1 + - v1beta1 + group: compute.functionmesh.io + names: + kind: BackendConfig + listKind: BackendConfigList + plural: backendconfigs + singular: backendconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + env: + additionalProperties: + type: string + type: object + name: + type: string + type: object + status: + type: object + type: object + x-kubernetes-preserve-unknown-fields: true + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} +{{- end }} diff --git a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-functions.yaml b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-functions.yaml index 70352c95c..f8aa5cbcc 100644 --- a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-functions.yaml +++ b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-functions.yaml @@ -3644,6 +3644,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sinks.yaml b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sinks.yaml index 63caa207a..21294ce9e 100644 --- a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sinks.yaml +++ b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sinks.yaml @@ -3380,6 +3380,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sources.yaml b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sources.yaml index 49f19b1c1..b050a1295 100644 --- a/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sources.yaml +++ b/charts/function-mesh-operator/charts/admission-webhook/templates/crd-compute.functionmesh.io-sources.yaml @@ -3361,6 +3361,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/charts/function-mesh-operator/templates/controller-manager-deployment.yaml b/charts/function-mesh-operator/templates/controller-manager-deployment.yaml index 332879300..85361789f 100644 --- a/charts/function-mesh-operator/templates/controller-manager-deployment.yaml +++ b/charts/function-mesh-operator/templates/controller-manager-deployment.yaml @@ -62,6 +62,9 @@ spec: - --pprof-addr=:{{ .Values.controllerManager.pprof.port }} - --config-file={{ .Values.controllerManager.configFile }} - --enable-init-containers={{ .Values.controllerManager.enableInitContainers }} + - --global-backend-config={{ .Values.controllerManager.globalBackendConfig }} + - --global-backend-config-namespace={{ .Values.controllerManager.globalBackendConfigNamespace }} + - --namespaced-backend-config={{ .Values.controllerManager.namespacedBackendConfig }} env: - name: NAMESPACE valueFrom: diff --git a/charts/function-mesh-operator/templates/controller-manager-rbac.yaml b/charts/function-mesh-operator/templates/controller-manager-rbac.yaml index bf1c0df40..4034fcc66 100644 --- a/charts/function-mesh-operator/templates/controller-manager-rbac.yaml +++ b/charts/function-mesh-operator/templates/controller-manager-rbac.yaml @@ -140,6 +140,26 @@ rules: - get - patch - update + - apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs/status + verbs: + - get + - patch + - update - apiGroups: - "" resources: diff --git a/charts/function-mesh-operator/values.yaml b/charts/function-mesh-operator/values.yaml index 205c4e5dd..bf560a2a2 100644 --- a/charts/function-mesh-operator/values.yaml +++ b/charts/function-mesh-operator/values.yaml @@ -70,6 +70,9 @@ controllerManager: enable: false port: 8090 enableInitContainers: false + globalBackendConfig: global-backend-config + globalBackendConfigNamespace: default + namespacedBackendConfig: backend-config admissionWebhook: enabled: true diff --git a/config/crd/bases/compute.functionmesh.io_backendconfigs.yaml b/config/crd/bases/compute.functionmesh.io_backendconfigs.yaml new file mode 100644 index 000000000..68b0d271c --- /dev/null +++ b/config/crd/bases/compute.functionmesh.io_backendconfigs.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: backendconfigs.compute.functionmesh.io +spec: + group: compute.functionmesh.io + names: + kind: BackendConfig + listKind: BackendConfigList + plural: backendconfigs + singular: backendconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + env: + additionalProperties: + type: string + type: object + name: + type: string + type: object + status: + type: object + type: object + x-kubernetes-preserve-unknown-fields: true + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/config/crd/bases/compute.functionmesh.io_functions.yaml b/config/crd/bases/compute.functionmesh.io_functions.yaml index 8f066dbdb..07b4ca0fc 100644 --- a/config/crd/bases/compute.functionmesh.io_functions.yaml +++ b/config/crd/bases/compute.functionmesh.io_functions.yaml @@ -3623,6 +3623,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/config/crd/bases/compute.functionmesh.io_sinks.yaml b/config/crd/bases/compute.functionmesh.io_sinks.yaml index d46d9b1c8..984826079 100644 --- a/config/crd/bases/compute.functionmesh.io_sinks.yaml +++ b/config/crd/bases/compute.functionmesh.io_sinks.yaml @@ -3359,6 +3359,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/config/crd/bases/compute.functionmesh.io_sources.yaml b/config/crd/bases/compute.functionmesh.io_sources.yaml index bba96bbc1..028ddf78c 100644 --- a/config/crd/bases/compute.functionmesh.io_sources.yaml +++ b/config/crd/bases/compute.functionmesh.io_sources.yaml @@ -3340,6 +3340,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index c944f5514..0af2ec9ed 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/compute.functionmesh.io_functions.yaml - bases/compute.functionmesh.io_sources.yaml - bases/compute.functionmesh.io_sinks.yaml +- bases/compute.functionmesh.io_backendconfigs.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -15,6 +16,7 @@ patchesStrategicMerge: - patches/webhook_in_functions.yaml - patches/webhook_in_sources.yaml - patches/webhook_in_sinks.yaml +- patches/webhook_in_backendconfigs.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -23,6 +25,7 @@ patchesStrategicMerge: - patches/cainjection_in_functions.yaml - patches/cainjection_in_sources.yaml - patches/cainjection_in_sinks.yaml +- patches/cainjection_in_backendconfigs.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_backendconfigs.yaml b/config/crd/patches/cainjection_in_backendconfigs.yaml new file mode 100644 index 000000000..491d7f208 --- /dev/null +++ b/config/crd/patches/cainjection_in_backendconfigs.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.16 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: backendconfigs.compute.functionmesh.io diff --git a/config/crd/patches/webhook_in_backendconfigs.yaml b/config/crd/patches/webhook_in_backendconfigs.yaml new file mode 100644 index 000000000..ad1d517a3 --- /dev/null +++ b/config/crd/patches/webhook_in_backendconfigs.yaml @@ -0,0 +1,22 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.16 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: backendconfigs.compute.functionmesh.io +spec: + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + - v1beta1 + clientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + # caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert + port: 443 # added this, used 443 bc it's the default from the k8s docs diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index 2edb4a730..99578019c 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -30,3 +30,4 @@ spec: args: - "--metrics-addr=127.0.0.1:8080" - "--enable-leader-election" + - "--namespaced-backend-config=backend-config" diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 5b123381c..d3e38b594 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -33,6 +33,7 @@ spec: - /manager args: - --enable-leader-election + - --namespaced-backend-config=backend-config image: controller:latest name: manager resources: diff --git a/config/rbac/backendconfig_editor_role.yaml b/config/rbac/backendconfig_editor_role.yaml new file mode 100644 index 000000000..b30613b54 --- /dev/null +++ b/config/rbac/backendconfig_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit functionmeshes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: backendconfig-editor-role +rules: + - apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs/status + verbs: + - get diff --git a/config/rbac/backendconfig_viewer_role.yaml b/config/rbac/backendconfig_viewer_role.yaml new file mode 100644 index 000000000..7db4c9424 --- /dev/null +++ b/config/rbac/backendconfig_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view functionmeshes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: backendconfig-viewer-role +rules: + - apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs + verbs: + - get + - list + - watch + - apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index aa94960e2..7ba2a2733 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -52,6 +52,18 @@ rules: - list - update - watch +- apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - compute.functionmesh.io resources: diff --git a/controllers/function.go b/controllers/function.go index 67d5ad4b0..f84bc594b 100644 --- a/controllers/function.go +++ b/controllers/function.go @@ -69,7 +69,12 @@ func (r *FunctionReconciler) ObserveFunctionStatefulSet(ctx context.Context, fun } function.Status.Selector = selector.String() - if r.checkIfStatefulSetNeedUpdate(statefulSet, function) { + needUpdate, err := r.checkIfStatefulSetNeedUpdate(ctx, statefulSet, function) + if err != nil { + r.Log.Error(err, "error comparing statefulSet") + return err + } + if needUpdate { condition.Status = metav1.ConditionFalse condition.Action = v1alpha1.Update function.Status.Conditions[v1alpha1.StatefulSet] = condition @@ -93,7 +98,10 @@ func (r *FunctionReconciler) ApplyFunctionStatefulSet(ctx context.Context, funct if condition.Status == metav1.ConditionTrue && !newGeneration { return nil } - desiredStatefulSet := spec.MakeFunctionStatefulSet(function) + desiredStatefulSet, err := spec.MakeFunctionStatefulSet(ctx, r.Client, function) + if err != nil { + return err + } keepStatefulSetUnchangeableFields(ctx, r, r.Log, desiredStatefulSet) desiredStatefulSetSpec := desiredStatefulSet.Spec if _, err := ctrl.CreateOrUpdate(ctx, r.Client, desiredStatefulSet, func() error { @@ -415,9 +423,13 @@ func (r *FunctionReconciler) ApplyFunctionCleanUpJob(ctx context.Context, functi return nil } -func (r *FunctionReconciler) checkIfStatefulSetNeedUpdate(statefulSet *appsv1.StatefulSet, - function *v1alpha1.Function) bool { - return !spec.CheckIfStatefulSetSpecIsEqual(&statefulSet.Spec, &spec.MakeFunctionStatefulSet(function).Spec) +func (r *FunctionReconciler) checkIfStatefulSetNeedUpdate(ctx context.Context, statefulSet *appsv1.StatefulSet, + function *v1alpha1.Function) (bool, error) { + desiredStatefulSet, err := spec.MakeFunctionStatefulSet(ctx, r.Client, function) + if err != nil { + return false, err + } + return !spec.CheckIfStatefulSetSpecIsEqual(&statefulSet.Spec, &desiredStatefulSet.Spec), nil } func (r *FunctionReconciler) checkIfHPANeedUpdate(hpa *autov2.HorizontalPodAutoscaler, diff --git a/controllers/function_controller.go b/controllers/function_controller.go index 5b390ba34..f3a6e5ea5 100644 --- a/controllers/function_controller.go +++ b/controllers/function_controller.go @@ -22,6 +22,9 @@ import ( "time" "github.com/streamnative/function-mesh/pkg/monitoring" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/source" v1 "k8s.io/api/batch/v1" "k8s.io/client-go/rest" @@ -55,6 +58,7 @@ type FunctionReconciler struct { // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=functions,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=functions/status,verbs=get;update;patch // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=functions/finalizers,verbs=get;update +// +kubebuilder:rbac:groups=compute.functionmesh.io,resources=backendconfigs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete @@ -184,6 +188,42 @@ func (r *FunctionReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Secret{}). Owns(&v1.Job{}) + manager.Watches(&source.Kind{Type: &v1alpha1.BackendConfig{}}, handler.EnqueueRequestsFromMapFunc( + func(object client.Object) []reconcile.Request { + if object.GetName() == utils.GlobalBackendConfig && object.GetNamespace() == utils.GlobalBackendConfigNamespace { + ctx := context.Background() + functions := &v1alpha1.FunctionList{} + err := mgr.GetClient().List(ctx, functions) + if err != nil { + mgr.GetLogger().Error(err, "failed to list all functions") + } + var requests []reconcile.Request + for _, function := range functions.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Namespace: function.Namespace, Name: function.Name}, + }) + } + return requests + } else if object.GetName() == utils.NamespacedBackendConfig { + ctx := context.Background() + functions := &v1alpha1.FunctionList{} + err := mgr.GetClient().List(ctx, functions, client.InNamespace(object.GetNamespace())) + if err != nil { + mgr.GetLogger().Error(err, "failed to list functions in namespace: "+object.GetNamespace()) + } + var requests []reconcile.Request + for _, function := range functions.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Namespace: function.Namespace, Name: function.Name}, + }) + } + return requests + } else { + return nil + } + }), + ) + if r.GroupVersionFlags != nil && r.GroupVersionFlags.WatchVPACRDs { manager.Owns(&vpav1.VerticalPodAutoscaler{}) } diff --git a/controllers/sink.go b/controllers/sink.go index a7ad1ab07..235cedab4 100644 --- a/controllers/sink.go +++ b/controllers/sink.go @@ -69,7 +69,12 @@ func (r *SinkReconciler) ObserveSinkStatefulSet(ctx context.Context, sink *v1alp } sink.Status.Selector = selector.String() - if r.checkIfStatefulSetNeedUpdate(statefulSet, sink) { + needUpdate, err := r.checkIfStatefulSetNeedUpdate(ctx, statefulSet, sink) + if err != nil { + r.Log.Error(err, "error comparing statefulSet") + return err + } + if needUpdate { condition.Status = metav1.ConditionFalse condition.Action = v1alpha1.Update sink.Status.Conditions[v1alpha1.StatefulSet] = condition @@ -92,7 +97,10 @@ func (r *SinkReconciler) ApplySinkStatefulSet(ctx context.Context, sink *v1alpha if condition.Status == metav1.ConditionTrue && !newGeneration { return nil } - desiredStatefulSet := spec.MakeSinkStatefulSet(sink) + desiredStatefulSet, err := spec.MakeSinkStatefulSet(ctx, r.Client, sink) + if err != nil { + return err + } keepStatefulSetUnchangeableFields(ctx, r, r.Log, desiredStatefulSet) desiredStatefulSetSpec := desiredStatefulSet.Spec if _, err := ctrl.CreateOrUpdate(ctx, r.Client, desiredStatefulSet, func() error { @@ -412,8 +420,12 @@ func (r *SinkReconciler) ApplySinkCleanUpJob(ctx context.Context, sink *v1alpha1 return nil } -func (r *SinkReconciler) checkIfStatefulSetNeedUpdate(statefulSet *appsv1.StatefulSet, sink *v1alpha1.Sink) bool { - return !spec.CheckIfStatefulSetSpecIsEqual(&statefulSet.Spec, &spec.MakeSinkStatefulSet(sink).Spec) +func (r *SinkReconciler) checkIfStatefulSetNeedUpdate(ctx context.Context, statefulSet *appsv1.StatefulSet, sink *v1alpha1.Sink) (bool, error) { + desiredStatefulSet, err := spec.MakeSinkStatefulSet(ctx, r.Client, sink) + if err != nil { + return false, err + } + return !spec.CheckIfStatefulSetSpecIsEqual(&statefulSet.Spec, &desiredStatefulSet.Spec), nil } func (r *SinkReconciler) checkIfHPANeedUpdate(hpa *autov2.HorizontalPodAutoscaler, sink *v1alpha1.Sink) bool { diff --git a/controllers/sink_controller.go b/controllers/sink_controller.go index 3c2718c4b..1c30f9f71 100644 --- a/controllers/sink_controller.go +++ b/controllers/sink_controller.go @@ -22,6 +22,9 @@ import ( "time" "github.com/streamnative/function-mesh/pkg/monitoring" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/source" v1 "k8s.io/api/batch/v1" "k8s.io/client-go/rest" @@ -55,6 +58,7 @@ type SinkReconciler struct { // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=sinks,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=sinks/status,verbs=get;update;patch // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=sinks/finalizers,verbs=get;update +// +kubebuilder:rbac:groups=compute.functionmesh.io,resources=backendconfigs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete @@ -189,5 +193,41 @@ func (r *SinkReconciler) SetupWithManager(mgr ctrl.Manager) error { AddControllerBuilderOwn(manager, r.GroupVersionFlags.APIAutoscalingGroupVersion) } + manager.Watches(&source.Kind{Type: &v1alpha1.BackendConfig{}}, handler.EnqueueRequestsFromMapFunc( + func(object client.Object) []reconcile.Request { + if object.GetName() == utils.GlobalBackendConfig && object.GetNamespace() == utils.GlobalBackendConfigNamespace { + ctx := context.Background() + sinks := &v1alpha1.SinkList{} + err := mgr.GetClient().List(ctx, sinks) + if err != nil { + mgr.GetLogger().Error(err, "failed to list all sinks") + } + var requests []reconcile.Request + for _, sink := range sinks.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Namespace: sink.Namespace, Name: sink.Name}, + }) + } + return requests + } else if object.GetName() == utils.NamespacedBackendConfig { + ctx := context.Background() + sinks := &v1alpha1.SinkList{} + err := mgr.GetClient().List(ctx, sinks, client.InNamespace(object.GetNamespace())) + if err != nil { + mgr.GetLogger().Error(err, "failed to list sinks in namespace: "+object.GetNamespace()) + } + var requests []reconcile.Request + for _, sink := range sinks.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Namespace: sink.Namespace, Name: sink.Name}, + }) + } + return requests + } else { + return nil + } + }), + ) + return manager.Complete(r) } diff --git a/controllers/sink_controller_test.go b/controllers/sink_controller_test.go index 38373fd2b..6b67a5a93 100644 --- a/controllers/sink_controller_test.go +++ b/controllers/sink_controller_test.go @@ -41,7 +41,7 @@ var _ = Describe("Sink Controller", func() { if sink.Status.Conditions == nil { sink.Status.Conditions = make(map[v1alpha1.Component]v1alpha1.ResourceCondition) } - statefulSet := spec.MakeSinkStatefulSet(sink) + statefulSet, _ := spec.MakeSinkStatefulSet(context.Background(), k8sClient, sink) It("Should create pulsar configmap successfully", func() { Expect(k8sClient.Create(context.Background(), pulsarConfig)).Should(Succeed()) diff --git a/controllers/source.go b/controllers/source.go index bbd145585..08685456e 100644 --- a/controllers/source.go +++ b/controllers/source.go @@ -69,7 +69,12 @@ func (r *SourceReconciler) ObserveSourceStatefulSet(ctx context.Context, source } source.Status.Selector = selector.String() - if r.checkIfStatefulSetNeedUpdate(statefulSet, source) { + needUpdate, err := r.checkIfStatefulSetNeedUpdate(ctx, statefulSet, source) + if err != nil { + r.Log.Error(err, "error comparing statefulSet") + return err + } + if needUpdate { condition.Status = metav1.ConditionFalse condition.Action = v1alpha1.Update source.Status.Conditions[v1alpha1.StatefulSet] = condition @@ -93,7 +98,10 @@ func (r *SourceReconciler) ApplySourceStatefulSet(ctx context.Context, source *v if condition.Status == metav1.ConditionTrue && !newGeneration { return nil } - desiredStatefulSet := spec.MakeSourceStatefulSet(source) + desiredStatefulSet, err := spec.MakeSourceStatefulSet(ctx, r.Client, source) + if err != nil { + return err + } keepStatefulSetUnchangeableFields(ctx, r, r.Log, desiredStatefulSet) desiredStatefulSetSpec := desiredStatefulSet.Spec if _, err := ctrl.CreateOrUpdate(ctx, r.Client, desiredStatefulSet, func() error { @@ -414,8 +422,12 @@ func (r *SourceReconciler) ApplySourceCleanUpJob(ctx context.Context, source *v1 return nil } -func (r *SourceReconciler) checkIfStatefulSetNeedUpdate(statefulSet *appsv1.StatefulSet, source *v1alpha1.Source) bool { - return !spec.CheckIfStatefulSetSpecIsEqual(&statefulSet.Spec, &spec.MakeSourceStatefulSet(source).Spec) +func (r *SourceReconciler) checkIfStatefulSetNeedUpdate(ctx context.Context, statefulSet *appsv1.StatefulSet, source *v1alpha1.Source) (bool, error) { + desiredStatefulSet, err := spec.MakeSourceStatefulSet(ctx, r.Client, source) + if err != nil { + return false, err + } + return !spec.CheckIfStatefulSetSpecIsEqual(&statefulSet.Spec, &desiredStatefulSet.Spec), nil } func (r *SourceReconciler) checkIfHPANeedUpdate(hpa *autov2.HorizontalPodAutoscaler, source *v1alpha1.Source) bool { diff --git a/controllers/source_controller.go b/controllers/source_controller.go index 461e3146f..2fee4b0ae 100644 --- a/controllers/source_controller.go +++ b/controllers/source_controller.go @@ -22,6 +22,9 @@ import ( "time" "github.com/streamnative/function-mesh/pkg/monitoring" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/handler" + k8ssource "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" "github.com/streamnative/function-mesh/api/compute/v1alpha1" @@ -54,6 +57,7 @@ type SourceReconciler struct { // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=sources,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=sources/status,verbs=get;update;patch // +kubebuilder:rbac:groups=compute.functionmesh.io,resources=sources/finalizers,verbs=get;update +// +kubebuilder:rbac:groups=compute.functionmesh.io,resources=backendconfigs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;delete // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete @@ -187,5 +191,40 @@ func (r *SourceReconciler) SetupWithManager(mgr ctrl.Manager) error { if r.GroupVersionFlags != nil && r.GroupVersionFlags.APIAutoscalingGroupVersion != "" { AddControllerBuilderOwn(manager, r.GroupVersionFlags.APIAutoscalingGroupVersion) } + manager.Watches(&k8ssource.Kind{Type: &v1alpha1.BackendConfig{}}, handler.EnqueueRequestsFromMapFunc( + func(object client.Object) []reconcile.Request { + if object.GetName() == utils.GlobalBackendConfig && object.GetNamespace() == utils.GlobalBackendConfigNamespace { + ctx := context.Background() + sources := &v1alpha1.SourceList{} + err := mgr.GetClient().List(ctx, sources) + if err != nil { + mgr.GetLogger().Error(err, "failed to list all sources") + } + var requests []reconcile.Request + for _, source := range sources.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Namespace: source.Namespace, Name: source.Name}, + }) + } + return requests + } else if object.GetName() == utils.NamespacedBackendConfig { + ctx := context.Background() + sources := &v1alpha1.SourceList{} + err := mgr.GetClient().List(ctx, sources, client.InNamespace(object.GetNamespace())) + if err != nil { + mgr.GetLogger().Error(err, "failed to list sources in namespace: "+object.GetNamespace()) + } + var requests []reconcile.Request + for _, source := range sources.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Namespace: source.Namespace, Name: source.Name}, + }) + } + return requests + } else { + return nil + } + }), + ) return manager.Complete(r) } diff --git a/controllers/source_controller_test.go b/controllers/source_controller_test.go index e2b20f1e5..59c8afbf2 100644 --- a/controllers/source_controller_test.go +++ b/controllers/source_controller_test.go @@ -40,7 +40,7 @@ var _ = Describe("Source Controller", func() { if source.Status.Conditions == nil { source.Status.Conditions = make(map[v1alpha1.Component]v1alpha1.ResourceCondition) } - statefulSet := spec.MakeSourceStatefulSet(source) + statefulSet, _ := spec.MakeSourceStatefulSet(context.Background(), k8sClient, source) It("Should create pulsar configmap successfully", func() { Expect(k8sClient.Create(context.Background(), pulsarConfig)).Should(Succeed()) diff --git a/controllers/spec/common.go b/controllers/spec/common.go index 697df7a3f..13f5e7f3d 100644 --- a/controllers/spec/common.go +++ b/controllers/spec/common.go @@ -39,7 +39,9 @@ import ( autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" v1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -277,6 +279,70 @@ func MakeStatefulSet(objectMeta *metav1.ObjectMeta, replicas *int32, downloaderI } } +// PatchStatefulSet Apply global and namespaced configs to StatefulSet +func PatchStatefulSet(ctx context.Context, cli client.Client, namespace string, statefulSet *appsv1.StatefulSet) (string, string, error) { + globalBackendConfigVersion := "" + namespacedBackendConfigVersion := "" + envData := make(map[string]string) + + if utils.GlobalBackendConfig != "" && utils.GlobalBackendConfigNamespace != "" { + globalBackendConfig := &v1alpha1.BackendConfig{} + err := cli.Get(ctx, types.NamespacedName{ + Namespace: utils.GlobalBackendConfigNamespace, + Name: utils.GlobalBackendConfig, + }, globalBackendConfig) + if err != nil { + // ignore not found error + if !k8serrors.IsNotFound(err) { + return "", "", err + } + } else { + globalBackendConfigVersion = globalBackendConfig.ResourceVersion + for key, val := range globalBackendConfig.Spec.Env { + envData[key] = val + } + } + } + + // patch namespaced configs + if utils.NamespacedBackendConfig != "" { + namespacedBackendConfig := &v1alpha1.BackendConfig{} + err := cli.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: utils.NamespacedBackendConfig, + }, namespacedBackendConfig) + if err != nil { + // ignore not found error + if !k8serrors.IsNotFound(err) { + return "", "", err + } + } else { + namespacedBackendConfigVersion = namespacedBackendConfig.ResourceVersion + for key, val := range namespacedBackendConfig.Spec.Env { + envData[key] = val + } + } + } + + // merge env + if len(envData) == 0 { + return globalBackendConfigVersion, namespacedBackendConfigVersion, nil + } + globalEnvs := make([]corev1.EnvVar, 0, len(envData)) + for key, val := range envData { + globalEnvs = append(globalEnvs, corev1.EnvVar{ + Name: key, + Value: val, + }) + } + for i := range statefulSet.Spec.Template.Spec.Containers { + statefulSet.Spec.Template.Spec.Containers[i].Env = append(statefulSet.Spec.Template.Spec.Containers[i].Env, + globalEnvs...) + } + + return globalBackendConfigVersion, namespacedBackendConfigVersion, nil +} + func MakeStatefulSetSpec(replicas *int32, container *corev1.Container, filebeatContainer *corev1.Container, volumes []corev1.Volume, labels map[string]string, policy v1alpha1.PodPolicy, serviceName string, downloaderContainer *corev1.Container, volumeClaimTemplates []corev1.PersistentVolumeClaim, diff --git a/controllers/spec/function.go b/controllers/spec/function.go index 0392746b6..65bfded84 100644 --- a/controllers/spec/function.go +++ b/controllers/spec/function.go @@ -18,8 +18,10 @@ package spec import ( + "context" "regexp" + "github.com/streamnative/function-mesh/api/compute/v1alpha1" "github.com/streamnative/function-mesh/utils" "google.golang.org/protobuf/encoding/protojson" appsv1 "k8s.io/api/apps/v1" @@ -27,9 +29,8 @@ import ( v1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/streamnative/function-mesh/api/compute/v1alpha1" ) // log is for logging in this package. @@ -57,9 +58,9 @@ func MakeFunctionService(function *v1alpha1.Function) *corev1.Service { return MakeService(objectMeta, labels) } -func MakeFunctionStatefulSet(function *v1alpha1.Function) *appsv1.StatefulSet { +func MakeFunctionStatefulSet(ctx context.Context, cli client.Client, function *v1alpha1.Function) (*appsv1.StatefulSet, error) { objectMeta := MakeFunctionObjectMeta(function) - return MakeStatefulSet(objectMeta, function.Spec.Replicas, function.Spec.DownloaderImage, + statefulSet := MakeStatefulSet(objectMeta, function.Spec.Replicas, function.Spec.DownloaderImage, makeFunctionContainer(function), makeFilebeatContainer(function.Spec.VolumeMounts, function.Spec.Pod.Env, function.Spec.Name, function.Spec.LogTopic, function.Spec.LogTopicAgent, function.Spec.Pulsar.TLSConfig, function.Spec.Pulsar.AuthConfig, function.Spec.Pulsar.PulsarConfig, function.Spec.Pulsar.TLSSecret, @@ -68,6 +69,19 @@ func MakeFunctionStatefulSet(function *v1alpha1.Function) *appsv1.StatefulSet { *function.Spec.Pulsar, function.Spec.Java, function.Spec.Python, function.Spec.Golang, function.Spec.VolumeMounts, function.Spec.VolumeClaimTemplates, function.Spec.PersistentVolumeClaimRetentionPolicy) + + globalBackendConfigVersion, namespacedBackendConfigVersion, err := PatchStatefulSet(ctx, cli, function.Namespace, statefulSet) + if err != nil { + return nil, err + } + if globalBackendConfigVersion != "" { + function.Status.GlobalBackendConfigRevision = globalBackendConfigVersion + } + if namespacedBackendConfigVersion != "" { + function.Status.NamespacedBackendConfigRevision = namespacedBackendConfigVersion + } + + return statefulSet, nil } func MakeFunctionObjectMeta(function *v1alpha1.Function) *metav1.ObjectMeta { diff --git a/controllers/spec/sink.go b/controllers/spec/sink.go index d88c27f89..321f9ecd1 100644 --- a/controllers/spec/sink.go +++ b/controllers/spec/sink.go @@ -18,6 +18,7 @@ package spec import ( + "context" "regexp" "github.com/streamnative/function-mesh/utils" @@ -27,6 +28,7 @@ import ( v1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/streamnative/function-mesh/api/compute/v1alpha1" ) @@ -53,14 +55,27 @@ func MakeSinkService(sink *v1alpha1.Sink) *corev1.Service { return MakeService(objectMeta, labels) } -func MakeSinkStatefulSet(sink *v1alpha1.Sink) *appsv1.StatefulSet { +func MakeSinkStatefulSet(ctx context.Context, cli client.Client, sink *v1alpha1.Sink) (*appsv1.StatefulSet, error) { objectMeta := MakeSinkObjectMeta(sink) - return MakeStatefulSet(objectMeta, sink.Spec.Replicas, sink.Spec.DownloaderImage, makeSinkContainer(sink), + statefulSet := MakeStatefulSet(objectMeta, sink.Spec.Replicas, sink.Spec.DownloaderImage, makeSinkContainer(sink), makeFilebeatContainer(sink.Spec.VolumeMounts, sink.Spec.Pod.Env, sink.Spec.Name, sink.Spec.LogTopic, sink.Spec.LogTopicAgent, sink.Spec.Pulsar.TLSConfig, sink.Spec.Pulsar.AuthConfig, sink.Spec.Pulsar.PulsarConfig, sink.Spec.Pulsar.TLSSecret, sink.Spec.Pulsar.AuthSecret, sink.Spec.FilebeatImage), makeSinkVolumes(sink, sink.Spec.Pulsar.AuthConfig), makeSinkLabels(sink), sink.Spec.Pod, *sink.Spec.Pulsar, sink.Spec.Java, sink.Spec.Python, sink.Spec.Golang, sink.Spec.VolumeMounts, nil, nil) + + globalBackendConfigVersion, namespacedBackendConfigVersion, err := PatchStatefulSet(ctx, cli, sink.Namespace, statefulSet) + if err != nil { + return nil, err + } + if globalBackendConfigVersion != "" { + sink.Status.GlobalBackendConfigRevision = globalBackendConfigVersion + } + if namespacedBackendConfigVersion != "" { + sink.Status.NamespacedBackendConfigRevision = namespacedBackendConfigVersion + } + + return statefulSet, nil } func MakeSinkServiceName(sink *v1alpha1.Sink) string { diff --git a/controllers/spec/source.go b/controllers/spec/source.go index 3fb249426..d077cb2aa 100644 --- a/controllers/spec/source.go +++ b/controllers/spec/source.go @@ -18,6 +18,7 @@ package spec import ( + "context" "fmt" "regexp" @@ -28,6 +29,7 @@ import ( v1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/streamnative/function-mesh/api/compute/v1alpha1" ) @@ -54,14 +56,27 @@ func MakeSourceService(source *v1alpha1.Source) *corev1.Service { return MakeService(objectMeta, labels) } -func MakeSourceStatefulSet(source *v1alpha1.Source) *appsv1.StatefulSet { +func MakeSourceStatefulSet(ctx context.Context, cli client.Client, source *v1alpha1.Source) (*appsv1.StatefulSet, error) { objectMeta := MakeSourceObjectMeta(source) - return MakeStatefulSet(objectMeta, source.Spec.Replicas, source.Spec.DownloaderImage, makeSourceContainer(source), + statefulSet := MakeStatefulSet(objectMeta, source.Spec.Replicas, source.Spec.DownloaderImage, makeSourceContainer(source), makeFilebeatContainer(source.Spec.VolumeMounts, source.Spec.Pod.Env, source.Spec.Name, source.Spec.LogTopic, source.Spec.LogTopicAgent, source.Spec.Pulsar.TLSConfig, source.Spec.Pulsar.AuthConfig, source.Spec.Pulsar.PulsarConfig, source.Spec.Pulsar.TLSSecret, source.Spec.Pulsar.AuthSecret, source.Spec.FilebeatImage), makeSourceVolumes(source, source.Spec.Pulsar.AuthConfig), makeSourceLabels(source), source.Spec.Pod, *source.Spec.Pulsar, source.Spec.Java, source.Spec.Python, source.Spec.Golang, source.Spec.VolumeMounts, nil, nil) + + globalBackendConfigVersion, namespacedBackendConfigVersion, err := PatchStatefulSet(ctx, cli, source.Namespace, statefulSet) + if err != nil { + return nil, err + } + if globalBackendConfigVersion != "" { + source.Status.GlobalBackendConfigRevision = globalBackendConfigVersion + } + if namespacedBackendConfigVersion != "" { + source.Status.NamespacedBackendConfigRevision = namespacedBackendConfigVersion + } + + return statefulSet, nil } func MakeSourceObjectMeta(source *v1alpha1.Source) *metav1.ObjectMeta { diff --git a/go.mod b/go.mod index bb5de1b39..177058204 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/prometheus/client_golang v1.12.2 github.com/streamnative/pulsarctl v0.4.3-0.20220702165443-e4c26e2c39cf github.com/stretchr/testify v1.7.0 - google.golang.org/protobuf v1.28.0 + google.golang.org/protobuf v1.33.0 gotest.tools v2.2.0+incompatible k8s.io/api v0.25.0 k8s.io/apimachinery v0.25.0 diff --git a/go.sum b/go.sum index 4cf81fab8..1971f24e0 100644 --- a/go.sum +++ b/go.sum @@ -840,8 +840,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hack/gen-helm-crd-templates.sh b/hack/gen-helm-crd-templates.sh index b6cf025a1..ff5affd2e 100755 --- a/hack/gen-helm-crd-templates.sh +++ b/hack/gen-helm-crd-templates.sh @@ -149,6 +149,13 @@ function crd::main() { "$KUSTOMIZE" build config/crd | "$YQ" eval '. | select(.metadata.name == "functionmeshes.compute.functionmesh.io")' > "$source_file" # set webhook_enabled to false since the functionmeshes don't have webhooks crd::generate_template "$source_file" "$target_file" false + + # crd-backendconfigs + file="crd-compute.functionmesh.io-backendconfigs.yaml" + target_file="charts/function-mesh-operator/charts/admission-webhook/templates/$file" + source_file="$tmp/$file" + "$KUSTOMIZE" build config/crd | "$YQ" eval '. | select(.metadata.name == "backendconfigs.compute.functionmesh.io")' > "$source_file" + crd::generate_template "$source_file" "$target_file" true } crd::main \ No newline at end of file diff --git a/main.go b/main.go index a58e12ebe..359faa95a 100644 --- a/main.go +++ b/main.go @@ -24,9 +24,8 @@ import ( "os" "strconv" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "github.com/streamnative/function-mesh/pkg/monitoring" + "sigs.k8s.io/controller-runtime/pkg/healthz" "github.com/go-logr/logr" computev1alpha1 "github.com/streamnative/function-mesh/api/compute/v1alpha1" @@ -72,6 +71,9 @@ func main() { var configFile string var watchedNamespace string var enableInitContainers bool + var globalBackendConfig string + var globalBackendConfigNamespace string + var namespacedBackendConfig string flag.StringVar(&metricsAddr, "metrics-addr", lookupEnvOrString("METRICS_ADDR", ":8080"), "The address the metric endpoint binds to.") flag.StringVar(&leaderElectionID, "leader-election-id", @@ -97,10 +99,19 @@ func main() { "The address the pprof binds to.") flag.BoolVar(&enableInitContainers, "enable-init-containers", lookupEnvOrBool("ENABLE_INIT_CONTAINERS", false), "Whether to use an init container to download package") + flag.StringVar(&globalBackendConfig, "global-backend-config", lookupEnvOrString("GLOBAL_BACKEND_CONFIG", ""), + "The global backend config name used for all functions&sinks&sources") + flag.StringVar(&globalBackendConfigNamespace, "global-backend-config-namespace", lookupEnvOrString("GLOBAL_BACKEND_CONFIG_NAMESPACE", "default"), + "The namespace of the global backend config name used for all functions&sinks&sources") + flag.StringVar(&namespacedBackendConfig, "namespaced-backend-config", lookupEnvOrString("NAMESPACED_BACKEND_CONFIG", "backend-config"), + "The backend config name used for functions&sinks&sources in the same namespace") flag.Parse() ctrl.SetLogger(zap.New(zap.UseDevMode(true))) utils.EnableInitContainers = enableInitContainers + utils.GlobalBackendConfig = globalBackendConfig + utils.GlobalBackendConfigNamespace = globalBackendConfigNamespace + utils.NamespacedBackendConfig = namespacedBackendConfig // enable pprof if enablePprof { diff --git a/manifests/crd.yaml b/manifests/crd.yaml index cbec5ea39..a19fa60bd 100644 --- a/manifests/crd.yaml +++ b/manifests/crd.yaml @@ -1,5 +1,65 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + controller-gen.kubebuilder.io/version: v0.9.2 + name: backendconfigs.compute.functionmesh.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: webhook-service + namespace: system + path: /convert + port: 443 + conversionReviewVersions: + - v1 + - v1beta1 + group: compute.functionmesh.io + names: + kind: BackendConfig + listKind: BackendConfigList + plural: backendconfigs + singular: backendconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + env: + additionalProperties: + type: string + type: object + name: + type: string + type: object + status: + type: object + type: object + x-kubernetes-preserve-unknown-fields: true + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) @@ -13943,6 +14003,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer @@ -17339,6 +17403,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer @@ -20716,6 +20784,10 @@ spec: type: string type: object type: object + globalBackendConfigRevision: + type: string + namespacedBackendConfigRevision: + type: string observedGeneration: format: int64 type: integer diff --git a/manifests/rbac.yaml b/manifests/rbac.yaml index 3a8ab8ad5..0a3848599 100644 --- a/manifests/rbac.yaml +++ b/manifests/rbac.yaml @@ -67,6 +67,41 @@ rules: - patch - update - watch +- apiGroups: + - autoscaling.k8s.io + resources: + - verticalpodautoscalers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - update + - watch +- apiGroups: + - compute.functionmesh.io + resources: + - backendconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - compute.functionmesh.io resources: @@ -99,6 +134,13 @@ rules: - patch - update - watch +- apiGroups: + - compute.functionmesh.io + resources: + - functions/finalizers + verbs: + - get + - update - apiGroups: - compute.functionmesh.io resources: @@ -119,6 +161,13 @@ rules: - patch - update - watch +- apiGroups: + - compute.functionmesh.io + resources: + - sinks/finalizers + verbs: + - get + - update - apiGroups: - compute.functionmesh.io resources: @@ -139,6 +188,13 @@ rules: - patch - update - watch +- apiGroups: + - compute.functionmesh.io + resources: + - sources/finalizers + verbs: + - get + - update - apiGroups: - compute.functionmesh.io resources: @@ -147,6 +203,40 @@ rules: - get - patch - update +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - list + - update +- apiGroups: + - "" + resources: + - pods + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods/exec + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - "" resources: @@ -174,6 +264,16 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole metadata: name: proxy-role rules: @@ -191,16 +291,6 @@ rules: - create --- apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: metrics-reader -rules: -- nonResourceURLs: - - /metrics - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: leader-election-rolebinding diff --git a/utils/configs.go b/utils/configs.go index c5c2cbc46..5e7bd9027 100644 --- a/utils/configs.go +++ b/utils/configs.go @@ -19,3 +19,6 @@ package utils var EnableInitContainers = false +var GlobalBackendConfig = "" +var GlobalBackendConfigNamespace = "default" +var NamespacedBackendConfig = ""