Upjet

repository·main·Indexed 19 days ago

https://github.com/crossplane/upjet

A framework for automating the creation of Crossplane providers by consuming existing Terraform providers. Upjet generates Kubernetes CRDs, Go code, and controllers to manage Terraform resource logic via Kubernetes APIs. It includes a code generation pipeline, a generic reconciler runtime, a documentation scraper, and frameworks for migration and API version conversion.

Tokens
48.6K
Snippets
89
Records
151
Agent score
66%

What's inside Upjet

  1. What is Upjet?

    main

    Upjet is a toolset designed to generate Crossplane providers from any Terraform provider. It consists of four primary components:

    1. Code Generator Framework: A pipeline used to build the code generation process for Crossplane providers.
    2. Upjet Runtime (Generic Reconciler): A standard reconciler implementation used by all generated CustomResourceDefinitions (CRDs) to handle resource lifecycle.
    3. Documentation Scraper: A tool that extracts documentation for all generated CRDs.
    4. Migration Framework: A system to support the transition from community-maintained providers to Official Crossplane Providers.
  2. Key Features of Upjet Providers

    main

    Upjet-generated providers include several advanced capabilities:

    • Multiple Terraform Execution Modes: Supports Terraform CLI (fork-based), Terraform Plugin SDK v2 (direct Go library), and Terraform Plugin Framework (protocol-based via protov6).
    • Automatic Resource Generation: Generates CRDs conforming to the Crossplane Resource Model (XRM) and reconciliation controllers with full CRUD lifecycles.
    • Cross-Resource References: Provides Kubernetes-native reference resolution and automatic selector generation for resource lookups.
    • Management Policies: Supports Crossplane v1.11+ fine-grained control, including observe-only mode for importing existing infrastructure.
    • Advanced Resource Handling: Includes external name mapping, late initialization from provider responses, sync/async operations for long-running resources, and sensitive field handling via connection secrets.
  3. What is Upjet and how does it work?

    main

    Upjet is a code generation framework and Kubernetes controller runtime designed to transform Terraform providers into Crossplane providers.

    It bridges the gap between Terraform's ecosystem and Kubernetes-native management by performing two main roles:

    1. Build Time: It reads a Terraform provider's schema and generates Go types, Custom Resource Definitions (CRDs), and reconciliation controllers.
    2. Runtime: It provides generic reconcilers that use Terraform's resource logic to manage infrastructure while exposing a pure Kubernetes API to users.

    Upjet is ideal when a Terraform provider already exists for your target platform and you want to leverage its battle-tested resource logic to achieve broad coverage quickly.

  4. What is the Upjet Migration Framework?

    main

    The Upjet Migration Framework is a toolkit for converting Crossplane resources from a source API (e.g., a classic provider) to a target API (e.g., an Upjet-based provider). It facilitates two main types of migration:

    1. API Migration: Converting Managed Resources (MRs) and Compositions due to changes in API schemas, such as different group names, kind names, or field structures.
    2. Configuration Package Migration: Transitioning from monolithic Crossplane configuration packages to family-based providers without changing the underlying resource APIs.

    The framework uses a registry of converters to transform resource manifests and generates a migration plan that outlines the necessary steps (patching, applying, deleting) to achieve the target state.

  5. Understand the generated output of the Main Template

    main

    The main template is executed once per Group value (each real API group plus the special monolith and config values). For each execution, a file named zz_main.go is written to the following location:

    <provider-cmd-dir>/<Group>/zz_main.go

    Examples:

    • For the ec2 API group: cmd/provider/ec2/zz_main.go
    • For the monolithic program: cmd/provider/monolith/zz_main.go
  6. Upjet Architecture and Components

    main

    Upjet's architecture consists of several specialized frameworks that handle the lifecycle of a generated provider:

    • Code Generation Pipeline: Transforms Terraform schemas into Go types, CRDs, and controllers.
    • Generic CR Reconcilers: Runtimes that handle reconciliation for all generated resources.
    • Documentation Scraper: Extracts API documentation and example manifests from Terraform docs.
    • Migration Framework: Supports migrating resources between different providers of the same external API (e.g., migrating from a community provider to an official one).
    • API Conversion Framework: Handles lifecycle management and breaking changes between API versions of the same CRD.
    • Resource Configuration Framework: Implements configuration aspects like external names, sync/async behavior, API naming, and cross-resource references.
  7. When to use Upjet vs. Native Crossplane Providers

    main

    Use Upjet when:

    • A Terraform provider already exists for your target platform.
    • You want to leverage Terraform's battle-tested resource logic.
    • You need broad coverage of infrastructure resources quickly.
    • Your team is familiar with Terraform provider semantics.

    Consider a native Crossplane provider when:

    • No Terraform provider exists for your platform.
    • You need deeply customized reconciliation logic.
    • You require precise control over the Kubernetes API surface.
    • Performance requirements exceed what Terraform execution allows.
  8. Implement Hub-and-Spoke conversion strategies

    main

    When serving multiple versions, Upjet uses a Hub-and-Spoke model. All conversions must pass through a central 'Hub' version. You cannot convert directly between two 'Spoke' versions.

    Pattern: v1alpha1 $\leftrightarrow$ v1beta1 (hub) $\leftrightarrow$ v1alpha2

    API-Level Converters

    Use the Conversions field to register functions that handle field renames or structural changes between specific versions.

    Terraform-Level Converters

    Use TerraformConversions to handle changes that occur at the Terraform provider level between different provider versions.

    func Configure(p *config.Provider) {
        p.AddResourceConfigurator("azurerm_example_resource", func(r *config.Resource) {
            r.Version = "v1beta1"
            r.PreviousVersions = []string{"v1alpha1"}
    
            // Register conversion functions
            r.Conversions = []config.Conversion{
                {
                    // Convert from v1alpha1 to v1beta1
                    FromVersion: "v1alpha1",
                    ToVersion:   "v1beta1",
                    ConvertFn:   convertV1Alpha1ToV1Beta1,
                },
                {
                    // Convert from v1beta1 to v1alpha1
                    FromVersion: "v1beta1",
                    ToVersion:   "v1alpha1",
                    ConvertFn:   convertV1Beta1ToV1Alpha1,
                },
            }
        })
    }
    
    func convertV1Alpha1ToV1Beta1(src, dst interface{}) error {
        // Implement conversion logic
        // Example: rename fields, restructure data
        return nil
    }
    
    func convertV1Beta1ToV1Alpha1(src, dst interface{}) error {
        // Implement reverse conversion logic
        return nil
    }
  9. Configure external names for SDK and Framework resources

    main

    External name configuration tells Upjet how to map a Kubernetes resource to its Terraform identity.

    For Terraform Plugin SDK Resources

    Add the configuration to the TerraformPluginSDKExternalNameConfigs table in config/externalname.go. If the resource belongs to a group (e.g., redshift), add it under that group.

    // redshift
    ...
    // Redshift endpoint access can be imported using the endpoint_name
    "aws_redshift_endpoint_access": config.ParameterAsIdentifier("endpoint_name"),

    For Terraform Plugin Framework Resources

    Add the configuration to the TerraformPluginFrameworkExternalNameConfigs table.

    Tip

    Check config/externalnamenottested.go to see if the resource is already configured there; if so, remove it from that file to avoid conflicts.

  10. Understand Crossplane Provider Authentication and Multi-tenancy Challenges

    main

    Crossplane providers authenticate to Cloud providers using a cluster-scoped ProviderConfig resource. Each managed resource contains a spec.providerConfigRef pointing to a ProviderConfig that holds credentials.

    The Multi-tenancy Problem

    In a namespace-based multi-tenancy model, tenants (application operators) are confined to their own namespaces. However, because ProviderConfigs are cluster-scoped and the Crossplane provider runs as a single shared deployment (using a single Kubernetes ServiceAccount), standard RBAC cannot prevent a tenant from referencing a ProviderConfig belonging to another tenant. If a tenant has permission to create managed resources, they can potentially point to any ProviderConfig in the cluster, leading to privilege escalation.

    Common Mitigation Strategies

    1. Naming Conventions: Infrastructure operators name ProviderConfigs after the tenant's namespace (e.g., tenant1-config).
    2. Composition Patching: Infrastructure operators design Compositions that automatically patch the spec.providerConfigRef.name using the Claim's namespace, preventing tenants from choosing their own config.
    3. Admission Controllers: Using tools like Kyverno to enforce policies that restrict which ProviderConfig names can be used in a Claim based on the user's namespace.
  11. Understand Controller Template Variables

    main
    Upjet uses a Go template (pkg/pipeline/templates/controller.go.tmpl) to generate the zz_controller.go file for every managed resource. The ControllerGenerator.Generate method populates several variables that are accessible within the template using the {{ .<Name> }} syntax. These variables control aspects like imports, package names, connector selection, and resource-specific logic.
  12. Implement requirements for custom Main Templates

    main

    Because the main template is parsed using the standard Go text/template package and is not processed by an import-tracking wrapper, your custom template is responsible for emitting several critical components:

    • The license header (if required).
    • The // Code generated ... DO NOT EDIT. marker (if required).
    • The full import block, including any group-specific imports derived from the {{ .Group }} variable.
    • The package clause.

    Failure to include these will result in the generated zz_main.go files failing to compile or lint.