pulumi / crd2pulumi

Generate typed CustomResources from a Kubernetes CustomResourceDefinition
Apache License 2.0
100 stars 17 forks source link

crd2pulumi

Generate typed CustomResources based on Kubernetes CustomResourceDefinitions.

Goals

crd2pulumi is a CLI tool that generates typed CustomResources based on Kubernetes CustomResourceDefinition (CRDs). CRDs allow you to extend the Kubernetes API by defining your own schemas for custom objects. While Pulumi lets you create CustomResources, there was previously no strong-typing for these objects since every schema was, well, custom. This can be a massive headache for popular CRDs such as cert-manager or istio, which contain thousands of lines of complex YAML schemas. By generating typed versions of CustomResources, crd2pulumi makes filling out their arguments more convenient by allowing you to leverage existing IDE type checking and autocomplete features.

Building and Installation

If you wish to use crd2pulumi without developing the tool itself, you can use one of the binary releases hosted on this repository.

Homebrew

crd2pulumi can be installed on Mac from the Pulumi Homebrew tap.

brew install pulumi/tap/crd2pulumi

crd2pulumi uses Go modules to manage dependencies. If you want to develop crd2pulumi itself, you'll need to have Go installed in order to build. Once you install this prerequisite, run the following to build the crd2pulumi binary and install it into $GOPATH/bin:

$ go build -ldflags="-X github.com/pulumi/crd2pulumi/gen.Version=dev" -o $GOPATH/bin/crd2pulumi main.go

The ldflags argument is necessary to dynamically set the crd2pulumi version at build time. However, the version itself can be anything, so you don't have to set it to dev.

Go should then automatically handle pulling the dependencies for you. If $GOPATH/bin is not on your path, you may want to move the crd2pulumi binary from $GOPATH/bin into a directory that is on your path.

Usage

crd2pulumi is a CLI tool that generates typed Kubernetes
CustomResources to use in Pulumi programs, based on a
CustomResourceDefinition YAML schema.

Usage:
  crd2pulumi [-dgnp] [--nodejsPath path] [--pythonPath path] [--dotnetPath path] [--goPath path] [--javaPath path] <crd1.yaml> [crd2.yaml ...] [flags]
  crd2pulumi [command]

Examples:
crd2pulumi --nodejs crontabs.yaml
crd2pulumi -dgnp crd-certificates.yaml crd-issuers.yaml crd-challenges.yaml
crd2pulumi --pythonPath=crds/python/istio --nodejsPath=crds/nodejs/istio crd-all.gen.yaml crd-mixer.yaml crd-operator.yaml
crd2pulumi --pythonPath=crds/python/gke https://raw.githubusercontent.com/GoogleCloudPlatform/gke-managed-certs/master/deploy/managedcertificates-crd.yaml

Notice that by just setting a language-specific output path (--pythonPath, --nodejsPath, etc) the code will
still get generated, so setting -p, -n, etc becomes unnecessary.

Available Commands:
  help        Help about any command
  version     Print the version number of crd2pulumi

Flags:
  -d, --dotnet              generate .NET
      --dotnetName string   name of .NET package (default "crds")
      --dotnetPath string   optional .NET output dir
  -f, --force               overwrite existing files
  -g, --go                  generate Go
      --goName string       name of Go package (default "crds")
      --goPath string       optional Go output dir
  -h, --help                help for crd2pulumi
  -j, --java                generate Java
      --javaName string     name of Java package (default "crds")
      --javaPath string     optional Java output dir
  -n, --nodejs              generate NodeJS
      --nodejsName string   name of NodeJS package (default "crds")
      --nodejsPath string   optional NodeJS output dir
  -p, --python              generate Python
      --pythonName string   name of Python package (default "crds")
      --pythonPath string   optional Python output dir

Use "crd2pulumi [command] --help" for more information about a command.

