This comprehensive guide covers advanced patterns, techniques, and best practices for building sophisticated Terraform providers with Pyvider.
🤖 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.
classBackupPolicy(BaseResource):asyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:# The depends_on is handled by Terraform# Resource creation happens after dependenciesstate=awaitself._create_backup_policy(ctx.config)returnstate,None
variable"server_count"{default=3}resource"mycloud_server""web"{count=var.server_countname="web-${count.index}"size="medium" # Access specific instance: mycloud_server.web[0]}# Use all instancesoutput"server_ips"{value=mycloud_server.web[*].ip_address}
# Generate secure passwordresource"random_password""db_pass"{length=32special=true}# Create database with generated passwordresource"mycloud_database""main"{name="production"password=random_password.db_pass.result}# Store connection string using functionresource"mycloud_secret""db_connection"{name="db-connection-string"value=mycloud_format_connection_string(host=mycloud_database.main.endpoint,port=mycloud_database.main.port,database=mycloud_database.main.name,username="admin",password=random_password.db_pass.result)}
# Find latest AMIdata"mycloud_ami""ubuntu"{filters={name="ubuntu-22.04-*"architecture="x86_64"}most_recent=true}# Use AMI in server configurationresource"mycloud_server""app"{ami=data.mycloud_ami.ubuntu.idsize="t3.medium"user_data=templatefile("${path.module}/init.sh",{ami_name=data.mycloud_ami.ubuntu.nameami_created=data.mycloud_ami.ubuntu.created_at})}
variable"enable_monitoring"{type=booldefault=false}resource"mycloud_monitoring""server"{count=var.enable_monitoring?1:0target_id=mycloud_server.main.idmetrics=["cpu", "memory", "disk"]}# Safe reference to optional resourceoutput"monitoring_dashboard"{value=try(mycloud_monitoring.server[0].dashboard_url,"Monitoring not enabled")}
classServer(BaseResource):asyncdefimport_resource(self,id:str)->State:"""Import existing server by ID."""frompyvider.hubimporthubprovider=hub.get_component("singleton","provider")try:server=awaitprovider.api.get_server(id)returnself.State(id=server.id,name=server.name,size=server.instance_type,ip_address=server.public_ip,status=server.status)exceptNotFoundError:raiseResourceError(f"Server {id} not found")
classDatabase(BaseResource):@attrs.defineclassState:# Version 2 schemaid:str=a_str(computed=True)name:str=a_str()size:str=a_str()# New field in v2backup_enabled:bool=a_bool(default=False)asyncdefread(self,state:State)->State:"""Read with state migration."""frompyvider.hubimporthubprovider=hub.get_component("singleton","provider")db=awaitprovider.api.get_database(state.id)# Migrate from v1 to v2ifnothasattr(state,'backup_enabled'):state.backup_enabled=db.backup_configisnotNonereturnstate
@register_provider("mycloud")classMyCloudProvider(BaseProvider):def__init__(self):super().__init__()# Shared cache for cross-resource dataself.resource_cache={}defcache_resource(self,key:str,data:dict):"""Cache resource data for other resources."""self.resource_cache[key]=datadefget_cached(self,key:str)->dict|None:"""Retrieve cached resource data."""returnself.resource_cache.get(key)
frompyvider.capabilitiesimportregister_capability@register_capability("taggable")classTaggableCapability:"""Adds tagging support to resources."""defbuild_tags(self,base_tags:dict,extra_tags:dict)->dict:"""Merge and validate tags."""# Add default tagstags={"ManagedBy":"Terraform","Provider":"MyCloud","CreatedAt":datetime.utcnow().isoformat()}tags.update(base_tags)tags.update(extra_tags)# Validate tag limitsiflen(tags)>50:raiseValidationError("Maximum 50 tags allowed")returntags# Apply to resources@register_resource("server",capabilities=["taggable"])classServer(BaseResource):asyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:tags=self.capabilities.taggable.build_tags(ctx.config.tags,{"ResourceType":"Server"})# Use tags in creation...returnState(...),None
@attrs.defineclassDatabaseConfig:engine:str=a_str(required=True)version:str=a_str(required=True)def__attrs_post_init__(self):"""Validate field combinations."""valid_versions={"postgres":["13","14","15"],"mysql":["5.7","8.0"],"mariadb":["10.6","10.11"]}ifself.enginenotinvalid_versions:raiseValidationError(f"Unsupported engine: {self.engine}")ifself.versionnotinvalid_versions[self.engine]:raiseValidationError(f"Invalid version {self.version} for {self.engine}")
fromhypothesisimportgiven,strategiesasst@given(port=st.integers(min_value=1,max_value=65535),protocol=st.sampled_from(["tcp","udp","icmp"]))deftest_firewall_rule_validation(port,protocol):"""Test firewall rules with random inputs."""rule=FirewallRule(port=port,protocol=protocol,action="allow")# Should always produce valid ruleassertrule.port==portassertrule.protocolin["tcp","udp","icmp"]
fromfunctoolsimportlru_cacheclassImageDataSource(BaseDataSource):@lru_cache(maxsize=128)asyncdefget_image_by_name(self,name:str)->Image:"""Cache image lookups."""frompyvider.hubimporthubprovider=hub.get_component("singleton","provider")returnawaitprovider.api.find_image(name)asyncdefread(self,config:Config)->State:# Uses cached result if called multiple timesimage=awaitself.get_image_by_name(config.name)returnState(id=image.id,name=image.name)
frompyvider.resources.private_stateimportencrypt_value,decrypt_valueclassApiKey(BaseResource):@attrs.defineclassPrivateState:"""Encrypted state storage."""encrypted_key:str=a_str()asyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,PrivateState|None]:frompyvider.hubimporthubprovider=hub.get_component("singleton","provider")# Generate API keyapi_key=awaitprovider.generate_api_key()# Store encryptedprivate_state=self.PrivateState(encrypted_key=encrypt_value(api_key.secret))returnState(id=api_key.id,name=ctx.config.name,# Don't store secret in regular state),private_stateasyncdefread(self,state:State)->State:# Decrypt when neededsecret=decrypt_value(self.private_state.encrypted_key)# Use secret...returnstate