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" ) const ( loadBalancerAddressName = "internal-lb.localdomain" defaultRecordTTL = 180 ) type Configuration struct { Name string Namespace string Image string Dns *string Replicas *int Env map[string]string AllowAllOrigin bool } type internalConfiguration struct { Name string Namespace string Image string Dns string Replicas int Env map[string]string ShouldCreateDns bool ShouldCreateCertificate bool ShouldCreateIngress bool ResponseHeaders *traefikv1alpha1.MiddlewareSpecHeadersArgs } type Application struct { pulumi.ResourceState DeploymentName pulumi.StringOutput `pulumi:"deployment"` } func NewApplication(ctx *pulumi.Context, publicConfiguration *Configuration) (*Application, error) { if publicConfiguration.Name != "" && publicConfiguration.Image != "" { configuration := &internalConfiguration{ Name: publicConfiguration.Name, Namespace: publicConfiguration.Namespace, Image: publicConfiguration.Image, Env: publicConfiguration.Env, } if publicConfiguration.Replicas != nil { configuration.Replicas = *publicConfiguration.Replicas } else { configuration.Replicas = 1 } if publicConfiguration.Dns != nil { configuration.Dns = *publicConfiguration.Dns configuration.ShouldCreateIngress = true configuration.ShouldCreateDns = true configuration.ShouldCreateCertificate = true } if publicConfiguration.AllowAllOrigin { configuration.ResponseHeaders = &traefikv1alpha1.MiddlewareSpecHeadersArgs{ AccessControlAllowOriginList: toPulumiStringArray([]string{"*"}), } } return createResources(ctx, configuration) } else { return nil, errors.New("missing required value Name or Image during generic application construction") } } func createResources(ctx *pulumi.Context, configuration *internalConfiguration, 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 } appLabels := pulumi.StringMap{ "app.kubernetes.io/name": pulumi.String(configuration.Name), } deployment, err := createDeployment(ctx, configuration, namespace, appLabels, application) if err != nil { return nil, err } application.DeploymentName = deployment.Metadata.Name().Elem() service, err := createService(ctx, configuration, namespace, appLabels, application) if err != nil { return nil, err } 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, service, application, headerMiddleware) if err != nil { return nil, err } _, err = createDNSRecord(ctx, configuration, namespace, application) if err != nil { return nil, err } err = ctx.RegisterResourceOutputs(application, pulumi.Map{ "deployment": deployment.Metadata.Name(), }) 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 *internalConfiguration, namespace *corev1.Namespace, application *Application, ) (*traefikv1alpha1.Middleware, error) { 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)) } func createIngress(ctx *pulumi.Context, configuration *internalConfiguration, namespace *corev1.Namespace, certificate *v1.Certificate, service *corev1.Service, application *Application, responseHeaderMiddleware *traefikv1alpha1.Middleware) (*netv1.Ingress, error) { if configuration.ShouldCreateIngress { host := pulumi.String(configuration.Dns) 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) ingressAnnotations := pulumi.StringMap{ "traefik.ingress.kubernetes.io/router.middlewares": middlewares, "traefik.ingress.kubernetes.io/router.entrypoints": pulumi.String("websecure"), } 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: &netv1.HTTPIngressPathArray{ netv1.HTTPIngressPathArgs{ Path: pulumi.String("/"), PathType: pulumi.String("Prefix"), Backend: &netv1.IngressBackendArgs{ Service: &netv1.IngressServiceBackendArgs{ Name: service.Metadata.Name().Elem(), Port: &netv1.ServiceBackendPortArgs{ Name: pulumi.String("exposed-port"), }, }, }, }, }, }, }, }, 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 *internalConfiguration, 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 *internalConfiguration, 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 *internalConfiguration, 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 *internalConfiguration, 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.Image), Ports: corev1.ContainerPortArray{ corev1.ContainerPortArgs{ Name: pulumi.String("http"), ContainerPort: pulumi.Int(80), Protocol: pulumi.String("TCP"), }, }, Env: env, }, }, }, }, }, }, pulumi.Parent(application)) } func createNamespace(ctx *pulumi.Context, configuration *internalConfiguration) (*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"), }, }, }) }