Skip to content

Commit

Permalink
Merge branch 'master' of github.com:pulumi/crd2pulumi
Browse files Browse the repository at this point in the history
  • Loading branch information
albert-zhong committed Sep 17, 2020
2 parents 223e024 + 1555409 commit 6aa256c
Show file tree
Hide file tree
Showing 20 changed files with 421 additions and 361 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: ci
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Go 1.14
uses: actions/setup-go@v2
with:
go-version: '1.14.x'
- name: Run Go Build
run: go build main.go
- name: Run tests
run: go test -v .
working-directory: tests
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: release
on:
push:
tags: [ "v*.[0-99]" ] # only a valid semver tag

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Unshallow clone
run: git fetch --prune --unshallow --tags
- name: Install Go 1.14
uses: actions/setup-go@v2
with:
go-version: '1.14.x'
- name: Goreleaser publish
uses: goreleaser/goreleaser-action@v1
with:
version: v0.134.0
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.PULUMI_BOT_TOKEN }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_STORE
.idea/
releases/
releases/
main
35 changes: 35 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
ldflags:
- -X github.com/pulumi/crd2pulumi/gen.Version={{.Tag}}
goarch:
- amd64
binary: crd2pulumi
main: ./main.go
archives:
- name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}"
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
skip: true
brews:
-
name: crd2pulumi
github:
owner: pulumi
name: homebrew-tap
commit_author:
name: pulumi-bot
email: [email protected]
homepage: "https://pulumi.com"
description: "Generate typed CustomResources in Pulumi from Kubernetes CRDs"
21 changes: 0 additions & 21 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
PROJECT := github.com/pulumi/crd2pulumi
VERSION := 1.0.4
LDFLAGS := "-X 'github.com/pulumi/crd2pulumi/gen.Version=$(VERSION)'"

GO ?= go
GOMODULE = GO111MODULE=on
Expand All @@ -10,22 +8,3 @@ ensure::

build::
$(GOMODULE) $(GO) build $(PROJECT)

release: rel-darwin rel-linux rel-windows

rel-darwin::
GOOS=darwin GOARCH=amd64 $(GO) build -ldflags=$(LDFLAGS) -o releases/crd2pulumi-darwin-amd64/crd2pulumi $(PROJECT)
tar -zcvf releases/crd2pulumi-darwin-amd64.tar.gz -C releases/crd2pulumi-darwin-amd64 .

rel-linux::
GOOS=linux GOARCH=386 $(GO) build -ldflags=$(LDFLAGS) -o releases/crd2pulumi-linux-386/crd2pulumi $(PROJECT)
tar -zcvf releases/crd2pulumi-linux-386.tar.gz -C releases/crd2pulumi-linux-386 .
GOOS=linux GOARCH=amd64 $(GO) build -ldflags=$(LDFLAGS) -o releases/crd2pulumi-linux-amd64/crd2pulumi $(PROJECT)
tar -zcvf releases/crd2pulumi-linux-amd64.tar.gz -C releases/crd2pulumi-linux-amd64 .

rel-windows::
GOOS=windows GOARCH=386 $(GO) build -ldflags=$(LDFLAGS) -o releases/crd2pulumi-windows-386/crd2pulumi.exe $(PROJECT)
zip -j releases/crd2pulumi-windows-386.zip releases/crd2pulumi-windows-386/crd2pulumi.exe
GOOS=windows GOARCH=amd64 $(GO) build -ldflags=$(LDFLAGS) -o releases/crd2pulumi-windows-amd64/crd2pulumi.exe $(PROJECT)
zip -j releases/crd2pulumi-windows-amd64.zip releases/crd2pulumi-windows-amd64/crd2pulumi.exe