Setting only a language-specific flag will output the generated code in the default directory; so -d will output to crds/dotnet, -g will output to crds/go, -j will output to crds/java, -n will output to crds/nodejs, and -p will output to crds/python. You can also specify a language-specific path (--pythonPath, --nodejsPath, etc) to control where the code will be outputted, in which case setting -p, -n, etc becomes unnecessary.

Examples

Let's use the example CronTab CRD specified in resourcedefinition.yaml from the Kubernetes Documentation.

TypeScript

To generate a strongly-typed CronTab CustomResource in TypeScript, we can run this command:

$ crd2pulumi --nodejsPath ./crontabs resourcedefinition.yaml

Now let's import the generated code into a Pulumi program that provisions the CRD and creates an instance of it.

import * as crontabs from "./crontabs"
import * as pulumi from "@pulumi/pulumi"
import * as k8s from "@pulumi/kubernetes";

// Register the CronTab CRD.
const cronTabDefinition = new k8s.yaml.ConfigFile("my-crontab-definition", { file: "resourcedefinition.yaml" });

// Instantiate a CronTab resource.
const myCronTab = new crontabs.stable.v1.CronTab("my-new-cron-object",
{
    metadata: {
        name: "my-new-cron-object",
    },
    spec: {
        cronSpec: "* * * * */5",
        image: "my-awesome-cron-image",
    }
})

As you can see, the CronTab object is typed! For example, if you try to set cronSpec to a non-string or add an extra field, your IDE should immediately warn you.

Python

$ crd2pulumi --pythonPath ./crontabs resourcedefinition.yaml
import pulumi_kubernetes as k8s
import crontabs.pulumi_crds as crontabs

# Register the CronTab CRD.
crontab_definition = k8s.yaml.ConfigFile("my-crontab-definition", file="resourcedefinition.yaml")

# Instantiate a CronTab resource.
crontab_instance = crontabs.stable.v1.CronTab(
    "my-new-cron-object",
    metadata=k8s.meta.v1.ObjectMetaArgs(
        name="my-new-cron-object"
    ),
    spec=crontabs.stable.v1.CronTabSpecArgs(
        cron_spec="* * * */5",
        image="my-awesome-cron-image",
    )
)

Go

$ crd2pulumi --goPath ./crontabs resourcedefinition.yaml

Now we can access the NewCronTab() constructor. Create a main.go file with the following code. In this example, the Pulumi project's module is named crds-go-final, so the import path is crds-go-final/crontabs/stable/v1. Make sure to swap this out with your own module's name.

package main

import (
    crontabs_v1 "crds-go-final/crontabs/stable/v1"

    meta_v1 "github.com/pulumi/pulumi-kubernetes/sdk/v2/go/kubernetes/meta/v1"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
    // Register the CronTab CRD.
    _, err := yaml.NewConfigFile(ctx, "my-crontab-definition",
      &yaml.ConfigFileArgs{
        File: "resourcedefinition.yaml",
      },
    )
    if err != nil {
      return err
    }

        // Instantiate a CronTab resource.
        _, err := crontabs_v1.NewCronTab(ctx, "cronTabInstance", &crontabs_v1.CronTabArgs{
            Metadata: &meta_v1.ObjectMetaArgs{
                Name: pulumi.String("my-new-cron-object"),
            },
            Spec: crontabs_v1.CronTabSpecArgs{
                CronSpec: pulumi.String("* * * * */5"),
                Image:    pulumi.String("my-awesome-cron-image"),
                Replicas: pulumi.IntPtr(3),
            },
        })
        if err != nil {
            return err
        }

        return nil
    })
}

C\

$ crd2pulumi --dotnetPath ./crontabs resourcedefinition.yaml
using Pulumi;
using Pulumi.Kubernetes.Yaml;
using Pulumi.Kubernetes.Types.Inputs.Meta.V1;

