This guide covers how to effectively manage infrastructure resources using Pyvider providers, including lifecycle operations, state management, dependencies, and advanced patterns.
🤖 AI-Generated Content
This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.
frompyvider.resourcesimportregister_resource,BaseResourcefrompyvider.resources.contextimportResourceContext@register_resource("server")classServer(BaseResource):config_class=ServerConfigstate_class=ServerStateasyncdef_create_apply(self,ctx:ResourceContext)->tuple[ServerState|None,None]:"""Create a new resource during terraform apply."""# Create infrastructureserver=awaitcreate_server(ctx.config)returnServerState(...),Noneasyncdefread(self,ctx:ResourceContext)->ServerState|None:"""Refresh resource state during terraform plan/refresh."""# Fetch current state from infrastructureserver=awaitget_server(ctx.state.id)returnServerState(...)ifserverelseNoneasyncdef_update_apply(self,ctx:ResourceContext)->tuple[ServerState|None,None]:"""Update existing resource during terraform apply."""# Update infrastructureserver=awaitupdate_server(ctx.state.id,ctx.config)returnServerState(...),Noneasyncdef_delete_apply(self,ctx:ResourceContext)->None:"""Delete resource during terraform destroy."""# Remove infrastructureawaitdelete_server(ctx.state.id)
Key Principles:
- Include resource ID (required)
- Store configuration values
- Store computed values that may change
- Avoid storing temporary or derivable data
asyncdefread(self,ctx:ResourceContext)->ServerState|None:"""Read current resource state."""ifnotctx.state:returnNone# Resource doesn't exist yet# Access state attributesserver_id=ctx.state.idcurrent_name=ctx.state.name# Fetch fresh data from infrastructureserver=awaitself.api.get_server(server_id)ifnotserver:returnNone# Resource was deleted outside TerraformreturnServerState(id=server.id,name=server.name,size=server.size,ip_address=server.ip,status=server.status,created_at=server.created_at,)
asyncdefread(self,ctx:ResourceContext)->ServerState|None:"""Read resource with drift detection."""ifnotctx.state:returnNonetry:server=awaitself.api.get_server(ctx.state.id)returnServerState(...)exceptNotFoundError:# Resource deleted outside Terraform - return None to mark as deletedreturnNoneexceptAPIErrorase:# Transient error - raise to retryraiseResourceError(f"Failed to read server: {e}")
asyncdef_create_apply(self,ctx:ResourceContext)->tuple[ServerState|None,None]:"""Create server with network dependency."""ifnotctx.config:returnNone,None# network_id will be populated from referenced resourcenetwork_id=ctx.config.network_idifnotnetwork_id:raiseResourceError("network_id is required")server=awaitself.api.create_server(name=ctx.config.name,network_id=network_id,# Use dependency's output)returnServerState(...),None
asyncdefimport_state(self,resource_id:str)->ServerState|None:"""Import existing server into Terraform state."""try:server=awaitself.api.get_server(resource_id)exceptNotFoundError:raiseResourceError(f"Server {resource_id} not found")returnServerState(id=server.id,name=server.name,size=server.size,ip_address=server.ip,status=server.status,created_at=server.created_at,)
asyncdef_update_apply(self,ctx:ResourceContext)->tuple[ServerState|None,None]:"""Update resource with changed configuration."""ifnotctx.configornotctx.state:returnNone,None# Detect which attributes changedupdates={}ifctx.config.name!=ctx.state.name:updates["name"]=ctx.config.nameifctx.config.tags!=ctx.state.tags:updates["tags"]=ctx.config.tags# Apply updatesifupdates:server=awaitself.api.update_server(ctx.state.id,**updates)else:# No changes neededserver=awaitself.api.get_server(ctx.state.id)returnServerState(...),None
@classmethoddefget_schema(cls)->PvsSchema:returns_resource({"name":a_str(required=True,description="Server name (forces replacement if changed)"),"size":a_str(required=True,description="Server size (forces replacement if changed)"),"tags":a_map(a_str(),description="Tags (can be updated in-place)"),})
By default, changing any required attribute triggers replacement. To allow in-place updates, implement _update_apply() appropriately.
In-Place Update:
- Modifiable attributes (tags, labels, etc.)
- No service disruption
- Implemented in _update_apply()
Force Replace:
- Immutable attributes (name, size, region, etc.)
- Resource destroyed then recreated
- Specify in schema or raise error in _update_apply()
asyncdef_update_apply(self,ctx:ResourceContext)->tuple[ServerState|None,None]:"""Update with replacement detection."""ifnotctx.configornotctx.state:returnNone,None# Check for attributes that require replacementifctx.config.size!=ctx.state.size:raiseResourceError("Server size cannot be changed in-place. ""Terraform will destroy and recreate the resource.")# Handle in-place updates# ... update logic ...
asyncdefread(self,ctx:ResourceContext)->ServerState|None:"""Detect configuration drift."""ifnotctx.state:returnNone# Fetch current infrastructure stateserver=awaitself.api.get_server(ctx.state.id)ifnotserver:returnNone# Resource deleted - Terraform will recreate# Return current state - Terraform compares with configreturnServerState(id=server.id,name=server.name,# May differ from configsize=server.size,# May differ from configip_address=server.ip,# Computed, OK to changestatus=server.status,# Computed, OK to changecreated_at=server.created_at,)
# Detect driftterraformplan
# Shows resources out of sync# Correct drift (apply configuration)terraformapply
# Or update configuration to match reality# Edit .tf files then apply
# Apply only specific resourceterraformapply-target=mycloud_server.web
# Destroy specific resourceterraformdestroy-target=mycloud_server.web
# Plan for specific resourceterraformplan-target=mycloud_server.web
Use Cases:
- Debugging specific resources
- Partial deployments
- Emergency fixes
Warning: Breaks dependency guarantees. Use sparingly.
asyncdef_validate_config(self,config:ServerConfig)->list[str]:"""Validate server configuration."""errors=[]# Validate name formatifnotconfig.name.isalnum():errors.append("Server name must be alphanumeric")# Validate sizevalid_sizes=["small","medium","large"]ifconfig.sizenotinvalid_sizes:errors.append(f"Size must be one of: {', '.join(valid_sizes)}")# Validate disk sizeifconfig.disk_size<10orconfig.disk_size>1000:errors.append("Disk size must be between 10 and 1000 GB")returnerrors
raiseResourceError(f"Server '{ctx.config.name}' already exists. "f"Choose a different name or import the existing server with: "f"terraform import mycloud_server.{resource_name}{existing_id}")
asyncdef_create_apply(self,ctx:ResourceContext)->tuple[ServerState|None,None]:"""Create server and wait for ready state."""server=awaitself.api.create_server(ctx.config.name)# Poll until readywhileserver.status!="running":awaitasyncio.sleep(5)server=awaitself.api.get_server(server.id)ifserver.status=="error":raiseResourceError(f"Server creation failed: {server.error}")returnServerState(...),None
asyncdefread(self,ctx:ResourceContext)->ServerState|None:try:server=awaitself.api.get_server(ctx.state.id)returnServerState(...)exceptNotFoundError:returnNone# Terraform will recreate