Thanks to visit codestin.com
Credit goes to github.com

Skip to content

hashicorp/terraform-migrate-utility

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Terraform Migrate Utility

A Go library and utility for migrating Terraform state files to Terraform Stacks using HashiCorp's Terraform RPC API.

Overview

This utility provides a Go-based interface to migrate traditional Terraform configurations and state files to the new Terraform Stacks format. It leverages Terraform's gRPC-based RPC API to perform the migration operations safely and efficiently.

Features

  • State Migration: Migrate existing Terraform state files to Terraform Stacks format
  • Configuration Handling: Process Terraform modules, dependencies, and provider configurations
  • gRPC Integration: Uses Terraform's official RPC API for reliable operations
  • Resource Mapping: Supports custom resource and module address mapping during migration
  • Provider Cache Management: Handles provider plugin caches and dependency locks

Requirements

Installation

go get github.com/hashicorp/terraform-migrate-utility

Usage

Example of migrating a Terraform state file to Stacks format:

package main

import (
    "context"
    "fmt"
    "io"
    "os"
    "path/filepath"
    "github.com/hashicorp/terraform-migrate-utility/rpcapi"
    "github.com/hashicorp/terraform-migrate-utility/rpcapi/terraform1/stacks"
    "github.com/hashicorp/terraform-migrate-utility/rpcapi/tfstacksagent1"
    stateOps "github.com/hashicorp/terraform-migrate-utility/stateops"

    _ "github.com/hashicorp/terraform-migrate-utility/rpcapi/tfstackdata1"

    "google.golang.org/protobuf/encoding/protojson"
)

var (
    jsonOpts = protojson.MarshalOptions{
        Multiline: true,
    }
)

func main() {
    ctx := context.Background()

    // Connect to Terraform RPC API
    client, err := rpcapi.Connect(ctx)
    if err != nil {
        fmt.Println("Error connecting to RPC API:", err)
        return
    }
    defer client.Stop()

    // Create state operations handler
    r := stateOps.NewTFStateOperations(ctx, client)

    // Define paths (adjust these to your project structure)
    workspaceDir := "/path/to/your/terraform/workspace"
    stackConfigDir := "/path/to/your/stack/config"

    // Open Terraform state from workspace directory
    tfStateHandle, closeTFState, err := r.OpenTerraformState(workspaceDir)
    if err != nil {
        fmt.Println("Error opening Terraform state:", err)
        return
    }
    defer closeTFState()

    // Open source bundle (modules directory)
    sourceBundleHandle, closeSourceBundle, err := r.OpenSourceBundle(filepath.Join(workspaceDir, ".terraform/modules/"))
    if err != nil {
        fmt.Println("Error opening source bundle:", err)
        return
    }
    defer closeSourceBundle()

    // Open stack configuration (relative path from current working directory)
    cwd, _ := os.Getwd()
    relStackPath, _ := filepath.Rel(cwd, stackConfigDir)
    stackConfigHandle, closeConfig, err := r.OpenStacksConfiguration(sourceBundleHandle, relStackPath)
    if err != nil {
        fmt.Println("Error opening stacks configuration:", err)
        return
    }
    defer closeConfig()

    // Open dependency lock file (relative path from current working directory)
    lockFilePath := filepath.Join(workspaceDir, ".terraform.lock.hcl")
    relLockPath, _ := filepath.Rel(cwd, lockFilePath)
    dependencyLocksHandle, closeLock, err := r.OpenDependencyLockFile(sourceBundleHandle, relLockPath)
    if err != nil {
        fmt.Println("Error opening dependency lock file:", err)
        return
    }
    defer closeLock()

    // Open provider cache
    providerCacheHandle, closeProviderCache, err := r.OpenProviderCache(filepath.Join(workspaceDir, ".terraform/providers"))
    if err != nil {
        fmt.Println("Error opening provider cache:", err)
        return
    }
    defer closeProviderCache()

    // Perform migration with custom mappings
    events, err := r.MigrateTFState(
        tfStateHandle,
        stackConfigHandle,
        dependencyLocksHandle,
        providerCacheHandle,
        map[string]string{}, // Resource mappings (empty in this example)
        map[string]string{   // Module mappings
            "random_number":   "triage-min",
            "random_number_2": "triage-max",
        },
    )
    if err != nil {
        fmt.Println("Error migrating Terraform state:", err)
        return
    }

    stackState := &tfstacksagent1.StackState{
		FormatVersion: 1,
		Raw:           make(map[string]*anypb.Any),
		Descriptions:  make(map[string]*stacks.AppliedChange_ChangeDescription),
	}

    // Process migration events
    for {
        item, err := events.Recv()
        if err == io.EOF {
            break 
        } else if err != nil {
            fmt.Println("Error receiving migration events:", err)
            return
        }

        // Handle different event types
        switch result := item.Result.(type) {
        case *stacks.MigrateTerraformState_Event_AppliedChange:
            for _, raw := range result.AppliedChange.Raw {
				stackState.Raw[raw.Key] = raw.Value
			}

			for _, change := range result.AppliedChange.Descriptions {
				stackState.Descriptions[change.Key] = change
			}
        case *stacks.MigrateTerraformState_Event_Diagnostic:
            fmt.Println("Diagnostic:", result.Diagnostic.Detail)
        default:
            fmt.Printf("Received event: %T\n", result)
        }
    }

    fmt.Println(jsonOpts.Format(stackState))
    fmt.Println("Migration completed successfully!")
}