65 changes: 47 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,75 @@ 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](https://www.pulumi.com/docs/reference/pkg/kubernetes/apiextensions/customresource/), 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](https://github.com/jetstack/cert-manager/tree/master/deploy/crds) or [istio](https://github.com/istio/istio/tree/0321da58ca86fc786fb03a68afd29d082477e4f2/manifests/charts/base/crds), 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.
`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](https://www.pulumi.com/docs/reference/pkg/kubernetes/apiextensions/customresource/), 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](https://github.com/jetstack/cert-manager/tree/master/deploy/crds) or
[istio](https://github.com/istio/istio/tree/0321da58ca86fc786fb03a68afd29d082477e4f2/manifests/charts/base/crds), 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](https://github.com/pulumi/crd2pulumi/releases) hosted on this repository.

`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`:
### Homebrew
`crd2pulumi` can be installed on Mac from the Pulumi Homebrew tap.
```console
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`:

```bash
$ go build -ldflags="-X 'github.com/pulumi/crd2pulumi/gen.Version=1.0.0'" -o $GOPATH/bin/crd2pulumi main.go
$ 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 `1.0.0`.
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.
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
```bash
crd2pulumi [-dgnp] [--nodejsPath path] [--pythonPath path] [--dotnetPath path] [--goPath path] <crd1.yaml> [crd2.yaml ...] [--force]
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] <crd1.yaml> [crd2.yaml ...] [flags]

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

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.

Example usage:
$ 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

Flags:
-d, --dotnet generate .NET
--dotnetPath string optional .NET output dir
-f, --force overwrite existing files
-g, --go generate Go
--goPath string optional Go output dir
-h, --help help for crd2pulumi
-n, --nodejs generate NodeJS
--nodejsPath string optional NodeJS output dir
-p, --python generate Python
--pythonPath string optional Python output dir

-f, --force overwrite existing files
-v, --version version for crd2pulumi
-h, --help help for crd2pulumi
```
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`, `-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.
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`, `-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](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/).
Let's use the example CronTab CRD specified in `resourcedefinition.yaml` from the
[Kubernetes Documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/).

