From b9ca51af014c47a1f13ba0a1e5a410c5917bc2b7 Mon Sep 17 00:00:00 2001 From: Brandon Kalinowski Date: Thu, 24 Apr 2025 15:46:06 -0400 Subject: [PATCH] customize resources --- .ci-mgmt.yaml | 2 - .gitignore | 5 + LICENSE | 1 + Makefile | 14 +-- README.md | 35 +----- examples/basic-ts/.gitignore | 2 + examples/basic-ts/index.ts | 13 ++- examples/basic-ts/package.json | 6 ++ provider/go.mod | 2 +- provider/resources.go | 192 +++++++++++++++++++++++++++++---- 10 files changed, 206 insertions(+), 66 deletions(-) create mode 100644 LICENSE diff --git a/.ci-mgmt.yaml b/.ci-mgmt.yaml index b07c6be..fbd7e0a 100644 --- a/.ci-mgmt.yaml +++ b/.ci-mgmt.yaml @@ -32,10 +32,8 @@ providerDefaultBranch: main # Explicit list of languages to support for SDKs. Java is currently excluded because it doesn't yet work well for non-internal providers. languages: - - dotnet - go - nodejs - - python # Disable Java publishing and pushing the provider binary to the CDN as these only work internally within Pulumi. publish: diff --git a/.gitignore b/.gitignore index 9560d10..c388ee5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Pulumi.*.yaml .vscode/settings.json yarn.lock +pnpm-lock.yaml **/pulumiManifest.go ci-scripts @@ -35,5 +36,9 @@ sdk/python/venv go.work go.work.sum +*.pem +*.key +*.crt + # Ignore local build tracking directory .make diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e2aa499 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +This SDK is © 2025 Brandon Kalinowski. No license is provided to users other than the author. diff --git a/Makefile b/Makefile index 1615a47..f796991 100644 --- a/Makefile +++ b/Makefile @@ -50,9 +50,9 @@ only_build: build prepare_local_workspace: install_plugins upstream # Creates all generated files which need to be committed generate: generate_sdks schema -generate_sdks: generate_dotnet generate_go generate_nodejs generate_python -build_sdks: build_dotnet build_go build_nodejs build_python -install_sdks: install_dotnet_sdk install_go_sdk install_nodejs_sdk install_python_sdk +generate_sdks: generate_go generate_nodejs +build_sdks: build_go build_nodejs +install_sdks: install_go_sdk install_nodejs_sdk .PHONY: development only_build build generate generate_sdks build_sdks install_sdks help: @@ -146,9 +146,9 @@ build_nodejs: .make/build_nodejs @touch $@ .make/build_nodejs: .make/generate_nodejs cd sdk/nodejs/ && \ - yarn install && \ - yarn run tsc && \ - cp ../../README.md ../../LICENSE* package.json yarn.lock ./bin/ + pnpm install && \ + pnpm tsc && \ + cp ../../README.md ../../LICENSE* package.json pnpm-lock.yaml ./bin/ @touch $@ .PHONY: generate_nodejs build_nodejs @@ -192,7 +192,7 @@ install_go_sdk: install_java_sdk: install_nodejs_sdk: .make/install_nodejs_sdk .make/install_nodejs_sdk: .make/build_nodejs - yarn link --cwd $(WORKING_DIR)/sdk/nodejs/bin + pnpm link --global $(WORKING_DIR)/sdk/nodejs/bin @touch $@ install_python_sdk: .PHONY: install_dotnet_sdk install_go_sdk install_java_sdk install_nodejs_sdk install_python_sdk diff --git a/README.md b/README.md index 60fd1f1..e46da80 100644 --- a/README.md +++ b/README.md @@ -8,48 +8,21 @@ This package is available for several languages/platforms: ### Node.js (JavaScript/TypeScript) -To use from JavaScript or TypeScript in Node.js, install using either `npm`: +To use from JavaScript or TypeScript in Node.js, install using `pnpm`: ```bash -npm install @kiterun/incus +pnpm install @kiterun/incus ``` -or `yarn`: - -```bash -yarn add @kiterun/incus -``` - -### Python - -To use from Python, install using `pip`: - -```bash -pip install pulumi_incus -``` - -### Go +### Go (unsupported) To use from Go, use `go get` to grab the latest version of the library: ```bash -go get github.com/pulumi/pulumi-incus/sdk/go/... +go get git.kalinow.ski/nimbus/pulumi-incus/sdk/go/... ``` - -### .NET - -To use from .NET, install using `dotnet add package`: - -```bash -dotnet add package Pulumi.Incus ``` -## Configuration - -The following configuration points are available for the `incus` provider: - -- `incus:region` (environment: `INCUS_REGION`) - the region in which to deploy resources - ## Reference For detailed reference documentation, please visit [the Pulumi registry](https://www.pulumi.com/registry/packages/incus/api-docs/). diff --git a/examples/basic-ts/.gitignore b/examples/basic-ts/.gitignore index c695889..0505a3d 100644 --- a/examples/basic-ts/.gitignore +++ b/examples/basic-ts/.gitignore @@ -1,2 +1,4 @@ /bin/ /node_modules/ +*.pem + diff --git a/examples/basic-ts/index.ts b/examples/basic-ts/index.ts index 940c60d..779c60c 100644 --- a/examples/basic-ts/index.ts +++ b/examples/basic-ts/index.ts @@ -1,6 +1,11 @@ -import * as pulumi from "@pulumi/pulumi"; -import * as xyz from "@pulumi/xyz"; +import * as incus from "@kiterun/incus"; -const resource = new xyz.Resource("Resource", { sampleAttribute: "attr" }); +const resource = new incus.Instance("instance1", { + image: "images:ubuntu/22.04", + project: "default", + config: { + "limits.cpu": "4", + }, +}) -export const sampleAttribute = resource.sampleAttribute; +export const ip1 = resource.ipv4Address; diff --git a/examples/basic-ts/package.json b/examples/basic-ts/package.json index f4a82b1..dcb7e37 100644 --- a/examples/basic-ts/package.json +++ b/examples/basic-ts/package.json @@ -5,6 +5,12 @@ "@types/node": "^18" }, "dependencies": { + "@kiterun/incus": "link:../../../../../../Library/pnpm/global/5/node_modules/@kiterun/incus", "@pulumi/pulumi": "^3.0.0" + }, + "pnpm": { + "overrides": { + "@kiterun/incus": "link:../../../../../../Library/pnpm/global/5/node_modules/@kiterun/incus" + } } } diff --git a/provider/go.mod b/provider/go.mod index c093bbf..b84e85d 100644 --- a/provider/go.mod +++ b/provider/go.mod @@ -12,6 +12,7 @@ require ( github.com/lxc/terraform-provider-incus/shim v0.0.0-00010101000000-000000000000 github.com/pulumi/pulumi-terraform-bridge/v3 v3.106.0 github.com/pulumi/pulumi/pkg/v3 v3.160.0 + github.com/pulumi/pulumi/sdk/v3 v3.160.0 ) @@ -143,7 +144,6 @@ require ( github.com/pulumi/inflector v0.1.1 // indirect github.com/pulumi/pulumi-java/pkg v1.8.0 // indirect github.com/pulumi/pulumi-yaml v1.15.1 // indirect - github.com/pulumi/pulumi/sdk/v3 v3.160.0 // indirect github.com/pulumi/schema-tools v0.1.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect diff --git a/provider/resources.go b/provider/resources.go index ccb4079..2b1111d 100644 --- a/provider/resources.go +++ b/provider/resources.go @@ -15,7 +15,10 @@ package incus import ( + "context" + "fmt" "path" + "strings" // Allow embedding bridge-metadata.json in the provider. _ "embed" @@ -23,8 +26,10 @@ import ( incus "github.com/lxc/terraform-provider-incus/shim" // Import the upstream provider pfbridge "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfbridge" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/tokens" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/brandonkal/pulumi-incus/provider/pkg/version" ) @@ -38,9 +43,7 @@ const ( mainMod = "index" // the incus module ) -// Temporarily comment out the bridge-metadata.json embedding -// //go:embed cmd/pulumi-resource-incus/bridge-metadata.json -// var metadata []byte +//go:embed cmd/pulumi-resource-incus/bridge-metadata.json var metadata []byte // Provider returns additional overlaid schema and metadata associated with the provider. @@ -131,37 +134,40 @@ func Provider() tfbridge.ProviderInfo { // category/cloud tag helps with categorizing the package in the Pulumi Registry. // For all available categories, see `Keywords` in // https://www.pulumi.com/docs/guides/pulumi-packages/schema/#package. - Keywords: []string{"incus", "category/cloud"}, + Keywords: []string{"incus", "category/cloud", "containers", "nimbus"}, License: "Apache-2.0", Homepage: "https://linuxcontainers.org", - Repository: "https://github.com/brandonkal/pulumi-incus", + Repository: "https://git.kalinow.ski/nimbus/pulumi-incus", // The GitHub Org for the provider - defaults to `terraform-providers`. Note that this should // match the TF provider module's require directive, not any replace directives. GitHubOrg: "lxc", // Temporarily comment out the MetadataInfo MetadataInfo: tfbridge.NewProviderMetadata(metadata), - Config: map[string]*tfbridge.SchemaInfo{ + Config: map[string]*tfbridge.SchemaInfo{ // Add any required configuration here, or remove the example below if // no additional points are required. - "region": { - Type: "incus:region/region:Region", - }, + // "region": { + // Type: "incus:region/region:Region", + // }, }, // If extra types are needed for configuration, they can be added here. ExtraTypes: map[string]schema.ComplexTypeSpec{ - "incus:region/region:Region": { - ObjectTypeSpec: schema.ObjectTypeSpec{ - Type: "string", - }, - Enum: []schema.EnumValueSpec{ - {Name: "here", Value: "HERE"}, - {Name: "overThere", Value: "OVER_THERE"}, - }, - }, + // "incus:region/region:Region": { + // ObjectTypeSpec: schema.ObjectTypeSpec{ + // Type: "string", + // }, + // Enum: []schema.EnumValueSpec{ + // {Name: "here", Value: "HERE"}, + // {Name: "overThere", Value: "OVER_THERE"}, + // }, + // }, }, JavaScript: &tfbridge.JavaScriptInfo{ // RespectSchemaVersion ensures the SDK is generated linking to the correct version of the provider. - RespectSchemaVersion: true, + RespectSchemaVersion: true, + PackageName: "@kiterun/incus", + TypeScriptVersion: "^5.8.3", + UseTypeOnlyReferences: true, }, Python: &tfbridge.PythonInfo{ // RespectSchemaVersion ensures the SDK is generated linking to the correct version of the provider. @@ -199,11 +205,155 @@ func Provider() tfbridge.ProviderInfo { // // You shouldn't need to override anything, but if you do, use the [tfbridge.ProviderInfo.Resources] // and [tfbridge.ProviderInfo.DataSources]. - prov.MustComputeTokens(tokens.SingleModule("incus_", mainMod, - tokens.MakeStandard(mainPkg))) + prov.MustComputeTokens(tokens.SingleModule("incus_", mainMod, tokens.MakeStandard(mainPkg))) + + // Add special ID handling for the incus_instance resource + // Docs here https://registry.terraform.io/providers/lxc/incus/latest/docs/resources/certificate + + defaultProjectField := map[string]*info.Schema{ + "project": { + Default: &info.Default{ + Value: "default", + }, + }, + } + + prov.Resources["incus_certificate"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "Certificate"), + ComputeID: tfbridge.DelegateIDField("name", prov.Name, prov.Repository), + } + + prov.Resources["incus_cluster_group"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "ClusterGroup"), + ComputeID: tfbridge.DelegateIDField("name", prov.Name, prov.Repository), + } + + prov.Resources["incus_image"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "Image"), + // Does this work if source image is used? + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "fingerprint"}, prov.Name, prov.Repository), + // DREAM: default project + // Fields: defaultProjectField, + } + + prov.Resources["incus_instance"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "Instance"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + DeleteBeforeReplace: true, + } + + prov.Resources["incus_instance_snapshot"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "InstanceSnapshot"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_network"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "Network"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_network_acl"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkAcl"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_network_forward"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkForward"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "network", "listen_address"}, prov.Name, prov.Repository), + // DREAM: project + // Fields: defaultProjectField, + } + + prov.Resources["incus_network_integration"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkIntegration"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name", "type"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_network_lb"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkLoadBalancer"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "network", "listen_address"}, prov.Name, prov.Repository), + // Fields: defaultProjectField, + } + + prov.Resources["incus_network_peer"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkPeer"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name", "network"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_network_zone"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkZone"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_network_zone_record"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "NetworkZoneRecord"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "zone", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_profile"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "Profile"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_project"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "Project"), + ComputeID: tfbridge.DelegateIDField("name", prov.Name, prov.Repository), + } + + prov.Resources["incus_storage_bucket"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "StorageBucket"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "pool", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_storage_bucket_key"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "StorageBucketKey"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "pool", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_storage_pool"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "StoragePool"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } + + prov.Resources["incus_storage_volume"] = &tfbridge.ResourceInfo{ + Tok: tfbridge.MakeResource(mainPkg, mainMod, "StorageVolume"), + ComputeID: delegateIDFields([]resource.PropertyKey{"project", "pool", "name"}, prov.Name, prov.Repository), + Fields: defaultProjectField, + } prov.MustApplyAutoAliases() prov.SetAutonaming(255, "-") return prov } + +func delegateIDFields(fields []resource.PropertyKey, providerName, repoURL string) tfbridge.ComputeID { + return func(ctx context.Context, state resource.PropertyMap) (resource.ID, error) { + if len(fields) == 0 { + return "", fmt.Errorf("no fields provided for ID computation") + } + + var idParts []string + for _, field := range fields { + id, err := tfbridge.DelegateIDProperty(resource.PropertyPath{string(field)}, providerName, repoURL)(ctx, state) + if err != nil { + return "", err + } + idParts = append(idParts, string(id)) + } + + return resource.ID(strings.Join(idParts, "/")), nil + } +}