package application import ( v1 "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/certmanager/v1" "antoine-roux.tk/projects/go/pulumi-library/crds/kubernetes/externaldns/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 } type internalConfiguration struct { Name string Namespace string Image string Dns string Replicas int Env map[string]string ShouldCreateDns bool ShouldCreateCertificate bool ShouldCreateIngress bool } 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 } return createResource(ctx, configuration) } else { return nil, errors.New("missing required value Name or Image during generic application construction") } } func createResource(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() _, 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 } _, err = createIngress(ctx, configuration, namespace, certificate, application) 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 createIngress( ctx *pulumi.Context, configuration *internalConfiguration, namespace *corev1.Namespace, certificate *v1.Certificate, application *Application, ) (*netv1.Ingress, error) { if configuration.ShouldCreateIngress { host := pulumi.String(configuration.Dns) 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: pulumi.StringMap{ "traefik.ingress.kubernetes.io/router.middlewares": pulumi.String("kube-ingress-gzip-compress@kubernetescrd"), "traefik.ingress.kubernetes.io/router.entrypoints": pulumi.String("websecure"), }, }, Spec: &netv1.IngressSpecArgs{ IngressClassName: nil, Rules: &netv1.IngressRuleArray{ netv1.IngressRuleArgs{ Host: host, Http: &netv1.HTTPIngressRuleValueArgs{}, }, }, 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) (*v1alpha1.DNSEndpoint, error) { if configuration.ShouldCreateDns { return v1alpha1.NewDNSEndpoint(ctx, fmt.Sprintf("%s-record", configuration.Name), &v1alpha1.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: &v1alpha1.DNSEndpointSpecArgs{ Endpoints: &v1alpha1.DNSEndpointSpecEndpointsArray{ &v1alpha1.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{ 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"), }, }, }) }