Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 1.6.0 (2025-10-17)

- Configurable package namespaces/prefixes. (https://github.com/pulumi/crd2pulumi/pull/247)

## 1.5.4 (2024-11-13)

- NodeJS now uses correct input/output types for object metadata. (https://github.com/pulumi/crd2pulumi/issues/158)
Expand Down
39 changes: 22 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,28 @@ Available Commands:
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
-d, --dotnet generate .NET
--dotnetName string name of generated .NET package (default "crds")
--dotnetNamespace string namespace of generated .NET package
--dotnetPath string optional .NET output dir
-f, --force overwrite existing files
-g, --go generate Go
--goName string name of generated Go package (default "crds")
--goPath string optional Go output dir
-h, --help help for crd2pulumi
-j, --java generate Java
--javaName string name of generated Java package (default "crds")
--javaNamespace string namespace of generated Java package
--javaPath string optional Java output dir
-n, --nodejs generate NodeJS
--nodejsName string name of generated NodeJS package (default "crds")
--nodejsNamespace string namespace of generated NodeJS package
--nodejsPath string optional NodeJS output dir
-p, --python generate Python
--pythonName string name of generated Python package (default "crds")
--pythonPackagePrefix string prefix of generated Python package
--pythonPath string optional Python output dir


Use "crd2pulumi [command] --help" for more information about a command.
```
Expand Down
5 changes: 5 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ func New() *cobra.Command {
f.StringVarP(&pythonSettings.PackageName, "pythonName", "", codegen.DefaultName, "name of generated Python package")
f.StringVarP(&javaSettings.PackageName, "javaName", "", codegen.DefaultName, "name of generated Java package")

f.StringVarP(&dotNetSettings.PackageNamespace, "dotnetNamespace", "", "", "namespace of generated .NET package")
f.StringVarP(&nodejsSettings.PackageNamespace, "nodejsNamespace", "", "", "namespace of generated NodeJS package")
f.StringVarP(&pythonSettings.PackageNamespace, "pythonPackagePrefix", "", "", "prefix of generated Python package")
f.StringVarP(&javaSettings.PackageNamespace, "javaNamespace", "", "", "namespace of generated Java package")

f.StringVarP(&dotNetSettings.OutputDir, "dotnetPath", "", "", "optional .NET output dir")
f.StringVarP(&goSettings.OutputDir, "goPath", "", "", "optional Go output dir")
f.StringVarP(&nodejsSettings.OutputDir, "nodejsPath", "", "", "optional NodeJS output dir")
Expand Down
9 changes: 9 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tools]

# Runtimes
go = "1.24.3"
node = '20.19.5'
python = '3.11.8'
dotnet = '8.0.414'
# Corretto version used as Java SE/OpenJDK version no longer offered
java = 'corretto-11'
4 changes: 2 additions & 2 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

// GenerateFunc is the function that is called by the generator to generate the code.
// It returns a mapping of filename to the contents of said file and any error that may have occurred.
type GenerateFunc func(pg *PackageGenerator, name string) (mapFileNameToData map[string]*bytes.Buffer, err error)
type GenerateFunc func(pg *PackageGenerator, cs *CodegenSettings) (mapFileNameToData map[string]*bytes.Buffer, err error)

var codeGenFuncs = map[string]GenerateFunc{
Go: GenerateGo,
Expand Down Expand Up @@ -59,7 +59,7 @@ func Generate(cs *CodegenSettings, yamls []io.ReadCloser) error {
}

// Do actual codegen
output, err := generate(pg, cs.PackageName)
output, err := generate(pg, cs)
if err != nil {
return fmt.Errorf("failed to generate %q package %q: %w", cs.Language, cs.PackageName, err)
}
Expand Down
23 changes: 14 additions & 9 deletions pkg/codegen/dotnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var unneededDotNetFiles = []string{
"Provider.cs",
}

func GenerateDotNet(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) {
func GenerateDotNet(pg *PackageGenerator, cs *CodegenSettings) (map[string]*bytes.Buffer, error) {
pkg := pg.SchemaPackageWithObjectMetaType()

// Set up C# namespaces
Expand All @@ -56,7 +56,8 @@ func GenerateDotNet(pg *PackageGenerator, name string) (map[string]*bytes.Buffer
// `Pulumi.Kubernetes.Types.Outputs.Meta.V1.ObjectMeta`. This path would
// only get generated properly if `compatibility` was `kubernetes20`.
oldName := pkg.Name
pkg.Name = name
pkg.Name = cs.PackageName
pkg.Namespace = cs.PackageNamespace
var err error

files, err := dotnet.GeneratePackage(PulumiToolName, pkg, nil, nil)
Expand All @@ -67,9 +68,13 @@ func GenerateDotNet(pg *PackageGenerator, name string) (map[string]*bytes.Buffer
pkg.Name = oldName
delete(pkg.Language, "csharp")

namespaceName := dotnet.Title(name)
files["KubernetesResource.cs"] = []byte(kubernetesResource(namespaceName))
files["Utilities.cs"] = []byte(dotNetUtilities(namespaceName))
packageName := dotnet.Title(cs.PackageName)
namespace := dotnet.Title(cs.PackageNamespace)
if namespace == "" {
namespace = "Pulumi"
}
files["KubernetesResource.cs"] = []byte(kubernetesResource(namespace, packageName))
files["Utilities.cs"] = []byte(dotNetUtilities(namespace, packageName))

// Delete unneeded files
for _, unneededFile := range unneededDotNetFiles {
Expand All @@ -84,9 +89,9 @@ func GenerateDotNet(pg *PackageGenerator, name string) (map[string]*bytes.Buffer
return buffers, nil
}

func kubernetesResource(name string) string {
func kubernetesResource(namespace string, name string) string {
return `// Copyright 2016-2022, Pulumi Corporation
namespace Pulumi.` + name + `{
namespace ` + namespace + `.` + name + `{
/// <summary>
/// A base class for all Kubernetes resources.
/// </summary>
Expand Down Expand Up @@ -116,15 +121,15 @@ namespace Pulumi.` + name + `{
// tried running `pulumi up` with the normal `Utilities.cs` file.
// As a temporary fix, this modified `Utilities.cs` file just removes the
// `static Utilities()` method.
func dotNetUtilities(name string) string {
func dotNetUtilities(namespace string, name string) string {
return `// *** WARNING: this file was generated by crd2pulumi. ***
// *** Do not edit by hand unless you're certain you know what you are doing! ***

using System;
using System.Reflection;
using Pulumi.Kubernetes;

namespace Pulumi.` + name + `
namespace ` + namespace + `.` + name + `
{
static class Utilities
{
Expand Down
4 changes: 2 additions & 2 deletions pkg/codegen/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var UnneededGoFiles = codegen.NewStringSet(
"utilities/pulumiVersion.go",
)

func GenerateGo(pg *PackageGenerator, name string) (buffers map[string]*bytes.Buffer, err error) {
func GenerateGo(pg *PackageGenerator, cs *CodegenSettings) (buffers map[string]*bytes.Buffer, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%v", r)
Expand All @@ -46,7 +46,7 @@ func GenerateGo(pg *PackageGenerator, name string) (buffers map[string]*bytes.Bu
pkg := pg.SchemaPackageWithObjectMetaType()
langName := "go"
oldName := pkg.Name
pkg.Name = name
pkg.Name = cs.PackageName
moduleToPackage, err := pg.ModuleToPackage()
if err != nil {
return nil, fmt.Errorf("%w", err)
Expand Down
25 changes: 19 additions & 6 deletions pkg/codegen/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import (
"bytes"
"fmt"
"regexp"
"strings"

"github.com/pulumi/crd2pulumi/internal/versions"
javaGen "github.com/pulumi/pulumi-java/pkg/codegen/java"
)

func GenerateJava(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) {
func GenerateJava(pg *PackageGenerator, cs *CodegenSettings) (map[string]*bytes.Buffer, error) {
pkg := pg.SchemaPackageWithObjectMetaType()

// These fields are required for the Java code generation
Expand All @@ -47,7 +48,19 @@ func GenerateJava(pg *PackageGenerator, name string) (map[string]*bytes.Buffer,

langName := "java"
oldName := pkg.Name
pkg.Name = name
pkg.Name = cs.PackageName
pkg.Namespace = cs.PackageNamespace

// Override Java base package to avoid pulumi-java defaulting logic adding an extra "com." prefix
if pkg.Language == nil {
pkg.Language = map[string]interface{}{}
}
pkg.Language[langName] = javaGen.PackageInfo{BasePackage: cs.PackageNamespace}

namespacePath := "com/pulumi"
if cs.PackageNamespace != "" {
namespacePath = strings.ReplaceAll(cs.PackageNamespace, ".", "/")
}

files, err := javaGen.GeneratePackage("crd2pulumi", pkg, nil, nil, true, false)
if err != nil {
Expand All @@ -58,19 +71,19 @@ func GenerateJava(pg *PackageGenerator, name string) (map[string]*bytes.Buffer,
delete(pkg.Language, langName)

// Pin the kubernetes provider version used
utilsPath := "src/main/java/com/pulumi/" + name + "/Utilities.java"
utilsPath := "src/main/java/" + namespacePath + "/" + cs.PackageName + "/Utilities.java"
utils, ok := files[utilsPath]
if !ok {
return nil, fmt.Errorf("cannot find generated utilities.ts")
return nil, fmt.Errorf("cannot find generated Utilities.java at path: %s", utilsPath)
}
re := regexp.MustCompile(`static \{(?:[^{}]|{[^{}]*})*}`)
files[utilsPath] = []byte(re.ReplaceAllString(string(utils), `static {
version = "4.9.0";
}`))

var unneededJavaFiles = []string{
"src/main/java/com/pulumi/" + name + "/Provider.java",
"src/main/java/com/pulumi/" + name + "/ProviderArgs.java",
"src/main/java/" + namespacePath + "/" + cs.PackageName + "/Provider.java",
"src/main/java/" + namespacePath + "/" + cs.PackageName + "/ProviderArgs.java",
"src/main/java/com/pulumi/kubernetes/meta/v1/inputs/ObjectMetaArgs.java",
"src/main/java/com/pulumi/kubernetes/meta/v1/outputs/ObjectMeta.java",
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/codegen/language.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ const Python string = "python"
const Java string = "java"

type CodegenSettings struct {
Language string
OutputDir string
PackageName string
PackageVersion string
Overwrite bool
ShouldGenerate bool
Language string
OutputDir string
PackageName string
PackageNamespace string
PackageVersion string
Overwrite bool
ShouldGenerate bool
}

func (cs *CodegenSettings) Path() string {
Expand Down
5 changes: 3 additions & 2 deletions pkg/codegen/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ export type ObjectMeta = k8s.types.input.meta.v1.ObjectMeta;
export type ObjectMetaPatch = k8s.types.input.meta.v1.ObjectMetaPatch;
`

func GenerateNodeJS(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) {
func GenerateNodeJS(pg *PackageGenerator, cs *CodegenSettings) (map[string]*bytes.Buffer, error) {
pkg := pg.SchemaPackageWithObjectMetaType()
oldName := pkg.Name
pkg.Name = name
pkg.Name = cs.PackageName
pkg.Namespace = cs.PackageNamespace

files, err := nodejs.GeneratePackage(PulumiToolName, pkg, nil, nil, true, nil)
if err != nil {
Expand Down
10 changes: 7 additions & 3 deletions pkg/codegen/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ const pythonMetaFile = `from pulumi_kubernetes.meta.v1._inputs import *
import pulumi_kubernetes.meta.v1.outputs
`

func GeneratePython(pg *PackageGenerator, name string) (map[string]*bytes.Buffer, error) {
func GeneratePython(pg *PackageGenerator, cs *CodegenSettings) (map[string]*bytes.Buffer, error) {
pkg := pg.SchemaPackageWithObjectMetaType()

langName := "python"
oldName := pkg.Name
pkg.Name = name
pkg.Name = cs.PackageName
pkg.Namespace = cs.PackageNamespace

files, err := python.GeneratePackage(PulumiToolName, pkg, nil, nil)
if err != nil {
Expand All @@ -42,7 +43,10 @@ func GeneratePython(pg *PackageGenerator, name string) (map[string]*bytes.Buffer
pkg.Name = oldName
delete(pkg.Language, langName)

pythonPackageDir := "pulumi_" + name
pythonPackageDir := "pulumi_" + cs.PackageName
if cs.PackageNamespace != "" {
pythonPackageDir = cs.PackageNamespace + "_" + cs.PackageName
}

// Remove unneeded files
var unneededPythonFiles = []string{
Expand Down
14 changes: 9 additions & 5 deletions tests/unneeded_go_files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,21 @@ spec:
}

// Pick a generated file we want to exclude
uneededGoFile := "uneededgofilestest/test/testResource.go"
codegen.UnneededGoFiles.Add(uneededGoFile)
unNeededGoFile := "unneededgofilestest/test/testResource.go"
codegen.UnneededGoFiles.Add(unNeededGoFile)

// Generate the code from the mocked CRD
buffers, err := codegen.GenerateGo(pg, "crds")
goSettings := &codegen.CodegenSettings{
Language: "go",
PackageName: "crds",
}
buffers, err := codegen.GenerateGo(pg, goSettings)
if err != nil {
t.Fatalf("GenerateGo failed: %v", err)
}

// Assert that buffers do not contain unneeded file
if _, exists := buffers["../kubernetes/"+uneededGoFile]; exists {
t.Errorf("Uneeded GO file was not excluded by GoGenerate, %s", uneededGoFile)
if _, exists := buffers["../kubernetes/"+unNeededGoFile]; exists {
t.Errorf("Unneeded GO file was not excluded by GoGenerate, %s", unNeededGoFile)
}
}
Loading