Example use of TfWorkspaceStateUtility Interface:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"terraform-migrate-utility/tfstateutil"
)

const (
	stackSourceBundleAbsPath  = "/Users/sujaysamanta/Workspace/Codebase/terraform-migrate-utility/terraform-no-mod-v1/modularized_config/_stacks_generated"
	terraformConfigDirAbsPath = "/Users/sujaysamanta/Workspace/Codebase/terraform-migrate-utility/terraform-no-mod-v1/modularized_config"
)

func main() {
	var err error
	ctx := context.Background()
	tfStateUtil := stateUtil.NewTfWorkspaceStateUtility(ctx)

	// Read all resources from the Terraform state file
	resources, err := listAllResourcesFromWorkspaceStateExample(tfStateUtil, terraformConfigDirAbsPath)
	if err != nil {
		panic("Failed to list all resources from workspace state: " + err.Error())
	} else {
		fmt.Println("Resources in the Terraform state:")
		for _, resource := range resources {
			fmt.Println(resource)
		}
		fmt.Println()
	}

	// Check if the resources are fully modular
	stateFullyModular := isFullyModularExample(tfStateUtil, resources)
	if stateFullyModular {
		fmt.Println("The Terraform state is fully modular.")
		fmt.Println()
	} else {
		fmt.Println("The Terraform state is not fully modular.")
		fmt.Println()
	}

	// Create a map of workspace to stack address
	workspaceToStackMap, err := workspaceToStackAddressMapExample(tfStateUtil, terraformConfigDirAbsPath, stackSourceBundleAbsPath)
	if err != nil {
		panic("Failed to create workspace to stack address map: " + err.Error())
	} else {
		fmt.Println("Workspace to Stack Address Map:")
		indent, _ := json.MarshalIndent(workspaceToStackMap, "", "  ")
		fmt.Println(string(indent))
		fmt.Println()
	}
}


func listAllResourcesFromWorkspaceStateExample(tfStateUtil stateUtil.TfWorkspaceStateUtility, workingDir string) ([]string, error) {
	return tfStateUtil.ListAllResourcesFromWorkspaceState(workingDir)
}

func isFullyModularExample(tfStateUtil stateUtil.TfWorkspaceStateUtility, resources []string) bool {
	return tfStateUtil.IsFullyModular(resources)
}

func workspaceToStackAddressMapExample(tfStateUtil stateUtil.TfWorkspaceStateUtility, terraformConfigFilesAbsPath string, stackSourceBundleAbsPath string) (map[string]string, error) {
	return tfStateUtil.WorkspaceToStackAddressMap(terraformConfigFilesAbsPath, stackSourceBundleAbsPath)
}

Contributing

This project follows HashiCorp's contribution guidelines. Please ensure:

  1. Code follows Go best practices
  2. All changes include appropriate tests
  3. Documentation is updated for API changes
  4. Protobuf changes are properly generated

About

A helper library for terraform migrate CLI and provider

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Contributors 5

Languages