From c9238e0c42cd452710460549037d2651d01d610a Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Tue, 18 Jun 2024 13:46:16 +0300 Subject: [PATCH 01/10] kyaml and tests Signed-off-by: gazarenkov --- api/v1alpha1/backstage_types.go | 7 +++ api/v1alpha1/zz_generated.deepcopy.go | 10 ++- .../crd/bases/rhdh.redhat.com_backstages.yaml | 5 ++ go.mod | 14 +++-- go.sum | 21 ++++--- integration_tests/cr-config_test.go | 61 ++++++++++++++++++ pkg/model/deployment.go | 25 +++++++- pkg/model/deployment_test.go | 63 +++++++++++++++++++ pkg/model/runtime.go | 2 +- 9 files changed, 190 insertions(+), 18 deletions(-) diff --git a/api/v1alpha1/backstage_types.go b/api/v1alpha1/backstage_types.go index 5fd7d794..15c958b1 100644 --- a/api/v1alpha1/backstage_types.go +++ b/api/v1alpha1/backstage_types.go @@ -15,6 +15,7 @@ package v1alpha1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" ) @@ -41,6 +42,12 @@ type BackstageSpec struct { // Configuration for database access. Optional. Database *Database `json:"database,omitempty"` + + // Valid fragment of Deployment to be merged with default/raw configuration. + // Set the Deployment's metadata and|or spec fields you want to override or add. + // Optional. + // +kubebuilder:pruning:PreserveUnknownFields + Deployment *apiextensionsv1.JSON `json:"deployment,omitempty"` } type RuntimeConfig struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index da31d8b8..580006c1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,8 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -173,6 +174,11 @@ func (in *BackstageSpec) DeepCopyInto(out *BackstageSpec) { *out = new(Database) (*in).DeepCopyInto(*out) } + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = new(v1.JSON) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackstageSpec. @@ -190,7 +196,7 @@ func (in *BackstageStatus) DeepCopyInto(out *BackstageStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) + *out = make([]metav1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/config/crd/bases/rhdh.redhat.com_backstages.yaml b/config/crd/bases/rhdh.redhat.com_backstages.yaml index 2b990068..804c2999 100644 --- a/config/crd/bases/rhdh.redhat.com_backstages.yaml +++ b/config/crd/bases/rhdh.redhat.com_backstages.yaml @@ -284,6 +284,11 @@ spec: to false if using for example an external Database for Backstage. type: boolean type: object + deployment: + description: Valid fragment of Deployment to be merged with default/raw + configuration. Set the Deployment's metadata and|or spec fields + you want to override or add. Optional. + x-kubernetes-preserve-unknown-fields: true rawRuntimeConfig: description: Raw Runtime RuntimeObjects configuration. For Advanced scenarios. diff --git a/go.mod b/go.mod index d19a2966..99445613 100644 --- a/go.mod +++ b/go.mod @@ -8,24 +8,28 @@ require ( github.com/openshift/api v0.0.0-20240419172957-f39cf2ef93fd github.com/stretchr/testify v1.9.0 k8s.io/api v0.29.4 + k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.4 k8s.io/client-go v0.29.4 k8s.io/utils v0.0.0-20240310230437-4693a0247e57 sigs.k8s.io/controller-runtime v0.17.3 + sigs.k8s.io/kustomize/kyaml v0.17.1 + sigs.k8s.io/yaml v1.4.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.8.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -34,7 +38,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -47,7 +51,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect @@ -69,11 +73,9 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.29.2 // indirect k8s.io/component-base v0.29.2 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 23524f21..a45c0091 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= @@ -17,6 +18,8 @@ github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1 github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -26,8 +29,9 @@ github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -47,8 +51,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -86,14 +90,13 @@ github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8 github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= -github.com/openshift/api v0.0.0-20240418150331-2449d07abb86 h1:m/w2kof5rKYG0O+Xx19mmvDenen7LQWgRdV46/iVud0= -github.com/openshift/api v0.0.0-20240418150331-2449d07abb86/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4= github.com/openshift/api v0.0.0-20240419172957-f39cf2ef93fd h1:DztdAsKaNJjfL12LyBCxL2ELPXn4NdWE/IxLCUpL7AY= github.com/openshift/api v0.0.0-20240419172957-f39cf2ef93fd/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= @@ -102,7 +105,7 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -204,6 +207,8 @@ sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9 sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/integration_tests/cr-config_test.go b/integration_tests/cr-config_test.go index 8dd3d1c8..ef08de1e 100644 --- a/integration_tests/cr-config_test.go +++ b/integration_tests/cr-config_test.go @@ -237,6 +237,67 @@ var _ = When("create backstage with CR configured", func() { }, time.Minute, time.Second).Should(Succeed()) }) + + It("creates Backstage deployment with valid spec.deployment ", func() { + + bs2 := &bsv1alpha1.Backstage{} + + err := utils.ReadYamlFile("testdata/backstage-valid-deployment.yaml", bs2) + Expect(err).To(Not(HaveOccurred())) + + backstageName := createAndReconcileBackstage(ctx, ns, bs2.Spec, "") + + Eventually(func(g Gomega) { + By("creating Deployment ") + deploy := &appsv1.Deployment{} + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DeploymentName(backstageName)}, deploy) + g.Expect(err).To(Not(HaveOccurred())) + var bscontainer corev1.Container + for _, c := range deploy.Spec.Template.Spec.Containers { + + if c.Name == "backstage-backend" { + bscontainer = c + break + } + } + + g.Expect(bscontainer).NotTo(BeNil()) + g.Expect(bscontainer.Image).To(HaveValue(Equal("busybox"))) + + var bsvolume corev1.Volume + for _, v := range deploy.Spec.Template.Spec.Volumes { + + if v.Name == "dynamic-plugins-root" { + bsvolume = v + break + } + } + + g.Expect(bsvolume).NotTo(BeNil()) + g.Expect(bsvolume.Ephemeral).NotTo(BeNil()) + g.Expect(*bsvolume.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName).To(Equal("special")) + + }, 10*time.Second, time.Second).Should(Succeed()) + + }) + + It("fails Backstage deployment with invalid spec.deployment ", func() { + + bs2 := &bsv1alpha1.Backstage{} + bs2.Namespace = ns + + err := utils.ReadYamlFile("testdata/backstage-invalid-deployment.yaml", bs2) + Expect(err).To(Not(HaveOccurred())) + + Eventually(func() error { + return k8sClient.Create(ctx, bs2) + }, time.Minute, time.Second).Should(Succeed()) + + //Expect(err).To(Not(HaveOccurred())) + + //backstageName := createAndReconcileBackstage(ctx, ns, bs2.Spec, "") + + }) }) // Duplicated files in different CMs diff --git a/pkg/model/deployment.go b/pkg/model/deployment.go index 39ef9d98..21ce2554 100644 --- a/pkg/model/deployment.go +++ b/pkg/model/deployment.go @@ -18,6 +18,11 @@ import ( "fmt" "os" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + "sigs.k8s.io/kustomize/kyaml/yaml/merge2" + + "sigs.k8s.io/yaml" + "k8s.io/utils/ptr" corev1 "k8s.io/api/core/v1" @@ -70,7 +75,7 @@ func (b *BackstageDeployment) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *BackstageDeployment) addToModel(model *BackstageModel, _ bsv1alpha1.Backstage) (bool, error) { +func (b *BackstageDeployment) addToModel(model *BackstageModel, backstage bsv1alpha1.Backstage) (bool, error) { if b.deployment == nil { return false, fmt.Errorf("Backstage Deployment is not initialized, make sure there is deployment.yaml in default or raw configuration") } @@ -80,6 +85,24 @@ func (b *BackstageDeployment) addToModel(model *BackstageModel, _ bsv1alpha1.Bac } b.deployment.Spec.Template.ObjectMeta.Annotations[ExtConfigHashAnnotation] = model.ExternalConfig.GetHash() + if conf := backstage.Spec.Deployment; conf != nil { + + deplStr, err := yaml.Marshal(b.deployment) + if err != nil { + return false, fmt.Errorf("Can not marshal deployment object: %s", err) + } + + merged, err := merge2.MergeStrings(string(conf.Raw), string(deplStr), false, kyaml.MergeOptions{}) + if err != nil { + return false, fmt.Errorf("Can not merge spec.deployment: %s", err) + } + + err = yaml.Unmarshal([]byte(merged), b.deployment) + if err != nil { + return false, fmt.Errorf("Can not unmarshal merged deployment: %s", err) + } + } + model.backstageDeployment = b model.setRuntimeObject(b) diff --git a/pkg/model/deployment_test.go b/pkg/model/deployment_test.go index 261ceb86..bec1af80 100644 --- a/pkg/model/deployment_test.go +++ b/pkg/model/deployment_test.go @@ -18,6 +18,8 @@ import ( "context" "testing" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/utils/ptr" bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" @@ -104,3 +106,64 @@ func TestSpecImagePullSecrets(t *testing.T) { assert.Equal(t, 0, len(model.backstageDeployment.deployment.Spec.Template.Spec.ImagePullSecrets)) } + +func TestMergeFromSpecDeployment(t *testing.T) { + bs := *deploymentTestBackstage.DeepCopy() + bs.Spec.Deployment = &apiextensionsv1.JSON{ + Raw: []byte(` +metadata: + labels: + mylabel: java +spec: + template: + metadata: + labels: + pod: backstage + spec: + containers: + - name: sidecar + image: my-image:1.0.0 + - name: backstage-backend + resources: + requests: + cpu: 251m + memory: 257Mi + volumes: + - ephemeral: + volumeClaimTemplate: + spec: + storageClassName: "special" + name: dynamic-plugins-root + - emptyDir: + name: my-vol +`), + } + + testObj := createBackstageTest(bs).withDefaultConfig(true). + addToDefaultConfig("deployment.yaml", "janus-deployment.yaml") + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, true, true, testObj.scheme) + assert.NoError(t, err) + + // label added + assert.Equal(t, "java", model.backstageDeployment.deployment.Labels["mylabel"]) + assert.Equal(t, "backstage", model.backstageDeployment.deployment.Spec.Template.Labels["pod"]) + + // sidecar added + assert.Equal(t, 2, len(model.backstageDeployment.deployment.Spec.Template.Spec.Containers)) + assert.Equal(t, "sidecar", model.backstageDeployment.deployment.Spec.Template.Spec.Containers[1].Name) + assert.Equal(t, "my-image:1.0.0", model.backstageDeployment.deployment.Spec.Template.Spec.Containers[1].Image) + + // backstage container resources updated + assert.Equal(t, "backstage-backend", model.backstageDeployment.container().Name) + assert.Equal(t, "257Mi", model.backstageDeployment.container().Resources.Requests.Memory().String()) + + // volumes + // dynamic-plugins-root, dynamic-plugins-npmrc, my-vol + assert.Equal(t, 3, len(model.backstageDeployment.deployment.Spec.Template.Spec.Volumes)) + assert.Equal(t, "dynamic-plugins-root", model.backstageDeployment.deployment.Spec.Template.Spec.Volumes[0].Name) + // overrides StorageClassName + assert.Equal(t, "special", *model.backstageDeployment.deployment.Spec.Template.Spec.Volumes[0].Ephemeral.VolumeClaimTemplate.Spec.StorageClassName) + // adds new volume + assert.Equal(t, "my-vol", model.backstageDeployment.deployment.Spec.Template.Spec.Volumes[2].Name) +} diff --git a/pkg/model/runtime.go b/pkg/model/runtime.go index 82f3f3db..0f30108f 100644 --- a/pkg/model/runtime.go +++ b/pkg/model/runtime.go @@ -146,7 +146,7 @@ func InitObjects(ctx context.Context, backstage bsv1alpha1.Backstage, externalCo // apply spec and add the object to the model and list if added, err := backstageObject.addToModel(model, backstage); err != nil { - return nil, fmt.Errorf("failed to initialize %s reason: %s", backstageObject, err) + return nil, fmt.Errorf("failed to initialize backstage, reason: %s", err) } else if added { setMetaInfo(backstageObject, backstage, ownsRuntime, scheme) } From fcdc6c223400cb787b0acedb4685a686bffee734 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Wed, 19 Jun 2024 10:43:02 +0300 Subject: [PATCH 02/10] added v1alpha2 Signed-off-by: gazarenkov --- Makefile | 2 +- PROJECT | 4 +- api/v1alpha1/backstage_types.go | 7 - api/v1alpha1/zz_generated.deepcopy.go | 10 +- api/v1alpha2/backstage_types.go | 302 ++++++++++++++ api/v1alpha2/groupversion_info.go | 36 ++ api/v1alpha2/zz_generated.deepcopy.go | 374 ++++++++++++++++++ ...kstage-operator.clusterserviceversion.yaml | 16 +- .../manifests/rhdh.redhat.com_backstages.yaml | 7 +- .../crd/bases/rhdh.redhat.com_backstages.yaml | 362 +++++++++++++++++ config/manager/kustomization.yaml | 2 +- ...kstage-operator.clusterserviceversion.yaml | 13 +- ...ackstage.yaml => _v1alpha2_backstage.yaml} | 2 +- config/samples/kustomization.yaml | 2 +- controllers/backstage_controller.go | 2 +- controllers/preprocessor_test.go | 12 +- controllers/spec_preprocessor.go | 2 +- examples/bs-existing-secret.yaml | 2 +- examples/bs-route-disabled.yaml | 2 +- examples/bs-route.yaml | 2 +- examples/bs1.yaml | 2 +- examples/rhdh-cr-with-app-configs.yaml | 2 +- examples/rhdh-cr.yaml | 2 +- examples/showcase-cr.yaml | 2 +- integration_tests/config-refresh_test.go | 14 +- integration_tests/cr-config_test.go | 42 +- integration_tests/db_test.go | 16 +- integration_tests/default-config_test.go | 10 +- integration_tests/rhdh-config_test.go | 4 +- integration_tests/route_test.go | 10 +- integration_tests/suite_test.go | 12 +- main.go | 2 +- pkg/model/appconfig.go | 8 +- pkg/model/appconfig_test.go | 20 +- pkg/model/configmapenvs.go | 8 +- pkg/model/configmapenvs_test.go | 20 +- pkg/model/configmapfiles.go | 8 +- pkg/model/configmapfiles_test.go | 18 +- pkg/model/db-secret.go | 6 +- pkg/model/db-secret_test.go | 8 +- pkg/model/db-service.go | 6 +- pkg/model/db-statefulset.go | 6 +- pkg/model/db-statefulset_test.go | 10 +- pkg/model/deployment.go | 10 +- pkg/model/deployment_test.go | 10 +- pkg/model/dynamic-plugins.go | 8 +- pkg/model/dynamic-plugins_test.go | 10 +- pkg/model/interfaces.go | 6 +- pkg/model/model_tests.go | 10 +- pkg/model/route.go | 8 +- pkg/model/route_test.go | 38 +- pkg/model/runtime.go | 6 +- pkg/model/runtime_test.go | 22 +- pkg/model/secretenvs.go | 8 +- pkg/model/secretfiles.go | 8 +- pkg/model/secretfiles_test.go | 18 +- pkg/model/service.go | 6 +- 57 files changed, 1316 insertions(+), 249 deletions(-) create mode 100644 api/v1alpha2/backstage_types.go create mode 100644 api/v1alpha2/groupversion_info.go create mode 100644 api/v1alpha2/zz_generated.deepcopy.go rename config/samples/{_v1alpha1_backstage.yaml => _v1alpha2_backstage.yaml} (90%) diff --git a/Makefile b/Makefile index c8bfbebe..355d0195 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 0.2.0 +VERSION ?= 0.3.0 # Using docker or podman to build and push images CONTAINER_ENGINE ?= docker diff --git a/PROJECT b/PROJECT index b119bbc8..db3c7202 100644 --- a/PROJECT +++ b/PROJECT @@ -13,6 +13,6 @@ resources: controller: true domain: rhdh.redhat.com kind: Backstage - path: redhat-developer/red-hat-developer-hub-operator/api/v1alpha1 - version: v1alpha1 + path: redhat-developer/red-hat-developer-hub-operator/api/v1alpha2 + version: v1alpha2 version: "3" diff --git a/api/v1alpha1/backstage_types.go b/api/v1alpha1/backstage_types.go index 15c958b1..5fd7d794 100644 --- a/api/v1alpha1/backstage_types.go +++ b/api/v1alpha1/backstage_types.go @@ -15,7 +15,6 @@ package v1alpha1 import ( - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" ) @@ -42,12 +41,6 @@ type BackstageSpec struct { // Configuration for database access. Optional. Database *Database `json:"database,omitempty"` - - // Valid fragment of Deployment to be merged with default/raw configuration. - // Set the Deployment's metadata and|or spec fields you want to override or add. - // Optional. - // +kubebuilder:pruning:PreserveUnknownFields - Deployment *apiextensionsv1.JSON `json:"deployment,omitempty"` } type RuntimeConfig struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 580006c1..da31d8b8 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -22,8 +22,7 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -174,11 +173,6 @@ func (in *BackstageSpec) DeepCopyInto(out *BackstageSpec) { *out = new(Database) (*in).DeepCopyInto(*out) } - if in.Deployment != nil { - in, out := &in.Deployment, &out.Deployment - *out = new(v1.JSON) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackstageSpec. @@ -196,7 +190,7 @@ func (in *BackstageStatus) DeepCopyInto(out *BackstageStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) + *out = make([]v1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/api/v1alpha2/backstage_types.go b/api/v1alpha2/backstage_types.go new file mode 100644 index 00000000..6c916df6 --- /dev/null +++ b/api/v1alpha2/backstage_types.go @@ -0,0 +1,302 @@ +// +// Copyright (c) 2023 Red Hat, Inc. +// 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 v1alpha2 + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +type BackstageConditionReason string + +type BackstageConditionType string + +const ( + BackstageConditionTypeDeployed BackstageConditionType = "Deployed" + + BackstageConditionReasonDeployed BackstageConditionReason = "Deployed" + BackstageConditionReasonFailed BackstageConditionReason = "DeployFailed" + BackstageConditionReasonInProgress BackstageConditionReason = "DeployInProgress" +) + +// BackstageSpec defines the desired state of Backstage +type BackstageSpec struct { + // Configuration for Backstage. Optional. + Application *Application `json:"application,omitempty"` + + // Raw Runtime RuntimeObjects configuration. For Advanced scenarios. + RawRuntimeConfig *RuntimeConfig `json:"rawRuntimeConfig,omitempty"` + + // Configuration for database access. Optional. + Database *Database `json:"database,omitempty"` + + // Valid fragment of Deployment to be merged with default/raw configuration. + // Set the Deployment's metadata and|or spec fields you want to override or add. + // Optional. + // +kubebuilder:pruning:PreserveUnknownFields + Deployment *apiextensionsv1.JSON `json:"deployment,omitempty"` +} + +type RuntimeConfig struct { + // Name of ConfigMap containing Backstage runtime objects configuration + BackstageConfigName string `json:"backstageConfig,omitempty"` + // Name of ConfigMap containing LocalDb (PostgreSQL) runtime objects configuration + LocalDbConfigName string `json:"localDbConfig,omitempty"` +} + +type Database struct { + // Control the creation of a local PostgreSQL DB. Set to false if using for example an external Database for Backstage. + // +optional + //+kubebuilder:default=true + EnableLocalDb *bool `json:"enableLocalDb,omitempty"` + + // Name of the secret for database authentication. Optional. + // For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist. + // The secret shall include information used for the database access. + // An example for PostgreSQL DB access: + // "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + // "POSTGRES_PORT": "5432" + // "POSTGRES_USER": "postgres" + // "POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" + // "POSTGRES_HOST": "backstage-psql-bs1" # For local database, set to "backstage-psql-". + AuthSecretName string `json:"authSecretName,omitempty"` +} + +type Application struct { + // References to existing app-configs ConfigMap objects, that will be mounted as files in the specified mount path. + // Each element can be a reference to any ConfigMap or Secret, + // and will be mounted inside the main application container under a specified mount directory. + // Additionally, each file will be passed as a `--config /mount/path/to/configmap/key` to the + // main container args in the order of the entries defined in the AppConfigs list. + // But bear in mind that for a single ConfigMap element containing several filenames, + // the order in which those files will be appended to the main container args cannot be guaranteed. + // So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap per app-config file. + // +optional + AppConfig *AppConfig `json:"appConfig,omitempty"` + + // Reference to an existing ConfigMap for Dynamic Plugins. + // A new one will be generated with the default config if not set. + // The ConfigMap object must have an existing key named: 'dynamic-plugins.yaml'. + // +optional + DynamicPluginsConfigMapName string `json:"dynamicPluginsConfigMapName,omitempty"` + + // References to existing Config objects to use as extra config files. + // They will be mounted as files in the specified mount path. + // Each element can be a reference to any ConfigMap or Secret. + // +optional + ExtraFiles *ExtraFiles `json:"extraFiles,omitempty"` + + // Extra environment variables + // +optional + ExtraEnvs *ExtraEnvs `json:"extraEnvs,omitempty"` + + // Number of desired replicas to set in the Backstage Deployment. + // Defaults to 1. + // +optional + //+kubebuilder:default=1 + Replicas *int32 `json:"replicas,omitempty"` + + // Custom image to use in all containers (including Init Containers). + // It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance + // +optional + Image *string `json:"image,omitempty"` + + // Image Pull Secrets to use in all containers (including Init Containers) + // +optional + ImagePullSecrets []string `json:"imagePullSecrets,omitempty"` + + // Route configuration. Used for OpenShift only. + Route *Route `json:"route,omitempty"` +} + +type AppConfig struct { + // Mount path for all app-config files listed in the ConfigMapRefs field + // +optional + // +kubebuilder:default=/opt/app-root/src + MountPath string `json:"mountPath,omitempty"` + + // List of ConfigMaps storing the app-config files. Will be mounted as files under the MountPath specified. + // For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + // Otherwise, only the specified key will be mounted as a file. + // Bear in mind not to put sensitive data in those ConfigMaps. Instead, your app-config content can reference + // environment variables (which you can set with the ExtraEnvs field) and/or include extra files (see the ExtraFiles field). + // More details on https://backstage.io/docs/conf/writing/. + // +optional + ConfigMaps []ObjectKeyRef `json:"configMaps,omitempty"` +} + +type ExtraFiles struct { + // Mount path for all extra configuration files listed in the Items field + // +optional + // +kubebuilder:default=/opt/app-root/src + MountPath string `json:"mountPath,omitempty"` + + // List of references to ConfigMaps objects mounted as extra files under the MountPath specified. + // For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + // Otherwise, only the specified key will be mounted as a file. + // +optional + ConfigMaps []ObjectKeyRef `json:"configMaps,omitempty"` + + // List of references to Secrets objects mounted as extra files under the MountPath specified. + // For each item in this array, a key must be specified that will be mounted as a file. + // +optional + Secrets []ObjectKeyRef `json:"secrets,omitempty"` +} + +type ExtraEnvs struct { + // List of references to ConfigMaps objects to inject as additional environment variables. + // For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be injected as additional environment variables. + // Otherwise, only the specified key will be injected as an additional environment variable. + // +optional + ConfigMaps []ObjectKeyRef `json:"configMaps,omitempty"` + + // List of references to Secrets objects to inject as additional environment variables. + // For each item in this array, if a key is not specified, it means that all keys in the Secret will be injected as additional environment variables. + // Otherwise, only the specified key will be injected as environment variable. + // +optional + Secrets []ObjectKeyRef `json:"secrets,omitempty"` + + // List of name and value pairs to add as environment variables. + // +optional + Envs []Env `json:"envs,omitempty"` +} + +type ObjectKeyRef struct { + // Name of the object + // We support only ConfigMaps and Secrets. + //+kubebuilder:validation:Required + Name string `json:"name"` + + // Key in the object + // +optional + Key string `json:"key,omitempty"` +} + +type Env struct { + // Name of the environment variable + //+kubebuilder:validation:Required + Name string `json:"name"` + + // Value of the environment variable + //+kubebuilder:validation:Required + Value string `json:"value"` +} + +// BackstageStatus defines the observed state of Backstage +type BackstageStatus struct { + // Conditions is the list of conditions describing the state of the runtime + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:storageversion + +// Backstage is the Schema for the backstages API +type Backstage struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackstageSpec `json:"spec,omitempty"` + Status BackstageStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BackstageList contains a list of Backstage +type BackstageList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Backstage `json:"items"` +} + +// Route specifies configuration parameters for OpenShift Route for Backstage. +// Only a secured edge route is supported for Backstage. +type Route struct { + // Control the creation of a Route on OpenShift. + // +optional + //+kubebuilder:default=true + Enabled *bool `json:"enabled,omitempty"` + + // Host is an alias/DNS that points to the service. Optional. + // Ignored if Enabled is false. + // If not specified a route name will typically be automatically + // chosen. Must follow DNS952 subdomain conventions. + // +optional + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$` + Host string `json:"host,omitempty" protobuf:"bytes,1,opt,name=host"` + + // Subdomain is a DNS subdomain that is requested within the ingress controller's + // domain (as a subdomain). + // Ignored if Enabled is false. + // Example: subdomain `frontend` automatically receives the router subdomain + // `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + // +optional + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$` + Subdomain string `json:"subdomain,omitempty"` + + // The tls field provides the ability to configure certificates for the route. + // Ignored if Enabled is false. + // +optional + TLS *TLS `json:"tls,omitempty"` +} + +type TLS struct { + // certificate provides certificate contents. This should be a single serving certificate, not a certificate + // chain. Do not include a CA certificate. + Certificate string `json:"certificate,omitempty"` + + // ExternalCertificateSecretName provides certificate contents as a secret reference. + // This should be a single serving certificate, not a certificate + // chain. Do not include a CA certificate. The secret referenced should + // be present in the same namespace as that of the Route. + // Forbidden when `certificate` is set. + // +optional + ExternalCertificateSecretName string `json:"externalCertificateSecretName,omitempty"` + + // key provides key file contents + Key string `json:"key,omitempty"` + + // caCertificate provides the cert authority certificate contents + CACertificate string `json:"caCertificate,omitempty"` +} + +func init() { + SchemeBuilder.Register(&Backstage{}, &BackstageList{}) +} + +// IsLocalDbEnabled returns true if Local database is configured and enabled +func (s *BackstageSpec) IsLocalDbEnabled() bool { + if s.Database == nil { + return true + } + return ptr.Deref(s.Database.EnableLocalDb, true) +} + +// IsRouteEnabled returns value of Application.Route.Enabled if defined or true by default +func (s *BackstageSpec) IsRouteEnabled() bool { + if s.Application != nil && s.Application.Route != nil { + return ptr.Deref(s.Application.Route.Enabled, true) + } + return true +} + +func (s *BackstageSpec) IsAuthSecretSpecified() bool { + return s.Database != nil && s.Database.AuthSecretName != "" +} diff --git a/api/v1alpha2/groupversion_info.go b/api/v1alpha2/groupversion_info.go new file mode 100644 index 00000000..fb8c4342 --- /dev/null +++ b/api/v1alpha2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 Red Hat Inc.. + +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 v1alpha2 contains API Schema definitions for the v1alpha2 API group +// +kubebuilder:object:generate=true +// +groupName=rhdh.redhat.com +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "rhdh.redhat.com", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 00000000..1fb9aa5e --- /dev/null +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,374 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2023 Red Hat Inc.. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppConfig) DeepCopyInto(out *AppConfig) { + *out = *in + if in.ConfigMaps != nil { + in, out := &in.ConfigMaps, &out.ConfigMaps + *out = make([]ObjectKeyRef, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppConfig. +func (in *AppConfig) DeepCopy() *AppConfig { + if in == nil { + return nil + } + out := new(AppConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Application) DeepCopyInto(out *Application) { + *out = *in + if in.AppConfig != nil { + in, out := &in.AppConfig, &out.AppConfig + *out = new(AppConfig) + (*in).DeepCopyInto(*out) + } + if in.ExtraFiles != nil { + in, out := &in.ExtraFiles, &out.ExtraFiles + *out = new(ExtraFiles) + (*in).DeepCopyInto(*out) + } + if in.ExtraEnvs != nil { + in, out := &in.ExtraEnvs, &out.ExtraEnvs + *out = new(ExtraEnvs) + (*in).DeepCopyInto(*out) + } + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(string) + **out = **in + } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Route != nil { + in, out := &in.Route, &out.Route + *out = new(Route) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application. +func (in *Application) DeepCopy() *Application { + if in == nil { + return nil + } + out := new(Application) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Backstage) DeepCopyInto(out *Backstage) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backstage. +func (in *Backstage) DeepCopy() *Backstage { + if in == nil { + return nil + } + out := new(Backstage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Backstage) 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 *BackstageList) DeepCopyInto(out *BackstageList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Backstage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackstageList. +func (in *BackstageList) DeepCopy() *BackstageList { + if in == nil { + return nil + } + out := new(BackstageList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackstageList) 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 *BackstageSpec) DeepCopyInto(out *BackstageSpec) { + *out = *in + if in.Application != nil { + in, out := &in.Application, &out.Application + *out = new(Application) + (*in).DeepCopyInto(*out) + } + if in.RawRuntimeConfig != nil { + in, out := &in.RawRuntimeConfig, &out.RawRuntimeConfig + *out = new(RuntimeConfig) + **out = **in + } + if in.Database != nil { + in, out := &in.Database, &out.Database + *out = new(Database) + (*in).DeepCopyInto(*out) + } + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = new(v1.JSON) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackstageSpec. +func (in *BackstageSpec) DeepCopy() *BackstageSpec { + if in == nil { + return nil + } + out := new(BackstageSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackstageStatus) DeepCopyInto(out *BackstageStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackstageStatus. +func (in *BackstageStatus) DeepCopy() *BackstageStatus { + if in == nil { + return nil + } + out := new(BackstageStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Database) DeepCopyInto(out *Database) { + *out = *in + if in.EnableLocalDb != nil { + in, out := &in.EnableLocalDb, &out.EnableLocalDb + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Database. +func (in *Database) DeepCopy() *Database { + if in == nil { + return nil + } + out := new(Database) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Env) DeepCopyInto(out *Env) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Env. +func (in *Env) DeepCopy() *Env { + if in == nil { + return nil + } + out := new(Env) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtraEnvs) DeepCopyInto(out *ExtraEnvs) { + *out = *in + if in.ConfigMaps != nil { + in, out := &in.ConfigMaps, &out.ConfigMaps + *out = make([]ObjectKeyRef, len(*in)) + copy(*out, *in) + } + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]ObjectKeyRef, len(*in)) + copy(*out, *in) + } + if in.Envs != nil { + in, out := &in.Envs, &out.Envs + *out = make([]Env, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraEnvs. +func (in *ExtraEnvs) DeepCopy() *ExtraEnvs { + if in == nil { + return nil + } + out := new(ExtraEnvs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtraFiles) DeepCopyInto(out *ExtraFiles) { + *out = *in + if in.ConfigMaps != nil { + in, out := &in.ConfigMaps, &out.ConfigMaps + *out = make([]ObjectKeyRef, len(*in)) + copy(*out, *in) + } + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]ObjectKeyRef, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraFiles. +func (in *ExtraFiles) DeepCopy() *ExtraFiles { + if in == nil { + return nil + } + out := new(ExtraFiles) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectKeyRef) DeepCopyInto(out *ObjectKeyRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectKeyRef. +func (in *ObjectKeyRef) DeepCopy() *ObjectKeyRef { + if in == nil { + return nil + } + out := new(ObjectKeyRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route) DeepCopyInto(out *Route) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLS) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route. +func (in *Route) DeepCopy() *Route { + if in == nil { + return nil + } + out := new(Route) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuntimeConfig) DeepCopyInto(out *RuntimeConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeConfig. +func (in *RuntimeConfig) DeepCopy() *RuntimeConfig { + if in == nil { + return nil + } + out := new(RuntimeConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLS) DeepCopyInto(out *TLS) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS. +func (in *TLS) DeepCopy() *TLS { + if in == nil { + return nil + } + out := new(TLS) + in.DeepCopyInto(out) + return out +} diff --git a/bundle/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/manifests/backstage-operator.clusterserviceversion.yaml index d0ab1148..62808195 100644 --- a/bundle/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/manifests/backstage-operator.clusterserviceversion.yaml @@ -5,7 +5,7 @@ metadata: alm-examples: |- [ { - "apiVersion": "rhdh.redhat.com/v1alpha1", + "apiVersion": "rhdh.redhat.com/v1alpha2", "kind": "Backstage", "metadata": { "labels": { @@ -21,12 +21,12 @@ metadata: } ] capabilities: Seamless Upgrades - createdAt: "2024-05-21T10:47:18Z" + createdAt: "2024-06-18T18:58:00Z" operatorframework.io/suggested-namespace: backstage-system operators.operatorframework.io/builder: operator-sdk-v1.33.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 - skipRange: '>=0.0.1 <0.2.0' - name: backstage-operator.v0.2.0 + skipRange: '>=0.0.1 <0.3.0' + name: backstage-operator.v0.3.0 namespace: placeholder spec: apiservicedefinitions: {} @@ -36,7 +36,7 @@ spec: displayName: Backstage kind: Backstage name: backstages.rhdh.redhat.com - version: v1alpha1 + version: v1alpha2 description: Operator to deploy Backstage on Kubernetes displayName: Red Hat Developer Hub Operator icon: @@ -209,7 +209,7 @@ spec: value: quay.io/fedora/postgresql-15:latest - name: RELATED_IMAGE_backstage value: quay.io/janus-idp/backstage-showcase:latest - image: quay.io/janus-idp/operator:0.2.0 + image: quay.io/janus-idp/operator:0.3.0 livenessProbe: httpGet: path: /healthz @@ -302,8 +302,6 @@ spec: name: Armel Soro - email: gazarenk@redhat.com name: Gennady Azarenkov - - email: jianrzha@redhat.com - name: Jianrong Zhang maturity: alpha minKubeVersion: 1.25.0 provider: @@ -314,4 +312,4 @@ spec: name: postgresql - image: quay.io/janus-idp/backstage-showcase:latest name: backstage - version: 0.2.0 + version: 0.3.0 diff --git a/bundle/manifests/rhdh.redhat.com_backstages.yaml b/bundle/manifests/rhdh.redhat.com_backstages.yaml index ebbb9bfb..79435165 100644 --- a/bundle/manifests/rhdh.redhat.com_backstages.yaml +++ b/bundle/manifests/rhdh.redhat.com_backstages.yaml @@ -14,7 +14,7 @@ spec: singular: backstage scope: Namespaced versions: - - name: v1alpha1 + - name: v1alpha2 schema: openAPIV3Schema: description: Backstage is the Schema for the backstages API @@ -283,6 +283,11 @@ spec: to false if using for example an external Database for Backstage. type: boolean type: object + deployment: + description: Valid fragment of Deployment to be merged with default/raw + configuration. Set the Deployment's metadata and|or spec fields + you want to override or add. Optional. + x-kubernetes-preserve-unknown-fields: true rawRuntimeConfig: description: Raw Runtime RuntimeObjects configuration. For Advanced scenarios. diff --git a/config/crd/bases/rhdh.redhat.com_backstages.yaml b/config/crd/bases/rhdh.redhat.com_backstages.yaml index 804c2999..7382c3e5 100644 --- a/config/crd/bases/rhdh.redhat.com_backstages.yaml +++ b/config/crd/bases/rhdh.redhat.com_backstages.yaml @@ -284,6 +284,368 @@ spec: to false if using for example an external Database for Backstage. type: boolean type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - name: v1alpha2 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: References to existing app-configs ConfigMap objects, + that will be mounted as files in the specified mount path. Each + element can be a reference to any ConfigMap or Secret, and will + be mounted inside the main application container under a specified + mount directory. Additionally, each file will be passed as a + `--config /mount/path/to/configmap/key` to the main container + args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing + several filenames, the order in which those files will be appended + to the main container args cannot be guaranteed. So if you want + to pass multiple app-config files, it is recommended to pass + one ConfigMap per app-config file. + properties: + configMaps: + description: List of ConfigMaps storing the app-config files. + Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, + it means that all keys in the ConfigMap will be mounted + as files. Otherwise, only the specified key will be mounted + as a file. Bear in mind not to put sensitive data in those + ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs + field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: 'Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: ''dynamic-plugins.yaml''.' + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: List of references to ConfigMaps objects to inject + as additional environment variables. For each item in this + array, if a key is not specified, it means that all keys + in the ConfigMap will be injected as additional environment + variables. Otherwise, only the specified key will be injected + as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: List of references to Secrets objects to inject + as additional environment variables. For each item in this + array, if a key is not specified, it means that all keys + in the Secret will be injected as additional environment + variables. Otherwise, only the specified key will be injected + as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: References to existing Config objects to use as extra + config files. They will be mounted as files in the specified + mount path. Each element can be a reference to any ConfigMap + or Secret. + properties: + configMaps: + description: List of references to ConfigMaps objects mounted + as extra files under the MountPath specified. For each item + in this array, if a key is not specified, it means that + all keys in the ConfigMap will be mounted as files. Otherwise, + only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: List of references to Secrets objects mounted + as extra files under the MountPath specified. For each item + in this array, a key must be specified that will be mounted + as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: Custom image to use in all containers (including + Init Containers). It is your responsibility to make sure the + image is from trusted sources and has been validated for security + compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: Number of desired replicas to set in the Backstage + Deployment. Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: Host is an alias/DNS that points to the service. + Optional. Ignored if Enabled is false. If not specified + a route name will typically be automatically chosen. Must + follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: 'Subdomain is a DNS subdomain that is requested + within the ingress controller''s domain (as a subdomain). + Ignored if Enabled is false. Example: subdomain `frontend` + automatically receives the router subdomain `apps.mycluster.com` + to have a full hostname `frontend.apps.mycluster.com`.' + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: The tls field provides the ability to configure + certificates for the route. Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: ExternalCertificateSecretName provides certificate + contents as a secret reference. This should be a single + serving certificate, not a certificate chain. Do not + include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: 'Name of the secret for database authentication. + Optional. For a local database deployment (EnableLocalDb=true), + a secret will be auto generated if it does not exist. The secret + shall include information used for the database access. An example + for PostgreSQL DB access: "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres" "POSTGRESQL_ADMIN_PASSWORD": + "rl4s3Fh4ng3M4" "POSTGRES_HOST": "backstage-psql-bs1" # For + local database, set to "backstage-psql-".' + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object deployment: description: Valid fragment of Deployment to be merged with default/raw configuration. Set the Deployment's metadata and|or spec fields diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index ceec4e07..5899ce69 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,7 +5,7 @@ kind: Kustomization images: - name: controller newName: quay.io/janus-idp/operator - newTag: 0.2.0 + newTag: 0.3.0 generatorOptions: disableNameSuffixHash: true diff --git a/config/manifests/bases/backstage-operator.clusterserviceversion.yaml b/config/manifests/bases/backstage-operator.clusterserviceversion.yaml index 67c444a8..625a6fdc 100644 --- a/config/manifests/bases/backstage-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/backstage-operator.clusterserviceversion.yaml @@ -5,8 +5,8 @@ metadata: alm-examples: '[]' capabilities: Seamless Upgrades operatorframework.io/suggested-namespace: backstage-system - skipRange: '>=0.0.1 <0.2.0' - name: backstage-operator.v0.2.0 + skipRange: '>=0.0.1 <0.3.0' + name: backstage-operator.v0.3.0 namespace: placeholder spec: apiservicedefinitions: {} @@ -17,6 +17,11 @@ spec: kind: Backstage name: backstages.rhdh.redhat.com version: v1alpha1 + - description: Backstage is the Schema for the backstages API + displayName: Backstage + kind: Backstage + name: backstages.rhdh.redhat.com + version: v1alpha2 description: Operator to deploy Backstage on Kubernetes displayName: Red Hat Developer Hub Operator icon: @@ -46,11 +51,9 @@ spec: name: Armel Soro - email: gazarenk@redhat.com name: Gennady Azarenkov - - email: jianrzha@redhat.com - name: Jianrong Zhang maturity: alpha minKubeVersion: 1.25.0 provider: name: Red Hat Inc. url: https://www.redhat.com/ - version: 0.2.0 + version: 0.3.0 diff --git a/config/samples/_v1alpha1_backstage.yaml b/config/samples/_v1alpha2_backstage.yaml similarity index 90% rename from config/samples/_v1alpha1_backstage.yaml rename to config/samples/_v1alpha2_backstage.yaml index 1b6a82cf..17e804b5 100644 --- a/config/samples/_v1alpha1_backstage.yaml +++ b/config/samples/_v1alpha2_backstage.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: labels: diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index f66b0cd5..7dc86ec2 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,4 +1,4 @@ ## Append samples you want in your CSV to this file as resources ## resources: -- _v1alpha1_backstage.yaml +- _v1alpha2_backstage.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/backstage_controller.go b/controllers/backstage_controller.go index 52ad5617..ec74b22b 100644 --- a/controllers/backstage_controller.go +++ b/controllers/backstage_controller.go @@ -41,7 +41,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/model" - bs "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bs "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" diff --git a/controllers/preprocessor_test.go b/controllers/preprocessor_test.go index 51e2db3a..9b7ed275 100644 --- a/controllers/preprocessor_test.go +++ b/controllers/preprocessor_test.go @@ -17,7 +17,7 @@ package controller import ( "context" "os" - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/model" "testing" @@ -30,15 +30,15 @@ import ( func updateConfigMap(t *testing.T) BackstageReconciler { ctx := context.TODO() - bs := v1alpha1.Backstage{ + bs := v1alpha2.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs1", Namespace: "ns1", }, - Spec: v1alpha1.BackstageSpec{ - Application: &v1alpha1.Application{ - AppConfig: &v1alpha1.AppConfig{ - ConfigMaps: []v1alpha1.ObjectKeyRef{{Name: "cm1"}}, + Spec: v1alpha2.BackstageSpec{ + Application: &v1alpha2.Application{ + AppConfig: &v1alpha2.AppConfig{ + ConfigMaps: []v1alpha2.ObjectKeyRef{{Name: "cm1"}}, }, }, }, diff --git a/controllers/spec_preprocessor.go b/controllers/spec_preprocessor.go index 12b33099..fd52f545 100644 --- a/controllers/spec_preprocessor.go +++ b/controllers/spec_preprocessor.go @@ -26,7 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" - bs "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bs "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/model" diff --git a/examples/bs-existing-secret.yaml b/examples/bs-existing-secret.yaml index 7e7b4e9e..7270e9d2 100644 --- a/examples/bs-existing-secret.yaml +++ b/examples/bs-existing-secret.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: bs-existing-secret diff --git a/examples/bs-route-disabled.yaml b/examples/bs-route-disabled.yaml index dd35530c..25ecbee9 100644 --- a/examples/bs-route-disabled.yaml +++ b/examples/bs-route-disabled.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: bs-route-disabled diff --git a/examples/bs-route.yaml b/examples/bs-route.yaml index 8ec5838f..e9f03790 100644 --- a/examples/bs-route.yaml +++ b/examples/bs-route.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: bs-route diff --git a/examples/bs1.yaml b/examples/bs1.yaml index a1b7a90f..458933da 100644 --- a/examples/bs1.yaml +++ b/examples/bs1.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: bs1 diff --git a/examples/rhdh-cr-with-app-configs.yaml b/examples/rhdh-cr-with-app-configs.yaml index a9e67f1d..a7b543ec 100644 --- a/examples/rhdh-cr-with-app-configs.yaml +++ b/examples/rhdh-cr-with-app-configs.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: bs-app-config diff --git a/examples/rhdh-cr.yaml b/examples/rhdh-cr.yaml index 66502d4a..73092af7 100644 --- a/examples/rhdh-cr.yaml +++ b/examples/rhdh-cr.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: my-rhdh diff --git a/examples/showcase-cr.yaml b/examples/showcase-cr.yaml index 4d2fa6bd..c8d86446 100644 --- a/examples/showcase-cr.yaml +++ b/examples/showcase-cr.yaml @@ -1,4 +1,4 @@ -apiVersion: rhdh.redhat.com/v1alpha1 +apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage metadata: name: bs-showcase diff --git a/integration_tests/config-refresh_test.go b/integration_tests/config-refresh_test.go index d8feb801..92fed3e8 100644 --- a/integration_tests/config-refresh_test.go +++ b/integration_tests/config-refresh_test.go @@ -29,7 +29,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/model" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "k8s.io/apimachinery/pkg/types" @@ -67,16 +67,16 @@ var _ = When("create backstage with external configuration", func() { generateConfigMap(ctx, k8sClient, appConfig1, ns, map[string]string{"key11": "app:", "key12": "app:"}, nil, nil) generateSecret(ctx, k8sClient, secretEnv1, ns, map[string]string{"sec11": "val11"}, nil, nil) - bs := bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - AppConfig: &bsv1alpha1.AppConfig{ + bs := bsv1.BackstageSpec{ + Application: &bsv1.Application{ + AppConfig: &bsv1.AppConfig{ MountPath: "/my/mount/path", - ConfigMaps: []bsv1alpha1.ObjectKeyRef{ + ConfigMaps: []bsv1.ObjectKeyRef{ {Name: appConfig1}, }, }, - ExtraEnvs: &bsv1alpha1.ExtraEnvs{ - Secrets: []bsv1alpha1.ObjectKeyRef{ + ExtraEnvs: &bsv1.ExtraEnvs{ + Secrets: []bsv1.ObjectKeyRef{ {Name: secretEnv1, Key: "sec11"}, }, }, diff --git a/integration_tests/cr-config_test.go b/integration_tests/cr-config_test.go index ef08de1e..23aed217 100644 --- a/integration_tests/cr-config_test.go +++ b/integration_tests/cr-config_test.go @@ -30,7 +30,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/model" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "k8s.io/apimachinery/pkg/types" @@ -71,35 +71,35 @@ var _ = When("create backstage with CR configured", func() { secretEnv1 := generateSecret(ctx, k8sClient, "secret-env1", ns, map[string]string{"sec11": "val11", "sec12": "val12"}, nil, nil) _ = generateSecret(ctx, k8sClient, "secret-env2", ns, map[string]string{"sec21": "val21", "sec22": "val22"}, nil, nil) - bs := bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - AppConfig: &bsv1alpha1.AppConfig{ + bs := bsv1.BackstageSpec{ + Application: &bsv1.Application{ + AppConfig: &bsv1.AppConfig{ MountPath: "/my/mount/path", - ConfigMaps: []bsv1alpha1.ObjectKeyRef{ + ConfigMaps: []bsv1.ObjectKeyRef{ {Name: appConfig1}, {Name: appConfig2, Key: "key21"}, }, }, - ExtraFiles: &bsv1alpha1.ExtraFiles{ + ExtraFiles: &bsv1.ExtraFiles{ MountPath: "/my/file/path", - ConfigMaps: []bsv1alpha1.ObjectKeyRef{ + ConfigMaps: []bsv1.ObjectKeyRef{ {Name: cmFile1}, {Name: cmFile2, Key: "cm21"}, }, - Secrets: []bsv1alpha1.ObjectKeyRef{ + Secrets: []bsv1.ObjectKeyRef{ {Name: secretFile1, Key: "sec11"}, {Name: secretFile2, Key: "sec21"}, }, }, - ExtraEnvs: &bsv1alpha1.ExtraEnvs{ - ConfigMaps: []bsv1alpha1.ObjectKeyRef{ + ExtraEnvs: &bsv1.ExtraEnvs{ + ConfigMaps: []bsv1.ObjectKeyRef{ {Name: cmEnv1}, {Name: cmEnv2, Key: "cm21"}, }, - Secrets: []bsv1alpha1.ObjectKeyRef{ + Secrets: []bsv1.ObjectKeyRef{ {Name: secretEnv1, Key: "sec11"}, }, - Envs: []bsv1alpha1.Env{ + Envs: []bsv1.Env{ {Name: "env1", Value: "val1"}, }, }, @@ -166,10 +166,10 @@ var _ = When("create backstage with CR configured", func() { appConfig := generateConfigMap(ctx, k8sClient, "app-config1", ns, map[string]string{"key11": "app:", "key12": "app:"}, nil, nil) - bs := bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - AppConfig: &bsv1alpha1.AppConfig{ - ConfigMaps: []bsv1alpha1.ObjectKeyRef{ + bs := bsv1.BackstageSpec{ + Application: &bsv1.Application{ + AppConfig: &bsv1.AppConfig{ + ConfigMaps: []bsv1.ObjectKeyRef{ {Name: appConfig}, }, }, @@ -195,7 +195,7 @@ var _ = When("create backstage with CR configured", func() { It("creates default Backstage and then update CR ", func() { - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{}, "") + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{}, "") Eventually(func(g Gomega) { By("creating Deployment with replicas=1 by default") @@ -210,10 +210,10 @@ var _ = When("create backstage with CR configured", func() { By("updating Backstage") imageName := "quay.io/my-org/my-awesome-image:1.2.3" ips := []string{"some-image-pull-secret-1", "some-image-pull-secret-2"} - update := &bsv1alpha1.Backstage{} + update := &bsv1.Backstage{} err := k8sClient.Get(ctx, types.NamespacedName{Name: backstageName, Namespace: ns}, update) Expect(err).To(Not(HaveOccurred())) - update.Spec.Application = &bsv1alpha1.Application{} + update.Spec.Application = &bsv1.Application{} update.Spec.Application.Replicas = ptr.To(int32(2)) update.Spec.Application.Image = ptr.To(imageName) update.Spec.Application.ImagePullSecrets = ips @@ -240,7 +240,7 @@ var _ = When("create backstage with CR configured", func() { It("creates Backstage deployment with valid spec.deployment ", func() { - bs2 := &bsv1alpha1.Backstage{} + bs2 := &bsv1.Backstage{} err := utils.ReadYamlFile("testdata/backstage-valid-deployment.yaml", bs2) Expect(err).To(Not(HaveOccurred())) @@ -283,7 +283,7 @@ var _ = When("create backstage with CR configured", func() { It("fails Backstage deployment with invalid spec.deployment ", func() { - bs2 := &bsv1alpha1.Backstage{} + bs2 := &bsv1.Backstage{} bs2.Namespace = ns err := utils.ReadYamlFile("testdata/backstage-invalid-deployment.yaml", bs2) diff --git a/integration_tests/db_test.go b/integration_tests/db_test.go index 7e8ec69b..78582256 100644 --- a/integration_tests/db_test.go +++ b/integration_tests/db_test.go @@ -30,7 +30,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/model" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "k8s.io/apimachinery/pkg/types" @@ -55,7 +55,7 @@ var _ = When("create backstage with CR configured", func() { }) It("creates default Backstage and then update CR to not to use local DB", func() { - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{}, "") + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{}, "") Eventually(func(g Gomega) { By("creating Deployment with database.enableLocalDb=true by default") @@ -72,10 +72,10 @@ var _ = When("create backstage with CR configured", func() { }, time.Minute, time.Second).Should(Succeed()) By("updating Backstage") - update := &bsv1alpha1.Backstage{} + update := &bsv1.Backstage{} err := k8sClient.Get(ctx, types.NamespacedName{Name: backstageName, Namespace: ns}, update) Expect(err).To(Not(HaveOccurred())) - update.Spec.Database = &bsv1alpha1.Database{} + update.Spec.Database = &bsv1.Database{} update.Spec.Database.EnableLocalDb = ptr.To(false) err = k8sClient.Update(ctx, update) Expect(err).To(Not(HaveOccurred())) @@ -102,8 +102,8 @@ var _ = When("create backstage with CR configured", func() { }) It("creates Backstage with disabled local DB and secret", func() { - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), AuthSecretName: "existing-secret", }, @@ -124,8 +124,8 @@ var _ = When("create backstage with CR configured", func() { }) It("creates Backstage with disabled local DB no secret", func() { - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), }, }, "") diff --git a/integration_tests/default-config_test.go b/integration_tests/default-config_test.go index b5f3e1f7..8bb053ef 100644 --- a/integration_tests/default-config_test.go +++ b/integration_tests/default-config_test.go @@ -23,7 +23,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/model" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" corev1 "k8s.io/api/core/v1" @@ -51,7 +51,7 @@ var _ = When("create default backstage", func() { It("creates runtime objects", func() { - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{}, "") + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{}, "") Eventually(func(g Gomega) { By("creating a secret for accessing the Database") @@ -101,7 +101,7 @@ var _ = When("create default backstage", func() { By("setting Backstage status (real cluster only)") Eventually(func(g Gomega) { - bs := &bsv1alpha1.Backstage{} + bs := &bsv1.Backstage{} err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: backstageName}, bs) g.Expect(err).ShouldNot(HaveOccurred()) @@ -129,8 +129,8 @@ var _ = When("create default backstage", func() { bsRaw := generateConfigMap(ctx, k8sClient, "bsraw", ns, bsConf, nil, nil) dbRaw := generateConfigMap(ctx, k8sClient, "dbraw", ns, dbConf, nil, nil) - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{ - RawRuntimeConfig: &bsv1alpha1.RuntimeConfig{ + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{ + RawRuntimeConfig: &bsv1.RuntimeConfig{ BackstageConfigName: bsRaw, LocalDbConfigName: dbRaw, }, diff --git a/integration_tests/rhdh-config_test.go b/integration_tests/rhdh-config_test.go index bb702e46..b2c877e8 100644 --- a/integration_tests/rhdh-config_test.go +++ b/integration_tests/rhdh-config_test.go @@ -24,7 +24,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/model" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -39,7 +39,7 @@ var _ = When("create default backstage", func() { ctx := context.Background() ns := createNamespace(ctx) - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{}, "") + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{}, "") Eventually(func(g Gomega) { deploy := &appsv1.Deployment{} diff --git a/integration_tests/route_test.go b/integration_tests/route_test.go index 404c0e5d..f9f59011 100644 --- a/integration_tests/route_test.go +++ b/integration_tests/route_test.go @@ -23,7 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "k8s.io/apimachinery/pkg/types" @@ -53,9 +53,9 @@ var _ = When("create default backstage", func() { Skip("Skipped for non-Openshift cluster") } - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - Route: &bsv1alpha1.Route{ + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{ + Application: &bsv1.Application{ + Route: &bsv1.Route{ //Host: "localhost", //Enabled: ptr.To(true), Subdomain: "test", @@ -64,7 +64,7 @@ var _ = When("create default backstage", func() { }, "") Eventually(func() error { - found := &bsv1alpha1.Backstage{} + found := &bsv1.Backstage{} return k8sClient.Get(ctx, types.NamespacedName{Name: backstageName, Namespace: ns}, found) }, time.Minute, time.Second).Should(Succeed()) diff --git a/integration_tests/suite_test.go b/integration_tests/suite_test.go index 8d708b37..a7f6e725 100644 --- a/integration_tests/suite_test.go +++ b/integration_tests/suite_test.go @@ -50,7 +50,7 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/util/rand" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -116,7 +116,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - err = bsv1alpha1.AddToScheme(scheme.Scheme) + err = bsv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme @@ -151,11 +151,11 @@ func generateRandName(name string) string { return "test-backstage-" + randString(5) } -func createBackstage(ctx context.Context, spec bsv1alpha1.BackstageSpec, ns string, name string) string { +func createBackstage(ctx context.Context, spec bsv1.BackstageSpec, ns string, name string) string { backstageName := generateRandName(name) - err := k8sClient.Create(ctx, &bsv1alpha1.Backstage{ + err := k8sClient.Create(ctx, &bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: backstageName, Namespace: ns, @@ -166,11 +166,11 @@ func createBackstage(ctx context.Context, spec bsv1alpha1.BackstageSpec, ns stri return backstageName } -func createAndReconcileBackstage(ctx context.Context, ns string, spec bsv1alpha1.BackstageSpec, name string) string { +func createAndReconcileBackstage(ctx context.Context, ns string, spec bsv1.BackstageSpec, name string) string { backstageName := createBackstage(ctx, spec, ns, name) Eventually(func() error { - found := &bsv1alpha1.Backstage{} + found := &bsv1.Backstage{} return k8sClient.Get(ctx, types.NamespacedName{Name: backstageName, Namespace: ns}, found) }, time.Minute, time.Second).Should(Succeed()) diff --git a/main.go b/main.go index 7a954211..abfc1831 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" - backstageiov1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + backstageiov1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" controller "redhat-developer/red-hat-developer-hub-operator/controllers" openshift "github.com/openshift/api/route/v1" diff --git a/pkg/model/appconfig.go b/pkg/model/appconfig.go index 93eaa0e9..9b544e12 100644 --- a/pkg/model/appconfig.go +++ b/pkg/model/appconfig.go @@ -19,7 +19,7 @@ import ( appsv1 "k8s.io/api/apps/v1" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -49,7 +49,7 @@ func AppConfigDefaultName(backstageName string) string { return utils.GenerateRuntimeObjectName(backstageName, "backstage-appconfig") } -func addAppConfigs(spec bsv1alpha1.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) { +func addAppConfigs(spec bsv1.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) { if spec.Application == nil || spec.Application.AppConfig == nil || spec.Application.AppConfig.ConfigMaps == nil { return @@ -89,7 +89,7 @@ func (b *AppConfig) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *AppConfig) addToModel(model *BackstageModel, _ bsv1alpha1.Backstage) (bool, error) { +func (b *AppConfig) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, error) { if b.ConfigMap != nil { model.setRuntimeObject(b) return true, nil @@ -98,7 +98,7 @@ func (b *AppConfig) addToModel(model *BackstageModel, _ bsv1alpha1.Backstage) (b } // implementation of RuntimeObject interface -func (b *AppConfig) validate(_ *BackstageModel, _ bsv1alpha1.Backstage) error { +func (b *AppConfig) validate(_ *BackstageModel, _ bsv1.Backstage) error { return nil } diff --git a/pkg/model/appconfig_test.go b/pkg/model/appconfig_test.go index 835a9931..da0b3d7e 100644 --- a/pkg/model/appconfig_test.go +++ b/pkg/model/appconfig_test.go @@ -19,7 +19,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/utils" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" corev1 "k8s.io/api/core/v1" @@ -54,16 +54,16 @@ var ( Data: map[string]string{"conf31.yaml": "", "conf32.yaml": ""}, } - appConfigTestBackstage = bsv1alpha1.Backstage{ + appConfigTestBackstage = bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - AppConfig: &bsv1alpha1.AppConfig{ + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + AppConfig: &bsv1.AppConfig{ MountPath: "/my/path", - ConfigMaps: []bsv1alpha1.ObjectKeyRef{}, + ConfigMaps: []bsv1.ObjectKeyRef{}, }, }, }, @@ -97,11 +97,11 @@ func TestSpecifiedAppConfig(t *testing.T) { bs := *appConfigTestBackstage.DeepCopy() bs.Spec.Application.AppConfig.ConfigMaps = append(bs.Spec.Application.AppConfig.ConfigMaps, - bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm.Name}) + bsv1.ObjectKeyRef{Name: appConfigTestCm.Name}) bs.Spec.Application.AppConfig.ConfigMaps = append(bs.Spec.Application.AppConfig.ConfigMaps, - bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm2.Name}) + bsv1.ObjectKeyRef{Name: appConfigTestCm2.Name}) bs.Spec.Application.AppConfig.ConfigMaps = append(bs.Spec.Application.AppConfig.ConfigMaps, - bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm3.Name, Key: "conf31.yaml"}) + bsv1.ObjectKeyRef{Name: appConfigTestCm3.Name, Key: "conf31.yaml"}) testObj := createBackstageTest(bs).withDefaultConfig(true) testObj.externalConfig.AppConfigs = map[string]corev1.ConfigMap{appConfigTestCm.Name: appConfigTestCm, appConfigTestCm2.Name: appConfigTestCm2, @@ -127,7 +127,7 @@ func TestDefaultAndSpecifiedAppConfig(t *testing.T) { bs := *appConfigTestBackstage.DeepCopy() cms := &bs.Spec.Application.AppConfig.ConfigMaps - *cms = append(*cms, bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm.Name}) + *cms = append(*cms, bsv1.ObjectKeyRef{Name: appConfigTestCm.Name}) testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("app-config.yaml", "raw-app-config.yaml") diff --git a/pkg/model/configmapenvs.go b/pkg/model/configmapenvs.go index 205aaa13..6900da9a 100644 --- a/pkg/model/configmapenvs.go +++ b/pkg/model/configmapenvs.go @@ -15,7 +15,7 @@ package model import ( - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" appsv1 "k8s.io/api/apps/v1" @@ -38,7 +38,7 @@ func init() { registerConfig("configmap-envs.yaml", ConfigMapEnvsFactory{}) } -func addConfigMapEnvs(spec v1alpha1.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) { +func addConfigMapEnvs(spec v1alpha2.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) { if spec.Application == nil || spec.Application.ExtraEnvs == nil || spec.Application.ExtraEnvs.ConfigMaps == nil { return @@ -72,7 +72,7 @@ func (p *ConfigMapEnvs) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (p *ConfigMapEnvs) addToModel(model *BackstageModel, _ v1alpha1.Backstage) (bool, error) { +func (p *ConfigMapEnvs) addToModel(model *BackstageModel, _ v1alpha2.Backstage) (bool, error) { if p.ConfigMap != nil { model.setRuntimeObject(p) return true, nil @@ -81,7 +81,7 @@ func (p *ConfigMapEnvs) addToModel(model *BackstageModel, _ v1alpha1.Backstage) } // implementation of RuntimeObject interface -func (p *ConfigMapEnvs) validate(_ *BackstageModel, _ v1alpha1.Backstage) error { +func (p *ConfigMapEnvs) validate(_ *BackstageModel, _ v1alpha2.Backstage) error { return nil } diff --git a/pkg/model/configmapenvs_test.go b/pkg/model/configmapenvs_test.go index aa3b4846..da62e10f 100644 --- a/pkg/model/configmapenvs_test.go +++ b/pkg/model/configmapenvs_test.go @@ -22,7 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,13 +31,13 @@ import ( func TestDefaultConfigMapEnvFrom(t *testing.T) { - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), }, }, @@ -60,22 +60,22 @@ func TestDefaultConfigMapEnvFrom(t *testing.T) { func TestSpecifiedConfigMapEnvs(t *testing.T) { - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - ExtraEnvs: &bsv1alpha1.ExtraEnvs{ - ConfigMaps: []bsv1alpha1.ObjectKeyRef{}, + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + ExtraEnvs: &bsv1.ExtraEnvs{ + ConfigMaps: []bsv1.ObjectKeyRef{}, }, }, }, } bs.Spec.Application.ExtraEnvs.ConfigMaps = append(bs.Spec.Application.ExtraEnvs.ConfigMaps, - bsv1alpha1.ObjectKeyRef{Name: "mapName", Key: "ENV1"}) + bsv1.ObjectKeyRef{Name: "mapName", Key: "ENV1"}) testObj := createBackstageTest(bs).withDefaultConfig(true) testObj.externalConfig.ExtraEnvConfigMaps["mapName"] = corev1.ConfigMap{Data: map[string]string{"mapName": "ENV1"}} diff --git a/pkg/model/configmapfiles.go b/pkg/model/configmapfiles.go index 1977987a..78c17efa 100644 --- a/pkg/model/configmapfiles.go +++ b/pkg/model/configmapfiles.go @@ -17,7 +17,7 @@ package model import ( appsv1 "k8s.io/api/apps/v1" - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -40,7 +40,7 @@ func init() { registerConfig("configmap-files.yaml", ConfigMapFilesFactory{}) } -func addConfigMapFiles(spec v1alpha1.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) { +func addConfigMapFiles(spec v1alpha2.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) { if spec.Application == nil || spec.Application.ExtraFiles == nil || spec.Application.ExtraFiles.ConfigMaps == nil { return @@ -80,7 +80,7 @@ func (p *ConfigMapFiles) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (p *ConfigMapFiles) addToModel(model *BackstageModel, _ v1alpha1.Backstage) (bool, error) { +func (p *ConfigMapFiles) addToModel(model *BackstageModel, _ v1alpha2.Backstage) (bool, error) { if p.ConfigMap != nil { model.setRuntimeObject(p) return true, nil @@ -89,7 +89,7 @@ func (p *ConfigMapFiles) addToModel(model *BackstageModel, _ v1alpha1.Backstage) } // implementation of RuntimeObject interface -func (p *ConfigMapFiles) validate(_ *BackstageModel, _ v1alpha1.Backstage) error { +func (p *ConfigMapFiles) validate(_ *BackstageModel, _ v1alpha2.Backstage) error { return nil } diff --git a/pkg/model/configmapfiles_test.go b/pkg/model/configmapfiles_test.go index 9dedb06a..2f0d711c 100644 --- a/pkg/model/configmapfiles_test.go +++ b/pkg/model/configmapfiles_test.go @@ -17,7 +17,7 @@ package model import ( "context" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,16 +43,16 @@ var ( // Data: map[string]string{"conf2.yaml": ""}, //} - configMapFilesTestBackstage = bsv1alpha1.Backstage{ + configMapFilesTestBackstage = bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - ExtraFiles: &bsv1alpha1.ExtraFiles{ + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + ExtraFiles: &bsv1.ExtraFiles{ MountPath: "/my/path", - ConfigMaps: []bsv1alpha1.ObjectKeyRef{}, + ConfigMaps: []bsv1.ObjectKeyRef{}, }, }, }, @@ -81,8 +81,8 @@ func TestSpecifiedConfigMapFiles(t *testing.T) { bs := *configMapFilesTestBackstage.DeepCopy() cmf := &bs.Spec.Application.ExtraFiles.ConfigMaps - *cmf = append(*cmf, bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm.Name}) - *cmf = append(*cmf, bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm2.Name}) + *cmf = append(*cmf, bsv1.ObjectKeyRef{Name: appConfigTestCm.Name}) + *cmf = append(*cmf, bsv1.ObjectKeyRef{Name: appConfigTestCm2.Name}) testObj := createBackstageTest(bs).withDefaultConfig(true) @@ -104,7 +104,7 @@ func TestDefaultAndSpecifiedConfigMapFiles(t *testing.T) { bs := *configMapFilesTestBackstage.DeepCopy() cmf := &bs.Spec.Application.ExtraFiles.ConfigMaps - *cmf = append(*cmf, bsv1alpha1.ObjectKeyRef{Name: appConfigTestCm.Name}) + *cmf = append(*cmf, bsv1.ObjectKeyRef{Name: appConfigTestCm.Name}) testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("configmap-files.yaml", "raw-cm-files.yaml") diff --git a/pkg/model/db-secret.go b/pkg/model/db-secret.go index aff1e278..d281fc56 100644 --- a/pkg/model/db-secret.go +++ b/pkg/model/db-secret.go @@ -17,7 +17,7 @@ package model import ( "strconv" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -56,7 +56,7 @@ func (b *DbSecret) setObject(obj client.Object) { } // implementation of RuntimeObject interface -func (b *DbSecret) addToModel(model *BackstageModel, backstage bsv1alpha1.Backstage) (bool, error) { +func (b *DbSecret) addToModel(model *BackstageModel, backstage bsv1.Backstage) (bool, error) { // do not add if specified if backstage.Spec.IsAuthSecretSpecified() { @@ -78,7 +78,7 @@ func (b *DbSecret) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *DbSecret) validate(model *BackstageModel, backstage bsv1alpha1.Backstage) error { +func (b *DbSecret) validate(model *BackstageModel, backstage bsv1.Backstage) error { pswd, _ := utils.GeneratePassword(24) diff --git a/pkg/model/db-secret_test.go b/pkg/model/db-secret_test.go index 2b8e1829..3abc7795 100644 --- a/pkg/model/db-secret_test.go +++ b/pkg/model/db-secret_test.go @@ -20,20 +20,20 @@ import ( "k8s.io/utils/ptr" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/stretchr/testify/assert" ) -var dbSecretBackstage = &bsv1alpha1.Backstage{ +var dbSecretBackstage = &bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), }, }, diff --git a/pkg/model/db-service.go b/pkg/model/db-service.go index 0e405130..d903d3dc 100644 --- a/pkg/model/db-service.go +++ b/pkg/model/db-service.go @@ -17,7 +17,7 @@ package model import ( "fmt" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -55,7 +55,7 @@ func (b *DbService) setObject(obj client.Object) { } // implementation of RuntimeObject interface -func (b *DbService) addToModel(model *BackstageModel, _ bsv1alpha1.Backstage) (bool, error) { +func (b *DbService) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, error) { if b.service == nil { if model.localDbEnabled { return false, fmt.Errorf("LocalDb Service not initialized, make sure there is db-service.yaml.yaml in default or raw configuration") @@ -79,7 +79,7 @@ func (b *DbService) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *DbService) validate(_ *BackstageModel, _ bsv1alpha1.Backstage) error { +func (b *DbService) validate(_ *BackstageModel, _ bsv1.Backstage) error { return nil } diff --git a/pkg/model/db-statefulset.go b/pkg/model/db-statefulset.go index fd7b144f..71d7c508 100644 --- a/pkg/model/db-statefulset.go +++ b/pkg/model/db-statefulset.go @@ -20,7 +20,7 @@ import ( corev1 "k8s.io/api/core/v1" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" appsv1 "k8s.io/api/apps/v1" @@ -60,7 +60,7 @@ func (b *DbStatefulSet) setObject(obj client.Object) { } // implementation of RuntimeObject interface -func (b *DbStatefulSet) addToModel(model *BackstageModel, _ bsv1alpha1.Backstage) (bool, error) { +func (b *DbStatefulSet) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, error) { if b.statefulSet == nil { if model.localDbEnabled { return false, fmt.Errorf("LocalDb StatefulSet not configured, make sure there is db-statefulset.yaml.yaml in default or raw configuration") @@ -90,7 +90,7 @@ func (b *DbStatefulSet) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *DbStatefulSet) validate(model *BackstageModel, backstage bsv1alpha1.Backstage) error { +func (b *DbStatefulSet) validate(model *BackstageModel, backstage bsv1.Backstage) error { if backstage.Spec.Application != nil { utils.SetImagePullSecrets(b.podSpec(), backstage.Spec.Application.ImagePullSecrets) diff --git a/pkg/model/db-statefulset_test.go b/pkg/model/db-statefulset_test.go index 778a0d4c..ffd1b132 100644 --- a/pkg/model/db-statefulset_test.go +++ b/pkg/model/db-statefulset_test.go @@ -21,21 +21,21 @@ import ( "k8s.io/utils/ptr" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/stretchr/testify/assert" ) -var dbStatefulSetBackstage = &bsv1alpha1.Backstage{ +var dbStatefulSetBackstage = &bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{}, - Application: &bsv1alpha1.Application{}, + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{}, + Application: &bsv1.Application{}, }, } diff --git a/pkg/model/deployment.go b/pkg/model/deployment.go index 21ce2554..6a780b05 100644 --- a/pkg/model/deployment.go +++ b/pkg/model/deployment.go @@ -27,7 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" @@ -75,7 +75,7 @@ func (b *BackstageDeployment) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *BackstageDeployment) addToModel(model *BackstageModel, backstage bsv1alpha1.Backstage) (bool, error) { +func (b *BackstageDeployment) addToModel(model *BackstageModel, backstage bsv1.Backstage) (bool, error) { if b.deployment == nil { return false, fmt.Errorf("Backstage Deployment is not initialized, make sure there is deployment.yaml in default or raw configuration") } @@ -116,7 +116,7 @@ func (b *BackstageDeployment) addToModel(model *BackstageModel, backstage bsv1al } // implementation of RuntimeObject interface -func (b *BackstageDeployment) validate(model *BackstageModel, backstage bsv1alpha1.Backstage) error { +func (b *BackstageDeployment) validate(model *BackstageModel, backstage bsv1.Backstage) error { if backstage.Spec.Application != nil { b.setReplicas(backstage.Spec.Application.Replicas) @@ -197,7 +197,7 @@ func (b *BackstageDeployment) setImage(image *string) { } // adds environment variables to the Backstage Container -func (b *BackstageDeployment) addContainerEnvVar(env bsv1alpha1.Env) { +func (b *BackstageDeployment) addContainerEnvVar(env bsv1.Env) { b.container().Env = append(b.deployment.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ Name: env.Name, @@ -206,7 +206,7 @@ func (b *BackstageDeployment) addContainerEnvVar(env bsv1alpha1.Env) { } // adds environment from source to the Backstage Container -func (b *BackstageDeployment) addExtraEnvs(extraEnvs *bsv1alpha1.ExtraEnvs) { +func (b *BackstageDeployment) addExtraEnvs(extraEnvs *bsv1.ExtraEnvs) { if extraEnvs != nil { for _, e := range extraEnvs.Envs { b.addContainerEnvVar(e) diff --git a/pkg/model/deployment_test.go b/pkg/model/deployment_test.go index bec1af80..3ea9859b 100644 --- a/pkg/model/deployment_test.go +++ b/pkg/model/deployment_test.go @@ -22,23 +22,23 @@ import ( "k8s.io/utils/ptr" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/stretchr/testify/assert" ) -var deploymentTestBackstage = bsv1alpha1.Backstage{ +var deploymentTestBackstage = bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), }, - Application: &bsv1alpha1.Application{}, + Application: &bsv1.Application{}, }, } diff --git a/pkg/model/dynamic-plugins.go b/pkg/model/dynamic-plugins.go index 6e528a45..3a92216f 100644 --- a/pkg/model/dynamic-plugins.go +++ b/pkg/model/dynamic-plugins.go @@ -20,7 +20,7 @@ import ( appsv1 "k8s.io/api/apps/v1" - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -48,7 +48,7 @@ func DynamicPluginsDefaultName(backstageName string) string { return utils.GenerateRuntimeObjectName(backstageName, "backstage-dynamic-plugins") } -func addDynamicPlugins(spec v1alpha1.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) error { +func addDynamicPlugins(spec v1alpha2.BackstageSpec, deployment *appsv1.Deployment, model *BackstageModel) error { if spec.Application == nil || spec.Application.DynamicPluginsConfigMapName == "" { return nil @@ -83,7 +83,7 @@ func (p *DynamicPlugins) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (p *DynamicPlugins) addToModel(model *BackstageModel, backstage v1alpha1.Backstage) (bool, error) { +func (p *DynamicPlugins) addToModel(model *BackstageModel, backstage v1alpha2.Backstage) (bool, error) { if p.ConfigMap == nil || (backstage.Spec.Application != nil && backstage.Spec.Application.DynamicPluginsConfigMapName != "") { return false, nil @@ -119,7 +119,7 @@ func (p *DynamicPlugins) updatePod(deployment *appsv1.Deployment) { // implementation of RuntimeObject interface // ConfigMap name must be the same as (deployment.yaml).spec.template.spec.volumes.name.dynamic-plugins-conf.ConfigMap.name -func (p *DynamicPlugins) validate(model *BackstageModel, _ v1alpha1.Backstage) error { +func (p *DynamicPlugins) validate(model *BackstageModel, _ v1alpha2.Backstage) error { _, initContainer := DynamicPluginsInitContainer(model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers) if initContainer == nil { diff --git a/pkg/model/dynamic-plugins_test.go b/pkg/model/dynamic-plugins_test.go index 8037c4af..6a93ff2d 100644 --- a/pkg/model/dynamic-plugins_test.go +++ b/pkg/model/dynamic-plugins_test.go @@ -21,7 +21,7 @@ import ( "k8s.io/utils/ptr" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,16 +29,16 @@ import ( "github.com/stretchr/testify/assert" ) -var testDynamicPluginsBackstage = bsv1alpha1.Backstage{ +var testDynamicPluginsBackstage = bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), }, - Application: &bsv1alpha1.Application{}, + Application: &bsv1.Application{}, }, } diff --git a/pkg/model/interfaces.go b/pkg/model/interfaces.go index 859ce115..cc679590 100644 --- a/pkg/model/interfaces.go +++ b/pkg/model/interfaces.go @@ -15,7 +15,7 @@ package model import ( - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" appsv1 "k8s.io/api/apps/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -45,10 +45,10 @@ type RuntimeObject interface { EmptyObject() client.Object // adds runtime object to the model // returns false if the object was not added to the model (not configured) - addToModel(model *BackstageModel, backstage bsv1alpha1.Backstage) (bool, error) + addToModel(model *BackstageModel, backstage bsv1.Backstage) (bool, error) // at this stage all the information is updated // set the final references validates the object at the end of initialization - validate(model *BackstageModel, backstage bsv1alpha1.Backstage) error + validate(model *BackstageModel, backstage bsv1.Backstage) error // sets object name, labels and other necessary meta information setMetaInfo(backstageName string) } diff --git a/pkg/model/model_tests.go b/pkg/model/model_tests.go index 3948d2c8..7dde3751 100644 --- a/pkg/model/model_tests.go +++ b/pkg/model/model_tests.go @@ -27,22 +27,22 @@ import ( "k8s.io/apimachinery/pkg/runtime" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" ) // testBackstageObject it is a helper object to simplify testing model component allowing to customize and isolate testing configuration // usual sequence of creating testBackstageObject contains such a steps: -// createBackstageTest(bsv1alpha1.Backstage). +// createBackstageTest(bsv1.Backstage). // withDefaultConfig(useDef bool) // addToDefaultConfig(key, fileName) type testBackstageObject struct { - backstage bsv1alpha1.Backstage + backstage bsv1.Backstage externalConfig ExternalConfig scheme *runtime.Scheme } // initialises testBackstageObject object -func createBackstageTest(bs bsv1alpha1.Backstage) *testBackstageObject { +func createBackstageTest(bs bsv1.Backstage) *testBackstageObject { ec := ExternalConfig{ RawConfig: map[string]string{}, AppConfigs: map[string]corev1.ConfigMap{}, @@ -50,7 +50,7 @@ func createBackstageTest(bs bsv1alpha1.Backstage) *testBackstageObject { ExtraEnvConfigMaps: map[string]corev1.ConfigMap{}, } b := &testBackstageObject{backstage: bs, externalConfig: ec, scheme: runtime.NewScheme()} - utilruntime.Must(bsv1alpha1.AddToScheme(b.scheme)) + utilruntime.Must(bsv1.AddToScheme(b.scheme)) return b } diff --git a/pkg/model/route.go b/pkg/model/route.go index 56601949..02767af8 100644 --- a/pkg/model/route.go +++ b/pkg/model/route.go @@ -15,7 +15,7 @@ package model import ( - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" openshift "github.com/openshift/api/route/v1" @@ -36,7 +36,7 @@ func RouteName(backstageName string) string { return utils.GenerateRuntimeObjectName(backstageName, "backstage") } -func (b *BackstageRoute) setRoute(specified *bsv1alpha1.Route) { +func (b *BackstageRoute) setRoute(specified *bsv1.Route) { if len(specified.Host) > 0 { b.route.Spec.Host = specified.Host @@ -101,7 +101,7 @@ func (b *BackstageRoute) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *BackstageRoute) addToModel(model *BackstageModel, backstage bsv1alpha1.Backstage) (bool, error) { +func (b *BackstageRoute) addToModel(model *BackstageModel, backstage bsv1.Backstage) (bool, error) { // not Openshift if !model.isOpenshift { @@ -137,7 +137,7 @@ func (b *BackstageRoute) addToModel(model *BackstageModel, backstage bsv1alpha1. } // implementation of RuntimeObject interface -func (b *BackstageRoute) validate(model *BackstageModel, _ bsv1alpha1.Backstage) error { +func (b *BackstageRoute) validate(model *BackstageModel, _ bsv1.Backstage) error { b.route.Spec.To.Name = model.backstageService.service.Name return nil } diff --git a/pkg/model/route_test.go b/pkg/model/route_test.go index c035af39..9189ce14 100644 --- a/pkg/model/route_test.go +++ b/pkg/model/route_test.go @@ -24,20 +24,20 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "github.com/stretchr/testify/assert" ) func TestDefaultRoute(t *testing.T) { - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "TestSpecifiedRoute", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - Route: &bsv1alpha1.Route{}, + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + Route: &bsv1.Route{}, }, }, } @@ -63,14 +63,14 @@ func TestDefaultRoute(t *testing.T) { } func TestSpecifiedRoute(t *testing.T) { - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "TestSpecifiedRoute", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - Route: &bsv1alpha1.Route{ + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + Route: &bsv1.Route{ Enabled: ptr.To(true), Host: "TestSpecifiedRoute", //TLS: nil, @@ -109,14 +109,14 @@ func TestSpecifiedRoute(t *testing.T) { func TestDisabledRoute(t *testing.T) { // Route.Enabled = false - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "TestSpecifiedRoute", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - Route: &bsv1alpha1.Route{ + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + Route: &bsv1.Route{ Enabled: ptr.To(false), Host: "TestSpecifiedRoute", //TLS: nil, @@ -141,12 +141,12 @@ func TestDisabledRoute(t *testing.T) { func TestExcludedRoute(t *testing.T) { // No route configured - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "TestSpecifiedRoute", Namespace: "ns123", }, - //Spec: bsv1alpha1.BackstageSpec{ // //Application: &bsv1alpha1.Application{}, + //Spec: bsv1.BackstageSpec{ // //Application: &bsv1.Application{}, //}, } @@ -165,14 +165,14 @@ func TestExcludedRoute(t *testing.T) { func TestEnabledRoute(t *testing.T) { // Route is enabled by default if configured - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "TestSpecifiedRoute", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - Route: &bsv1alpha1.Route{}, + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + Route: &bsv1.Route{}, }, }, } diff --git a/pkg/model/runtime.go b/pkg/model/runtime.go index 0f30108f..cdf26b49 100644 --- a/pkg/model/runtime.go +++ b/pkg/model/runtime.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" ) @@ -105,7 +105,7 @@ func registerConfig(key string, factory ObjectFactory) { } // InitObjects performs a main loop for configuring and making the array of objects to reconcile -func InitObjects(ctx context.Context, backstage bsv1alpha1.Backstage, externalConfig ExternalConfig, ownsRuntime bool, isOpenshift bool, scheme *runtime.Scheme) (*BackstageModel, error) { +func InitObjects(ctx context.Context, backstage bsv1.Backstage, externalConfig ExternalConfig, ownsRuntime bool, isOpenshift bool, scheme *runtime.Scheme) (*BackstageModel, error) { // 3 phases of Backstage configuration: // 1- load from Operator defaults, modify metadata (labels, selectors..) and namespace as needed @@ -167,7 +167,7 @@ func InitObjects(ctx context.Context, backstage bsv1alpha1.Backstage, externalCo } // Every RuntimeObject.setMetaInfo should as minimum call this -func setMetaInfo(modelObject RuntimeObject, backstage bsv1alpha1.Backstage, ownsRuntime bool, scheme *runtime.Scheme) { +func setMetaInfo(modelObject RuntimeObject, backstage bsv1.Backstage, ownsRuntime bool, scheme *runtime.Scheme) { modelObject.setMetaInfo(backstage.Name) modelObject.Object().SetNamespace(backstage.Namespace) modelObject.Object().SetLabels(utils.SetKubeLabels(modelObject.Object().GetLabels(), backstage.Name)) diff --git a/pkg/model/runtime_test.go b/pkg/model/runtime_test.go index 3f5354cb..f69ac680 100644 --- a/pkg/model/runtime_test.go +++ b/pkg/model/runtime_test.go @@ -22,8 +22,8 @@ import ( "k8s.io/utils/ptr" - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,13 +42,13 @@ func TestIfEmptyObjectsContainTypeinfo(t *testing.T) { // NOTE: to make it work locally env var LOCALBIN should point to the directory where default-config folder located func TestInitDefaultDeploy(t *testing.T) { - bs := v1alpha1.Backstage{ + bs := v1alpha2.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: v1alpha1.BackstageSpec{ - Database: &v1alpha1.Database{ + Spec: v1alpha2.BackstageSpec{ + Database: &v1alpha2.Database{ EnableLocalDb: ptr.To(false), }, }, @@ -78,13 +78,13 @@ func TestInitDefaultDeploy(t *testing.T) { func TestIfEmptyObjectIsValid(t *testing.T) { - bs := bsv1alpha1.Backstage{ + bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Database: &bsv1alpha1.Database{ + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ EnableLocalDb: ptr.To(false), }, }, @@ -103,13 +103,13 @@ func TestIfEmptyObjectIsValid(t *testing.T) { func TestAddToModel(t *testing.T) { - bs := v1alpha1.Backstage{ + bs := v1alpha2.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: v1alpha1.BackstageSpec{ - Database: &v1alpha1.Database{ + Spec: v1alpha2.BackstageSpec{ + Database: &v1alpha2.Database{ EnableLocalDb: ptr.To(false), }, }, diff --git a/pkg/model/secretenvs.go b/pkg/model/secretenvs.go index a6277351..86ed9660 100644 --- a/pkg/model/secretenvs.go +++ b/pkg/model/secretenvs.go @@ -15,7 +15,7 @@ package model import ( - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" appsv1 "k8s.io/api/apps/v1" @@ -44,7 +44,7 @@ func (p *SecretEnvs) Object() client.Object { return p.Secret } -func addSecretEnvs(spec v1alpha1.BackstageSpec, deployment *appsv1.Deployment) error { +func addSecretEnvs(spec v1alpha2.BackstageSpec, deployment *appsv1.Deployment) error { if spec.Application == nil || spec.Application.ExtraEnvs == nil || spec.Application.ExtraEnvs.Secrets == nil { return nil @@ -73,7 +73,7 @@ func (p *SecretEnvs) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (p *SecretEnvs) addToModel(model *BackstageModel, _ v1alpha1.Backstage) (bool, error) { +func (p *SecretEnvs) addToModel(model *BackstageModel, _ v1alpha2.Backstage) (bool, error) { if p.Secret != nil { model.setRuntimeObject(p) return true, nil @@ -82,7 +82,7 @@ func (p *SecretEnvs) addToModel(model *BackstageModel, _ v1alpha1.Backstage) (bo } // implementation of RuntimeObject interface -func (p *SecretEnvs) validate(_ *BackstageModel, _ v1alpha1.Backstage) error { +func (p *SecretEnvs) validate(_ *BackstageModel, _ v1alpha2.Backstage) error { return nil } diff --git a/pkg/model/secretfiles.go b/pkg/model/secretfiles.go index 17430ab3..93dec9e1 100644 --- a/pkg/model/secretfiles.go +++ b/pkg/model/secretfiles.go @@ -20,7 +20,7 @@ import ( appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -43,7 +43,7 @@ func init() { registerConfig("secret-files.yaml", SecretFilesFactory{}) } -func addSecretFiles(spec v1alpha1.BackstageSpec, deployment *appsv1.Deployment) error { +func addSecretFiles(spec v1alpha2.BackstageSpec, deployment *appsv1.Deployment) error { if spec.Application == nil || spec.Application.ExtraFiles == nil || spec.Application.ExtraFiles.Secrets == nil { return nil @@ -90,7 +90,7 @@ func (p *SecretFiles) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (p *SecretFiles) addToModel(model *BackstageModel, _ v1alpha1.Backstage) (bool, error) { +func (p *SecretFiles) addToModel(model *BackstageModel, _ v1alpha2.Backstage) (bool, error) { if p.Secret != nil { model.setRuntimeObject(p) return true, nil @@ -99,7 +99,7 @@ func (p *SecretFiles) addToModel(model *BackstageModel, _ v1alpha1.Backstage) (b } // implementation of RuntimeObject interface -func (p *SecretFiles) validate(_ *BackstageModel, _ v1alpha1.Backstage) error { +func (p *SecretFiles) validate(_ *BackstageModel, _ v1alpha2.Backstage) error { return nil } diff --git a/pkg/model/secretfiles_test.go b/pkg/model/secretfiles_test.go index c463d470..fe2057cd 100644 --- a/pkg/model/secretfiles_test.go +++ b/pkg/model/secretfiles_test.go @@ -19,7 +19,7 @@ import ( "redhat-developer/red-hat-developer-hub-operator/pkg/utils" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -45,16 +45,16 @@ var ( // StringData: map[string]string{"conf2.yaml": ""}, //} - secretFilesTestBackstage = bsv1alpha1.Backstage{ + secretFilesTestBackstage = bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ Name: "bs", Namespace: "ns123", }, - Spec: bsv1alpha1.BackstageSpec{ - Application: &bsv1alpha1.Application{ - ExtraFiles: &bsv1alpha1.ExtraFiles{ + Spec: bsv1.BackstageSpec{ + Application: &bsv1.Application{ + ExtraFiles: &bsv1.ExtraFiles{ MountPath: "/my/path", - Secrets: []bsv1alpha1.ObjectKeyRef{}, + Secrets: []bsv1.ObjectKeyRef{}, }, }, }, @@ -83,8 +83,8 @@ func TestSpecifiedSecretFiles(t *testing.T) { bs := *secretFilesTestBackstage.DeepCopy() sf := &bs.Spec.Application.ExtraFiles.Secrets - *sf = append(*sf, bsv1alpha1.ObjectKeyRef{Name: "secret1", Key: "conf.yaml"}) - *sf = append(*sf, bsv1alpha1.ObjectKeyRef{Name: "secret2", Key: "conf.yaml"}) + *sf = append(*sf, bsv1.ObjectKeyRef{Name: "secret1", Key: "conf.yaml"}) + *sf = append(*sf, bsv1.ObjectKeyRef{Name: "secret2", Key: "conf.yaml"}) testObj := createBackstageTest(bs).withDefaultConfig(true) @@ -108,7 +108,7 @@ func TestDefaultAndSpecifiedSecretFiles(t *testing.T) { bs := *secretFilesTestBackstage.DeepCopy() sf := &bs.Spec.Application.ExtraFiles.Secrets - *sf = append(*sf, bsv1alpha1.ObjectKeyRef{Name: "secret1", Key: "conf.yaml"}) + *sf = append(*sf, bsv1.ObjectKeyRef{Name: "secret1", Key: "conf.yaml"}) testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("secret-files.yaml", "raw-secret-files.yaml") model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, true, false, testObj.scheme) diff --git a/pkg/model/service.go b/pkg/model/service.go index 519a77d6..5f03e7f0 100644 --- a/pkg/model/service.go +++ b/pkg/model/service.go @@ -17,7 +17,7 @@ package model import ( "fmt" - bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1" + bsv1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha2" "redhat-developer/red-hat-developer-hub-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -55,7 +55,7 @@ func (b *BackstageService) setObject(obj client.Object) { } // implementation of RuntimeObject interface -func (b *BackstageService) addToModel(model *BackstageModel, _ bsv1alpha1.Backstage) (bool, error) { +func (b *BackstageService) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, error) { if b.service == nil { return false, fmt.Errorf("Backstage Service is not initialized, make sure there is service.yaml in default or raw configuration") } @@ -72,7 +72,7 @@ func (b *BackstageService) EmptyObject() client.Object { } // implementation of RuntimeObject interface -func (b *BackstageService) validate(_ *BackstageModel, _ bsv1alpha1.Backstage) error { +func (b *BackstageService) validate(_ *BackstageModel, _ bsv1.Backstage) error { return nil } From b7c6db559b47a8ab3fc8f94536e26b4f3950982d Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Wed, 19 Jun 2024 16:23:16 +0300 Subject: [PATCH 03/10] deprecation message Signed-off-by: gazarenkov --- api/v1alpha1/backstage_types.go | 1 + api/v1alpha2/backstage_types.go | 2 +- config/crd/bases/rhdh.redhat.com_backstages.yaml | 6 +++++- integration_tests/default-config_test.go | 6 +++--- pkg/model/secretfiles_test.go | 2 -- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/v1alpha1/backstage_types.go b/api/v1alpha1/backstage_types.go index 5fd7d794..0facd95a 100644 --- a/api/v1alpha1/backstage_types.go +++ b/api/v1alpha1/backstage_types.go @@ -197,6 +197,7 @@ type BackstageStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:deprecatedversion:warning="Since 1.3.0 spec.application.image, spec.application.replicas, spec.application.imagePullSecrets are deprecated in favor of corresponding spec.deployment fields" // Backstage is the Schema for the backstages API type Backstage struct { diff --git a/api/v1alpha2/backstage_types.go b/api/v1alpha2/backstage_types.go index 6c916df6..ca591597 100644 --- a/api/v1alpha2/backstage_types.go +++ b/api/v1alpha2/backstage_types.go @@ -204,7 +204,7 @@ type BackstageStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// +kubebuilder:storageversion +//+kubebuilder:storageversion // Backstage is the Schema for the backstages API type Backstage struct { diff --git a/config/crd/bases/rhdh.redhat.com_backstages.yaml b/config/crd/bases/rhdh.redhat.com_backstages.yaml index 7382c3e5..94bc0993 100644 --- a/config/crd/bases/rhdh.redhat.com_backstages.yaml +++ b/config/crd/bases/rhdh.redhat.com_backstages.yaml @@ -15,7 +15,11 @@ spec: singular: backstage scope: Namespaced versions: - - name: v1alpha1 + - deprecated: true + deprecationWarning: Since 1.3.0 spec.application.image, spec.application.replicas, + spec.application.imagePullSecrets are deprecated in favor of related spec.deployment + fields + name: v1alpha1 schema: openAPIV3Schema: description: Backstage is the Schema for the backstages API diff --git a/integration_tests/default-config_test.go b/integration_tests/default-config_test.go index 0ea4b043..a4e00aa0 100644 --- a/integration_tests/default-config_test.go +++ b/integration_tests/default-config_test.go @@ -169,8 +169,8 @@ var _ = When("create default backstage", func() { dbRaw := generateConfigMap(ctx, k8sClient, "dbraw", ns, dbConf, nil, nil) - backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{ - RawRuntimeConfig: &bsv1alpha1.RuntimeConfig{ + backstageName := createAndReconcileBackstage(ctx, ns, bsv1.BackstageSpec{ + RawRuntimeConfig: &bsv1.RuntimeConfig{ LocalDbConfigName: dbRaw, }, }, "") @@ -192,7 +192,7 @@ var _ = When("create default backstage", func() { }, time.Minute, time.Second).Should(Succeed()) By("updating CR to default config") - update := &bsv1alpha1.Backstage{} + update := &bsv1.Backstage{} err := k8sClient.Get(ctx, types.NamespacedName{Name: backstageName, Namespace: ns}, update) Expect(err).To(Not(HaveOccurred())) update.Spec.RawRuntimeConfig = nil diff --git a/pkg/model/secretfiles_test.go b/pkg/model/secretfiles_test.go index c3520aed..98718707 100644 --- a/pkg/model/secretfiles_test.go +++ b/pkg/model/secretfiles_test.go @@ -84,8 +84,6 @@ func TestSpecifiedSecretFiles(t *testing.T) { sf := &bs.Spec.Application.ExtraFiles.Secrets *sf = append(*sf, bsv1.ObjectKeyRef{Name: "secret1", Key: "conf.yaml"}) *sf = append(*sf, bsv1.ObjectKeyRef{Name: "secret2", Key: "conf.yaml"}) - *sf = append(*sf, bsv1alpha1.ObjectKeyRef{Name: "secret1", Key: "conf.yaml"}) - *sf = append(*sf, bsv1alpha1.ObjectKeyRef{Name: "secret2", Key: "conf.yaml"}) // https://issues.redhat.com/browse/RHIDP-2246 - mounting secret/CM with dot in the name *sf = append(*sf, bsv1.ObjectKeyRef{Name: "secret.dot", Key: "conf3.yaml"}) From 38b5d7e0b6e6dbe8c69b72b88a7bc300abd34a42 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Thu, 20 Jun 2024 09:42:05 +0300 Subject: [PATCH 04/10] fix spec.deployment test Signed-off-by: gazarenkov --- .../crd/bases/rhdh.redhat.com_backstages.yaml | 2 +- integration_tests/cr-config_test.go | 21 ++----------------- .../testdata/spec-deployment.yaml | 16 ++++++++++++++ 3 files changed, 19 insertions(+), 20 deletions(-) create mode 100644 integration_tests/testdata/spec-deployment.yaml diff --git a/config/crd/bases/rhdh.redhat.com_backstages.yaml b/config/crd/bases/rhdh.redhat.com_backstages.yaml index 94bc0993..e6cc9bb6 100644 --- a/config/crd/bases/rhdh.redhat.com_backstages.yaml +++ b/config/crd/bases/rhdh.redhat.com_backstages.yaml @@ -17,7 +17,7 @@ spec: versions: - deprecated: true deprecationWarning: Since 1.3.0 spec.application.image, spec.application.replicas, - spec.application.imagePullSecrets are deprecated in favor of related spec.deployment + spec.application.imagePullSecrets are deprecated in favor of corresponding spec.deployment fields name: v1alpha1 schema: diff --git a/integration_tests/cr-config_test.go b/integration_tests/cr-config_test.go index 001fbf60..d92dbae3 100644 --- a/integration_tests/cr-config_test.go +++ b/integration_tests/cr-config_test.go @@ -251,11 +251,11 @@ var _ = When("create backstage with CR configured", func() { }) - It("creates Backstage deployment with valid spec.deployment ", func() { + It("creates Backstage deployment with spec.deployment ", func() { bs2 := &bsv1.Backstage{} - err := utils.ReadYamlFile("testdata/backstage-valid-deployment.yaml", bs2) + err := utils.ReadYamlFile("testdata/spec-deployment.yaml", bs2) Expect(err).To(Not(HaveOccurred())) backstageName := createAndReconcileBackstage(ctx, ns, bs2.Spec, "") @@ -294,23 +294,6 @@ var _ = When("create backstage with CR configured", func() { }) - It("fails Backstage deployment with invalid spec.deployment ", func() { - - bs2 := &bsv1.Backstage{} - bs2.Namespace = ns - - err := utils.ReadYamlFile("testdata/backstage-invalid-deployment.yaml", bs2) - Expect(err).To(Not(HaveOccurred())) - - Eventually(func() error { - return k8sClient.Create(ctx, bs2) - }, time.Minute, time.Second).Should(Succeed()) - - //Expect(err).To(Not(HaveOccurred())) - - //backstageName := createAndReconcileBackstage(ctx, ns, bs2.Spec, "") - - }) }) // Duplicated files in different CMs diff --git a/integration_tests/testdata/spec-deployment.yaml b/integration_tests/testdata/spec-deployment.yaml new file mode 100644 index 00000000..6084684f --- /dev/null +++ b/integration_tests/testdata/spec-deployment.yaml @@ -0,0 +1,16 @@ +apiVersion: rhdh.redhat.com/v1alpha2 +kind: Backstage +spec: + deployment: + spec: + template: + spec: + containers: + - name: backstage-backend + image: busybox + volumes: + - ephemeral: + volumeClaimTemplate: + spec: + storageClassName: "special" + name: dynamic-plugins-root \ No newline at end of file From e307937dc291121610f8d97cd07dbd15b578892c Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Thu, 20 Jun 2024 10:04:09 +0300 Subject: [PATCH 05/10] .rhdh/csv modified Signed-off-by: gazarenkov --- .rhdh/bundle/manifests/rhdh-operator.csv.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.rhdh/bundle/manifests/rhdh-operator.csv.yaml b/.rhdh/bundle/manifests/rhdh-operator.csv.yaml index 4b028830..5f441c9a 100644 --- a/.rhdh/bundle/manifests/rhdh-operator.csv.yaml +++ b/.rhdh/bundle/manifests/rhdh-operator.csv.yaml @@ -7,7 +7,7 @@ metadata: alm-examples: |- [ { - "apiVersion": "rhdh.redhat.com/v1alpha1", + "apiVersion": "rhdh.redhat.com/v1alpha2", "kind": "Backstage", "metadata": { "name": "developer-hub" @@ -56,7 +56,7 @@ spec: displayName: Red Hat Developer Hub kind: Backstage name: backstages.rhdh.redhat.com - version: v1alpha1 + version: v1alpha2 description: | Red Hat Developer Hub is an enterprise-grade platform for building developer portals, containing a supported and opinionated framework. By implementing a unified and open platform designed to maximize developer skills, ease onboarding, and increase development productivity, focus can be centered on what really matters: writing great code. Red Hat Developer Hub also offers Software Templates to simplify the development process, which can reduce friction and frustration for development teams, boosting their productivity and increasing an organization's competitive advantage. From e2809e5c827ca878f6bcb8e579891537ce271c66 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Jun 2024 07:33:51 +0000 Subject: [PATCH 06/10] Regenerate bundle manifests Co-authored-by: gazarenkov --- ...kstage-operator.clusterserviceversion.yaml | 12 +- .../manifests/rhdh.redhat.com_backstages.yaml | 366 ++++++++++++++++++ 2 files changed, 373 insertions(+), 5 deletions(-) diff --git a/bundle/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/manifests/backstage-operator.clusterserviceversion.yaml index 592d07c9..f3c451fe 100644 --- a/bundle/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/manifests/backstage-operator.clusterserviceversion.yaml @@ -21,7 +21,7 @@ metadata: } ] capabilities: Seamless Upgrades - createdAt: "2024-06-18T18:58:00Z" + createdAt: "2024-06-20T07:33:49Z" operatorframework.io/suggested-namespace: backstage-system operators.operatorframework.io/builder: operator-sdk-v1.33.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -32,15 +32,17 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: Backstage is the Schema for the backstages API + displayName: Backstage + kind: Backstage + name: backstages.rhdh.redhat.com + version: v1alpha1 - description: Backstage is the Schema for the backstages API displayName: Backstage kind: Backstage name: backstages.rhdh.redhat.com version: v1alpha2 - description: | - Operator to deploy Backstage on Kubernetes - - The telemetry data collection feature is enabled by default. In the default configuration of the operator, telemetry data will be sent to Red Hat by using the `backstage-plugin-analytics-provider-segment` plugin. To disable this and to learn what data is being collected, see https://github.com/janus-idp/backstage-showcase/blob/main/showcase-docs/getting-started.md#telemetry-collection + description: Operator to deploy Backstage on Kubernetes displayName: Red Hat Developer Hub Operator icon: - base64data: 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 diff --git a/bundle/manifests/rhdh.redhat.com_backstages.yaml b/bundle/manifests/rhdh.redhat.com_backstages.yaml index 79435165..6384165c 100644 --- a/bundle/manifests/rhdh.redhat.com_backstages.yaml +++ b/bundle/manifests/rhdh.redhat.com_backstages.yaml @@ -14,6 +14,372 @@ spec: singular: backstage scope: Namespaced versions: + - deprecated: true + deprecationWarning: Since 1.3.0 spec.application.image, spec.application.replicas, + spec.application.imagePullSecrets are deprecated in favor of corresponding spec.deployment + fields + name: v1alpha1 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: References to existing app-configs ConfigMap objects, + that will be mounted as files in the specified mount path. Each + element can be a reference to any ConfigMap or Secret, and will + be mounted inside the main application container under a specified + mount directory. Additionally, each file will be passed as a + `--config /mount/path/to/configmap/key` to the main container + args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing + several filenames, the order in which those files will be appended + to the main container args cannot be guaranteed. So if you want + to pass multiple app-config files, it is recommended to pass + one ConfigMap per app-config file. + properties: + configMaps: + description: List of ConfigMaps storing the app-config files. + Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, + it means that all keys in the ConfigMap will be mounted + as files. Otherwise, only the specified key will be mounted + as a file. Bear in mind not to put sensitive data in those + ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs + field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: 'Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: ''dynamic-plugins.yaml''.' + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: List of references to ConfigMaps objects to inject + as additional environment variables. For each item in this + array, if a key is not specified, it means that all keys + in the ConfigMap will be injected as additional environment + variables. Otherwise, only the specified key will be injected + as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: List of references to Secrets objects to inject + as additional environment variables. For each item in this + array, if a key is not specified, it means that all keys + in the Secret will be injected as additional environment + variables. Otherwise, only the specified key will be injected + as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: References to existing Config objects to use as extra + config files. They will be mounted as files in the specified + mount path. Each element can be a reference to any ConfigMap + or Secret. + properties: + configMaps: + description: List of references to ConfigMaps objects mounted + as extra files under the MountPath specified. For each item + in this array, if a key is not specified, it means that + all keys in the ConfigMap will be mounted as files. Otherwise, + only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: List of references to Secrets objects mounted + as extra files under the MountPath specified. For each item + in this array, a key must be specified that will be mounted + as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: Custom image to use in all containers (including + Init Containers). It is your responsibility to make sure the + image is from trusted sources and has been validated for security + compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: Number of desired replicas to set in the Backstage + Deployment. Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: Host is an alias/DNS that points to the service. + Optional. Ignored if Enabled is false. If not specified + a route name will typically be automatically chosen. Must + follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: 'Subdomain is a DNS subdomain that is requested + within the ingress controller''s domain (as a subdomain). + Ignored if Enabled is false. Example: subdomain `frontend` + automatically receives the router subdomain `apps.mycluster.com` + to have a full hostname `frontend.apps.mycluster.com`.' + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: The tls field provides the ability to configure + certificates for the route. Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: ExternalCertificateSecretName provides certificate + contents as a secret reference. This should be a single + serving certificate, not a certificate chain. Do not + include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: 'Name of the secret for database authentication. + Optional. For a local database deployment (EnableLocalDb=true), + a secret will be auto generated if it does not exist. The secret + shall include information used for the database access. An example + for PostgreSQL DB access: "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres" "POSTGRESQL_ADMIN_PASSWORD": + "rl4s3Fh4ng3M4" "POSTGRES_HOST": "backstage-psql-bs1" # For + local database, set to "backstage-psql-".' + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} - name: v1alpha2 schema: openAPIV3Schema: From 9733168cf1cda3839c71aaff181961f31b0798d1 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Mon, 8 Jul 2024 15:36:34 +0300 Subject: [PATCH 07/10] fixes after comments Signed-off-by: gazarenkov --- .rhdh/bundle/manifests/rhdh-operator.csv.yaml | 8 + api/v1alpha2/backstage_types.go | 10 +- api/v1alpha2/zz_generated.deepcopy.go | 22 +- ...kstage-operator.clusterserviceversion.yaml | 12 +- .../manifests/rhdh.redhat.com_backstages.yaml | 373 ++++++++++++++++++ .../crd/bases/rhdh.redhat.com_backstages.yaml | 7 + .../testdata/spec-deployment.yaml | 25 +- pkg/model/deployment.go | 58 +-- pkg/model/deployment_test.go | 32 +- 9 files changed, 504 insertions(+), 43 deletions(-) diff --git a/.rhdh/bundle/manifests/rhdh-operator.csv.yaml b/.rhdh/bundle/manifests/rhdh-operator.csv.yaml index 5f441c9a..09329808 100644 --- a/.rhdh/bundle/manifests/rhdh-operator.csv.yaml +++ b/.rhdh/bundle/manifests/rhdh-operator.csv.yaml @@ -56,6 +56,14 @@ spec: displayName: Red Hat Developer Hub kind: Backstage name: backstages.rhdh.redhat.com + version: v1alpha1 + - description: Backstage is the Schema for the Red Hat Developer Hub backstages API. + It comes with pre-built plug-ins, configuration settings, and deployment mechanisms, + which can help streamline the process of setting up a self-managed internal + developer portal for adopters who are just starting out. + displayName: Red Hat Developer Hub + kind: Backstage + name: backstages.rhdh.redhat.com version: v1alpha2 description: | Red Hat Developer Hub is an enterprise-grade platform for building developer portals, containing a supported and opinionated framework. By implementing a unified and open platform designed to maximize developer skills, ease onboarding, and increase development productivity, focus can be centered on what really matters: writing great code. Red Hat Developer Hub also offers Software Templates to simplify the development process, which can reduce friction and frustration for development teams, boosting their productivity and increasing an organization's competitive advantage. diff --git a/api/v1alpha2/backstage_types.go b/api/v1alpha2/backstage_types.go index ca591597..a1288242 100644 --- a/api/v1alpha2/backstage_types.go +++ b/api/v1alpha2/backstage_types.go @@ -47,7 +47,15 @@ type BackstageSpec struct { // Set the Deployment's metadata and|or spec fields you want to override or add. // Optional. // +kubebuilder:pruning:PreserveUnknownFields - Deployment *apiextensionsv1.JSON `json:"deployment,omitempty"` + Deployment *BackstageDeployment `json:"deployment,omitempty"` +} + +type BackstageDeployment struct { + // Valid fragment of Deployment to be merged with default/raw configuration. + // Set the Deployment's metadata and|or spec fields you want to override or add. + // Optional. + // +kubebuilder:pruning:PreserveUnknownFields + Patch *apiextensionsv1.JSON `json:"patch,omitempty"` } type RuntimeConfig struct { diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 1fb9aa5e..280d13d4 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -124,6 +124,26 @@ func (in *Backstage) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackstageDeployment) DeepCopyInto(out *BackstageDeployment) { + *out = *in + if in.Patch != nil { + in, out := &in.Patch, &out.Patch + *out = new(v1.JSON) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackstageDeployment. +func (in *BackstageDeployment) DeepCopy() *BackstageDeployment { + if in == nil { + return nil + } + out := new(BackstageDeployment) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackstageList) DeepCopyInto(out *BackstageList) { *out = *in @@ -176,7 +196,7 @@ func (in *BackstageSpec) DeepCopyInto(out *BackstageSpec) { } if in.Deployment != nil { in, out := &in.Deployment, &out.Deployment - *out = new(v1.JSON) + *out = new(BackstageDeployment) (*in).DeepCopyInto(*out) } } diff --git a/bundle/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/manifests/backstage-operator.clusterserviceversion.yaml index ddf4770e..f86745e2 100644 --- a/bundle/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/manifests/backstage-operator.clusterserviceversion.yaml @@ -21,7 +21,7 @@ metadata: } ] capabilities: Seamless Upgrades - createdAt: "2024-07-02T14:30:16Z" + createdAt: "2024-07-08T12:19:45Z" operatorframework.io/suggested-namespace: backstage-system operators.operatorframework.io/builder: operator-sdk-v1.33.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -32,15 +32,17 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: Backstage is the Schema for the backstages API + displayName: Backstage + kind: Backstage + name: backstages.rhdh.redhat.com + version: v1alpha1 - description: Backstage is the Schema for the backstages API displayName: Backstage kind: Backstage name: backstages.rhdh.redhat.com version: v1alpha2 - description: | - Operator to deploy Backstage on Kubernetes - - The telemetry data collection feature is enabled by default. In the default configuration of the operator, telemetry data will be sent to Red Hat by using the `backstage-plugin-analytics-provider-segment` plugin. To disable this and to learn what data is being collected, see https://github.com/janus-idp/backstage-showcase/blob/main/showcase-docs/getting-started.md#telemetry-collection + description: Operator to deploy Backstage on Kubernetes displayName: Red Hat Developer Hub Operator icon: - base64data: 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 diff --git a/bundle/manifests/rhdh.redhat.com_backstages.yaml b/bundle/manifests/rhdh.redhat.com_backstages.yaml index 79435165..8acba889 100644 --- a/bundle/manifests/rhdh.redhat.com_backstages.yaml +++ b/bundle/manifests/rhdh.redhat.com_backstages.yaml @@ -14,6 +14,372 @@ spec: singular: backstage scope: Namespaced versions: + - deprecated: true + deprecationWarning: Since 1.3.0 spec.application.image, spec.application.replicas, + spec.application.imagePullSecrets are deprecated in favor of corresponding spec.deployment + fields + name: v1alpha1 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: References to existing app-configs ConfigMap objects, + that will be mounted as files in the specified mount path. Each + element can be a reference to any ConfigMap or Secret, and will + be mounted inside the main application container under a specified + mount directory. Additionally, each file will be passed as a + `--config /mount/path/to/configmap/key` to the main container + args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing + several filenames, the order in which those files will be appended + to the main container args cannot be guaranteed. So if you want + to pass multiple app-config files, it is recommended to pass + one ConfigMap per app-config file. + properties: + configMaps: + description: List of ConfigMaps storing the app-config files. + Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, + it means that all keys in the ConfigMap will be mounted + as files. Otherwise, only the specified key will be mounted + as a file. Bear in mind not to put sensitive data in those + ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs + field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: 'Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: ''dynamic-plugins.yaml''.' + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: List of references to ConfigMaps objects to inject + as additional environment variables. For each item in this + array, if a key is not specified, it means that all keys + in the ConfigMap will be injected as additional environment + variables. Otherwise, only the specified key will be injected + as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: List of references to Secrets objects to inject + as additional environment variables. For each item in this + array, if a key is not specified, it means that all keys + in the Secret will be injected as additional environment + variables. Otherwise, only the specified key will be injected + as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: References to existing Config objects to use as extra + config files. They will be mounted as files in the specified + mount path. Each element can be a reference to any ConfigMap + or Secret. + properties: + configMaps: + description: List of references to ConfigMaps objects mounted + as extra files under the MountPath specified. For each item + in this array, if a key is not specified, it means that + all keys in the ConfigMap will be mounted as files. Otherwise, + only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: List of references to Secrets objects mounted + as extra files under the MountPath specified. For each item + in this array, a key must be specified that will be mounted + as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: Name of the object We support only ConfigMaps + and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: Custom image to use in all containers (including + Init Containers). It is your responsibility to make sure the + image is from trusted sources and has been validated for security + compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: Number of desired replicas to set in the Backstage + Deployment. Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: Host is an alias/DNS that points to the service. + Optional. Ignored if Enabled is false. If not specified + a route name will typically be automatically chosen. Must + follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: 'Subdomain is a DNS subdomain that is requested + within the ingress controller''s domain (as a subdomain). + Ignored if Enabled is false. Example: subdomain `frontend` + automatically receives the router subdomain `apps.mycluster.com` + to have a full hostname `frontend.apps.mycluster.com`.' + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: The tls field provides the ability to configure + certificates for the route. Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: certificate provides certificate contents. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: ExternalCertificateSecretName provides certificate + contents as a secret reference. This should be a single + serving certificate, not a certificate chain. Do not + include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: 'Name of the secret for database authentication. + Optional. For a local database deployment (EnableLocalDb=true), + a secret will be auto generated if it does not exist. The secret + shall include information used for the database access. An example + for PostgreSQL DB access: "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" "POSTGRES_USER": "postgres" "POSTGRESQL_ADMIN_PASSWORD": + "rl4s3Fh4ng3M4" "POSTGRES_HOST": "backstage-psql-bs1" # For + local database, set to "backstage-psql-".' + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} - name: v1alpha2 schema: openAPIV3Schema: @@ -287,6 +653,13 @@ spec: description: Valid fragment of Deployment to be merged with default/raw configuration. Set the Deployment's metadata and|or spec fields you want to override or add. Optional. + properties: + patch: + description: Valid fragment of Deployment to be merged with default/raw + configuration. Set the Deployment's metadata and|or spec fields + you want to override or add. Optional. + x-kubernetes-preserve-unknown-fields: true + type: object x-kubernetes-preserve-unknown-fields: true rawRuntimeConfig: description: Raw Runtime RuntimeObjects configuration. For Advanced diff --git a/config/crd/bases/rhdh.redhat.com_backstages.yaml b/config/crd/bases/rhdh.redhat.com_backstages.yaml index e6cc9bb6..e42a54e1 100644 --- a/config/crd/bases/rhdh.redhat.com_backstages.yaml +++ b/config/crd/bases/rhdh.redhat.com_backstages.yaml @@ -654,6 +654,13 @@ spec: description: Valid fragment of Deployment to be merged with default/raw configuration. Set the Deployment's metadata and|or spec fields you want to override or add. Optional. + properties: + patch: + description: Valid fragment of Deployment to be merged with default/raw + configuration. Set the Deployment's metadata and|or spec fields + you want to override or add. Optional. + x-kubernetes-preserve-unknown-fields: true + type: object x-kubernetes-preserve-unknown-fields: true rawRuntimeConfig: description: Raw Runtime RuntimeObjects configuration. For Advanced diff --git a/integration_tests/testdata/spec-deployment.yaml b/integration_tests/testdata/spec-deployment.yaml index 6084684f..bd1b1e9c 100644 --- a/integration_tests/testdata/spec-deployment.yaml +++ b/integration_tests/testdata/spec-deployment.yaml @@ -2,15 +2,16 @@ apiVersion: rhdh.redhat.com/v1alpha2 kind: Backstage spec: deployment: - spec: - template: - spec: - containers: - - name: backstage-backend - image: busybox - volumes: - - ephemeral: - volumeClaimTemplate: - spec: - storageClassName: "special" - name: dynamic-plugins-root \ No newline at end of file + patch: + spec: + template: + spec: + containers: + - name: backstage-backend + image: busybox + volumes: + - ephemeral: + volumeClaimTemplate: + spec: + storageClassName: "special" + name: dynamic-plugins-root \ No newline at end of file diff --git a/pkg/model/deployment.go b/pkg/model/deployment.go index 6a780b05..607c425c 100644 --- a/pkg/model/deployment.go +++ b/pkg/model/deployment.go @@ -85,22 +85,8 @@ func (b *BackstageDeployment) addToModel(model *BackstageModel, backstage bsv1.B } b.deployment.Spec.Template.ObjectMeta.Annotations[ExtConfigHashAnnotation] = model.ExternalConfig.GetHash() - if conf := backstage.Spec.Deployment; conf != nil { - - deplStr, err := yaml.Marshal(b.deployment) - if err != nil { - return false, fmt.Errorf("Can not marshal deployment object: %s", err) - } - - merged, err := merge2.MergeStrings(string(conf.Raw), string(deplStr), false, kyaml.MergeOptions{}) - if err != nil { - return false, fmt.Errorf("Can not merge spec.deployment: %s", err) - } - - err = yaml.Unmarshal([]byte(merged), b.deployment) - if err != nil { - return false, fmt.Errorf("Can not unmarshal merged deployment: %s", err) - } + if err := b.setDeployment(backstage); err != nil { + return false, err } model.backstageDeployment = b @@ -118,13 +104,6 @@ func (b *BackstageDeployment) addToModel(model *BackstageModel, backstage bsv1.B // implementation of RuntimeObject interface func (b *BackstageDeployment) validate(model *BackstageModel, backstage bsv1.Backstage) error { - if backstage.Spec.Application != nil { - b.setReplicas(backstage.Spec.Application.Replicas) - utils.SetImagePullSecrets(b.podSpec(), backstage.Spec.Application.ImagePullSecrets) - b.setImage(backstage.Spec.Application.Image) - b.addExtraEnvs(backstage.Spec.Application.ExtraEnvs) - } - for _, bso := range model.RuntimeObjects { if bs, ok := bso.(BackstagePodContributor); ok { bs.updatePod(b.deployment) @@ -172,6 +151,39 @@ func (b *BackstageDeployment) podSpec() *corev1.PodSpec { return &b.deployment.Spec.Template.Spec } +func (b *BackstageDeployment) setDeployment(backstage bsv1.Backstage) error { + + // set from backstage.Spec.Application + if backstage.Spec.Application != nil { + b.setReplicas(backstage.Spec.Application.Replicas) + utils.SetImagePullSecrets(b.podSpec(), backstage.Spec.Application.ImagePullSecrets) + b.setImage(backstage.Spec.Application.Image) + b.addExtraEnvs(backstage.Spec.Application.ExtraEnvs) + } + + // set from backstage.Spec.Deployment + if backstage.Spec.Deployment != nil { + if conf := backstage.Spec.Deployment.Patch; conf != nil { + + deplStr, err := yaml.Marshal(b.deployment) + if err != nil { + return fmt.Errorf("can not marshal deployment object: %w", err) + } + + merged, err := merge2.MergeStrings(string(conf.Raw), string(deplStr), false, kyaml.MergeOptions{}) + if err != nil { + return fmt.Errorf("can not merge spec.deployment: %w", err) + } + + err = yaml.Unmarshal([]byte(merged), b.deployment) + if err != nil { + return fmt.Errorf("can not unmarshal merged deployment: %w", err) + } + } + } + return nil +} + // sets the amount of replicas (used by CR config) func (b *BackstageDeployment) setReplicas(replicas *int32) { if replicas != nil { diff --git a/pkg/model/deployment_test.go b/pkg/model/deployment_test.go index 3ea9859b..f257cbaa 100644 --- a/pkg/model/deployment_test.go +++ b/pkg/model/deployment_test.go @@ -109,7 +109,8 @@ func TestSpecImagePullSecrets(t *testing.T) { func TestMergeFromSpecDeployment(t *testing.T) { bs := *deploymentTestBackstage.DeepCopy() - bs.Spec.Deployment = &apiextensionsv1.JSON{ + bs.Spec.Deployment = &bsv1.BackstageDeployment{} + bs.Spec.Deployment.Patch = &apiextensionsv1.JSON{ Raw: []byte(` metadata: labels: @@ -167,3 +168,32 @@ spec: // adds new volume assert.Equal(t, "my-vol", model.backstageDeployment.deployment.Spec.Template.Spec.Volumes[2].Name) } + +// to remove when stop supporting v1alpha1 +func TestDeploymentFieldPrevailsOnDeprecated(t *testing.T) { + bs := *deploymentTestBackstage.DeepCopy() + bs.Spec.Application.Image = ptr.To("app-image") + bs.Spec.Application.Replicas = ptr.To(int32(2)) + bs.Spec.Deployment = &bsv1.BackstageDeployment{} + bs.Spec.Deployment.Patch = &apiextensionsv1.JSON{ + Raw: []byte(` +spec: + replicas: 3 + template: + spec: + containers: + - name: backstage-backend + image: deployment-image +`), + } + + testObj := createBackstageTest(bs).withDefaultConfig(true). + addToDefaultConfig("deployment.yaml", "janus-deployment.yaml") + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, true, true, testObj.scheme) + assert.NoError(t, err) + + assert.Equal(t, "backstage-backend", model.backstageDeployment.container().Name) + assert.Equal(t, "deployment-image", model.backstageDeployment.container().Image) + assert.Equal(t, int32(3), *model.backstageDeployment.deployment.Spec.Replicas) +} From ca05c8217eb557d1bb8a4f15671e96809a534c17 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jul 2024 12:45:18 +0000 Subject: [PATCH 08/10] Regenerate bundle manifests Co-authored-by: gazarenkov --- .../backstage-operator.clusterserviceversion.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bundle/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/manifests/backstage-operator.clusterserviceversion.yaml index 621b8b79..b6396b6a 100644 --- a/bundle/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/manifests/backstage-operator.clusterserviceversion.yaml @@ -21,7 +21,7 @@ metadata: } ] capabilities: Seamless Upgrades - createdAt: "2024-07-08T12:19:45Z" + createdAt: "2024-07-08T12:45:16Z" operatorframework.io/suggested-namespace: backstage-system operators.operatorframework.io/builder: operator-sdk-v1.33.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -42,10 +42,7 @@ spec: kind: Backstage name: backstages.rhdh.redhat.com version: v1alpha2 - description: | - Operator to deploy Backstage on Kubernetes - - The telemetry data collection feature is enabled by default. In the default configuration of the operator, telemetry data will be sent to Red Hat by using the `backstage-plugin-analytics-provider-segment` plugin. To disable this and to learn what data is being collected, see https://github.com/janus-idp/backstage-showcase/blob/main/showcase-docs/getting-started.md#telemetry-collection + description: Operator to deploy Backstage on Kubernetes displayName: Red Hat Developer Hub Operator icon: - base64data: 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 From 496bb19c634a575c5f38589034103fbe1c862fb7 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Mon, 8 Jul 2024 17:35:53 +0300 Subject: [PATCH 09/10] fixes after comments Signed-off-by: gazarenkov --- pkg/model/db-statefulset.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/model/db-statefulset.go b/pkg/model/db-statefulset.go index 4b97640b..4e6a3dae 100644 --- a/pkg/model/db-statefulset.go +++ b/pkg/model/db-statefulset.go @@ -95,9 +95,10 @@ func (b *DbStatefulSet) validate(model *BackstageModel, backstage bsv1.Backstage // point ServiceName to localDb b.statefulSet.Spec.ServiceName = model.LocalDbService.service.Name - if backstage.Spec.Application != nil { + if backstage.Spec.Application != nil && backstage.Spec.Application.ImagePullSecrets != nil { utils.SetImagePullSecrets(b.podSpec(), backstage.Spec.Application.ImagePullSecrets) } + if backstage.Spec.IsAuthSecretSpecified() { utils.SetDbSecretEnvVar(b.container(), backstage.Spec.Database.AuthSecretName) } else if model.LocalDbSecret != nil { From 2aa8525728e2a4a7c872b3277bf5fc5a98f65b79 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Mon, 15 Jul 2024 11:49:49 +0300 Subject: [PATCH 10/10] go.sum for 1.21 Signed-off-by: gazarenkov --- go.sum | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/go.sum b/go.sum index 2df7f759..52efd064 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= @@ -19,6 +20,8 @@ github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1 github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -28,8 +31,9 @@ github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -49,8 +53,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -81,6 +85,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= @@ -93,8 +99,9 @@ github.com/openshift/api v0.0.0-20240419172957-f39cf2ef93fd h1:DztdAsKaNJjfL12Ly github.com/openshift/api v0.0.0-20240419172957-f39cf2ef93fd/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= @@ -103,8 +110,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -117,6 +124,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -207,6 +216,8 @@ sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9 sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=