README ¶
tfpgen
This project aims to provide a Terraform provider for managing Custom Resource Definitions (CRDs) in Kubernetes. The tfpgen
tool generates the necessary provider code based on the CRD schemas provided. This provider allows users to easily create, update, and delete CRDs using Terraform. The generated provider code can be installed and used locally or published to the Terraform registry.
Usage
- Create a repository (the repository address is used as the Go module name)
- Initialize the Go module
e.g.go mod init <module-name>
go mod init github.com/vvbogdanov87/terraform-provider-crd
- Create a
schemas
directory in the repository root and copy CRDs in the directory - Create a
tfpgen.yaml
file in the repository rootname: "crd" # Name is the provider name. address: "registry.terraform.io/vvbogdanov87/crd" # Address is the provider address for the Terraform registry. moduleName: "github.com/vvbogdanov87/terraform-provider-crd" # ModuleName is the name of the Go module. schemasDir: "schemas" # SchemasDir is the directory containing the CRD schemas. outputDir: "." # OutputDir is the directory to write the generated provider code.
- Generate code
go run github.com/vvbogdanov87/tfpgen --config tfpgen.yaml
- Add dependencies
go mod tidy
- Install the provider
go install
- Point terraform to the installed provider. Create file
~/.terraformrc
provider_installation { dev_overrides { # "provider address" = "$HOME/go/bin" "registry.terraform.io/vvbogdanov87/crd" = "/home/viktor/go/bin" } direct {} }
CRD properties and TF attributes
CRD camel case property names are mapped to TF snake case attribute names.
CRD/OpenAPI type | GO type | TF attribyte type |
---|---|---|
string | string | schema.StringAttribute |
integer | int64 | schema.Int64Attribute |
number | float64 | schema.Float64Attribute |
boolean | boolean | schema.BoolAttribute |
object with AdditionalProperties and Schema.Type = object |
map[string]struct | schema.MapNestedAttribute |
object with AdditionalProperties |
map[string]primitive | schema.MapAttribute |
object with Properties |
struct | schema.SingleNestedAttribute |
array with Schema.Type = object |
[]struct | schema.ListNestedAttribute |
array | []primitive | schema.ListAttribute |
Note: the field additionalProperties
is mutually exclusive with properties
.
OpenAPI Data Types
OpenAPI Schema Object default
field is supported for string
integer
number
and boolean
types.
OpenAPI Schema Object enum
field is supported via terraform-plugin-framework-validators
for string
integer
and number
types.
OpenAPI Schema Object minimum
and maximum
fields are supported via terraform-plugin-framework-validators
for integer
and number
types.
OpenAPI Schema Object MinLength
MaxLength
Pattern
Format: "byte"
and Format: "date-time"
fields are supported via terraform-plugin-framework-validators
for string
type.
Immutable fields
OpenAPI schema doesn't support immutable fields. Kubernetes uses a Common Expression Language (CEL) extension to make fields immutable.
To tell the generator that a property is immutable and needs TF attribute plan modifier RequiresReplace
the prefix (immutable)
must be added to the property description. E.g.:
prefix:
type: string
description: "(immutable) The prefix to use for the bucket name"
x-kubernetes-validations:
- rule: self == oldSelf
message: Value is immutable
Computed fields
This project relies on the implicit conversion of Kubernetes Go CRD types to Terraform attributes. This is because generating code for explicit conversion is quite tricky.
In addition to Null
values (absence of a value) Terraform type system also has Unknown
values(that is not yet known). The value is Unknown
if a Terraform attribute is marked as computed so it will be computed and set by the provider (or rather returned by the API that the provider calls). For example, if a Crossplane composition creates an AWS bucket, the bucket ARN will be known only after the bucket is created. The provider's job is to create the bucket, get the bucket ARN, and save it in the state.
But this creates some complications. If we try to populate the Go CRD type with the plan that has an attribute in the Unknown
state, the implicit Terraform conversion will fail. To overcome this limitation the project relies on the partial plan retrieval. All computed CRD fields/TF attributes
(with the exception, see below) must be defined under the Status
field. Only the Spec
field is implicitly converted from Terraform attributes to CRD Go types when the plan is read. The idea is that all user input goes under the Spec
section and everything that is set by Kubernetes or its controllers (eg Crossplane) goes under the Status
section. The Status
section is not needed when an object is created in Kubernetes, so it is OK that it is not retrieved when the plan is read. After the object is created the provider retrieves the whole object including the Status
field and saves it in the state.
The exception is computed fields that also have a default value set. In this case Terraform returns the default value when Terraform plan is converted to the CRD Go type. Therefore a field with a default value can be defined in the Spec
section.
Well known Crossplane CRD properties
Crossplane adds fields when it generates a CRD from XRD. The generator skips the next Crossplane-specific fields:
in Spec
:
- compositeDeletePolicy
- compositionRef
- compositionRevisionRef
- compositionRevisionSelector
- compositionSelector
- compositionUpdatePolicy
- publishConnectionDetailsTo
- resourceRef
- writeConnectionSecretToRef
in Status
:
- connectionDetails
- in
conditions
we only needtype
andstatus
Crossplane delete operation
To properly handle the delete operation in Terraform, XRD defaultCompositeDeletePolicy should be set to Foreground
. This causes Kubernetes to use foreground cascading deletion which deletes all child resources before deleting the parent resource. The claim controller waits for the composite deletion to finish before returning.
Testing
Replace /home/runner/go/bin
in ./tests/terraform-provider-crd/.terraformrc
with your absolute go/bin
path. This is needed because $HOME
interpolation does not work in the provider_installation
block. Don't commit the change to the .terraformrc
file.
kind create cluster
make test-local
kind delete cluster
Acknowledgements
tfpgen
is inspired by terraform-provider-k8s
Documentation ¶
There is no documentation for this package.