Configuration Guide¶
This guide covers how to configure Pyvider providers in your Terraform configurations, including provider-level settings, environment variables, and best practices for managing sensitive data.
Provider Configuration Basics¶
Provider configuration is defined in the Terraform configuration file using the provider block:
The configuration attributes available depend on the provider's schema definition.
Defining Provider Configuration¶
In your provider Python code, define the configuration schema in _build_schema():
from pyvider.providers import register_provider, BaseProvider, ProviderMetadata
from pyvider.schema import s_provider, a_str, a_num, a_bool, PvsSchema
import attrs
@attrs.define
class MyCloudConfig:
"""Provider configuration structure."""
api_key: str
endpoint: str = "https://api.mycloud.com"
timeout: int = 30
debug: bool = False
@register_provider("mycloud")
class MyCloudProvider(BaseProvider):
"""MyCloud infrastructure provider."""
def __init__(self):
super().__init__(
metadata=ProviderMetadata(
name="mycloud",
version="1.0.0",
protocol_version="6"
)
)
self.config: MyCloudConfig | None = None
def _build_schema(self) -> PvsSchema:
"""Define provider configuration schema."""
return s_provider({
"api_key": a_str(
required=True,
sensitive=True,
description="API key for authentication"
),
"endpoint": a_str(
default="https://api.mycloud.com",
description="API endpoint URL"
),
"timeout": a_num(
default=30,
description="Request timeout in seconds"
),
"debug": a_bool(
default=False,
description="Enable debug logging"
),
})
async def configure(self, config: dict) -> None:
"""Configure the provider with validated configuration."""
await super().configure(config)
# Convert dict to attrs instance
self.config = MyCloudConfig(
api_key=config["api_key"],
endpoint=config.get("endpoint", "https://api.mycloud.com"),
timeout=config.get("timeout", 30),
debug=config.get("debug", False),
)
# Initialize API client
self.client = MyCloudClient(
api_key=self.config.api_key,
endpoint=self.config.endpoint,
timeout=self.config.timeout,
)
Environment Variables¶
Terraform supports environment variable interpolation in provider configuration:
Define variables:
variable "mycloud_api_key" {
type = string
description = "MyCloud API key"
sensitive = true
}
variable "mycloud_endpoint" {
type = string
default = "https://api.mycloud.com"
description = "MyCloud API endpoint"
}
Set via environment variables:
export TF_VAR_mycloud_api_key="your-api-key"
export TF_VAR_mycloud_endpoint="https://api.mycloud.com"
Or via .tfvars file:
Sensitive Data Handling¶
Mark sensitive attributes in your schema:
def _build_schema(self) -> PvsSchema:
return s_provider({
"api_key": a_str(
required=True,
sensitive=True, # Marks value as sensitive
description="API key for authentication"
),
"password": a_str(
required=True,
sensitive=True,
description="Password for authentication"
),
})
Benefits of marking attributes as sensitive:
- Values are masked in Terraform output
- Not shown in terraform plan or terraform show
- Protected in state file (when using remote state with encryption)
- Hidden in logs
Configuration Patterns¶
Multiple Provider Instances¶
You can configure multiple instances of the same provider:
# Production environment
provider "mycloud" {
alias = "prod"
api_key = var.prod_api_key
endpoint = "https://api.prod.mycloud.com"
}
# Staging environment
provider "mycloud" {
alias = "staging"
api_key = var.staging_api_key
endpoint = "https://api.staging.mycloud.com"
}
# Use specific provider instance
resource "mycloud_server" "web" {
provider = mycloud.prod
name = "web-server"
}
Region-Based Configuration¶
Provider implementation:
def _build_schema(self) -> PvsSchema:
return s_provider({
"api_key": a_str(required=True, sensitive=True),
"region": a_str(
default="us-east-1",
description="AWS-compatible region identifier"
),
})
Retry and Timeout Configuration¶
Implementation:
@attrs.define
class RetryConfig:
"""Retry configuration."""
max_retries: int = 3
retry_delay: int = 5
timeout: int = 60
def _build_schema(self) -> PvsSchema:
return s_provider({
"timeout": a_num(
default=60,
description="Request timeout in seconds"
),
"max_retries": a_num(
default=3,
description="Maximum number of retry attempts"
),
"retry_delay": a_num(
default=5,
description="Delay between retries in seconds"
),
})
async def configure(self, config: dict) -> None:
await super().configure(config)
retry_config = RetryConfig(
timeout=config.get("timeout", 60),
max_retries=config.get("max_retries", 3),
retry_delay=config.get("retry_delay", 5),
)
self.client = MyCloudClient(
api_key=config["api_key"],
retry_config=retry_config,
)
Configuration Validation¶
Implement custom validation logic in the provider:
async def configure(self, config: dict) -> None:
"""Configure provider with validation."""
await super().configure(config)
# Validate endpoint URL
endpoint = config.get("endpoint", "")
if not endpoint.startswith(("http://", "https://")):
raise ProviderConfigurationError(
"Endpoint must be a valid HTTP(S) URL"
)
# Validate timeout
timeout = config.get("timeout", 30)
if timeout < 1 or timeout > 300:
raise ProviderConfigurationError(
"Timeout must be between 1 and 300 seconds"
)
# Test connection
try:
self.client = MyCloudClient(
api_key=config["api_key"],
endpoint=endpoint,
timeout=timeout,
)
await self.client.test_connection()
except ConnectionError as e:
raise ProviderConfigurationError(
f"Failed to connect to {endpoint}: {e}"
)
self.config = MyCloudConfig(
api_key=config["api_key"],
endpoint=endpoint,
timeout=timeout,
)
Accessing Provider Configuration in Resources¶
Resources can access provider configuration through the hub:
from pyvider.resources import register_resource, BaseResource
from pyvider.resources.context import ResourceContext
from pyvider.hub import hub
@register_resource("server")
class Server(BaseResource):
async def _create_apply(self, ctx: ResourceContext) -> tuple[ServerState | None, None]:
"""Create server using provider configuration."""
# Get provider instance
provider = hub.get_component("singleton", "provider")
if provider is None or provider.config is None:
raise RuntimeError("Provider has not been configured yet.")
# Access provider config
api_key = provider.config.api_key
endpoint = provider.config.endpoint
# Use provider's client
server = await provider.client.create_server(
name=ctx.config.name,
region=provider.config.region,
)
return ServerState(...), None
Configuration Examples¶
Basic Authentication¶
def _build_schema(self) -> PvsSchema:
return s_provider({
"username": a_str(required=True, description="Username"),
"password": a_str(required=True, sensitive=True, description="Password"),
})
Token Authentication¶
def _build_schema(self) -> PvsSchema:
return s_provider({
"token": a_str(
required=True,
sensitive=True,
description="API authentication token"
),
})
OAuth2 Configuration¶
provider "mycloud" {
client_id = var.client_id
client_secret = var.client_secret
auth_url = "https://auth.mycloud.com/oauth2/token"
}
def _build_schema(self) -> PvsSchema:
return s_provider({
"client_id": a_str(required=True),
"client_secret": a_str(required=True, sensitive=True),
"auth_url": a_str(
default="https://auth.mycloud.com/oauth2/token"
),
})
TLS/SSL Configuration¶
provider "mycloud" {
api_key = var.api_key
tls_verify = true
tls_ca_cert_file = "/path/to/ca.pem"
tls_client_cert = "/path/to/client.pem"
tls_client_key = "/path/to/client-key.pem"
}
def _build_schema(self) -> PvsSchema:
return s_provider({
"api_key": a_str(required=True, sensitive=True),
"tls_verify": a_bool(default=True),
"tls_ca_cert_file": a_str(description="Path to CA certificate"),
"tls_client_cert": a_str(description="Path to client certificate"),
"tls_client_key": a_str(sensitive=True, description="Path to client key"),
})
Best Practices¶
1. Use Sensible Defaults¶
Provide defaults for optional configuration:
"timeout": a_num(
default=30,
description="Request timeout in seconds"
),
"max_retries": a_num(
default=3,
description="Maximum retry attempts"
),
2. Mark Sensitive Data¶
Always mark sensitive attributes:
"api_key": a_str(
required=True,
sensitive=True, # Critical for security
description="API key for authentication"
),
3. Validate Early¶
Validate configuration in configure() before creating resources:
async def configure(self, config: dict) -> None:
await super().configure(config)
# Validate configuration
if config["timeout"] < 1:
raise ProviderConfigurationError("Timeout must be positive")
# Test connectivity
await self.test_connection(config)
4. Document Configuration¶
Provide clear descriptions for all configuration attributes:
"endpoint": a_str(
default="https://api.mycloud.com",
description="API endpoint URL. Use https://api.staging.mycloud.com for staging."
),
5. Use Type-Safe Config Classes¶
Create attrs classes for configuration:
@attrs.define
class ProviderConfig:
"""Type-safe provider configuration."""
api_key: str
endpoint: str = "https://api.mycloud.com"
timeout: int = 30
def __attrs_post_init__(self):
"""Validate after initialization."""
if self.timeout < 1:
raise ValueError("Timeout must be positive")
6. Cache Clients¶
Initialize API clients once in configure():
async def configure(self, config: dict) -> None:
await super().configure(config)
# Initialize client once
self.client = MyCloudClient(
api_key=config["api_key"],
endpoint=config.get("endpoint"),
)
# Test connection
await self.client.ping()
Common Issues¶
Missing Required Configuration¶
Error:
Solution: Ensure required attributes are marked in schema and provided in configuration:
Invalid Configuration Values¶
Error:
Solution:
Add validation in configure():
async def configure(self, config: dict) -> None:
if not config.get("api_key"):
raise ProviderConfigurationError("api_key cannot be empty")
Sensitive Data Exposure¶
Issue: Sensitive values appearing in logs or output
Solution: Mark all sensitive attributes:
"password": a_str(required=True, sensitive=True),
"api_token": a_str(required=True, sensitive=True),
See Also¶
- Creating Providers - Complete provider development guide
- Best Practices - Provider development best practices
- Error Handling - Error handling patterns
- Schema System - Schema definition reference