package application import ( v1 "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/certmanager/v1" dnsv1alpha1 "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/externaldns/v1alpha1" traefikv1alpha1 "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/traefik/v1alpha1" "errors" "fmt" appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1" metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1" netv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "slices" ) const ( loadBalancerAddressName = "internal-lb.localdomain" defaultRecordTTL = 180 ) type ImagesConfiguration struct { Image string Path string Health string } type Configuration struct { Name string Namespace string Images []ImagesConfiguration Dns *string Replicas *int Env map[string]string AllowAllOrigin bool } type configuration struct { Name string Namespace string Images []ImagesConfiguration Dns string Replicas int Env map[string]string ShouldCreateDns bool ShouldCreateCertificate bool ShouldCreateIngress bool ResponseHeaders *traefikv1alpha1.MiddlewareSpecHeadersArgs } type deploymentConfiguration struct { Env map[string]string Name string Replicas int ImageReference ImagesConfiguration } type serviceConfiguration struct { Name string } type ingressConfiguration struct { Service *corev1.Service ImageReference ImagesConfiguration } type Application struct { pulumi.ResourceState DeploymentName pulumi.StringOutput `pulumi:"deployment"` } func NewApplication(ctx *pulumi.Context, publicConfiguration *Configuration) (*Application, error) { if publicConfiguration.Name != "" && len(publicConfiguration.Images) > 0 { localConfiguration := &configuration{ Name: publicConfiguration.Name, Namespace: publicConfiguration.Namespace, Env: publicConfiguration.Env, } var preventDuplicatePath []string for _, publicImageConfiguration := range publicConfiguration.Images { localImagesConfiguration := ImagesConfiguration{ Image: publicImageConfiguration.Image, Path: publicImageConfiguration.Path, Health: publicImageConfiguration.Health, } if publicImageConfiguration.Path == "" { localImagesConfiguration.Path = "/" } if publicImageConfiguration.Health == "" { localImagesConfiguration.Health = "/" } if slices.Contains(preventDuplicatePath, localImagesConfiguration.Path) { return nil, errors.New("duplicate path in ingress configuration") } localConfiguration.Images = append(localConfiguration.Images, localImagesConfiguration) preventDuplicatePath = append(preventDuplicatePath, localImagesConfiguration.Path) } if publicConfiguration.Replicas != nil { localConfiguration.Replicas = *publicConfiguration.Replicas } else { localConfiguration.Replicas = 1 } if publicConfiguration.Dns != nil { localConfiguration.Dns = *publicConfiguration.Dns localConfiguration.ShouldCreateIngress = true localConfiguration.ShouldCreateDns = true localConfiguration.ShouldCreateCertificate = true } if publicConfiguration.AllowAllOrigin { localConfiguration.ResponseHeaders = &traefikv1alpha1.MiddlewareSpecHeadersArgs{ AccessControlAllowOriginList: toPulumiStringArray([]string{"*"}), } } return createResources(ctx, localConfiguration) } else { return nil, errors.New("missing required value Name or Image during generic application construction") } } func createResources(ctx *pulumi.Context, configuration *configuration, opts ...pulumi.ResourceOption) (*Application, error) { application := &Application{} err := ctx.RegisterComponentResource("pkg:application:Application", configuration.Name, application) if err != nil { return nil, err } namespace, err := createNamespace(ctx, configuration) if err != nil { return nil, err } var ingressesParameter []ingressConfiguration var deployments []*appsv1.Deployment for index, image := range configuration.Images { indexedName := fmt.Sprintf("%s-%d", configuration.Name, index) appLabels := pulumi.StringMap{ "app.kubernetes.io/name": pulumi.String(indexedName), } deploymentParameter := &deploymentConfiguration{ Name: indexedName, Env: configuration.Env, Replicas: configuration.Replicas, ImageReference: image, } deployment, err := createDeployment(ctx, deploymentParameter, namespace, appLabels, application) if err != nil { return nil, err } application.DeploymentName = deployment.Metadata.Name().Elem() serviceParameter := serviceConfiguration{ Name: indexedName, } service, err := createService(ctx, serviceParameter, namespace, appLabels, application) if err != nil { return nil, err } ingressesParameter = append(ingressesParameter, ingressConfiguration{ Service: service, ImageReference: image, }) deployments = append(deployments, deployment) } certificate, err := createCertificate(ctx, configuration, namespace, application) if err != nil { return nil, err } headerMiddleware, err := createMiddlewareAddResponseHeader(ctx, configuration, namespace, application) if err != nil { return nil, err } _, err = createIngress(ctx, configuration, namespace, certificate, ingressesParameter, application, headerMiddleware) if err != nil { return nil, err } _, err = createDNSRecord(ctx, configuration, namespace, application) if err != nil { return nil, err } outs := pulumi.Map{} for i, deployment := range deployments { outs[fmt.Sprintf("deployment-%d", i)] = deployment.Metadata.Name() } err = ctx.RegisterResourceOutputs(application, outs) if err != nil { return nil, err } return application, nil } func toPulumiStringArray(values []string) pulumi.StringArray { array := pulumi.StringArray{} for _, value := range values { array = append(array, pulumi.String(value)) } return array } func createMiddlewareAddResponseHeader( ctx *pulumi.Context, configuration *configuration, namespace *corev1.Namespace, application *Application, ) (*traefikv1alpha1.Middleware, error) { if configuration.ResponseHeaders != nil { middlewareName := fmt.Sprintf("%s-response-header-middleware", configuration.Name) return traefikv1alpha1.NewMiddleware(ctx, middlewareName, &traefikv1alpha1.MiddlewareArgs{ Metadata: &metav1.ObjectMetaArgs{ Namespace: namespace.Metadata.Name(), Labels: pulumi.StringMap{ "app.kubernetes.io/part-of": pulumi.String(configuration.Name), "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, }, Spec: &traefikv1alpha1.MiddlewareSpecArgs{ Headers: configuration.ResponseHeaders, }, }, pulumi.Parent(application)) } return nil, nil } func createIngress(ctx *pulumi.Context, configuration *configuration, namespace *corev1.Namespace, certificate *v1.Certificate, ingressConfiguration []ingressConfiguration, application *Application, responseHeaderMiddleware *traefikv1alpha1.Middleware) (*netv1.Ingress, error) { if configuration.ShouldCreateIngress { host := pulumi.String(configuration.Dns) var middlewares pulumi.StringInput if configuration.ResponseHeaders != nil { middlewares = pulumi.All(namespace.Metadata.Name().Elem(), responseHeaderMiddleware.Metadata.Name().Elem()).ApplyT(func(args []interface{}) string { return fmt.Sprintf("kube-ingress-gzip-compress@kubernetescrd,%s-%s@kubernetescrd", args[0], args[1]) }).(pulumi.StringOutput) } else { middlewares = pulumi.String("kube-ingress-gzip-compress@kubernetescrd") } ingressAnnotations := pulumi.StringMap{ "traefik.ingress.kubernetes.io/router.middlewares": middlewares, "traefik.ingress.kubernetes.io/router.entrypoints": pulumi.String("websecure"), } var ingressPaths netv1.HTTPIngressPathArray for _, service := range ingressConfiguration { ingressPaths = append(ingressPaths, netv1.HTTPIngressPathArgs{ Path: pulumi.String(service.ImageReference.Path), PathType: pulumi.String("Prefix"), Backend: &netv1.IngressBackendArgs{ Service: &netv1.IngressServiceBackendArgs{ Name: service.Service.Metadata.Name().Elem(), Port: &netv1.ServiceBackendPortArgs{ Name: pulumi.String("exposed-port"), }, }, }, }) } return netv1.NewIngress(ctx, configuration.Name, &netv1.IngressArgs{ Metadata: &metav1.ObjectMetaArgs{ Namespace: namespace.Metadata.Name(), Labels: pulumi.StringMap{ "app.kubernetes.io/part-of": pulumi.String(configuration.Name), "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, Annotations: ingressAnnotations, }, Spec: &netv1.IngressSpecArgs{ IngressClassName: nil, Rules: &netv1.IngressRuleArray{ netv1.IngressRuleArgs{ Host: host, Http: &netv1.HTTPIngressRuleValueArgs{ Paths: ingressPaths, }, }, }, Tls: &netv1.IngressTLSArray{ netv1.IngressTLSArgs{ Hosts: pulumi.StringArray{host}, SecretName: certificate.Spec.SecretName(), }, }, }, }, pulumi.Parent(application)) } return nil, nil } func createCertificate( ctx *pulumi.Context, configuration *configuration, namespace *corev1.Namespace, application *Application, ) (*v1.Certificate, error) { if configuration.ShouldCreateCertificate { return v1.NewCertificate(ctx, configuration.Name, &v1.CertificateArgs{ Metadata: &metav1.ObjectMetaArgs{ Namespace: namespace.Metadata.Name(), Labels: pulumi.StringMap{ "app.kubernetes.io/part-of": pulumi.String(configuration.Name), "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, }, Spec: &v1.CertificateSpecArgs{ SecretName: pulumi.String(fmt.Sprintf("%s-certificate", configuration.Name)), DnsNames: pulumi.StringArray{ pulumi.String(configuration.Dns), }, IssuerRef: &v1.CertificateSpecIssuerRefArgs{ Name: pulumi.String("localdomain-issuer"), Kind: pulumi.String("ClusterIssuer"), Group: pulumi.String("cfssl-issuer.wikimedia.org"), }, }, }, pulumi.Parent(application)) } return nil, nil } func createDNSRecord(ctx *pulumi.Context, configuration *configuration, namespace *corev1.Namespace, application *Application) (*dnsv1alpha1.DNSEndpoint, error) { if configuration.ShouldCreateDns { return dnsv1alpha1.NewDNSEndpoint(ctx, fmt.Sprintf("%s-record", configuration.Name), &dnsv1alpha1.DNSEndpointArgs{ Metadata: &metav1.ObjectMetaArgs{ Namespace: namespace.Metadata.Name(), Labels: pulumi.StringMap{ "app.kubernetes.io/part-of": pulumi.String(configuration.Name), "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, }, Spec: &dnsv1alpha1.DNSEndpointSpecArgs{ Endpoints: &dnsv1alpha1.DNSEndpointSpecEndpointsArray{ &dnsv1alpha1.DNSEndpointSpecEndpointsArgs{ DnsName: pulumi.String(configuration.Dns), RecordTTL: pulumi.Int(defaultRecordTTL), RecordType: pulumi.String("CNAME"), Targets: pulumi.StringArray{ pulumi.String(loadBalancerAddressName), }, }, }, }, }, pulumi.Parent(application)) } return nil, nil } func createService(ctx *pulumi.Context, configuration serviceConfiguration, namespace *corev1.Namespace, appLabels pulumi.StringMap, application *Application) (*corev1.Service, error) { return corev1.NewService(ctx, fmt.Sprintf("%s-service", configuration.Name), &corev1.ServiceArgs{ Metadata: &metav1.ObjectMetaArgs{ Namespace: namespace.Metadata.Name(), Labels: pulumi.StringMap{ "app.kubernetes.io/part-of": pulumi.String(configuration.Name), "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, }, Spec: &corev1.ServiceSpecArgs{ Type: pulumi.String("ClusterIP"), Selector: appLabels, Ports: corev1.ServicePortArray{ corev1.ServicePortArgs{ Name: pulumi.String("exposed-port"), Port: pulumi.Int(8090), TargetPort: pulumi.String("http"), Protocol: pulumi.String("TCP"), }, }, }, }, pulumi.Parent(application)) } func createDeployment( ctx *pulumi.Context, configuration *deploymentConfiguration, namespace *corev1.Namespace, appLabels pulumi.StringMap, application *Application, ) (*appsv1.Deployment, error) { env := corev1.EnvVarArray{} for key, value := range configuration.Env { env = append(env, &corev1.EnvVarArgs{ Name: pulumi.String(key), Value: pulumi.String(value), }) } return appsv1.NewDeployment(ctx, fmt.Sprintf("%s-deployment", configuration.Name), &appsv1.DeploymentArgs{ Metadata: &metav1.ObjectMetaArgs{ Namespace: namespace.Metadata.Name(), Labels: pulumi.StringMap{ "app.kubernetes.io/part-of": pulumi.String(configuration.Name), "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, }, Spec: appsv1.DeploymentSpecArgs{ Selector: &metav1.LabelSelectorArgs{ MatchLabels: appLabels, }, Replicas: pulumi.Int(configuration.Replicas), Template: &corev1.PodTemplateSpecArgs{ Metadata: &metav1.ObjectMetaArgs{ Labels: appLabels, }, Spec: &corev1.PodSpecArgs{ Containers: corev1.ContainerArray{ corev1.ContainerArgs{ Name: pulumi.String(configuration.Name), Image: pulumi.String(configuration.ImageReference.Image), Ports: corev1.ContainerPortArray{ corev1.ContainerPortArgs{ Name: pulumi.String("http"), ContainerPort: pulumi.Int(80), Protocol: pulumi.String("TCP"), }, }, Env: env, LivenessProbe: &corev1.ProbeArgs{ HttpGet: &corev1.HTTPGetActionArgs{ Path: pulumi.String(configuration.ImageReference.Health), Port: pulumi.Int(80), }, }, }, }, }, }, }, }, pulumi.Parent(application)) } func createNamespace(ctx *pulumi.Context, configuration *configuration) (*corev1.Namespace, error) { return corev1.NewNamespace(ctx, fmt.Sprintf("%s-namespace", configuration.Name), &corev1.NamespaceArgs{ Metadata: &metav1.ObjectMetaArgs{ Name: pulumi.String(configuration.Namespace), Labels: pulumi.StringMap{ "app.kubernetes.io/managed-by": pulumi.String("pulumi"), }, }, }) }