diff --git a/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml b/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml index 5211aab6..f0e6dfd2 100644 --- a/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml +++ b/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml @@ -215,6 +215,7 @@ spec: defaultCAPackageVersion field of the Bundle's status field. type: boolean type: object + minItems: 1 type: array target: description: Target is the target location in all namespaces to sync source data to. @@ -230,6 +231,7 @@ spec: properties: key: description: Key is the key of the entry in the object's `data` field to be used. + minLength: 1 type: string password: default: changeit @@ -247,6 +249,7 @@ spec: properties: key: description: Key is the key of the entry in the object's `data` field to be used. + minLength: 1 type: string password: default: "" @@ -264,6 +267,7 @@ spec: properties: key: description: Key is the key of the entry in the object's `data` field to be used. + minLength: 1 type: string required: - key @@ -289,11 +293,15 @@ spec: properties: key: description: Key is the key of the entry in the object's `data` field to be used. + minLength: 1 type: string required: - key type: object type: object + x-kubernetes-validations: + - message: must define at least one target configMap/secret + rule: '[has(self.configMap), has(self.secret)].exists(x,x)' required: - sources - target diff --git a/pkg/apis/trust/v1alpha1/types_bundle.go b/pkg/apis/trust/v1alpha1/types_bundle.go index 014839ad..657a897d 100644 --- a/pkg/apis/trust/v1alpha1/types_bundle.go +++ b/pkg/apis/trust/v1alpha1/types_bundle.go @@ -59,6 +59,7 @@ type BundleList struct { // BundleSpec defines the desired state of a Bundle. type BundleSpec struct { // Sources is a set of references to data whose data will sync to the target. + // +kubebuilder:validation:MinItems=1 Sources []BundleSource `json:"sources"` // Target is the target location in all namespaces to sync source data to. @@ -96,6 +97,7 @@ type BundleSource struct { // BundleTarget is the target resource that the Bundle will sync all source // data to. +// +kubebuilder:validation:XValidation:rule="[has(self.configMap), has(self.secret)].exists(x,x)", message="must define at least one target configMap/secret" type BundleTarget struct { // ConfigMap is the target ConfigMap in Namespaces that all Bundle source // data will be synced to. @@ -182,6 +184,7 @@ type SourceObjectKeySelector struct { // KeySelector is a reference to a key for some map data object. type KeySelector struct { // Key is the key of the entry in the object's `data` field to be used. + // +kubebuilder:validation:MinLength=1 Key string `json:"key"` } diff --git a/test/integration/bundle/integration.go b/test/integration/bundle/integration.go index 665c368d..470a5110 100644 --- a/test/integration/bundle/integration.go +++ b/test/integration/bundle/integration.go @@ -18,6 +18,7 @@ package test import ( "os" + "path/filepath" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -38,14 +39,17 @@ var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) crdsYamlFile := os.Getenv("TRUST_MANAGER_CRDS") - Expect(crdsYamlFile).NotTo(BeEmpty(), "TRUST_MANAGER_CRDS must be set to the path of the CRDs to install") + if len(crdsYamlFile) == 0 { + crdsYamlFile = filepath.Join("..", "..", "..", "_bin", "scratch", "crds") + } env = &envtest.Environment{ UseExistingCluster: ptr.To(false), CRDDirectoryPaths: []string{ crdsYamlFile, }, - Scheme: trustapi.GlobalScheme, + ErrorIfCRDPathMissing: true, + Scheme: trustapi.GlobalScheme, } _, err := env.Start() diff --git a/test/integration/bundle/validation_test.go b/test/integration/bundle/validation_test.go new file mode 100644 index 00000000..07f0005a --- /dev/null +++ b/test/integration/bundle/validation_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package test + +import ( + "context" + "k8s.io/utils/ptr" + + "k8s.io/klog/v2/ktesting" + "sigs.k8s.io/controller-runtime/pkg/client" + + trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Bundle Validation", func() { + var ( + ctx context.Context + cl client.Client + bundle *trustapi.Bundle + ) + BeforeEach(func() { + _, ctx = ktesting.NewTestContext(GinkgoT()) + + var err error + cl, err = client.New(env.Config, client.Options{ + Scheme: trustapi.GlobalScheme, + }) + Expect(err).NotTo(HaveOccurred()) + + bundle = &trustapi.Bundle{} + bundle.GenerateName = "validation-" + bundle.Spec.Sources = []trustapi.BundleSource{{ + UseDefaultCAs: ptr.To(true), + }} + }) + + Context("Sources", func() { + It("should require at least one source", func() { + bundle.Spec.Sources = make([]trustapi.BundleSource, 0) + + expectedErr := "spec.sources: Invalid value: 0: spec.sources in body should have at least 1 items" + Expect(cl.Create(ctx, bundle)).Should(MatchError(ContainSubstring(expectedErr))) + }) + }) + + Context("Target", func() { + DescribeTable("should require at least one target configMap/secret", + func(target trustapi.BundleTarget, wantErr bool) { + bundle.Spec.Target = target + if wantErr { + expectedErr := "spec.target: Invalid value: \"object\": must define at least one target configMap/secret" + Expect(cl.Create(ctx, bundle)).Should(MatchError(ContainSubstring(expectedErr))) + } else { + Expect(cl.Create(ctx, bundle)).To(Succeed()) + } + }, + Entry("when none set", trustapi.BundleTarget{NamespaceSelector: &trustapi.NamespaceSelector{}}, true), + Entry("when configMap set", trustapi.BundleTarget{ConfigMap: &trustapi.KeySelector{Key: "ca-bundle.crt"}}, false), + Entry("when secret set", trustapi.BundleTarget{Secret: &trustapi.KeySelector{Key: "ca-bundle.crt"}}, false), + Entry("when both set", trustapi.BundleTarget{Secret: &trustapi.KeySelector{Key: "ca-bundle.crt"}}, false), + ) + }) +})