diff --git a/components/serverless/.gitignore b/components/serverless/.gitignore index 259403fd8..f4a4a637f 100644 --- a/components/serverless/.gitignore +++ b/components/serverless/.gitignore @@ -28,3 +28,6 @@ vendor/ cover.out filered.cov log_config.yaml + +# Directory with temporary files used to do development +hack/test_files \ No newline at end of file diff --git a/components/serverless/Makefile b/components/serverless/Makefile index 70ba6b042..ac3884752 100644 --- a/components/serverless/Makefile +++ b/components/serverless/Makefile @@ -11,8 +11,6 @@ override VERIFY_IGNORE := /vendor\|/automock\|/pkg/apis/serverless/v1alpha2/zz_g # Image URL to use all building/pushing image targets IMG ?= $(APP_NAME):latest -# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) -CRD_OPTIONS ?= "crd:trivialVersions=true,crdVersions=v1" # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -34,21 +32,20 @@ test-local: @go tool cover -func=/tmp/artifacts/cover.out | grep total | awk '{print $$3}' # Generate manifests e.g. CRD, RBAC etc. -CONTROLLER_GEN_VERSION=v0.6.2 -CONFIGOPERATOR = config/operator +CONTROLLER_GEN_VERSION=v0.9.2 .PHONY: manifests manifests: controller-gen-local generate-local @make -C ${PROJECT_ROOT} kustomize - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=serverless webhook paths="./..." \ + $(CONTROLLER_GEN) rbac:roleName=serverless crd webhook paths="./..." \ object:headerFile=hack/boilerplate.go.txt \ - output:crd:artifacts:config=$(CONFIGOPERATOR)/crd/bases \ - output:rbac:artifacts:config=$(CONFIGOPERATOR)/rbac \ - output:webhook:artifacts:config=$(CONFIGOPERATOR)/webhook - $(KUSTOMIZE) build $(CONFIGOPERATOR)/crd > $(CONFIGOPERATOR)/crd/crd-serverless.yaml - cp $(CONFIGOPERATOR)/crd/crd-serverless.yaml $(PROJECT_ROOT)/config/serverless/templates/crds.yaml -# TODO: Fix it. Now this docu is in https://kyma-project.io/#/serverless-manager/user/resources/06-10-function-cr?id=custom-resource-parameters. Remove table-gen from kyma. -# (cd ../../hack/table-gen && make serverless-docs ) + output:crd:artifacts:config=config/crd/bases \ + output:rbac:artifacts:config=config/rbac \ + output:webhook:artifacts:config=config/webhook + $(KUSTOMIZE) build config/crd > config/crd/crd-serverless.yaml + cp config/crd/crd-serverless.yaml $(PROJECT_ROOT)/config/serverless/templates/crds.yaml + # TODO: Fix it. Now this docu is in https://kyma-project.io/#/serverless-manager/user/resources/06-10-function-cr?id=custom-resource-parameters. Remove table-gen from kyma. + # (cd ../../hack/table-gen && make serverless-docs ) # Generate code .PHONY: generate-local diff --git a/components/serverless/config/crd/bases/serverless.kyma-project.io_functions.yaml b/components/serverless/config/crd/bases/serverless.kyma-project.io_functions.yaml index 9b76cc746..bcd2de34e 100644 --- a/components/serverless/config/crd/bases/serverless.kyma-project.io_functions.yaml +++ b/components/serverless/config/crd/bases/serverless.kyma-project.io_functions.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 + controller-gen.kubebuilder.io/version: v0.9.2 creationTimestamp: null name: functions.serverless.kyma-project.io spec: @@ -112,6 +111,7 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic fieldRef: description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, @@ -129,6 +129,7 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, @@ -153,6 +154,7 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic secretKeyRef: description: Selects a key of a secret in the pod's namespace properties: @@ -171,6 +173,7 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic type: object required: - name @@ -234,6 +237,10 @@ spec: type: object type: object type: object + x-kubernetes-validations: + - message: Use profile or resources + rule: has(self.profile) && !has(self.resources) || !has(self.profile) + && has(self.resources) function: description: Specifies resources requested by the Function's Pod. properties: @@ -271,6 +278,10 @@ spec: type: object type: object type: object + x-kubernetes-validations: + - message: Use profile or resources + rule: has(self.profile) && !has(self.resources) || !has(self.profile) + && has(self.resources) type: object runtime: description: Specifies the runtime of the Function. The available @@ -480,9 +491,3 @@ spec: specReplicasPath: .spec.replicas statusReplicasPath: .status.replicas status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/components/serverless/config/rbac/role.yaml b/components/serverless/config/rbac/role.yaml index efb29f9a0..4570315c4 100644 --- a/components/serverless/config/rbac/role.yaml +++ b/components/serverless/config/rbac/role.yaml @@ -1,4 +1,3 @@ - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/components/serverless/config/webhook/manifests.yaml b/components/serverless/config/webhook/manifests.yaml index e8f40b203..a86be4045 100644 --- a/components/serverless/config/webhook/manifests.yaml +++ b/components/serverless/config/webhook/manifests.yaml @@ -1,4 +1,3 @@ - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration diff --git a/components/serverless/internal/testenv/testenv.go b/components/serverless/internal/testenv/testenv.go index e96026099..c79cb6c89 100644 --- a/components/serverless/internal/testenv/testenv.go +++ b/components/serverless/internal/testenv/testenv.go @@ -45,10 +45,10 @@ func buildCrdPath(wd string) string { crdPath := []string{"/"} for _, path := range wdPath { crdPath = append(crdPath, path) - if path == "serverless" { + if path == "components" { break } } - crdPath = append(crdPath, "config", "crd", "bases") + crdPath = append(crdPath, "serverless", "config", "crd", "bases") return filepath.Join(crdPath...) } diff --git a/components/serverless/pkg/apis/serverless/v1alpha2/function_types.go b/components/serverless/pkg/apis/serverless/v1alpha2/function_types.go index 0a5020dee..c6b7e9875 100644 --- a/components/serverless/pkg/apis/serverless/v1alpha2/function_types.go +++ b/components/serverless/pkg/apis/serverless/v1alpha2/function_types.go @@ -130,10 +130,12 @@ type ScaleConfig struct { type ResourceConfiguration struct { // Specifies resources requested by the build Job's Pod. // +optional + // +kubebuilder:validation:XValidation:message="Use profile or resources",rule="has(self.profile) && !has(self.resources) || !has(self.profile) && has(self.resources)" Build *ResourceRequirements `json:"build,omitempty"` // Specifies resources requested by the Function's Pod. // +optional + // +kubebuilder:validation:XValidation:message="Use profile or resources",rule="has(self.profile) && !has(self.resources) || !has(self.profile) && has(self.resources)" Function *ResourceRequirements `json:"function,omitempty"` } diff --git a/components/serverless/pkg/apis/serverless/v1alpha2/function_x_validation_test.go b/components/serverless/pkg/apis/serverless/v1alpha2/function_x_validation_test.go new file mode 100644 index 000000000..df19ceb8a --- /dev/null +++ b/components/serverless/pkg/apis/serverless/v1alpha2/function_x_validation_test.go @@ -0,0 +1,142 @@ +package v1alpha2_test + +import ( + "context" + "github.com/kyma-project/kyma/components/function-controller/internal/testenv" + serverlessv1alpha2 "github.com/kyma-project/kyma/components/function-controller/pkg/apis/serverless/v1alpha2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" +) + +func Test_XKubernetesValidations(t *testing.T) { + ctx := context.TODO() + k8sClient, testEnv := testenv.Start(t) + defer testenv.Stop(t, testEnv) + + testNs := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + } + err := k8sClient.Create(ctx, &testNs) + require.NoError(t, err) + + testCases := map[string]struct { + fn *serverlessv1alpha2.Function + expectedErr bool + expectedErrMsg string + fieldPath string + }{ + "Resource and Profiles used together in function": { + fn: &serverlessv1alpha2.Function{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test", + Namespace: "test", + }, + Spec: serverlessv1alpha2.FunctionSpec{ + ResourceConfiguration: &serverlessv1alpha2.ResourceConfiguration{Function: &serverlessv1alpha2.ResourceRequirements{ + Profile: "Test", + Resources: &corev1.ResourceRequirements{}, + }}, + }, + }, + expectedErr: true, + expectedErrMsg: "Use profile or resources", + fieldPath: "spec.resourceConfiguration.function", + }, + "Profile set only for function": { + fn: &serverlessv1alpha2.Function{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test", + Namespace: "test", + }, + Spec: serverlessv1alpha2.FunctionSpec{ + ResourceConfiguration: &serverlessv1alpha2.ResourceConfiguration{Function: &serverlessv1alpha2.ResourceRequirements{ + Profile: "Test", + }}, + }, + }, + }, + "Resource set only for function": { + fn: &serverlessv1alpha2.Function{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test", + Namespace: "test", + }, + Spec: serverlessv1alpha2.FunctionSpec{ + ResourceConfiguration: &serverlessv1alpha2.ResourceConfiguration{Function: &serverlessv1alpha2.ResourceRequirements{ + Profile: "Test", + }}, + }, + }, + }, + "Resource and Profiles used together in buildJob": { + fn: &serverlessv1alpha2.Function{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test", + Namespace: "test", + }, + Spec: serverlessv1alpha2.FunctionSpec{ + ResourceConfiguration: &serverlessv1alpha2.ResourceConfiguration{Build: &serverlessv1alpha2.ResourceRequirements{ + Profile: "Test", + Resources: &corev1.ResourceRequirements{}, + }}, + }, + }, + expectedErr: true, + expectedErrMsg: "Use profile or resources", + fieldPath: "spec.resourceConfiguration.build", + }, + "Profile set only for buildJob": { + fn: &serverlessv1alpha2.Function{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test", + Namespace: "test", + }, + Spec: serverlessv1alpha2.FunctionSpec{ + ResourceConfiguration: &serverlessv1alpha2.ResourceConfiguration{Build: &serverlessv1alpha2.ResourceRequirements{ + Profile: "Test", + }}, + }, + }, + }, + "Resource set only for buildJob": { + fn: &serverlessv1alpha2.Function{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test", + Namespace: "test", + }, + Spec: serverlessv1alpha2.FunctionSpec{ + ResourceConfiguration: &serverlessv1alpha2.ResourceConfiguration{Build: &serverlessv1alpha2.ResourceRequirements{ + Profile: "Test", + }}, + }, + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + //GIVEN + + //WHEN + err := k8sClient.Create(ctx, tc.fn) + //THEN + if tc.expectedErr { + require.Error(t, err) + errStatus, ok := err.(*k8serrors.StatusError) + require.True(t, ok) + causes := errStatus.Status().Details.Causes + require.Len(t, causes, 1) + cause := causes[0] + assert.Equal(t, metav1.CauseTypeFieldValueInvalid, cause.Type) + assert.Equal(t, tc.fieldPath, cause.Field) + assert.Contains(t, cause.Message, tc.expectedErrMsg) + } else { + require.NoError(t, err) + require.NoError(t, k8sClient.Delete(ctx, tc.fn)) + } + }) + } +} diff --git a/config/serverless/templates/crds.yaml b/config/serverless/templates/crds.yaml index b5fb5a81e..38cc5d191 100644 --- a/config/serverless/templates/crds.yaml +++ b/config/serverless/templates/crds.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.2 + controller-gen.kubebuilder.io/version: v0.9.2 creationTimestamp: null name: functions.serverless.kyma-project.io spec: @@ -110,6 +110,7 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic fieldRef: description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, @@ -127,6 +128,7 @@ spec: required: - fieldPath type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, @@ -151,6 +153,7 @@ spec: required: - resource type: object + x-kubernetes-map-type: atomic secretKeyRef: description: Selects a key of a secret in the pod's namespace properties: @@ -169,6 +172,7 @@ spec: required: - key type: object + x-kubernetes-map-type: atomic type: object required: - name @@ -232,6 +236,10 @@ spec: type: object type: object type: object + x-kubernetes-validations: + - message: Use profile or resources + rule: has(self.profile) && !has(self.resources) || !has(self.profile) + && has(self.resources) function: description: Specifies resources requested by the Function's Pod. properties: @@ -269,6 +277,10 @@ spec: type: object type: object type: object + x-kubernetes-validations: + - message: Use profile or resources + rule: has(self.profile) && !has(self.resources) || !has(self.profile) + && has(self.resources) type: object runtime: description: Specifies the runtime of the Function. The available @@ -478,9 +490,3 @@ spec: specReplicasPath: .spec.replicas statusReplicasPath: .status.replicas status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/serverless/values.yaml b/config/serverless/values.yaml index 1f517b599..3b673c683 100644 --- a/config/serverless/values.yaml +++ b/config/serverless/values.yaml @@ -82,19 +82,19 @@ global: directory: "prod" function_controller: name: "function-controller" - version: "PR-356" + version: "PR-338" directory: "dev" function_webhook: name: "function-webhook" - version: "PR-356" + version: "PR-338" directory: "dev" function_build_init: name: "function-build-init" - version: "PR-356" + version: "PR-338" directory: "dev" function_registry_gc: name: "function-registry-gc" - version: "PR-356" + version: "PR-338" directory: "dev" function_runtime_nodejs16: name: "function-runtime-nodejs16"