class MyStack : Stack
{
    public MyStack()
    {
    // Register a CronTab CRD.
    var cronTabDefinition = new Pulumi.Kubernetes.Yaml.ConfigFile("my-crontab-definition",
        new ConfigFileArgs{
            File = "resourcedefinition.yaml"
        }
    );

    // Instantiate a CronTab resource.
    var cronTabInstance = new Pulumi.Crds.Stable.V1.CronTab("cronTabInstance",
        new Pulumi.Kubernetes.Types.Inputs.Stable.V1.CronTabArgs{
            Metadata = new ObjectMetaArgs{
                Name = "my-new-cron-object"
            },
            Spec = new Pulumi.Kubernetes.Types.Inputs.Stable.V1.CronTabSpecArgs{
                CronSpec = "* * * * */5",
                Image = "my-awesome-cron-image"
            }
        });    
    }
}

If you get an Duplicate 'global::System.Runtime.Versioning.TargetFrameworkAttribute' attribute error when trying to run pulumi up, then try deleting the crontabs/bin and crontabs/obj folders.

Java

$ crd2pulumi --javaPath ./crontabs resourcedefinition.yaml
package com.example;

import com.pulumi.Pulumi;

public class MyStack {

    public static void main(String[] args) {
        Pulumi.run(ctx -> {
            // Register a CronTab CRD (Coming Soon - see https://www.pulumi.com/registry/packages/kubernetes/api-docs/yaml/configfile/)

            // Instantiate a CronTab resource.
            var cronTabInstance = new com.pulumi.crds.stable.v1.CronTab("cronTabInstance",
                    com.pulumi.crds.stable.v1.CronTabArgs.builder()
                            .metadata(com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs.builder()
                                    .name("my-new-cron-object")
                                    .build())
                            .spec(com.pulumi.kubernetes.stable.v1.inputs.CronTabSpecArgs.builder()
                                    .cronSpec("* * * * */5")
                                    .image("my-awesome-cron-image")
                                    .build())
                            .build());
        });
    }
}

Now let's run the program and perform the update.

$ pulumi up
Previewing update (dev):
  Type                                                      Name                Plan
  pulumi:pulumi:Stack                                       examples-dev
 +   ├─ kubernetes:stable.example.com:CronTab                   my-new-cron-object  create
 +   └─ kubernetes:apiextensions.k8s.io:CustomResourceDefinition  my-crontab-definition  create
Resources:
  + 2 to create
  1 unchanged
Do you want to perform this update? yes
Updating (dev):
  Type                                                      Name                Status
  pulumi:pulumi:Stack                                       examples-dev
 +   ├─ kubernetes:stable.example.com:CronTab                   my-new-cron-object  created
 +   └─ kubernetes:apiextensions.k8s.io:CustomResourceDefinition  my-crontab-definition  created
Outputs:
  urn: "urn:pulumi:dev::examples::kubernetes:stable.example.com/v1:CronTab::my-new-cron-object"
Resources:
  + 2 created
  1 unchanged
Duration: 17s
Permalink: https://app.pulumi.com/albert-zhong/examples/dev/updates/4

It looks like both the CronTab definition and instance were both created! Finally, let's verify that they were created by manually viewing the raw YAML data:

$ kubectl get ct -o yaml
- apiVersion: stable.example.com/v1
  kind: CronTab
  metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"stable.example.com/v1","kind":"CronTab","metadata":{"labels":{"app.kubernetes.io/managed-by":"pulumi"},"name":"my-new-cron-object"},"spec":{"cronSpec":"* * * * */5","image":"my-awesome-cron-image"}}
  creationTimestamp: "2020-08-10T09:50:38Z"
  generation: 1
  labels:
    app.kubernetes.io/managed-by: pulumi
  name: my-new-cron-object
  namespace: default
  resourceVersion: "1658962"
  selfLink: /apis/stable.example.com/v1/namespaces/default/crontabs/my-new-cron-object
  uid: 5e2c56a2-7332-49cf-b0fc-211a0892c3d5
  spec:
  cronSpec: '* * * * */5'
  image: my-awesome-cron-image
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""