### TypeScript
To generate a strongly-typed CronTab CustomResource in TypeScript, we can run this command:
Expand Down Expand Up @@ -105,7 +132,9 @@ crontab_instance = crontabs.stable.v1.CronTab(
```bash
$ 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.
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.
```go
package main

Expand Down
90 changes: 69 additions & 21 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ const (
PythonPath string = "pythonPath"
)

var defaultOutputPath = "crds/"
const (
DotNetName string = "dotnetName"
GoName string = "goName"
NodeJSName string = "nodejsName"
PythonName string = "pythonName"
)

const defaultOutputPath = "crds/"

const long = `crd2pulumi is a CLI tool that generates typed Kubernetes
CustomResources to use in Pulumi programs, based on a
Expand All @@ -53,7 +60,9 @@ Notice that by just setting a language-specific output path (--pythonPath, --nod
still get generated, so setting -p, -n, etc becomes unnecessary.
`

func getLanguageSettings(flags *pflag.FlagSet) gen.LanguageSettings {
// NewLanguageSettings returns the parsed language settings given a set of flags. Also returns a list of notices for
// possibly misinterpreted flags.
func NewLanguageSettings(flags *pflag.FlagSet) (gen.LanguageSettings, []string) {
nodejs, _ := flags.GetBool(NodeJS)
python, _ := flags.GetBool(Python)
dotnet, _ := flags.GetBool(DotNet)
Expand All @@ -64,32 +73,55 @@ func getLanguageSettings(flags *pflag.FlagSet) gen.LanguageSettings {
dotnetPath, _ := flags.GetString(DotNetPath)
goPath, _ := flags.GetString(GoPath)

ls := gen.LanguageSettings{}
nodejsName, _ := flags.GetString(NodeJSName)
pythonName, _ := flags.GetString(PythonName)
dotNetName, _ := flags.GetString(DotNetName)
goName, _ := flags.GetString(GoName)

var notices []string
ls := gen.LanguageSettings{
NodeJSName: nodejsName,
PythonName: pythonName,
DotNetName: dotNetName,
GoName: goName,
}
if nodejsPath != "" {
ls.NodeJSPath = &nodejsPath
} else if nodejs {
if nodejs {
notices = append(notices, "-n is not necessary if --nodejsPath is already set")
}
} else if nodejs || nodejsName != gen.DefaultName {
path := filepath.Join(defaultOutputPath, NodeJS)
ls.NodeJSPath = &path
}
if pythonPath != "" {
ls.PythonPath = &pythonPath
} else if python {
if python {
notices = append(notices, "-p is not necessary if --pythonPath is already set")
}
} else if python || pythonName != gen.DefaultName {
path := filepath.Join(defaultOutputPath, Python)
ls.PythonPath = &path
}
if dotnetPath != "" {
ls.DotNetPath = &dotnetPath
} else if dotnet {
if dotnet {
notices = append(notices, "-d is not necessary if --dotnetPath is already set")
}
} else if dotnet || dotNetName != gen.DefaultName {
path := filepath.Join(defaultOutputPath, DotNet)
ls.DotNetPath = &path
}
if goPath != "" {
ls.GoPath = &goPath
} else if golang {
if golang {
notices = append(notices, "-g is not necessary if --goPath is already set")
}
} else if golang || goName != gen.DefaultName{
path := filepath.Join(defaultOutputPath, Go)
ls.GoPath = &path
}
return ls
return ls, notices
}

var (
Expand All @@ -100,8 +132,7 @@ var (
Example: example,
Version: gen.Version,
Args: func(cmd *cobra.Command, args []string) error {
emptyLanguageSettings := gen.LanguageSettings{}
if getLanguageSettings(cmd.Flags()) == emptyLanguageSettings {
if ls, _ := NewLanguageSettings(cmd.Flags()); !ls.GeneratesAtLeastOneLanguage() {
return errors.New("must specify at least one language")
}

Expand All @@ -114,9 +145,12 @@ var (
},
Run: func(cmd *cobra.Command, args []string) {
force, _ := cmd.Flags().GetBool("force")
languageSettings := getLanguageSettings(cmd.Flags())
ls, notices := NewLanguageSettings(cmd.Flags())
for _, notice := range notices {
fmt.Println("notice: " + notice)
}

err := gen.Generate(languageSettings, args, force)
err := gen.Generate(ls, args, force)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(-1)
Expand All @@ -134,17 +168,31 @@ func Execute() error {
var forceValue bool
var nodeJSValue, pythonValue, dotNetValue, goValue bool
var nodeJSPathValue, pythonPathValue, dotNetPathValue, goPathValue string
var nodeJSNameValue, pythonNameValue, dotNetNameValue, goNameValue string

func init() {
rootCmd.PersistentFlags().BoolVarP(&nodeJSValue, NodeJS, "n", false, "generate NodeJS")
rootCmd.PersistentFlags().BoolVarP(&pythonValue, Python, "p", false, "generate Python")
rootCmd.PersistentFlags().BoolVarP(&dotNetValue, DotNet, "d", false, "generate .NET")
rootCmd.PersistentFlags().BoolVarP(&goValue, Go, "g", false, "generate Go")
addBoolFlag := func(p *bool, name, shorthand string, value bool, usage string) {
rootCmd.PersistentFlags().BoolVarP(p, name, shorthand, value, usage)
}

addBoolFlag(&forceValue, "force", "f", false, "overwrite existing files")

addBoolFlag(&nodeJSValue, NodeJS, "n", false, "generate NodeJS")
addBoolFlag(&pythonValue, Python, "p", false, "generate Python")
addBoolFlag(&dotNetValue, DotNet, "d", false, "generate .NET")
addBoolFlag(&goValue, Go, "g", false, "generate Go")

addStringFlag := func(p *string, name string, value string, usage string) {
rootCmd.PersistentFlags().StringVar(p, name, value, usage)
}

rootCmd.PersistentFlags().StringVar(&nodeJSPathValue, NodeJSPath, "", "optional NodeJS output dir")
rootCmd.PersistentFlags().StringVar(&pythonPathValue, PythonPath, "", "optional Python output dir")
rootCmd.PersistentFlags().StringVar(&dotNetPathValue, DotNetPath, "", "optional .NET output dir")
rootCmd.PersistentFlags().StringVar(&goPathValue, GoPath, "", "optional Go output dir")
addStringFlag(&nodeJSPathValue, NodeJSPath, "", "optional NodeJS output dir")
addStringFlag(&pythonPathValue, PythonPath, "", "optional Python output dir")
addStringFlag(&dotNetPathValue, DotNetPath, "", "optional .NET output dir")
addStringFlag(&goPathValue, GoPath, "", "optional Go output dir")

rootCmd.PersistentFlags().BoolVarP(&forceValue, "force", "f", false, "overwrite existing files")
addStringFlag(&nodeJSNameValue, NodeJSName, gen.DefaultName, "name of NodeJS package")
addStringFlag(&pythonNameValue, PythonName, gen.DefaultName, "name of Python package")
addStringFlag(&dotNetNameValue, DotNetName, gen.DefaultName, "name of .NET package")
addStringFlag(&goNameValue, GoName, gen.DefaultName, "name of Go package")
}
Loading

0 comments on commit 6aa256c

Please sign in to comment.