diff --git a/pkg/comp-functions/functions/common/ingress.go b/pkg/comp-functions/functions/common/ingress.go new file mode 100644 index 0000000000..641ea6e217 --- /dev/null +++ b/pkg/comp-functions/functions/common/ingress.go @@ -0,0 +1,286 @@ +package common + +import ( + "fmt" + "strings" + + "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + "gopkg.in/yaml.v2" + netv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +// IngressConfig contains general information for generating an Ingress object +type IngressConfig struct { + AdditionalAnnotations map[string]string // Optional + AdditionalIngressNames []string // Optional + FQDNs []string + ServiceConfig IngressRuleConfig + TlsCertBaseName string +} + +// IngressRuleConfig describes an ingress rule configuration +type IngressRuleConfig struct { + RelPath string // Optional, defaults to "/" + ServiceNameSuffix string // Optional + ServicePortName string // Has preference over ServicePortNumber + ServicePortNumber int32 +} + +// Checks if an FQDN is part of a reference FQDN, e.g. an OpenShift Apps domain; "*nextcloud*.apps.cluster.com". +// Returns true if yes and FQDN is not a 2nd level subdomain (i.e. *sub2.sub1*.apps.cluster.com) +func IsSingleSubdomainOfRefDomain(fqdn string, reference string) bool { + if !strings.Contains(fqdn, reference) || reference == "" { + return false + } + + noSuffix, _ := strings.CutSuffix(fqdn, reference) + return len(strings.Split(noSuffix, ".")) == 2 // Handles prefixed dot of reference domain +} + +// Obtain ingress annotations and optionally extend them using additionalAnnotations +func getIngressAnnotations(svc *runtime.ServiceRuntime, additionalAnnotations map[string]string) map[string]string { + annotations := map[string]string{} + if svc.Config.Data["ingress_annotations"] != "" { + err := yaml.Unmarshal([]byte(svc.Config.Data["ingress_annotations"]), annotations) + if err != nil { + svc.Log.Error(err, "cannot unmarshal ingress annotations from input") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot unmarshal ingress annotations from input: %s", err))) + } + } else { + svc.Log.Info("no ingress annotations are defined") + } + + for k, v := range additionalAnnotations { + annotations[k] = v + } + + return annotations +} + +// Creates ingress rules based on a single service name and port. +// Will use svcPortName over svcPortNumber (if specified) +func createIngressRule(comp InfoGetter, fqdns []string, ruleConfig IngressRuleConfig) ([]netv1.IngressRule, error) { + if ruleConfig.ServicePortName == "" && ruleConfig.ServicePortNumber == 0 { + return nil, fmt.Errorf("no service port name or number has been defined") + } + if len(fqdns) == 0 { + return nil, fmt.Errorf("no FQDNs have been defined") + } + + svcNameSuffix := ruleConfig.ServiceNameSuffix + if !strings.HasPrefix(svcNameSuffix, "-") && len(svcNameSuffix) > 0 { + svcNameSuffix = "-" + svcNameSuffix + } + + relPath := ruleConfig.RelPath + if relPath == "" { + relPath = "/" + } + + ingressRules := []netv1.IngressRule{} + + // prefer ServicePortName over ServicePortNumber + serviceBackendPort := netv1.ServiceBackendPort{} + if ruleConfig.ServicePortName != "" { + serviceBackendPort.Name = ruleConfig.ServicePortName + } else { + serviceBackendPort.Number = ruleConfig.ServicePortNumber + } + + for _, fqdn := range fqdns { + rule := netv1.IngressRule{ + Host: fqdn, + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: relPath, + PathType: ptr.To(netv1.PathType("Prefix")), + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: comp.GetName() + svcNameSuffix, + Port: serviceBackendPort, + }, + }, + }, + }, + }, + }, + } + ingressRules = append(ingressRules, rule) + } + + return ingressRules, nil +} + +// Generate an ingress TLS secret name as "-ingress-cert" +func generateTlsSecretName(baseName string) string { + return strings.Trim(baseName, "-") + "-ingress-cert" +} + +// Generate an ingress name. additionalNames will be assembled as "comp.GetName()----ingress" +func generateIngressName(comp InfoGetter, additionalNames ...string) string { + ingressName := comp.GetName() + if len(additionalNames) > 0 { + for _, n := range additionalNames { + n = strings.Trim(n, "-") + ingressName = ingressName + "-" + n + } + } + ingressName = ingressName + "-ingress" + + return ingressName +} + +// Generate up to 2 ingresses that bundle FQDNs depending on the following: +// FQDNs that are one subdomain ON defaultAppsDomain (e.g. sub1.apps.cluster.com) -> Empty TLS config (uses wildcard cert on OCP). +// FQDNs that do not statisfy the former -> TLS config using a Let's Encrypt certificate. +func GenerateBundledIngresses(comp InfoGetter, svc *runtime.ServiceRuntime, ingressConfig IngressConfig) ([]*netv1.Ingress, error) { + if len(ingressConfig.FQDNs) == 0 { + return nil, fmt.Errorf("no FQDNs") + } + + for _, fqdn := range ingressConfig.FQDNs { + if fqdn == "" { + return nil, fmt.Errorf("an empty FQDN has been passed. Passed FQDNs are: %v", ingressConfig.FQDNs) + } + } + + // bool: true -> Use wildcard, false -> Use LE, []string: List of FQDNs + ingressMap := map[bool][]string{} + + ocpDefaultAppsDomain := svc.Config.Data["ocpDefaultAppsDomain"] + svc.Log.Info(fmt.Sprintf("ocpAppsDomain is: '%s'", ocpDefaultAppsDomain)) + + if ocpDefaultAppsDomain != "" { + for _, fqdn := range ingressConfig.FQDNs { + useWildcard := IsSingleSubdomainOfRefDomain(fqdn, ocpDefaultAppsDomain) + svc.Log.Info(fmt.Sprintf("FQDN %s will use wildcard cert: %v", fqdn, useWildcard)) + ingressMap[useWildcard] = append(ingressMap[useWildcard], fqdn) + } + } else { + ingressMap[false] = ingressConfig.FQDNs + } + + // Create ingresses that bundle FQDNs depending on their certificate requirements (LE/Wildcard) + var ingresses []*netv1.Ingress + fqdnsLetsEncrypt, fqdnsWildcard := ingressMap[false], ingressMap[true] + + tlsName := generateTlsSecretName(ingressConfig.TlsCertBaseName) + if tlsName == "" { + return nil, fmt.Errorf("a TLS cert base name must be defined") + } + + ingressMetadata := metav1.ObjectMeta{ + Namespace: comp.GetInstanceNamespace(), + Annotations: getIngressAnnotations(svc, ingressConfig.AdditionalAnnotations), + } + + // Ingress using Let's Encrypt + if len(fqdnsLetsEncrypt) > 0 { + ingressMetadata.Name = generateIngressName(comp, "letsencrypt") + rules, err := createIngressRule(comp, fqdnsLetsEncrypt, ingressConfig.ServiceConfig) + if err != nil { + return nil, fmt.Errorf("cannot create ingress rules for '%s': %w", ingressMetadata.Name, err) + } + + ingresses = append(ingresses, &netv1.Ingress{ + ObjectMeta: ingressMetadata, + Spec: netv1.IngressSpec{ + Rules: rules, + TLS: []netv1.IngressTLS{ + { + Hosts: fqdnsLetsEncrypt, + SecretName: tlsName, + }, + }, + }, + }) + } + + // Ingress using apps domain wildcard + if len(fqdnsWildcard) > 0 { + ingressMetadata.Name = generateIngressName(comp, "wildcard") + rules, err := createIngressRule(comp, fqdnsWildcard, ingressConfig.ServiceConfig) + if err != nil { + return nil, fmt.Errorf("cannot create ingress rules for '%s': %w", ingressMetadata.Name, err) + } + + ingresses = append(ingresses, &netv1.Ingress{ + ObjectMeta: ingressMetadata, + Spec: netv1.IngressSpec{ + Rules: rules, + TLS: []netv1.IngressTLS{{}}, + }, + }) + } + + return ingresses, nil +} + +// Generate an ingress containing a single FQDN using a TLS config as such: +// FQDN is one subdomain ON defaultAppsDomain (e.g. sub1.apps.cluster.com) -> Empty TLS config (uses wildcard cert on OCP). +// FQDN does not statisfy the former -> TLS config using a Let's Encrypt certificate. +func GenerateIngress(comp InfoGetter, svc *runtime.ServiceRuntime, ingressConfig IngressConfig) (*netv1.Ingress, error) { + if len(ingressConfig.FQDNs) == 0 || ingressConfig.FQDNs[0] == "" { + return nil, fmt.Errorf("no FQDN passed") + } + + fqdn := ingressConfig.FQDNs[0] + if len(ingressConfig.FQDNs) > 1 { + svc.AddResult( + runtime.NewWarningResult( + fmt.Sprintf("More than 1 FQDN has been passed to a singleton ingress object, using first available: %s", fqdn), + ), + ) + } + + annotations := getIngressAnnotations(svc, ingressConfig.AdditionalAnnotations) + + tlsName := ingressConfig.TlsCertBaseName + if tlsName == "" { + return nil, fmt.Errorf("a TLS cert base name must be defined") + } + for _, an := range ingressConfig.AdditionalIngressNames { + tlsName = tlsName + "-" + strings.Trim(an, "-") + } + + tlsConfig := netv1.IngressTLS{} + if !IsSingleSubdomainOfRefDomain(fqdn, svc.Config.Data["ocpDefaultAppsDomain"]) { + tlsConfig.Hosts = []string{fqdn} + tlsConfig.SecretName = generateTlsSecretName(tlsName) + } + + rules, err := createIngressRule(comp, []string{fqdn}, ingressConfig.ServiceConfig) + if err != nil { + return nil, fmt.Errorf("cannot create ingress rules for '%s': %w", fqdn, err) + } + + ingress := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateIngressName(comp, ingressConfig.AdditionalIngressNames...), + Namespace: comp.GetInstanceNamespace(), + Annotations: annotations, + }, + Spec: netv1.IngressSpec{ + Rules: rules, + TLS: []netv1.IngressTLS{tlsConfig}, + }, + } + + return ingress, nil +} + +// Apply generated ingresses using svc.SetDesiredKubeObject() +func CreateIngresses(comp InfoGetter, svc *runtime.ServiceRuntime, ingresses []*netv1.Ingress, opts ...runtime.KubeObjectOption) error { + for _, ingress := range ingresses { + err := svc.SetDesiredKubeObject(ingress, ingress.Name, opts...) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/comp-functions/functions/common/ingress_test.go b/pkg/comp-functions/functions/common/ingress_test.go new file mode 100644 index 0000000000..2c4469a3ff --- /dev/null +++ b/pkg/comp-functions/functions/common/ingress_test.go @@ -0,0 +1,392 @@ +package common + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vshn/appcat/v4/pkg/comp-functions/functions/commontest" + netv1 "k8s.io/api/networking/v1" + v1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +const ( + compName = "my-service" + tlsCertBaseName = "my-service" + svcBackendPortName = "https" + svcNameSuffix = "http" + relativePath = "/path" +) + +// Static objects that will always be the same across tests +var ingObjectMeta metav1.ObjectMeta = metav1.ObjectMeta{ + Annotations: map[string]string{}, + Name: compName + "-ingress", // May change in tests + Namespace: "vshn-" + compName, +} + +var svcName string = compName + "-" + svcNameSuffix +var expectedIngressBackend v1.IngressBackend = v1.IngressBackend{ + Service: &v1.IngressServiceBackend{ + Name: svcName, + Port: v1.ServiceBackendPort{ + Name: svcBackendPortName, + }, + }, +} + +// Barebones dummy composite for testing +type baaSComposite struct { + metav1.TypeMeta + metav1.ObjectMeta + InfoGetter + Spec baaSSpec +} + +type baaSSpec struct { + Parameters baaSParameters +} + +type baaSParameters struct { + Service baaSServiceSpec +} + +type baaSServiceSpec struct { + FQDN string + FQDNs []string + RelativePath string +} + +func (b *baaSComposite) GetLabels() map[string]string { + return b.Labels +} +func (b *baaSComposite) GetName() string { + return b.ObjectMeta.Name +} +func (b *baaSComposite) GetInstanceNamespace() string { + return fmt.Sprintf("vshn-%s", b.GetName()) +} + +type args struct { + comp *baaSComposite +} + +// Test the generation of multiple ingress objects with various configurations +func TestGenerateMultipleIngresses(t *testing.T) { + ingObjectMetaLetsEncrypt := ingObjectMeta + ingObjectMetaLetsEncrypt.Name = compName + "-letsencrypt-ingress" + ingObjectMetaWildcard := ingObjectMeta + ingObjectMetaWildcard.Name = compName + "-wildcard-ingress" + + test := struct { + name string + args args + want []*v1.Ingress + }{ + name: "ExpectMultipleIngresses_WithSeparatedFQDNs", + args: struct { + comp *baaSComposite + }{ + comp: &baaSComposite{ + ObjectMeta: metav1.ObjectMeta{ + Name: compName, + }, + Spec: baaSSpec{ + Parameters: baaSParameters{ + Service: baaSServiceSpec{ + FQDNs: []string{"my-domain.com", "instance.apps.example.com"}, + RelativePath: relativePath, + }, + }, + }, + }, + }, + want: []*v1.Ingress{ + // Let's encrypt ingress + { + ObjectMeta: ingObjectMetaLetsEncrypt, + Spec: v1.IngressSpec{ + Rules: []v1.IngressRule{{ + Host: "my-domain.com", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: relativePath, + PathType: ptr.To(v1.PathType("Prefix")), + Backend: expectedIngressBackend, + }, + }, + }, + }, + }}, + TLS: []v1.IngressTLS{ + { + Hosts: []string{"my-domain.com"}, + SecretName: tlsCertBaseName + "-ingress-cert", + }, + }, + }, + }, + // Wildcard ingress + { + ObjectMeta: ingObjectMetaWildcard, + Spec: v1.IngressSpec{ + Rules: []v1.IngressRule{{ + Host: "instance.apps.example.com", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: relativePath, + PathType: ptr.To(v1.PathType("Prefix")), + Backend: expectedIngressBackend, + }, + }, + }, + }, + }}, + TLS: []v1.IngressTLS{{}}, + }, + }, + }, + } + + t.Run(test.name, func(t *testing.T) { + svc := commontest.LoadRuntimeFromFile(t, "common/02_ingress.yaml") + fqdns := test.args.comp.Spec.Parameters.Service.FQDNs + relPath := test.args.comp.Spec.Parameters.Service.RelativePath + + ings, err := GenerateBundledIngresses(test.args.comp, svc, IngressConfig{ + FQDNs: fqdns, + ServiceConfig: IngressRuleConfig{ + RelPath: relPath, + ServiceNameSuffix: svcNameSuffix, + ServicePortName: svcBackendPortName, + }, + TlsCertBaseName: tlsCertBaseName, + }) + + assert.NoError(t, err) + for i, _ := range ings { + fmt.Printf("Testing ingress: %s\n", ings[i].Name) + doMarshalledAssert(t, test.want[i], ings[i]) + } + }) +} + +// Test the generation of a single ingress object with various configurations +func TestGenerateSingleIngress(t *testing.T) { + tests := []struct { + name string + args args + want *v1.Ingress + }{ + { + name: "GivenNonAppsFQDN_Then_ExpectIngress_WithPopulatedTLS", + args: struct { + comp *baaSComposite + }{ + comp: &baaSComposite{ + ObjectMeta: metav1.ObjectMeta{ + Name: compName, + }, + Spec: baaSSpec{ + Parameters: baaSParameters{ + Service: baaSServiceSpec{ + FQDN: "my-domain.com", + RelativePath: relativePath, + }, + }, + }, + }, + }, + want: &v1.Ingress{ + ObjectMeta: ingObjectMeta, + Spec: v1.IngressSpec{ + Rules: []v1.IngressRule{{ + Host: "my-domain.com", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: relativePath, + PathType: ptr.To(v1.PathType("Prefix")), + Backend: expectedIngressBackend, + }, + }, + }, + }, + }}, + TLS: []v1.IngressTLS{ + { + Hosts: []string{"my-domain.com"}, + SecretName: tlsCertBaseName + "-ingress-cert", + }, + }, + }, + }, + }, + { + name: "GivenAppsFQDN_Then_ExpectIngressWithEmptyTLS", + args: struct { + comp *baaSComposite + }{ + comp: &baaSComposite{ + ObjectMeta: metav1.ObjectMeta{ + Name: compName, + }, + Spec: baaSSpec{ + Parameters: baaSParameters{ + Service: baaSServiceSpec{ + FQDN: "instance.apps.example.com", + RelativePath: relativePath, + }, + }, + }, + }, + }, + want: &v1.Ingress{ + ObjectMeta: ingObjectMeta, + Spec: v1.IngressSpec{ + Rules: []v1.IngressRule{{ + Host: "instance.apps.example.com", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: relativePath, + PathType: ptr.To(v1.PathType("Prefix")), + Backend: expectedIngressBackend, + }, + }, + }, + }, + }}, + TLS: []v1.IngressTLS{{}}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svc := commontest.LoadRuntimeFromFile(t, "common/02_ingress.yaml") + fqdn := tt.args.comp.Spec.Parameters.Service.FQDN + relPath := tt.args.comp.Spec.Parameters.Service.RelativePath + + ing, err := GenerateIngress(tt.args.comp, svc, IngressConfig{ + FQDNs: []string{fqdn}, + ServiceConfig: IngressRuleConfig{ + RelPath: relPath, + ServiceNameSuffix: svcNameSuffix, + ServicePortName: svcBackendPortName, + }, + TlsCertBaseName: tlsCertBaseName, + }) + + assert.NoError(t, err) + doMarshalledAssert(t, tt.want, ing) + }) + } +} + +func TestIngressRuleGeneration(t *testing.T) { + comp := &baaSComposite{ + ObjectMeta: metav1.ObjectMeta{ + Name: compName, + }, + Spec: baaSSpec{ + Parameters: baaSParameters{ + Service: baaSServiceSpec{ + FQDNs: []string{ + "instance.apps.example.com", + "another.instance.apps.example.com", + "epic-instance.my-domain.com", + }, + RelativePath: relativePath, + }, + }, + }, + } + fqdns := comp.Spec.Parameters.Service.FQDNs + + t.Run("GivenFQDNs_ExpectRules", func(t *testing.T) { + rules, err := createIngressRule(comp, comp.Spec.Parameters.Service.FQDNs, IngressRuleConfig{ + RelPath: relativePath, + ServiceNameSuffix: svcNameSuffix, + ServicePortName: svcBackendPortName, + ServicePortNumber: 1337, + }) + + assert.NoError(t, err) + assert.Len(t, rules, len(fqdns)) + + for i, r := range rules { + nameWithSuffix := comp.GetName() + "-" + svcNameSuffix + assert.Equal(t, nameWithSuffix, r.HTTP.Paths[0].Backend.Service.Name) + assert.Equal(t, relativePath, r.HTTP.Paths[0].Path) + assert.Equal(t, netv1.PathType("Prefix"), *r.HTTP.Paths[0].PathType) + assert.Equal(t, nameWithSuffix, r.HTTP.Paths[0].Backend.Service.Name) + assert.Equal(t, svcBackendPortName, r.HTTP.Paths[0].Backend.Service.Port.Name) + assert.Equal(t, fqdns[i], r.Host) + } + }) + + t.Run("GivenNoRelativePath_ExpectDefault", func(t *testing.T) { + comp.Spec.Parameters.Service.RelativePath = "" + + rules, err := createIngressRule(comp, comp.Spec.Parameters.Service.FQDNs, IngressRuleConfig{ + ServicePortName: svcBackendPortName, + }) + + assert.NoError(t, err) + assert.Equal(t, "/", rules[0].HTTP.Paths[0].Path) + }) + + t.Run("GivenNeitherPortNameNorPortNumber_ExpectError", func(t *testing.T) { + _, err := createIngressRule(comp, []string{"example.com"}, IngressRuleConfig{}) + assert.Error(t, err) + }) + + t.Run("GivenNoFQDNs_ExpectError", func(t *testing.T) { + _, err := createIngressRule(comp, []string{}, IngressRuleConfig{}) + assert.Error(t, err) + }) + + t.Run("GivenSvcPortNameAndPortNumber_ExpectPortNameToBeUsed", func(t *testing.T) { + rules, err := createIngressRule(comp, []string{"example.com"}, IngressRuleConfig{ + ServicePortName: svcBackendPortName, + ServicePortNumber: 1337, + }) + + assert.NoError(t, err) + for _, r := range rules { + assert.Equal(t, svcBackendPortName, r.HTTP.Paths[0].Backend.Service.Port.Name) + assert.Equal(t, int32(0), r.HTTP.Paths[0].Backend.Service.Port.Number) + } + }) +} + +func TestIngressNameGeneration(t *testing.T) { + t.Run("GivenAdditionalNames_ExpectIngressSuffix", func(t *testing.T) { + comp := &baaSComposite{ + ObjectMeta: metav1.ObjectMeta{ + Name: compName, + }, + } + + name := generateIngressName(comp, "a", "b", "c") + assert.Equal(t, comp.GetName()+"-a-b-c-ingress", name) + }) +} + +func doMarshalledAssert(t *testing.T, want any, got any) { + // By marshalling v1.Ingress first, we can prevent assert.Equal from comparing pointer addresses and thus always failing + wantMarshal, _ := json.MarshalIndent(want, "", "") + gotMarshal, _ := json.MarshalIndent(got, "", "") + assert.Equal(t, string(wantMarshal), string(gotMarshal)) +} diff --git a/pkg/comp-functions/functions/common/util.go b/pkg/comp-functions/functions/common/util.go index 653d6516a8..b862c02b94 100644 --- a/pkg/comp-functions/functions/common/util.go +++ b/pkg/comp-functions/functions/common/util.go @@ -1,6 +1,8 @@ package common -import "fmt" +import ( + "fmt" +) // SetNestedObjectValue is necessary as unstructured can't handle anything except basic values and maps. // this is a recursive function, it will traverse the map until it reaches the last element of the path. diff --git a/pkg/comp-functions/functions/vshnforgejo/deploy.go b/pkg/comp-functions/functions/vshnforgejo/deploy.go index 4e5d8351ca..3a80b50913 100644 --- a/pkg/comp-functions/functions/vshnforgejo/deploy.go +++ b/pkg/comp-functions/functions/vshnforgejo/deploy.go @@ -6,7 +6,6 @@ import ( "strings" xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" - "github.com/ghodss/yaml" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" @@ -50,6 +49,8 @@ func DeployForgejo(ctx context.Context, comp *vshnv1.VSHNForgejo, svc *runtime.S return runtime.NewWarningResult(fmt.Sprintf("cannot add forgejo release: %s", err)) } + svc.Log.Info("Have added forgejo release!") + return nil } @@ -143,19 +144,6 @@ func addForgejo(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.V "enabled": true, }, }, - "ingress": map[string]any{ - "annotations": map[string]string{ - "cert-manager.io/cluster-issuer": "letsencrypt-staging", - }, - "enabled": true, - "hosts": []map[string]any{}, - "tls": []map[string]any{ - { - "hosts": []string{}, - "secretName": "forgejo-tls", - }, - }, - }, "persistence": map[string]any{ "enabled": true, }, @@ -183,38 +171,22 @@ func addForgejo(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.V "type": "Recreate", }, } - /* - hosts: - - host: forgejo213.apps.whatever.cloud - paths: - - path: / - pathType: Prefix - */ - nonTLShosts := []map[string]any{} - - for _, host := range comp.Spec.Parameters.Service.FQDN { - // building an array of maps with the host and the paths - nonTLShosts = append(nonTLShosts, map[string]any{ - "host": host, - "paths": []map[string]any{ - { - "path": "/", - "pathType": "Prefix", - }, - }, - }) - err := common.SetNestedObjectValue(values, []string{"ingress", "tls"}, []map[string]any{ - { - "hosts": comp.Spec.Parameters.Service.FQDN, - "secretName": "forgejo-tls", - }}) - if err != nil { - return err - } + svc.Log.Info("Adding ingress") + ingressConfig := common.IngressConfig{ + FQDNs: comp.Spec.Parameters.Service.FQDN, + ServiceConfig: common.IngressRuleConfig{ + ServiceNameSuffix: "http", + ServicePortNumber: 3000, + }, + TlsCertBaseName: "forgejo", + } + ingresses, err := common.GenerateBundledIngresses(comp, svc, ingressConfig) + if err != nil { + return err } - err := common.SetNestedObjectValue(values, []string{"ingress", "hosts"}, nonTLShosts) + err = common.CreateIngresses(comp, svc, ingresses) if err != nil { return err } @@ -224,23 +196,8 @@ func addForgejo(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.V values["podSecurityContext"] = securityContext["podSecurityContext"] } - if svc.Config.Data["ingress_annotations"] != "" { - annotations := map[string]string{} - - err := yaml.Unmarshal([]byte(svc.Config.Data["ingress_annotations"]), &annotations) - if err != nil { - // do nothing and use default annotation which sets issuer to staging - svc.Log.Error(fmt.Errorf("cannot unmarshal ingress annotations"), "error", err) - } - - err = common.SetNestedObjectValue(values, []string{"ingress", "annotations"}, annotations) - if err != nil { - return err - } - } - if comp.Spec.Parameters.Service.AdminEmail != "" { - err = common.SetNestedObjectValue(values, []string{"gitea", "config", "admin", "ADMIN_EMAIL"}, comp.Spec.Parameters.Service.AdminEmail) + err := common.SetNestedObjectValue(values, []string{"gitea", "config", "admin", "ADMIN_EMAIL"}, comp.Spec.Parameters.Service.AdminEmail) if err != nil { return err } diff --git a/pkg/comp-functions/functions/vshnkeycloak/ingress.go b/pkg/comp-functions/functions/vshnkeycloak/ingress.go index 84dc03dcbe..ff1a677cf5 100644 --- a/pkg/comp-functions/functions/vshnkeycloak/ingress.go +++ b/pkg/comp-functions/functions/vshnkeycloak/ingress.go @@ -10,10 +10,9 @@ import ( vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" - "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) // AddIngress adds an inrgess to the Keycloak instance. @@ -24,7 +23,8 @@ func AddIngress(_ context.Context, comp *vshnv1.VSHNKeycloak, svc *runtime.Servi return runtime.NewFatalResult(fmt.Errorf("cannot get composite: %w", err)) } - if comp.Spec.Parameters.Service.FQDN == "" { + fqdn := comp.Spec.Parameters.Service.FQDN + if fqdn == "" { return nil } @@ -33,8 +33,32 @@ func AddIngress(_ context.Context, comp *vshnv1.VSHNKeycloak, svc *runtime.Servi return runtime.NewWarningResult(fmt.Sprintf("cannot get desired release values: %s", err)) } - svc.Log.Info("Enable ingress for release") - enableIngresValues(svc, comp, values) + svc.Log.Info("Adding ingress") + ingress, err := common.GenerateIngress(comp, svc, common.IngressConfig{ + FQDNs: []string{fqdn}, + ServiceConfig: common.IngressRuleConfig{ + RelPath: comp.Spec.Parameters.Service.RelativePath, + ServiceNameSuffix: "keycloakx-http", + ServicePortName: "https", + }, + TlsCertBaseName: "keycloak", + }) + if err != nil { + return runtime.NewWarningResult(fmt.Sprintf("cannot generate ingress: %s", err)) + } + + err = common.CreateIngresses(comp, svc, []*netv1.Ingress{ingress}) + if err != nil { + return runtime.NewWarningResult(fmt.Sprintf("cannot create ingress: %s", err)) + } + + if svc.GetBoolFromCompositionConfig("isOpenshift") { + err := addOpenShiftCa(svc, comp) + if err != nil { + svc.Log.Error(err, "cannot add openshift ca secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot add openshift ca secret: %s", err))) + } + } release := &xhelmv1.Release{} err = svc.GetDesiredComposedResourceByName(release, comp.GetName()+"-release") @@ -57,61 +81,6 @@ func AddIngress(_ context.Context, comp *vshnv1.VSHNKeycloak, svc *runtime.Servi return nil } -func enableIngresValues(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNKeycloak, values map[string]any) { - fqdn := comp.Spec.Parameters.Service.FQDN - - relPath := `'{{ tpl .Values.http.relativePath $ | trimSuffix " / " }}/'` - if comp.Spec.Parameters.Service.RelativePath == "/" { - relPath = "/" - } - - values["ingress"] = map[string]any{ - "enabled": true, - "servicePort": "https", - - "rules": []map[string]any{ - { - "host": fqdn, - "paths": []map[string]any{ - { - "path": relPath, - "pathType": "Prefix", - }, - }, - }, - }, - "tls": []map[string]any{ - { - "hosts": []string{ - fqdn, - }, - "secretName": "keycloak-ingress-cert", - }, - }, - } - - if svc.Config.Data["ingress_annotations"] != "" { - annotations := map[string]any{} - - err := yaml.Unmarshal([]byte(svc.Config.Data["ingress_annotations"]), annotations) - if err != nil { - svc.Log.Error(err, "cannot unmarshal ingress annotations from input") - svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot unmarshal ingress annotations from input: %s", err))) - } - - unstructured.SetNestedMap(values, annotations, "ingress", "annotations") - } - - if svc.GetBoolFromCompositionConfig("isOpenshift") { - err := addOpenShiftCa(svc, comp) - if err != nil { - svc.Log.Error(err, "cannot add openshift ca secret") - svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot add openshift ca secret: %s", err))) - } - } - -} - // addOpenShiftCa creates a separate secret just with the ca in it. // This is required so that the ca on the route is properly set. // For some reason openshift doesn't take the CA certificate from the ca.crt diff --git a/pkg/comp-functions/functions/vshnkeycloak/ingress_test.go b/pkg/comp-functions/functions/vshnkeycloak/ingress_test.go index 43565bc9ad..1382582708 100644 --- a/pkg/comp-functions/functions/vshnkeycloak/ingress_test.go +++ b/pkg/comp-functions/functions/vshnkeycloak/ingress_test.go @@ -1,34 +1,55 @@ package vshnkeycloak import ( - "context" + "encoding/json" "testing" "github.com/stretchr/testify/assert" - xhelmv1 "github.com/vshn/appcat/v4/apis/helm/release/v1beta1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/functions/commontest" + v1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" ) -func TestEnableIngresValues(t *testing.T) { +func TestCreateIngress(t *testing.T) { type args struct { - comp *vshnv1.VSHNKeycloak - values map[string]any + comp *vshnv1.VSHNKeycloak + } + + // Static objects that will always be the same across tests + keycloakObjectMeta := metav1.ObjectMeta{ + Name: "keycloak", + } + + ingObjectMeta := metav1.ObjectMeta{ + Annotations: map[string]string{}, + Name: "keycloak-ingress", + Namespace: "vshn-keycloak-keycloak", + } + + expectedIngressBackend := v1.IngressBackend{ + Service: &v1.IngressServiceBackend{ + Name: "keycloak-keycloakx-http", + Port: v1.ServiceBackendPort{ + Name: "https", + }, + }, } tests := []struct { name string args args - want map[string]any + want *v1.Ingress }{ { name: "GivenFQDN_Then_ExpectIngress", args: struct { - comp *vshnv1.VSHNKeycloak - values map[string]any + comp *vshnv1.VSHNKeycloak }{ comp: &vshnv1.VSHNKeycloak{ + ObjectMeta: keycloakObjectMeta, Spec: vshnv1.VSHNKeycloakSpec{ Parameters: vshnv1.VSHNKeycloakParameters{ Service: vshnv1.VSHNKeycloakServiceSpec{ @@ -38,93 +59,108 @@ func TestEnableIngresValues(t *testing.T) { }, }, }, - values: map[string]any{}, }, - want: map[string]any{ - "ingress": map[string]any{ - "enabled": true, - "servicePort": "https", - "rules": []map[string]any{ - { - "host": "example.com", - "paths": []map[string]any{ - { - "path": `'{{ tpl .Values.http.relativePath $ | trimSuffix " / " }}/'`, - "pathType": "Prefix", + want: &v1.Ingress{ + ObjectMeta: ingObjectMeta, + Spec: v1.IngressSpec{ + Rules: []v1.IngressRule{{ + Host: "example.com", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: "/path", + PathType: ptr.To(v1.PathType("Prefix")), + Backend: expectedIngressBackend, + }, }, }, }, - }, - "tls": []map[string]any{ + }}, + TLS: []v1.IngressTLS{ { - "hosts": []string{"example.com"}, - "secretName": "keycloak-ingress-cert", + Hosts: []string{"example.com"}, + SecretName: "keycloak-ingress-cert", }, }, }, }, }, { - name: "GivenNoFQDN_Then_ExpectNoIngress", + name: "GivenAppsFQDN_Then_ExpectIngressWithEmptyTLS", args: struct { - comp *vshnv1.VSHNKeycloak - values map[string]any + comp *vshnv1.VSHNKeycloak }{ comp: &vshnv1.VSHNKeycloak{ - Spec: vshnv1.VSHNKeycloakSpec{}, + ObjectMeta: keycloakObjectMeta, + Spec: vshnv1.VSHNKeycloakSpec{ + Parameters: vshnv1.VSHNKeycloakParameters{ + Service: vshnv1.VSHNKeycloakServiceSpec{ + FQDN: "instance.apps.example.com", + RelativePath: "/path", + }, + }, + }, }, - values: map[string]any{}, }, - want: map[string]any{ - "ingress": map[string]any{ - "enabled": true, - "servicePort": "https", - "rules": []map[string]any{ - { - "host": "", - "paths": []map[string]any{ - { - "path": `'{{ tpl .Values.http.relativePath $ | trimSuffix " / " }}/'`, - "pathType": "Prefix", + want: &v1.Ingress{ + ObjectMeta: ingObjectMeta, + Spec: v1.IngressSpec{ + Rules: []v1.IngressRule{{ + Host: "instance.apps.example.com", + IngressRuleValue: v1.IngressRuleValue{ + HTTP: &v1.HTTPIngressRuleValue{ + Paths: []v1.HTTPIngressPath{ + { + Path: "/path", + PathType: ptr.To(v1.PathType("Prefix")), + Backend: expectedIngressBackend, + }, }, }, }, - }, - "tls": []map[string]any{ - { - "hosts": []string{""}, - "secretName": "keycloak-ingress-cert", - }, - }, + }}, + TLS: []v1.IngressTLS{{}}, }, }, }, + { + name: "GivenNoFQDN_Then_ExpectNoIngress", + args: struct { + comp *vshnv1.VSHNKeycloak + }{ + comp: &vshnv1.VSHNKeycloak{}, + }, + want: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { svc := commontest.LoadRuntimeFromFile(t, "vshnkeycloak/01_default.yaml") - enableIngresValues(svc, tt.args.comp, tt.args.values) - assert.Equal(t, tt.want, tt.args.values) - }) - } -} - -func TestAddIngress(t *testing.T) { - // Given - svc := commontest.LoadRuntimeFromFile(t, "vshnkeycloak/02_withFQDN.yaml") + fqdn := tt.args.comp.Spec.Parameters.Service.FQDN + relPath := tt.args.comp.Spec.Parameters.Service.RelativePath - // When - res := AddIngress(context.TODO(), &vshnv1.VSHNKeycloak{}, svc) - assert.Nil(t, res) - - // Then - release := &xhelmv1.Release{} - assert.NoError(t, svc.GetDesiredComposedResourceByName(release, "keycloak-app1-prod-release")) + var err error + var ing *v1.Ingress + if fqdn != "" { // Handle GivenNoFQDN_Then_ExpectNoIngress + ing, err = common.GenerateIngress(tt.args.comp, svc, common.IngressConfig{ + FQDNs: []string{fqdn}, + ServiceConfig: common.IngressRuleConfig{ + RelPath: relPath, + ServiceNameSuffix: "keycloakx-http", + ServicePortName: "https", + }, + TlsCertBaseName: "keycloak", + }) + } - values, err := common.GetDesiredReleaseValues(svc, "keycloak-app1-prod-release") - assert.NoError(t, err) - assert.NotEmpty(t, values) - assert.NotEmpty(t, values["ingress"]) + assert.NoError(t, err) + // By marshalling v1.Ingress first, we can prevent assert.Equal from comparing pointer addresses and thus always failing + want, _ := json.MarshalIndent(tt.want, "", "") + got, _ := json.MarshalIndent(ing, "", "") + assert.Equal(t, string(want), string(got)) + }) + } } diff --git a/pkg/comp-functions/functions/vshnnextcloud/collabora.go b/pkg/comp-functions/functions/vshnnextcloud/collabora.go index 2c1714e8f1..76cb045421 100644 --- a/pkg/comp-functions/functions/vshnnextcloud/collabora.go +++ b/pkg/comp-functions/functions/vshnnextcloud/collabora.go @@ -17,7 +17,6 @@ import ( "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" - "gopkg.in/yaml.v2" v1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -315,15 +314,8 @@ func AddCollaboraService(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime } func AddCollaboraIngress(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { - annotations := map[string]string{} - if svc.Config.Data["ingress_annotations"] != "" { - err := yaml.Unmarshal([]byte(svc.Config.Data["ingress_annotations"]), annotations) - if err != nil { - svc.Log.Error(err, "cannot unmarshal ingress annotations from input") - svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot unmarshal ingress annotations from input: %s", err))) - } - } + annotations := map[string]string{} if svc.Config.Data["isOpenshift"] == "true" { annotations["route.openshift.io/termination"] = "reencrypt" @@ -332,49 +324,21 @@ func AddCollaboraIngress(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime annotations["haproxy.router.openshift.io/hsts_header"] = "max-age=31536000;preload" } - ingress := &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: comp.GetName() + "-collabora-code", - Namespace: comp.GetInstanceNamespace(), - Labels: map[string]string{ - "app": comp.GetName() + "-collabora-code", - }, - Annotations: annotations, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: comp.Spec.Parameters.Service.Collabora.FQDN, - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: ptr.To(networkingv1.PathTypePrefix), - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: comp.GetName() + "-collabora-code", - Port: networkingv1.ServiceBackendPort{ - Number: 9980, - }, - }, - }, - }, - }, - }, - }, - }, - }, - TLS: []networkingv1.IngressTLS{ - { - Hosts: []string{comp.Spec.Parameters.Service.Collabora.FQDN}, - SecretName: comp.GetName() + "-collabora-code-ingress-tls", - }, - }, + ingress, err := common.GenerateIngress(comp, svc, common.IngressConfig{ + AdditionalAnnotations: annotations, + AdditionalIngressNames: []string{"collabora", "code"}, + FQDNs: []string{comp.Spec.Parameters.Service.Collabora.FQDN}, + ServiceConfig: common.IngressRuleConfig{ + ServiceNameSuffix: "collabora-code", + ServicePortNumber: 9980, }, + TlsCertBaseName: "nextcloud", + }) + if err != nil { + return err } - return svc.SetDesiredKubeObject(ingress, comp.GetName()+"-collabora-code-ingress", runtime.KubeOptionAddLabels(labelMap)) + return common.CreateIngresses(comp, svc, []*networkingv1.Ingress{ingress}, runtime.KubeOptionAddLabels(labelMap)) } func createIssuer(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error { diff --git a/pkg/comp-functions/functions/vshnnextcloud/collabora_test.go b/pkg/comp-functions/functions/vshnnextcloud/collabora_test.go index 5b4e87fc0f..addd1d4036 100644 --- a/pkg/comp-functions/functions/vshnnextcloud/collabora_test.go +++ b/pkg/comp-functions/functions/vshnnextcloud/collabora_test.go @@ -2,6 +2,7 @@ package vshnnextcloud import ( "context" + "fmt" "strings" "testing" @@ -49,7 +50,14 @@ func Test_addCollabora(t *testing.T) { } for _, val := range collabora_objects { - assert.Equal(t, "1", resources[val]) + var check func(string) error = func(val string) error { + if resources[val] != "1" { + return fmt.Errorf("%s does not exist", val) + } + return nil + } + + assert.NoError(t, check(val)) } comp.Spec.Parameters.Service.Collabora.Enabled = false diff --git a/pkg/comp-functions/functions/vshnnextcloud/ingress.go b/pkg/comp-functions/functions/vshnnextcloud/ingress.go index b6d5636727..f57c0e8f9f 100644 --- a/pkg/comp-functions/functions/vshnnextcloud/ingress.go +++ b/pkg/comp-functions/functions/vshnnextcloud/ingress.go @@ -7,11 +7,8 @@ import ( xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" - "gopkg.in/yaml.v2" - netv1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" ) // AddIngress adds an inrgess to the Nextcloud instance. @@ -26,67 +23,20 @@ func AddIngress(_ context.Context, comp *vshnv1.VSHNNextcloud, svc *runtime.Serv return runtime.NewFatalResult(fmt.Errorf("FQDN array is empty, but requires at least one entry, %w", errors.New("empty fqdn"))) } - annotations := map[string]string{} - if svc.Config.Data["ingress_annotations"] != "" { - err := yaml.Unmarshal([]byte(svc.Config.Data["ingress_annotations"]), annotations) - if err != nil { - svc.Log.Error(err, "cannot unmarshal ingress annotations from input") - svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot unmarshal ingress annotations from input: %s", err))) - } - } - - ingress := &netv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: comp.GetName(), - Namespace: comp.GetInstanceNamespace(), - Annotations: annotations, - }, - Spec: netv1.IngressSpec{ - Rules: createIngressRule(comp), - TLS: []netv1.IngressTLS{ - { - Hosts: comp.Spec.Parameters.Service.FQDN, - SecretName: "nextcloud-ingress-cert", - }, - }, + ingressConfig := common.IngressConfig{ + FQDNs: comp.Spec.Parameters.Service.FQDN, + ServiceConfig: common.IngressRuleConfig{ + ServicePortNumber: 8080, }, + TlsCertBaseName: "nextcloud", } - err = svc.SetDesiredKubeObject(ingress, comp.GetName()+"-ingress") + ingresses, err := common.GenerateBundledIngresses(comp, svc, ingressConfig) if err != nil { - return runtime.NewWarningResult(fmt.Sprintf("cannot set create ingress: %s", err)) + return runtime.NewFatalResult(fmt.Errorf("Could not generate ingresses: %w", err)) } - return nil -} + common.CreateIngresses(comp, svc, ingresses) -func createIngressRule(comp *vshnv1.VSHNNextcloud) []netv1.IngressRule { - - ingressRules := []netv1.IngressRule{} - - for _, fqdn := range comp.Spec.Parameters.Service.FQDN { - rule := netv1.IngressRule{ - Host: fqdn, - IngressRuleValue: netv1.IngressRuleValue{ - HTTP: &netv1.HTTPIngressRuleValue{ - Paths: []netv1.HTTPIngressPath{ - { - Path: "/", - PathType: ptr.To(netv1.PathType("Prefix")), - Backend: netv1.IngressBackend{ - Service: &netv1.IngressServiceBackend{ - Name: comp.GetName(), - Port: netv1.ServiceBackendPort{ - Number: 8080, - }, - }, - }, - }, - }, - }, - }, - } - ingressRules = append(ingressRules, rule) - } - return ingressRules + return nil } diff --git a/test/functions/common/02_ingress.yaml b/test/functions/common/02_ingress.yaml new file mode 100644 index 0000000000..7756c0ed04 --- /dev/null +++ b/test/functions/common/02_ingress.yaml @@ -0,0 +1,11 @@ +desired: {} +input: + apiVersion: v1 + kind: ConfigMap + metadata: + annotations: {} + labels: + name: xfn-config + name: xfn-config + data: + ocpDefaultAppsDomain: "apps.example.com" \ No newline at end of file diff --git a/test/functions/vshnkeycloak/01_default.yaml b/test/functions/vshnkeycloak/01_default.yaml index 17249f7ccc..c425126c3a 100644 --- a/test/functions/vshnkeycloak/01_default.yaml +++ b/test/functions/vshnkeycloak/01_default.yaml @@ -14,6 +14,7 @@ input: true, "memory": "2Gi"}}, "standard-4": {"size": {"cpu": "1", "disk": "16Gi", "enabled": true, "memory": "4Gi"}}, "standard-8": {"size": {"cpu": "2", "disk": "16Gi", "enabled": true, "memory": "8Gi"}}}' + ocpDefaultAppsDomain: "apps.example.com" observed: resources: mycloak-pg: diff --git a/test/functions/vshnkeycloak/03_withFQDNAndAppsDomain.yaml b/test/functions/vshnkeycloak/03_withFQDNAndAppsDomain.yaml new file mode 100644 index 0000000000..6bc7388d8d --- /dev/null +++ b/test/functions/vshnkeycloak/03_withFQDNAndAppsDomain.yaml @@ -0,0 +1,39 @@ +desired: + resources: + keycloak-app1-prod-release: + resource: + apiVersion: helm.crossplane.io/v1beta1 + kind: Release + metadata: + name: keycloak-app1-prod + spec: {} +input: + apiVersion: v1 + kind: ConfigMap + metadata: + annotations: {} + labels: + name: xfn-config + name: xfn-config + data: + defaultPlan: standard-2 + controlNamespace: appcat-control + plans: '{"standard-2": {"size": {"cpu": "500m", "disk": "16Gi", "enabled": + true, "memory": "2Gi"}}, "standard-4": {"size": {"cpu": "1", "disk": "16Gi", + "enabled": true, "memory": "4Gi"}}, "standard-8": {"size": {"cpu": "2", + "disk": "16Gi", "enabled": true, "memory": "8Gi"}}}' +observed: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: VSHNKeycloak + metadata: + name: keycloak-app1-prod + spec: + parameters: + service: + fqdn: instance.apps.example.com + resources: + mycloak-pg: + connection_details: + foo: YmFyCg==