Skillshub provider-actions
Implement Terraform Provider actions using the Plugin Framework. Use when developing imperative operations that execute at lifecycle events (before/after create, update, destroy).
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/hashicorp/agent-skills/provider-actions" ~/.claude/skills/comeonoliver-skillshub-provider-actions && rm -rf "$T"
skills/hashicorp/agent-skills/provider-actions/SKILL.mdTerraform Provider Actions Implementation Guide
Overview
Terraform Actions enable imperative operations during the Terraform lifecycle. Actions are experimental features that allow performing provider operations at specific lifecycle events (before/after create, update, destroy).
References:
File Structure
Actions follow the standard service package structure:
internal/service/<service>/ ├── <action_name>_action.go # Action implementation ├── <action_name>_action_test.go # Action tests └── service_package_gen.go # Auto-generated service registration
Documentation structure:
website/docs/actions/ └── <service>_<action_name>.html.markdown # User-facing documentation
Changelog entry:
.changelog/ └── <pr_number_or_description>.txt # Release note entry
Action Schema Definition
Actions use the Terraform Plugin Framework with a standard schema pattern:
func (a *actionType) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ // Required configuration parameters "resource_id": schema.StringAttribute{ Required: true, Description: "ID of the resource to operate on", }, // Optional parameters with defaults "timeout": schema.Int64Attribute{ Optional: true, Description: "Operation timeout in seconds", Default: int64default.StaticInt64(1800), Computed: true, }, }, } }
Common Schema Issues
Pay special attention to the schema definition - common issues after a first draft:
-
Type Mismatches
- Using
instead oftypes.String
in model structsfwtypes.String - Using
instead oftypes.StringType
in schemafwtypes.StringType - Mixing framework types with plugin-framework types
- Using
-
List/Map Element Types
// WRONG - missing ElementType "items": schema.ListAttribute{ Optional: true, } // CORRECT "items": schema.ListAttribute{ Optional: true, ElementType: fwtypes.StringType, } -
Computed vs Optional
- Attributes with defaults must be both
andOptional: trueComputed: true - Don't mark action inputs as
unless they have defaultsComputed
- Attributes with defaults must be both
-
Validator Imports
// Ensure proper imports "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" -
Region/Provider Attribute
- Use framework-provided region handling when available
- Don't manually define provider-specific config in schema if framework handles it
-
Nested Attributes
- Use appropriate nested object types for complex structures
- Ensure nested types are properly defined
Schema Validation Checklist
Before submitting, verify:
- All attributes have descriptions
- List/Map attributes have ElementType defined
- Validators are imported and applied correctly
- Model struct uses correct framework types
- Optional attributes with defaults are marked Computed
- Code compiles without type errors
- Run
to catch type mismatchesgo build
Action Invoke Method
The Invoke method contains the action logic:
func (a *actionType) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) { var data actionModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) // Create provider client conn := a.Meta().Client(ctx) // Progress updates for long-running operations resp.Progress.Set(ctx, "Starting operation...") // Implement action logic with error handling // Use context for timeout management // Poll for completion if async operation resp.Progress.Set(ctx, "Operation completed") }
Key Implementation Requirements
1. Progress Reporting
- Use
for real-time updatesresp.SendProgress(action.InvokeProgressEvent{...}) - Provide meaningful progress messages during long operations
- Update progress at key milestones
- Include elapsed time for long operations
2. Timeout Management
- Always include configurable timeout parameter (default: 1800s)
- Use
for API callscontext.WithTimeout() - Handle timeout errors gracefully
- Validate timeout ranges (typically 60-7200 seconds)
3. Error Handling
- Add diagnostics with
resp.Diagnostics.AddError() - Provide clear error messages with context
- Include API error details when relevant
- Map provider error types to user-friendly messages
- Document all possible error cases
Example error handling:
// Handle specific errors var notFound *types.ResourceNotFoundException if errors.As(err, ¬Found) { resp.Diagnostics.AddError( "Resource Not Found", fmt.Sprintf("Resource %s was not found", resourceID), ) return } // Generic error handling resp.Diagnostics.AddError( "Operation Failed", fmt.Sprintf("Could not complete operation for %s: %s", resourceID, err), )
4. Provider SDK Integration
- Use provider SDK clients from
a.Meta().<Service>Client(ctx) - Handle pagination for list operations
- Implement retry logic for transient failures
- Use appropriate error types
5. Parameter Validation
- Use framework validators for input validation
- Validate resource existence before operations
- Check for conflicting parameters
- Validate against provider naming requirements
6. Polling and Waiting
For operations that require waiting for completion:
result, err := wait.WaitForStatus(ctx, func(ctx context.Context) (wait.FetchResult[*ResourceType], error) { // Fetch current status resource, err := findResource(ctx, conn, id) if err != nil { return wait.FetchResult[*ResourceType]{}, err } return wait.FetchResult[*ResourceType]{ Status: wait.Status(resource.Status), Value: resource, }, nil }, wait.Options[*ResourceType]{ Timeout: timeout, Interval: wait.FixedInterval(5 * time.Second), SuccessStates: []wait.Status{"AVAILABLE", "COMPLETED"}, TransitionalStates: []wait.Status{"CREATING", "PENDING"}, ProgressInterval: 30 * time.Second, ProgressSink: func(fr wait.FetchResult[any], meta wait.ProgressMeta) { resp.SendProgress(action.InvokeProgressEvent{ Message: fmt.Sprintf("Status: %s, Elapsed: %v", fr.Status, meta.Elapsed.Round(time.Second)), }) }, }, )
Common Action Patterns
Batch Operations
- Process items in configurable batches
- Report progress per batch
- Handle partial failures gracefully
- Support prefix/filter parameters
Command Execution
- Submit command and get operation ID
- Poll for completion status
- Retrieve and report output
- Handle timeout during polling
- Validate resources exist before execution
Service Invocation
- Invoke service with parameters
- Wait for completion (if synchronous)
- Return output/results
- Handle service-specific errors
Resource State Changes
- Validate current state
- Apply state change
- Poll for target state
- Handle transitional states
Async Job Submission
- Submit job with configuration
- Get job ID
- Optionally wait for completion
- Report job status
Action Triggers
Actions are invoked via
action_trigger lifecycle blocks in Terraform configurations:
action "provider_service_action" "name" { config { parameter = value } } resource "terraform_data" "trigger" { lifecycle { action_trigger { events = [after_create] actions = [action.provider_service_action.name] } } }
Available Trigger Events
Terraform 1.14.0 Supported Events:
- Before resource creationbefore_create
- After resource creationafter_create
- Before resource updatebefore_update
- After resource updateafter_update
Not Supported in Terraform 1.14.0:
- Not available (will cause validation error)before_destroy
- Not available (will cause validation error)after_destroy
Testing Actions
Acceptance Tests
- Test action invocation with valid parameters
- Test timeout scenarios
- Test error conditions
- Verify provider state changes
- Test progress reporting
- Test with custom parameters
- Test trigger-based invocation
Test Pattern
func TestAccServiceAction_basic(t *testing.T) { ctx := acctest.Context(t) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ { Config: testAccActionConfig_basic(), Check: resource.ComposeTestCheckFunc( testAccCheckResourceExists(ctx, "provider_resource.test"), ), }, }, }) }
Test Cleanup with Sweep Functions
Add sweep functions to clean up test resources:
func sweepResources(region string) error { ctx := context.Background() client := /* get client for region */ input := &service.ListInput{ // Filter for test resources } var sweeperErrs *multierror.Error pages := service.NewListPaginator(client, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) if err != nil { sweeperErrs = multierror.Append(sweeperErrs, err) continue } for _, item := range page.Items { id := item.Id // Skip non-test resources if !strings.HasPrefix(id, "tf-acc-test") { continue } _, err := client.Delete(ctx, &service.DeleteInput{ Id: id, }) if err != nil { sweeperErrs = multierror.Append(sweeperErrs, err) } } } return sweeperErrs.ErrorOrNil() }
Testing Best Practices
Service-Specific Prerequisites
- Always check for service-specific prerequisites that must be met before actions can succeed
- Document prerequisites in action documentation and test configurations
Error Pattern Matching
- Terraform wraps action errors with additional context
- Use flexible regex patterns:
(?s)Error Title.*key phrase`)`regexache.MustCompile(\
Test Patterns Not Applicable to Actions
- Actions trigger on lifecycle events, not config reapplication
- Before/After Destroy Tests: Not supported in Terraform 1.14.0
Running Tests
Compile test to check for errors:
go test -c -o /dev/null ./internal/service/<service>
Run specific action tests:
TF_ACC=1 go test ./internal/service/<service> -run TestAccServiceAction_ -v
Run sweep to clean up test resources:
TF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v
Documentation Standards
Each action documentation file must include:
-
Front Matter
--- subcategory: "Service Name" layout: "provider" page_title: "Provider: provider_service_action" description: |- Brief description of what the action does. --- -
Header with Warnings
- Beta/Alpha notice about experimental status
- Warning about potential unintended consequences
- Link to provider documentation
-
Example Usage
- Basic usage example
- Advanced usage with all options
- Trigger-based example with
terraform_data - Real-world use case examples
-
Argument Reference
- List all required and optional arguments
- Include descriptions and defaults
- Note any validation rules
-
Documentation Linting
- Run
before submissionterrafmt fmt - Verify with
terrafmt diff
- Run
Changelog Entry Format
Create a changelog entry in
.changelog/ directory:
.changelog/<pr_number_or_description>.txt
Content format:
action/provider_service_action: Brief description of the action
Pre-Submission Checklist
Before submitting your action implementation:
- Code compiles:
go build -o /dev/null . - Tests compile:
go test -c -o /dev/null ./internal/service/<service> - Code formatted:
make fmt - Documentation formatted:
terrafmt fmt website/docs/actions/<action>.html.markdown - Changelog entry created
- Schema uses correct types
- All List/Map attributes have ElementType
- Progress updates implemented for long operations
- Error messages include context and resource identifiers
- Documentation includes multiple examples
- Documentation includes prerequisites and warnings