This guide provides solutions to common problems you may encounter when developing or using Pyvider providers. Each issue includes symptoms, root causes, and step-by-step solutions.
🤖 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.
asyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:# Create the resourceresult=awaitself.api.create_resource(ctx.config)# Verify it was createdverification=awaitself.api.get_resource(result.id)ifnotverification:raiseResourceError(f"Resource {result.id} not found after creation")returnState(id=result.id,**ctx.config.__dict__),None
Symptom:
- Manually change resource outside Terraform
- terraform plan shows no changes
- Drift is not detected
Root Causes:
1. read() not implemented
2. read() returns cached state instead of current state
3. read() returns wrong values
4. Computed attributes not updated
asyncdefread(self,ctx:ResourceContext)->State|None:"""Must fetch CURRENT state, not return cached state."""# Get the resource ID from stateresource_id=ctx.state.id# Fetch CURRENT state from actual resourcecurrent=awaitself.api.get_resource(resource_id)# Return None if resource doesn't exist (was deleted)ifnotcurrent:logger.debug("Resource not found, returning None",resource_id=resource_id)returnNone# Return CURRENT state, not ctx.state!returnState(id=current.id,name=current.name,value=current.value,# Update all attributes from current state)
# Wrong: Returning stored stateasyncdefread(self,ctx:ResourceContext):returnctx.state# This is cached, not current!# Correct: Fetch current stateasyncdefread(self,ctx:ResourceContext):current=awaitself._fetch_current_state(ctx.state.id)returncurrent
asyncdef_update_apply(self,ctx:ResourceContext)->tuple[State|None,None]:"""Update must modify the actual resource."""logger.debug("Updating resource",resource_id=ctx.state.id,changes=ctx.config)# Actually update the resourceawaitself.api.update_resource(ctx.state.id,ctx.config)# Return updated statereturnState(id=ctx.state.id,**ctx.config.__dict__),None
asyncdef_update_apply(self,ctx:ResourceContext)->tuple[State|None,None]:# Determine what changedchanges={}forkeyinctx.config.__dict__:new_value=getattr(ctx.config,key,None)old_value=getattr(ctx.state,key,None)ifold_value!=new_value:changes[key]={"old":old_value,"new":new_value}logger.debug("Detected changes",changes=changes)# Some attributes might require resource replacementif"immutable_field"inchanges:raiseResourceError("Cannot update immutable_field, resource must be recreated")awaitself.api.update_resource(ctx.state.id,ctx.config)returnState(id=ctx.state.id,**ctx.config.__dict__),None
asyncdef_delete_apply(self,ctx:ResourceContext)->None:"""Delete should be idempotent."""try:awaitself.api.delete_resource(ctx.state.id)logger.info("Resource deleted",resource_id=ctx.state.id)exceptResourceNotFoundError:# Already deleted - this is OKlogger.debug("Resource already deleted",resource_id=ctx.state.id)returnexceptPermissionErrorase:raiseResourceError(f"Permission denied deleting resource: {e}")exceptExceptionase:logger.error("Delete failed",error=str(e))raise
asyncdef_delete_apply(self,ctx:ResourceContext)->None:# Check if resource has dependenciesdependencies=awaitself.api.get_dependencies(ctx.state.id)ifdependencies:raiseResourceError(f"Cannot delete resource {ctx.state.id}, it has dependencies: "f"{', '.join(d.idfordindependencies)}. "f"Delete dependent resources first.")awaitself.api.delete_resource(ctx.state.id)
# Schema says "filename""filename":a_str(required=True)# Config must use "filename" (not "file_name" or "file")resource"pyvider_file_content""example"{filename="/tmp/test.txt"# Must match schema exactly}
# In schema, mark attribute as computed@classmethoddefget_schema(cls)->PvsSchema:returns_resource({"filename":a_str(required=True),"content":a_str(required=True),"content_hash":a_str(computed=True),# Computed, not set by user})
# Wrong: Setting computed attributeresource"pyvider_file_content""example"{filename="/tmp/test.txt"content="test"content_hash="abc123" # Error! This is computed}# Correct: Don't set computed attributesresource"pyvider_file_content""example"{filename="/tmp/test.txt"content="test" # content_hash will be computed by provider}
# Wrong: N+1 query problemasyncdefread_multiple(self,resource_ids:list):results=[]forridinresource_ids:result=awaitself.api.get_resource(rid)# N API calls!results.append(result)returnresults# Correct: Single batch callasyncdefread_multiple(self,resource_ids:list):returnawaitself.api.batch_get_resources(resource_ids)# 1 API call
importtracemalloctracemalloc.start()# ... run operations ...snapshot=tracemalloc.take_snapshot()top_stats=snapshot.statistics('lineno')forstatintop_stats[:10]:print(stat)
# Configuration-based importimport{to=pyvider_file_content.exampleid="/path/to/file.txt" # Must match import_resource expectations}resource"pyvider_file_content""example"{filename="/path/to/file.txt"content=file("/path/to/file.txt")}
asyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:try:result=awaitself.api.create_resource(ctx.config)returnState(**result.__dict__),NoneexceptExceptionase:logger.error("Create failed with exception",error=str(e),error_type=type(e).__name__,traceback=traceback.format_exc())raise
# Wrong: Infinite loopasyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:whileTrue:awaitself.api.check_status()# Never exits!# Correct: With timeout/limitasyncdef_create_apply(self,ctx:ResourceContext)->tuple[State|None,None]:max_attempts=10forattemptinrange(max_attempts):ifawaitself.api.check_status():breakawaitasyncio.sleep(1)else:raiseTimeoutError("Operation timed out after 10 attempts")result=awaitself.api.create_resource(ctx.config)returnState(**result.__dict__),None
Remember: Most issues can be quickly diagnosed with debug logging enabled. Start with TF_LOG=DEBUG and PYVIDER_LOG_LEVEL=DEBUG to see what's actually happening.