// Copyright 2016-2024, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package incus import ( "context" "fmt" "path" "strings" // Allow embedding bridge-metadata.json in the provider. _ "embed" 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" ) // all of the token components used below. const ( // This variable controls the default name of the package in the package // registries for nodejs and python: mainPkg = "incus" // modules: mainMod = "index" // the incus module ) //go:embed cmd/pulumi-resource-incus/bridge-metadata.json var metadata []byte // Provider returns additional overlaid schema and metadata associated with the provider. func Provider() tfbridge.ProviderInfo { // Create a Pulumi provider mapping prov := tfbridge.ProviderInfo{ // Instantiate the Terraform provider // // The [pulumi-terraform-bridge](https://github.com/pulumi/pulumi-terraform-bridge) supports 3 // types of Terraform providers: // // 1. Providers written with the terraform-plugin-sdk/v1: // // If the provider you are bridging is written with the terraform-plugin-sdk/v1, then you // will need to adapt the boilerplate: // // - Change the import "shimv2" to "shimv1" and change the associated import to // "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v1". // // You can then proceed as normal. // // 2. Providers written with terraform-plugin-sdk/v2: // // This boilerplate is already geared towards providers written with the // terraform-plugin-sdk/v2, since it is the most common provider framework used. No // adaptions are needed. // // 3. Providers written with terraform-plugin-framework: // // If the provider you are bridging is written with the terraform-plugin-framework, then // you will need to adapt the boilerplate: // // - Remove the `shimv2` import and add: // // pfbridge "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfbridge" // // - Replace `shimv2.NewProvider` with `pfbridge.ShimProvider`. // // - In provider/cmd/pulumi-tfgen-incus/main.go, replace the // "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen" import with // "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfgen". Remove the `version.Version` // argument to `tfgen.Main`. // // - In provider/cmd/pulumi-resource-incus/main.go, replace the // "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" import with // "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfbridge". Replace the arguments to the // `tfbridge.Main` so it looks like this: // // tfbridge.Main(context.Background(), "incus", incus.Provider(), // tfbridge.ProviderMetadata{PulumiSchema: pulumiSchema}) // // Detailed instructions can be found at // https://pulumi-developer-docs.readthedocs.io/projects/pulumi-terraform-bridge/en/latest/docs/guides/new-pf-provider.html // After that, you can proceed as normal. // // This is where you give the bridge a handle to the upstream terraform provider. SDKv2 // convention is to have a function at "github.com/lxc/terraform-provider-incus/provider".New // which takes a version and produces a factory function. The provider you are bridging may // not do that. You will need to find the function (generally called in upstream's main.go) // that produces a: // // - *"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema".Provider (for SDKv2) // - *"github.com/hashicorp/terraform-plugin-sdk/v1/helper/schema".Provider (for SDKv1) // - "github.com/hashicorp/terraform-plugin-framework/provider".Provider (for plugin-framework) // //nolint:lll P: pfbridge.ShimProvider(incus.Provider()()), Name: "incus", Version: version.Version, // DisplayName is a way to be able to change the casing of the provider name when being // displayed on the Pulumi registry DisplayName: "Incus", // Change this to your personal name (or a company name) that you would like to be shown in // the Pulumi Registry if this package is published there. Publisher: "Kite.run", // LogoURL is optional but useful to help identify your package in the Pulumi Registry // if this package is published there. // // You may host a logo on a domain you control or add an PNG logo (100x100) for your package // in your repository and use the raw content URL for that file as your logo URL. LogoURL: "https://linuxcontainers.org/static/img/containers.png", // PluginDownloadURL is an optional URL used to download the Provider // for use in Pulumi programs // e.g. https://github.com/org/pulumi-provider-name/releases/download/v${VERSION}/ PluginDownloadURL: "", Description: "A Pulumi package for creating and managing incus cloud resources.", // 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", "containers", "nimbus"}, License: "Apache-2.0", Homepage: "https://linuxcontainers.org", 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{ // Add any required configuration here, or remove the example below if // no additional points are required. // "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"}, // }, // }, }, JavaScript: &tfbridge.JavaScriptInfo{ // RespectSchemaVersion ensures the SDK is generated linking to the correct version of the provider. 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. RespectSchemaVersion: true, // Enable modern PyProject support in the generated Python SDK. PyProject: struct{ Enabled bool }{true}, }, Golang: &tfbridge.GolangInfo{ // Set where the SDK is going to be published to. ImportBasePath: path.Join( "github.com/brandonkal/pulumi-incus/sdk/", tfbridge.GetModuleMajorVersion(version.Version), "go", mainPkg, ), // Opt in to all available code generation features. GenerateResourceContainerTypes: true, GenerateExtraInputTypes: true, // RespectSchemaVersion ensures the SDK is generated linking to the correct version of the provider. RespectSchemaVersion: true, }, CSharp: &tfbridge.CSharpInfo{ // RespectSchemaVersion ensures the SDK is generated linking to the correct version of the provider. RespectSchemaVersion: true, // Use a wildcard import so NuGet will prefer the latest possible version. PackageReferences: map[string]string{ "Pulumi": "3.*", }, }, } // MustComputeTokens maps all resources and datasources from the upstream provider into Pulumi. // // tokens.SingleModule puts every upstream item into your provider's main module. // // 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))) // 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 } }