From 5af6346a37175eab2b14e70230674c714af8faee Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Tue, 22 Oct 2024 16:58:25 +0200 Subject: [PATCH] Add service tiles entry to generated config map. --- api/v1alpha1/frontend_types.go | 15 +- api/v1alpha1/frontendenvironment_types.go | 29 ++++ api/v1alpha1/zz_generated.deepcopy.go | 92 +++++++++++ ...cloud.redhat.com_frontendenvironments.yaml | 29 ++++ .../crd/bases/cloud.redhat.com_frontends.yaml | 3 + controllers/frontend_controller_suite_test.go | 146 ++++++++++++++++++ controllers/reconcile.go | 65 ++++++++ deploy.yml | 33 ++++ examples/feenvironment.yaml | 13 ++ examples/landing.yaml | 16 ++ kuttl-config.yml | 2 +- .../00-create-namespace.yaml | 8 + .../01-create-resources.yaml | 57 +++++++ .../e2e/generate-service-tiles/02-assert.yaml | 52 +++++++ 14 files changed, 552 insertions(+), 8 deletions(-) create mode 100644 tests/e2e/generate-service-tiles/00-create-namespace.yaml create mode 100644 tests/e2e/generate-service-tiles/01-create-resources.yaml create mode 100644 tests/e2e/generate-service-tiles/02-assert.yaml diff --git a/api/v1alpha1/frontend_types.go b/api/v1alpha1/frontend_types.go index 7efbbcf5..04a2ff0a 100644 --- a/api/v1alpha1/frontend_types.go +++ b/api/v1alpha1/frontend_types.go @@ -49,13 +49,14 @@ type SearchEntry struct { } type ServiceTile struct { - Section string `json:"section" yaml:"section"` - Group string `json:"group" yaml:"group"` - ID string `json:"id" yaml:"id"` - Href string `json:"href" yaml:"href"` - Title string `json:"title" yaml:"title"` - Icon string `json:"icon" yaml:"icon"` - IsExternal bool `json:"isExternal,omitempty" yaml:"isExternal,omitempty"` + Section string `json:"section" yaml:"section"` + Group string `json:"group" yaml:"group"` + ID string `json:"id" yaml:"id"` + Href string `json:"href" yaml:"href"` + Title string `json:"title" yaml:"title"` + Description string `json:"description" yaml:"description"` + Icon string `json:"icon" yaml:"icon"` + IsExternal bool `json:"isExternal,omitempty" yaml:"isExternal,omitempty"` } type WidgetHeaderLink struct { diff --git a/api/v1alpha1/frontendenvironment_types.go b/api/v1alpha1/frontendenvironment_types.go index 3632abee..ed1e79e6 100644 --- a/api/v1alpha1/frontendenvironment_types.go +++ b/api/v1alpha1/frontendenvironment_types.go @@ -24,6 +24,33 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +type FrontendServiceCategoryGroup struct { + ID string `json:"id" yaml:"id"` + Title string `json:"title" yaml:"title"` +} + +// FrontendServiceCategory defines the category to which service can inject ServiceTiles +// Chroming UI will use this to render the service dropdown component +type FrontendServiceCategory struct { + ID string `json:"id" yaml:"id"` + Title string `json:"title" yaml:"title"` + // +kubebuilder:validation:items:MinItems:=1 + Groups []FrontendServiceCategoryGroup `json:"groups" yaml:"groups"` +} + +type FrontendServiceCategoryGroupGenerated struct { + ID string `json:"id" yaml:"id"` + Title string `json:"title" yaml:"title"` + Tiles *[]ServiceTile `json:"tiles" yaml:"tiles"` +} + +// The categories but with the groups filled with service tiles +type FrontendServiceCategoryGenerated struct { + ID string `json:"id" yaml:"id"` + Title string `json:"title" yaml:"title"` + Groups []FrontendServiceCategoryGroupGenerated `json:"groups" yaml:"groups"` +} + // FrontendEnvironmentSpec defines the desired state of FrontendEnvironment type FrontendEnvironmentSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster @@ -65,6 +92,8 @@ type FrontendEnvironmentSpec struct { // List of namespaces that should receive a copy of the frontend configuration as a config map // By configurations we mean the fed-modules.json, navigation files, etc. TargetNamespaces []string `json:"targetNamespaces,omitempty" yaml:"targetNamespaces,omitempty"` + // For the ChromeUI to render additional global components + ServiceCategories *[]FrontendServiceCategory `json:"serviceCategories,omitempty" yaml:"serviceCategories,omitempty"` } type MonitoringConfig struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c7a00ee2..a9ef641e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -442,6 +442,17 @@ func (in *FrontendEnvironmentSpec) DeepCopyInto(out *FrontendEnvironmentSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ServiceCategories != nil { + in, out := &in.ServiceCategories, &out.ServiceCategories + *out = new([]FrontendServiceCategory) + if **in != nil { + in, out := *in, *out + *out = make([]FrontendServiceCategory, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrontendEnvironmentSpec. @@ -521,6 +532,87 @@ func (in *FrontendList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FrontendServiceCategory) DeepCopyInto(out *FrontendServiceCategory) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]FrontendServiceCategoryGroup, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrontendServiceCategory. +func (in *FrontendServiceCategory) DeepCopy() *FrontendServiceCategory { + if in == nil { + return nil + } + out := new(FrontendServiceCategory) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FrontendServiceCategoryGenerated) DeepCopyInto(out *FrontendServiceCategoryGenerated) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]FrontendServiceCategoryGroupGenerated, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrontendServiceCategoryGenerated. +func (in *FrontendServiceCategoryGenerated) DeepCopy() *FrontendServiceCategoryGenerated { + if in == nil { + return nil + } + out := new(FrontendServiceCategoryGenerated) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FrontendServiceCategoryGroup) DeepCopyInto(out *FrontendServiceCategoryGroup) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrontendServiceCategoryGroup. +func (in *FrontendServiceCategoryGroup) DeepCopy() *FrontendServiceCategoryGroup { + if in == nil { + return nil + } + out := new(FrontendServiceCategoryGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FrontendServiceCategoryGroupGenerated) DeepCopyInto(out *FrontendServiceCategoryGroupGenerated) { + *out = *in + if in.Tiles != nil { + in, out := &in.Tiles, &out.Tiles + *out = new([]ServiceTile) + if **in != nil { + in, out := *in, *out + *out = make([]ServiceTile, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrontendServiceCategoryGroupGenerated. +func (in *FrontendServiceCategoryGroupGenerated) DeepCopy() *FrontendServiceCategoryGroupGenerated { + if in == nil { + return nil + } + out := new(FrontendServiceCategoryGroupGenerated) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FrontendSpec) DeepCopyInto(out *FrontendSpec) { *out = *in diff --git a/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml b/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml index d17949cd..821c57a4 100644 --- a/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml +++ b/config/crd/bases/cloud.redhat.com_frontendenvironments.yaml @@ -92,6 +92,35 @@ spec: - disabled - mode type: object + serviceCategories: + description: For the ChromeUI to render additional global components + items: + description: |- + FrontendServiceCategory defines the category to which service can inject ServiceTiles + Chroming UI will use this to render the service dropdown component + properties: + groups: + items: + properties: + id: + type: string + title: + type: string + required: + - id + - title + type: object + type: array + id: + type: string + title: + type: string + required: + - groups + - id + - title + type: object + type: array ssl: description: |- SSL mode requests SSL from the services in openshift and k8s and then applies them to the diff --git a/config/crd/bases/cloud.redhat.com_frontends.yaml b/config/crd/bases/cloud.redhat.com_frontends.yaml index 4c944baa..20ba72df 100644 --- a/config/crd/bases/cloud.redhat.com_frontends.yaml +++ b/config/crd/bases/cloud.redhat.com_frontends.yaml @@ -343,6 +343,8 @@ spec: description: Data for the all services dropdown items: properties: + description: + type: string group: type: string href: @@ -358,6 +360,7 @@ spec: title: type: string required: + - description - group - href - icon diff --git a/controllers/frontend_controller_suite_test.go b/controllers/frontend_controller_suite_test.go index 8118a152..b369fb1f 100644 --- a/controllers/frontend_controller_suite_test.go +++ b/controllers/frontend_controller_suite_test.go @@ -1291,3 +1291,149 @@ var _ = ginkgo.Describe("Widget registry", func() { }) }) }) + +type ServiceTileTestEntry struct { + ServiceTiles []*crd.ServiceTile + FrontendName string +} + +type ServiceTileCase struct { + ServiceTiles []*ServiceTileTestEntry + Namespace string + Environment string + ExpectedConfigMapEntry string +} + +func frontendFromServiceTile(sct ServiceTileCase, ste ServiceTileTestEntry, st []*crd.ServiceTile) *crd.Frontend { + frontend := &crd.Frontend{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "cloud.redhat.com/v1", + Kind: "Frontend", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: ste.FrontendName, + Namespace: sct.Namespace, + }, + Spec: crd.FrontendSpec{ + EnvName: sct.Environment, + Title: "", + DeploymentRepo: "", + Frontend: crd.FrontendInfo{ + Paths: []string{""}, + }, + Image: "my-image:version", + Module: &crd.FedModule{ + ManifestLocation: "", + Modules: []crd.Module{}, + }, + ServiceTiles: st, + }, + } + return frontend +} + +var _ = ginkgo.Describe("Service tiles", func() { + const ( + FrontendName = "test-service-tile" + FrontendName2 = "test-service-tile2" + FrontendNamespace = "default" + FrontendEnvName = "test-service-tile-env" + ServiceSectionID = "test-service-section" + ServiceSectionGroupID1 = "test-service-section-group1" + ServiceSectionGroupID2 = "test-service-section-group2" + + timeout = time.Second * 10 + duration = time.Second * 10 + interval = time.Millisecond * 250 + ) + + var ( + ServiceTile1 = &crd.ServiceTile{ + Section: ServiceSectionID, + Group: ServiceSectionGroupID1, + ID: "test-service-tile1", + Href: "/foo", + Title: "bar", + Description: "", + Icon: "", + } + ServiceTile2 = &crd.ServiceTile{ + Section: ServiceSectionID, + Group: ServiceSectionGroupID1, + ID: "test-service-tile2", + Href: "/bar", + Title: "bar", + Description: "", + Icon: "", + } + ServiceTile3 = &crd.ServiceTile{ + Section: ServiceSectionID, + Group: ServiceSectionGroupID2, + ID: "test-service-tile3", + Href: "/baz", + Title: "baz", + Description: "", + Icon: "", + } + ExpectedServiceTiles1 = []crd.FrontendServiceCategoryGenerated{ + { + ID: ServiceSectionID, + Title: "Service Section", + Groups: []crd.FrontendServiceCategoryGroupGenerated{{ + ID: ServiceSectionGroupID1, + Title: "Service Section Group 1", + Tiles: &[]crd.ServiceTile{*ServiceTile1, *ServiceTile2}, + }, { + ID: ServiceSectionGroupID2, + Title: "Service Section Group 2", + Tiles: &[]crd.ServiceTile{*ServiceTile3}, + }}, + }, + } + ) + + ginkgo.It("Should create service tiles", func() { + ginkgo.By("collection entries from Frontend resources", func() { + expectedResult, err := json.Marshal(ExpectedServiceTiles1) + gomega.Expect(err).Should(gomega.BeNil()) + serviceTileCases := []ServiceTileCase{{ + Namespace: FrontendNamespace, + Environment: FrontendEnvName, + ExpectedConfigMapEntry: string(expectedResult), + ServiceTiles: []*ServiceTileTestEntry{{ + ServiceTiles: []*crd.ServiceTile{ServiceTile1, ServiceTile2, ServiceTile3}, + FrontendName: FrontendName, + }}, + }} + + for _, serviceCase := range serviceTileCases { + ctx := context.Background() + configMapLookupKey := types.NamespacedName{Name: serviceCase.Environment, Namespace: serviceCase.Namespace} + for _, sc := range serviceCase.ServiceTiles { + frontend := frontendFromServiceTile(serviceCase, *sc, sc.ServiceTiles) + gomega.Expect(k8sClient.Create(ctx, frontend)).Should(gomega.Succeed()) + } + + frontendEnvironment := mockFrontendEnv(serviceCase.Environment, serviceCase.Namespace) + gomega.Expect(k8sClient.Create(ctx, frontendEnvironment)).Should(gomega.Succeed()) + createdConfigMap := &v1.ConfigMap{} + gomega.Eventually(func() bool { + err := k8sClient.Get(ctx, configMapLookupKey, createdConfigMap) + if err != nil { + return err == nil + } + if len(createdConfigMap.Data) != 2 { + return false + } + return true + }, timeout, interval).Should(gomega.BeTrue()) + + widgetRegistryMap := createdConfigMap.Data["service-tiles.json"] + + gomega.Expect(createdConfigMap.Name).Should(gomega.Equal(serviceCase.Environment)) + gomega.Expect(widgetRegistryMap).Should(gomega.Equal(serviceCase.ExpectedConfigMapEntry)) + gomega.Expect(createdConfigMap.ObjectMeta.OwnerReferences[0].Name).Should(gomega.Equal(serviceCase.Environment)) + } + }) + }) +}) diff --git a/controllers/reconcile.go b/controllers/reconcile.go index 6b927458..57c96589 100644 --- a/controllers/reconcile.go +++ b/controllers/reconcile.go @@ -889,6 +889,58 @@ func setupWidgetRegistry(felist *crd.FrontendList) []crd.WidgetEntry { return widgetRegistry } +func getServiceTilePath(section string, group string) string { + return fmt.Sprintf("%s-%s", section, group) +} + +func setupServiceTilesData(felist *crd.FrontendList, feEnvironment crd.FrontendEnvironment) []crd.FrontendServiceCategoryGenerated { + categories := []crd.FrontendServiceCategoryGenerated{} + if feEnvironment.Spec.ServiceCategories == nil { + // skip if we do not have service categories + return categories + } + + // just a quick cache to make it easier and faster to assign tiles to their destination + tileGroupAccessMap := make(map[string]*[]crd.ServiceTile) + + for _, category := range *feEnvironment.Spec.ServiceCategories { + groups := []crd.FrontendServiceCategoryGroupGenerated{} + for _, gr := range category.Groups { + tiles := []crd.ServiceTile{} + group := crd.FrontendServiceCategoryGroupGenerated{ + ID: gr.ID, + Title: gr.Title, + Tiles: &tiles, + } + groups = append(groups, group) + groupKey := getServiceTilePath(category.ID, gr.ID) + tileGroupAccessMap[groupKey] = &tiles + } + newCategory := crd.FrontendServiceCategoryGenerated{ + ID: category.ID, + Title: category.Title, + Groups: groups, + } + + categories = append(categories, newCategory) + } + + for _, frontend := range felist.Items { + if frontend.Spec.ServiceTiles != nil { + for _, tile := range frontend.Spec.ServiceTiles { + groupKey := getServiceTilePath(tile.Section, tile.Group) + // ignore the tile if destination does not exist + if groupTiles, ok := tileGroupAccessMap[groupKey]; ok { + // assign the tile to the service category and group + *groupTiles = append(*groupTiles, *tile) + } + } + } + } + + return categories +} + func (r *FrontendReconciliation) setupBundleData(cfgMap *v1.ConfigMap, cacheMap map[string]crd.Frontend) error { bundleList := &crd.BundleList{} @@ -1019,6 +1071,8 @@ func (r *FrontendReconciliation) populateConfigMap(cfgMap *v1.ConfigMap, cacheMa widgetRegistry := setupWidgetRegistry(feList) + serviceCategories := setupServiceTilesData(feList, *r.FrontendEnvironment) + fedModulesJSONData, err := json.Marshal(fedModules) if err != nil { return err @@ -1035,6 +1089,12 @@ func (r *FrontendReconciliation) populateConfigMap(cfgMap *v1.ConfigMap, cacheMa return err } + serviceCategoriesJSONData, err := json.Marshal(serviceCategories) + + if err != nil { + return err + } + cfgMap.Data["fed-modules.json"] = string(fedModulesJSONData) if len(searchIndex) > 0 { cfgMap.Data["search-index.json"] = string(searchIndexJSONData) @@ -1043,6 +1103,11 @@ func (r *FrontendReconciliation) populateConfigMap(cfgMap *v1.ConfigMap, cacheMa if len(widgetRegistry) > 0 { cfgMap.Data["widget-registry.json"] = string(widgetRegistryJSONData) } + + if len(serviceCategories) > 0 { + cfgMap.Data["service-tiles.json"] = string(serviceCategoriesJSONData) + } + return nil } diff --git a/deploy.yml b/deploy.yml index 8b2da6a3..5393e0fd 100644 --- a/deploy.yml +++ b/deploy.yml @@ -410,6 +410,36 @@ objects: - disabled - mode type: object + serviceCategories: + description: For the ChromeUI to render additional global components + items: + description: 'FrontendServiceCategory defines the category to + which service can inject ServiceTiles + + Chroming UI will use this to render the service dropdown component' + properties: + groups: + items: + properties: + id: + type: string + title: + type: string + required: + - id + - title + type: object + type: array + id: + type: string + title: + type: string + required: + - groups + - id + - title + type: object + type: array ssl: description: 'SSL mode requests SSL from the services in openshift and k8s and then applies them to the @@ -799,6 +829,8 @@ objects: description: Data for the all services dropdown items: properties: + description: + type: string group: type: string href: @@ -814,6 +846,7 @@ objects: title: type: string required: + - description - group - href - icon diff --git a/examples/feenvironment.yaml b/examples/feenvironment.yaml index 059da5db..d1dcb5ce 100644 --- a/examples/feenvironment.yaml +++ b/examples/feenvironment.yaml @@ -9,3 +9,16 @@ spec: monitoring: mode: "local" disabled: false + serviceCategories: + - id: automation + title: Automation + groups: + - id: ansible + title: Ansible + - id: rhel + title: Red Hat Enterprise Linux + - id: iam + title: Identity and Access Management + groups: + - id: iam + title: IAM diff --git a/examples/landing.yaml b/examples/landing.yaml index a4179259..380e2790 100644 --- a/examples/landing.yaml +++ b/examples/landing.yaml @@ -61,3 +61,19 @@ spec: h: 1 maxH: 1 minH: 1 + serviceTiles: + - section: automation + group: ansible + id: ansible-link + title: Ansible FOO + href: /ansible/foo + description: Ansible FOO description thing + icon: AnsibleIcon + - section: iam + group: iam + id: iam-link + title: IAM FOO + href: /iam + description: Some Iam thing + icon: IAMIcon + isExternal: false diff --git a/kuttl-config.yml b/kuttl-config.yml index 4ad2fc36..e18b057d 100644 --- a/kuttl-config.yml +++ b/kuttl-config.yml @@ -5,7 +5,7 @@ startControlPlane: true crdDir: config/crd/test-resources/ testDirs: - tests/e2e/ -timeout: 320 +timeout: 30 parallel: 1 commands: - command: make install diff --git a/tests/e2e/generate-service-tiles/00-create-namespace.yaml b/tests/e2e/generate-service-tiles/00-create-namespace.yaml new file mode 100644 index 00000000..f4964545 --- /dev/null +++ b/tests/e2e/generate-service-tiles/00-create-namespace.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test-service-tiles +spec: + finalizers: + - kubernetes diff --git a/tests/e2e/generate-service-tiles/01-create-resources.yaml b/tests/e2e/generate-service-tiles/01-create-resources.yaml new file mode 100644 index 00000000..e5ca068b --- /dev/null +++ b/tests/e2e/generate-service-tiles/01-create-resources.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: FrontendEnvironment +metadata: + name: test-service-tiles-environment +spec: + generateNavJSON: false + ssl: false + hostname: foo.redhat.com + sso: https://sso.foo.redhat.com + serviceCategories: + - id: automation + title: Automation + groups: + - id: ansible + title: Ansible + - id: rhel + title: Red Hat Enterprise Linux + - id: iam + title: Identity and Access Management + groups: + - id: iam + title: IAM +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: Frontend +metadata: + name: service-tiles + namespace: test-service-tiles +spec: + envName: test-service-tiles-environment + title: service-tiles + deploymentRepo: https://github.com/RedHatInsights/service-tiles-frontend + frontend: + paths: + - /apps/service-tiles + image: "quay.io/cloudservices/service-tiles-frontend:3244a17" + module: + manifestLocation: /apps/service-tiles/fed-mods.json + modules: [] + moduleID: service-tiles + serviceTiles: + - section: automation + group: ansible + id: ansible-link + title: Ansible FOO + href: /ansible/foo + description: Ansible FOO description thing + icon: AnsibleIcon + - section: iam + group: iam + id: iam-link + title: IAM FOO + href: /iam + description: Some Iam thing + icon: IAMIcon + isExternal: true diff --git a/tests/e2e/generate-service-tiles/02-assert.yaml b/tests/e2e/generate-service-tiles/02-assert.yaml new file mode 100644 index 00000000..50511d79 --- /dev/null +++ b/tests/e2e/generate-service-tiles/02-assert.yaml @@ -0,0 +1,52 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: service-tiles-frontend + namespace: test-service-tiles + labels: + frontend: service-tiles + ownerReferences: + - apiVersion: cloud.redhat.com/v1alpha1 + kind: Frontend + name: service-tiles +spec: + selector: + matchLabels: + frontend: service-tiles + template: + spec: + volumes: + - name: config + configMap: + name: test-service-tiles-environment + defaultMode: 420 + containers: + - name: fe-image + image: quay.io/cloudservices/service-tiles-frontend:3244a17 + ports: + - name: web + containerPort: 80 + protocol: TCP + - name: metrics + containerPort: 9000 + protocol: TCP + volumeMounts: + - name: config + mountPath: /opt/app-root/src/build/stable/operator-generated +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: test-service-tiles-environment + namespace: test-service-tiles + labels: + frontendenv: test-service-tiles-environment + ownerReferences: + - apiVersion: cloud.redhat.com/v1alpha1 + name: test-service-tiles-environment +data: + fed-modules.json: >- + {"service-tiles":{"manifestLocation":"/apps/service-tiles/fed-mods.json","moduleID":"service-tiles","fullProfile":false}} + service-tiles.json: >- + [{"id":"automation","title":"Automation","groups":[{"id":"ansible","title":"Ansible","tiles":[{"section":"automation","group":"ansible","id":"ansible-link","href":"/ansible/foo","title":"Ansible FOO","description":"Ansible FOO description thing","icon":"AnsibleIcon"}]},{"id":"rhel","title":"Red Hat Enterprise Linux","tiles":[]}]},{"id":"iam","title":"Identity and Access Management","groups":[{"id":"iam","title":"IAM","tiles":[{"section":"iam","group":"iam","id":"iam-link","href":"/iam","title":"IAM FOO","description":"Some Iam thing","icon":"IAMIcon","isExternal":true}]}]}]