You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 1 MVP Implementation Tracker: Core Architecture and Provider Modules
Overview
This document tracks the implementation progress of the Phase 1 MVP for our new Infrastructure as Code (IaC) architecture. The goal of Phase 1 is to implement the full core module architecture and establish functional provider modules for AWS, Azure, and Kubernetes that comply with our design principles.
Last Updated: March 18, 2025
Milestone Summary
Area
Status
Progress
Core Architecture
🟡 In Progress
15%
AWS Provider
🟡 In Progress
5%
Azure Provider
🔴 Not Started
0%
Kubernetes Provider
🔴 Not Started
0%
Integration Testing
🔴 Not Started
0%
1. Core Architecture Implementation
The core architecture establishes the foundation for our ComponentResource pattern and provider abstractions.
1.1 Core Type System and Interfaces
Task
Status
Assignee
Notes
Define base interfaces and protocols
🔴 Not Started
Define core interfaces in src/core/interfaces
Implement type definitions
🔴 Not Started
Create common type definitions in src/core/types
Create exception hierarchy
🔴 Not Started
Establish exception classes in src/core/exceptions
Implement interface documentation
🔴 Not Started
Document all interfaces with docstrings
1.2 Core Component Resources
Task
Status
Assignee
Notes
Implement BaseComponent
🔴 Not Started
Create core component base class
Implement parent-child relationship handling
🔴 Not Started
Proper implementation of resource parenting
Implement output registration mechanisms
🔴 Not Started
Handle component output registration
Implement resource options propagation
🔴 Not Started
Properly propagate resource options
1.3 Configuration Management
Task
Status
Assignee
Notes
Implement ConfigManager
🔴 Not Started
Core configuration management
Implement configuration loader
🔴 Not Started
Loading from Pulumi config and files
Implement schema validation
🔴 Not Started
JSON schema validation for configs
Implement default configuration
🔴 Not Started
Default configuration values
Implement credential environment variable support
🔴 Not Started
Environment-based credential configuration only
Implement Kubernetes version configuration
🔴 Not Started
Support for stack config and remote version files
1.4 Metadata Management
Task
Status
Assignee
Notes
Implement MetadataManager
🔴 Not Started
Core metadata management
Implement tagging models
🔴 Not Started
Standardized tagging system
Implement resource transformations
🔴 Not Started
Support for resource transformations
Implement compliance metadata propagation
🔴 Not Started
Tag/label/annotation propagation for compliance
1.5 Provider Framework
Task
Status
Assignee
Notes
Implement provider registry
🔴 Not Started
Provider registration system
Implement provider factory
🔴 Not Started
Factory for creating providers
Implement provider context models
🔴 Not Started
Models for provider context
Implement multi-provider support
🔴 Not Started
Support for multiple named providers per module
1.6 Deployment Orchestration
Task
Status
Assignee
Notes
Implement DeploymentManager
🔴 Not Started
Core deployment orchestration
Implement deployment context
🔴 Not Started
Models for deployment context
Implement module registry
🔴 Not Started
Module registration system
1.7 Logging Framework
Task
Status
Assignee
Notes
Implement LogManager
🔴 Not Started
Core logging infrastructure
Implement standardized logging interfaces
🔴 Not Started
Common logging patterns and methods
Implement context-aware logging
🔴 Not Started
Logging with provider/resource context
Implement log level configuration
🔴 Not Started
Configurable logging verbosity
1.8 Core Utilities
Task
Status
Assignee
Notes
Implement Git utilities
🔴 Not Started
Git integration utilities
Implement Pulumi utilities
🔴 Not Started
Pulumi-specific utilities
Implement serialization helpers
🔴 Not Started
Data serialization
2. AWS Provider Implementation
The AWS provider module implements AWS-specific resources and components using our architecture.
2.1 AWS Provider Core
Task
Status
Assignee
Notes
Implement AWS provider class
🔴 Not Started
Core AWS provider implementation
Implement AWS config schema
🔴 Not Started
AWS-specific config schema
Implement credential management
🔴 Not Started
AWS credential handling via environment variables
Implement multi-region provider support
🔴 Not Started
Support for multiple named AWS providers
2.2 AWS Core Resources
Task
Status
Assignee
Notes
Implement S3 bucket resource
🔴 Not Started
S3 bucket implementation
Implement EC2 instance resource
🔴 Not Started
EC2 instance implementation
Implement VPC resource
🔴 Not Started
VPC implementation
Implement IAM resources
🔴 Not Started
IAM role/policy implementation
2.3 AWS Components
Task
Status
Assignee
Notes
Implement secure VPC component
🔴 Not Started
VPC with security best practices
Implement EKS cluster component
🔴 Not Started
Kubernetes cluster on AWS
3. Azure Provider Implementation
The Azure provider module implements Azure-specific resources and components using our architecture.
3.1 Azure Provider Core
Task
Status
Assignee
Notes
Implement Azure provider class
🔴 Not Started
Core Azure provider implementation
Implement Azure config schema
🔴 Not Started
Azure-specific config schema
Implement credential management
🔴 Not Started
Azure credential handling via environment variables
Implement multi-region provider support
🔴 Not Started
Support for multiple named Azure providers
3.2 Azure Core Resources
Task
Status
Assignee
Notes
Implement storage account resource
🔴 Not Started
Storage account implementation
Implement virtual machine resource
🔴 Not Started
VM implementation
Implement virtual network resource
🔴 Not Started
VNet implementation
Implement IAM resources
🔴 Not Started
RBAC implementation
3.3 Azure Components
Task
Status
Assignee
Notes
Implement secure VNet component
🔴 Not Started
VNet with security best practices
Implement AKS cluster component
🔴 Not Started
Kubernetes cluster on Azure
4. Kubernetes Provider Implementation
The Kubernetes provider module implements Kubernetes-specific resources and components using our architecture.
4.1 Kubernetes Provider Core
Task
Status
Assignee
Notes
Implement Kubernetes provider class
🔴 Not Started
Core K8s provider implementation
Implement Kubernetes config schema
🔴 Not Started
K8s-specific config schema
Implement kubeconfig management
🔴 Not Started
Kubeconfig handling
Implement multi-cluster provider support
🔴 Not Started
Support for multiple named K8s providers
Implement version management system
🔴 Not Started
Support for component version configuration
4.2 Kubernetes Core Resources
Task
Status
Assignee
Notes
Implement namespace resource
🔴 Not Started
Namespace implementation
Implement deployment resource
🔴 Not Started
Deployment implementation
Implement service resource
🔴 Not Started
Service implementation
Implement configmap/secret resources
🔴 Not Started
ConfigMap and Secret implementation
4.3 Kubernetes Components
Task
Status
Assignee
Notes
Implement application component
🔴 Not Started
Standard app deployment pattern
Implement service mesh component
🔴 Not Started
Service mesh integration
5. Testing Framework
5.1 Unit Testing
Task
Status
Assignee
Notes
Implement core module tests
🔴 Not Started
Tests for core functionality
Implement AWS provider tests
🔴 Not Started
Tests for AWS provider
Implement Azure provider tests
🔴 Not Started
Tests for Azure provider
Implement Kubernetes provider tests
🔴 Not Started
Tests for Kubernetes provider
5.2 Integration Testing
Task
Status
Assignee
Notes
Implement end-to-end AWS tests
🔴 Not Started
Deploy real AWS resources
Implement end-to-end Azure tests
🔴 Not Started
Deploy real Azure resources
Implement end-to-end K8s tests
🔴 Not Started
Deploy real K8s resources
Implement multi-provider tests
🔴 Not Started
Cross-provider integration
6. Documentation
6.1 API Documentation
Task
Status
Assignee
Notes
Document core interfaces
🔴 Not Started
Interface documentation
Document AWS provider
🔴 Not Started
AWS provider documentation
Document Azure provider
🔴 Not Started
Azure provider documentation
Document Kubernetes provider
🔴 Not Started
K8s provider documentation
6.2 User Guide
Task
Status
Assignee
Notes
Create getting started guide
🔴 Not Started
Initial user onboarding
Create configuration guide
🔴 Not Started
Configuration documentation
Create provider-specific guides
🔴 Not Started
Provider-specific documentation
Key Implementation Notes
Component Resource Pattern
All implementations should follow these key principles:
Proper Parenting: Always use parent=self for resources inside components to maintain hierarchy and lifecycle management
Output Registration: Always call self.register_outputs({...}) at the end of component constructors
Resource Dependencies: Use implicit dependencies through property references rather than explicit depends_on when possible
Provider Propagation: Ensure provider instances are correctly propagated through the resource hierarchy
Dependency Management
Implementations should follow these dependency best practices:
Avoid Circular Dependencies: Design interfaces to avoid circular references between resources
Leverage Implicit Dependencies: Prefer using resource outputs as inputs to establish dependencies
Minimize Explicit Dependencies: Use depends_on only when necessary for special cases
Proper Resource Ordering: Ensure resources are created and destroyed in the correct order
Configuration Management
Environment Variables: Only credential information should come from environment variables
Pulumi Stack Config: All other configuration must come from Pulumi stack config YAML
Kubernetes Version Management: Kubernetes component versions may be specified in stack config or via version files
Progress Updates
March 18, 2025
Created initial project tracking document
Established directory structure for implementation
Next Actions
Begin implementation of core interfaces and base component resources
Set up unit testing framework
Implement basic AWS provider functionality
This is a living document that will be updated regularly as we make progress on the implementation.
The Core Module is the central foundation of our Infrastructure as Code (IaC) framework. It provides essential functionality that all provider modules depend on, and it must operate correctly even when all provider modules are disabled (<provider_module_name>.enabled: false). This document outlines the design and implementation details for the Core Module, focusing on its inputs, outputs, and standalone functionality.
2. Core Module Architecture
2.1 Overall Architecture
The Core Module follows a layered architecture with the following key components:
Configuration Management: Loads, validates, and propagates configuration from Pulumi stack YAML. Provider modules handle their own credential management with a fallback mechanism (config → environment variables → ambient credentials).
Metadata Management: Collects, validates, and propagates metadata such as tags, labels, and compliance annotations. Always collects Git metadata regardless of configuration.
Logging System: Provides standardized logging interfaces for all modules by wrapping Pulumi's native logging system. Supports multiple log levels (standard, verbose, debug) configurable via Pulumi stack config.
Provider Registry: Manages provider registration and discovery, including support for multiple named providers per module. Allows provider modules to register cross-provider dependencies (e.g., AWS EKS registering Kubernetes providers).
Module Discovery: Dynamically discovers provider modules without hardcoding, looking for modules in src/providers/<provider_module_directory_name>.
Dynamic Loading: Loads provider modules at runtime based on a standardized hook in each provider module's __init__.py.
Deployment Orchestration: Handles dependency management and deployment sequencing.
Git Integration: Always retrieves information from the local Git repository for metadata enrichment, returning fallback values if Git information is unavailable.
Type System & Interfaces: Defines core interfaces, types, and protocols that all modules implement.
3. Core Module Execution Flow
When running pulumi up with all provider modules disabled, the Core Module will execute the following sequence:
# src/__main__.py"""Main entry point for the Pulumi program."""# Minimal entry point that delegates to the Core Modulefromcore.core_moduleimportCoreModuledefmain():
"""Initialize and run the Core Module."""core_module=CoreModule()
core_module.run()
# Entry pointif__name__=="__main__":
main()
5.2.2 Core Module Implementation
# src/core/core_module.py"""Core Module implementation."""importpulumiimportosimportimportlibimportimportlib.utilimportpkgutilimportsysfrompathlibimportPathfromtypingimportDict, Any, List, Optional, Typefrom .loggingimportLogManagerfrom .configimportConfigManagerfrom .metadataimportMetadataManagerfrom .providersimportProviderRegistry, ProviderDiscoveryclassCoreModule:
"""Main Core Module implementation."""def__init__(self):
"""Initialize the Core Module."""# Initialize logging firstself.log_manager=LogManager()
self.logger=self.log_manager.get_logger("core")
self.logger.info("Initializing core module")
# Initialize configurationself.config_manager=ConfigManager(self.log_manager)
# Initialize metadata managementself.metadata_manager=MetadataManager(self.config_manager, self.log_manager)
# Initialize provider registryself.provider_registry=ProviderRegistry(self.config_manager, self.log_manager)
# Initialize provider discoveryself.provider_discovery=ProviderDiscovery(self.log_manager)
defrun(self):
"""Execute the Core Module."""try:
# Apply log level from configurationself._configure_logging()
# Discover and load provider modulesself._discover_provider_modules()
# Load and validate configurationself.logger.info("Loading and validating configuration")
self.config_manager.load()
validation_errors=self.config_manager.validate()
ifvalidation_errors:
forerrorinvalidation_errors:
self.logger.error(f"Configuration error: {error}")
raiseException("Configuration validation failed")
# Collect Git metadataself.logger.info("Collecting Git metadata")
git_metadata=self.metadata_manager.collect_git_metadata()
# Process all metadataself.logger.info("Processing metadata")
metadata=self.metadata_manager.collect_metadata()
# Register provider modulesself.logger.info("Registering providers")
self._register_provider_modules()
# Initialize module registryself.logger.info("Initializing module registry")
# Generate stack outputsself._generate_stack_outputs(metadata)
# Log completion statusself.logger.info("Core module execution complete")
exceptExceptionase:
self.logger.error(f"Core module execution failed: {str(e)}")
raiseedef_configure_logging(self):
"""Configure logging based on configuration."""config=self.config_manager.get_config().get("logging", {})
log_level=config.get("level", "standard")
self.log_manager.set_log_level(log_level)
self.logger.info(f"Log level set to: {log_level}")
def_discover_provider_modules(self):
"""Discover available provider modules."""self.logger.info("Discovering provider modules")
provider_modules=self.provider_discovery.discover_provider_modules()
ifprovider_modules:
self.logger.info(f"Found provider modules: {', '.join(provider_modules)}")
else:
self.logger.warning("No provider modules found")
def_register_provider_modules(self):
"""Register discovered provider modules with the provider registry."""forprovider_module_name, provider_moduleinself.provider_discovery.get_provider_modules().items():
self.logger.info(f"Registering provider module: {provider_module_name}")
try:
# Call the provider's register_provider hookifhasattr(provider_module, "register_provider"):
provider_module.register_provider(
self.provider_registry,
self.config_manager,
self.log_manager
)
else:
self.logger.warning(f"Provider module {provider_module_name} does not implement register_provider hook")
exceptExceptionase:
self.logger.error(f"Failed to register provider module {provider_module_name}: {str(e)}")
def_generate_stack_outputs(self, metadata: Dict[str, Any]):
"""Generate Pulumi stack outputs."""self.logger.info("Generating stack outputs")
# Export main state outputstate_output= {
"metadata": metadata,
"providers": {}
}
# Add provider status to state outputforprovider_nameinself.provider_discovery.get_provider_modules().keys():
enabled=self.config_manager.is_provider_enabled(provider_name)
state_output["providers"][provider_name] = {"enabled": enabled}
self.logger.info(f"Provider {provider_name} is {'enabled'ifenabledelse'disabled'}")
# Export state outputpulumi.export("state", state_output)
# Export secrets output if any secrets are registeredsecrets= {}
# Collect secrets from provider modules (would be implemented in full version)ifsecrets:
pulumi.export("secrets", pulumi.Output.secret(secrets))
5.2.3 Dynamic Provider Discovery
# src/core/providers/provider_discovery.py"""Provider module discovery and loading."""importosimportsysimportimportlibimportimportlib.utilfrompathlibimportPathfromtypingimportDict, Any, Optional, ListclassProviderDiscovery:
"""Discovers and loads provider modules dynamically."""def__init__(self, log_manager):
"""Initialize provider discovery."""self.log_manager=log_managerself.logger=log_manager.get_logger("provider_discovery")
self.provider_modules= {}
self.providers_dir=Path("src/providers")
defdiscover_provider_modules(self) ->List[str]:
"""Discover available provider modules."""ifnotself.providers_dir.exists() ornotself.providers_dir.is_dir():
self.logger.warning(f"Providers directory not found: {self.providers_dir}")
return []
# Find all subdirectories in the providers directoryprovider_dirs= [dfordinself.providers_dir.iterdir() ifd.is_dir() andnotd.name.startswith('_')]
forprovider_dirinprovider_dirs:
provider_name=provider_dir.nameself.logger.debug(f"Found potential provider module: {provider_name}")
# Check for __init__.py to confirm it's a Python moduleinit_file=provider_dir/"__init__.py"ifnotinit_file.exists():
self.logger.warning(f"Provider directory {provider_name} missing __init__.py, skipping")
continue# Load the provider moduletry:
# Construct the module namemodule_name=f"providers.{provider_name}"# Add the parent directory to sys.path if neededparent_dir=str(self.providers_dir.parent)
ifparent_dirnotinsys.path:
sys.path.insert(0, parent_dir)
# Import the moduleprovider_module=importlib.import_module(module_name)
# Verify it has the required attributesifnothasattr(provider_module, "PROVIDER_NAME"):
self.logger.warning(f"Provider module {provider_name} missing PROVIDER_NAME attribute")
continue# Store the module for later useself.provider_modules[provider_name] =provider_moduleself.logger.info(f"Successfully loaded provider module: {provider_name}")
exceptExceptionase:
self.logger.error(f"Failed to load provider module {provider_name}: {str(e)}")
returnlist(self.provider_modules.keys())
defget_provider_modules(self) ->Dict[str, Any]:
"""Get all discovered provider modules."""returnself.provider_modulesdefget_provider_module(self, provider_name: str) ->Optional[Any]:
"""Get a specific provider module."""returnself.provider_modules.get(provider_name)
5.2.4 Enhanced Logging System
# src/core/logging/log_manager.py"""Standardized logging system that wraps Pulumi's native logging."""importpulumiimportloggingimportsysfromtypingimportDict, Any, OptionalclassLogManager:
"""Manages logging for all modules, wrapping Pulumi's native logging."""# Log levels mappingLOG_LEVELS= {
"standard": 0, # Default level"verbose": 1, # More detailed information"debug": 2# Low-level debugging
}
def__init__(self, config: Optional[Dict[str, Any]] =None):
"""Initialize the logging system."""self.config=configor {}
self.loggers= {}
self.current_level="standard"# Default levelself._configure_logging()
def_configure_logging(self) ->None:
"""Configure the logging system."""# Set up console handler for developmentconsole_handler=logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(
"[%(name)s:%(levelname)s] %(message)s"
))
# Configure root loggerroot_logger=logging.getLogger()
root_logger.addHandler(console_handler)
root_logger.setLevel(logging.INFO)
defget_logger(self, name: str) ->logging.Logger:
"""Get a logger instance with standardized configuration."""ifnamenotinself.loggers:
logger=logging.getLogger(name)
self.loggers[name] =logger# Wrap log methods to also use Pulumi loggingoriginal_info=logger.infooriginal_warning=logger.warningoriginal_error=logger.errororiginal_debug=logger.debug# Override methods to also log to Pulumidefinfo_wrapper(msg, *args, **kwargs):
original_info(msg, *args, **kwargs)
pulumi.log.info(msg, resource=None)
defwarning_wrapper(msg, *args, **kwargs):
original_warning(msg, *args, **kwargs)
pulumi.log.warn(msg, resource=None)
deferror_wrapper(msg, *args, **kwargs):
original_error(msg, *args, **kwargs)
pulumi.log.error(msg, resource=None)
defdebug_wrapper(msg, *args, **kwargs):
original_debug(msg, *args, **kwargs)
ifself.get_log_level_value() >=self.LOG_LEVELS["debug"]:
pulumi.log.debug(msg, resource=None)
# Replace the methodslogger.info=info_wrapperlogger.warning=warning_wrapperlogger.error=error_wrapperlogger.debug=debug_wrapperreturnself.loggers[name]
defset_log_level(self, level: str) ->None:
"""Set the log level."""iflevelnotinself.LOG_LEVELS:
level="standard"# Default to standard if invalid levelself.current_level=level# Set Python logging levelroot_logger=logging.getLogger()
iflevel=="debug":
root_logger.setLevel(logging.DEBUG)
eliflevel=="verbose":
root_logger.setLevel(logging.INFO)
else: # standardroot_logger.setLevel(logging.INFO)
defget_log_level_value(self) ->int:
"""Get the numeric value of the current log level."""returnself.LOG_LEVELS.get(self.current_level, 0)
5.2.5 Provider Registry System
# src/core/providers/provider_registry.py"""Provider registration and management."""fromtypingimportDict, Any, List, Optional, Type, CallablefromabcimportABC, abstractmethod# Provider interfaceclassProviderProtocol(ABC):
"""Protocol defining the provider interface."""@property@abstractmethoddefname(self) ->str:
"""Get the provider name."""pass@property@abstractmethoddefprovider_type(self) ->str:
"""Get the provider type."""pass@abstractmethoddefis_enabled(self) ->bool:
"""Check if the provider is enabled."""pass@abstractmethoddefget_config(self) ->Dict[str, Any]:
"""Get the provider configuration."""passclassProviderRegistry:
"""Registry for all providers."""def__init__(self, config_manager, log_manager):
"""Initialize the provider registry."""self.config_manager=config_managerself.log_manager=log_managerself.logger=log_manager.get_logger("provider_registry")
# Structure: {provider_type: {provider_name: provider_instance}}self.providers: Dict[str, Dict[str, Any]] = {}
# Structure: {provider_type: factory_function}self.provider_factories: Dict[str, Callable] = {}
defregister_provider_factory(self, provider_type: str, factory_function: Callable) ->None:
"""Register a provider factory function. Args: provider_type: Type of the provider (e.g., 'aws', 'azure', 'kubernetes') factory_function: Function that creates provider instances """self.logger.info(f"Registering provider factory for type: {provider_type}")
self.provider_factories[provider_type] =factory_function# Initialize the provider type registry if neededifprovider_typenotinself.providers:
self.providers[provider_type] = {}
defcreate_provider(self, provider_type: str, provider_name: str, **kwargs) ->Optional[Any]:
"""Create a provider instance using the registered factory. Args: provider_type: Type of the provider provider_name: Name of the provider instance **kwargs: Additional parameters for the factory function Returns: The created provider instance or None if factory not found """ifprovider_typenotinself.provider_factories:
self.logger.warning(f"No provider factory registered for type: {provider_type}")
returnNonetry:
self.logger.info(f"Creating provider: {provider_name} of type: {provider_type}")
provider=self.provider_factories[provider_type](provider_name, **kwargs)
# Store the provider in the registryself.providers[provider_type][provider_name] =providerreturnproviderexceptExceptionase:
self.logger.error(f"Failed to create provider {provider_name} of type {provider_type}: {str(e)}")
returnNonedefget_provider(self, provider_type: str, provider_name: str) ->Optional[Any]:
"""Get a provider instance from the registry. Args: provider_type: Type of the provider provider_name: Name of the provider instance Returns: The provider instance or None if not found """ifprovider_typenotinself.providers:
returnNonereturnself.providers[provider_type].get(provider_name)
defget_enabled_providers(self) ->List[Any]:
"""Get all enabled providers. Returns: List of enabled provider instances """enabled_providers= []
forprovider_type, providersinself.providers.items():
forproviderinproviders.values():
ifprovider.is_enabled():
enabled_providers.append(provider)
returnenabled_providersdefget_all_providers(self) ->Dict[str, Dict[str, Any]]:
"""Get all registered providers. Returns: Dictionary of all registered providers """returnself.providersdefhas_provider_type(self, provider_type: str) ->bool:
"""Check if a provider type is registered. Args: provider_type: Type of the provider Returns: True if the provider type is registered, False otherwise """returnprovider_typeinself.providers
5.2.6 Provider Module Registration Hook Example
# src/providers/aws/__init__.py"""AWS Provider Module for the IaC framework."""PROVIDER_TYPE="aws"PROVIDER_NAME="aws"# Default provider namedefregister_provider(provider_registry, config_manager, log_manager):
"""Register the AWS provider with the provider registry. This is the standardized hook called by the Core Module during provider registration. Args: provider_registry: The provider registry instance config_manager: The configuration manager instance log_manager: The logging manager instance """from .providerimportcreate_aws_providerlogger=log_manager.get_logger("aws_provider")
logger.info(f"Registering AWS provider factory")
# Register the AWS provider factoryprovider_registry.register_provider_factory(PROVIDER_TYPE, create_aws_provider)
# Get AWS provider configurationsaws_config=config_manager.get_provider_config(PROVIDER_TYPE)
# Create default AWS provider if enabledifconfig_manager.is_provider_enabled(PROVIDER_TYPE):
# Create the default AWS providerprovider=provider_registry.create_provider(
PROVIDER_TYPE,
PROVIDER_NAME,
config=aws_config.get("default", {}),
config_manager=config_manager,
log_manager=log_manager
)
ifprovider:
logger.info(f"Created default AWS provider: {PROVIDER_NAME}")
# Create any additional named AWS providersnamed_providers=aws_config.get("providers", {})
forname, provider_configinnamed_providers.items():
ifname=="default":
continue# Skip default, already createdprovider=provider_registry.create_provider(
PROVIDER_TYPE,
name,
config=provider_config,
config_manager=config_manager,
log_manager=log_manager
)
ifprovider:
logger.info(f"Created named AWS provider: {name}")
else:
logger.info("AWS provider is disabled, skipping provider creation")
5.3 Main Entry Point
importpulumifromtypingimportDict, Any# Import core module componentsfromcore.configimportConfigManagerfromcore.metadataimportMetadataManagerfromcore.loggingimportLogManagerfromcore.providersimportProviderRegistrydefmain():
"""Main entry point for the Pulumi program."""# Initialize logginglog_manager=LogManager()
logger=log_manager.get_logger("main")
logger.info("Initializing core module")
# Initialize configuration managementconfig_manager=ConfigManager()
logger.info("Loaded configuration")
# Validate configurationvalidation_errors=config_manager.validate_config()
ifvalidation_errors:
forerrorinvalidation_errors:
logger.error(f"Configuration error: {error}")
raiseException("Configuration validation failed")
# Initialize metadata managementmetadata_manager=MetadataManager(config_manager)
metadata=metadata_manager.collect_metadata()
logger.info("Collected metadata")
# Initialize provider registryprovider_registry=ProviderRegistry(config_manager)
logger.info("Initialized provider registry")
# Check which providers are enabledaws_enabled=config_manager.is_provider_enabled("aws")
azure_enabled=config_manager.is_provider_enabled("azure")
kubernetes_enabled=config_manager.is_provider_enabled("kubernetes")
logger.info(f"Provider status: AWS={'enabled'ifaws_enabledelse'disabled'}, "f"Azure={'enabled'ifazure_enabledelse'disabled'}, "f"Kubernetes={'enabled'ifkubernetes_enabledelse'disabled'}")
# Log metadatalogger.info(f"Project: {metadata.get('project', {}).get('name', 'unknown')}")
logger.info(f"Environment: {metadata.get('project', {}).get('environment', 'unknown')}")
# Get Git metadatagit_metadata=metadata.get("git", {})
ifgit_metadata:
logger.info(f"Git repository: {git_metadata.get('repository', 'unknown')}")
logger.info(f"Git branch: {git_metadata.get('branch', 'unknown')}")
logger.info(f"Git commit: {git_metadata.get('commit', 'unknown')}")
if"tag"ingit_metadata:
logger.info(f"Git tag: {git_metadata.get('tag')}")
# Register Pulumi stack outputspulumi.export("metadata", metadata)
pulumi.export("providers", {
"aws": {"enabled": aws_enabled},
"azure": {"enabled": azure_enabled},
"kubernetes": {"enabled": kubernetes_enabled}
})
# Only proceed with provider initialization if they are enabledifnotany([aws_enabled, azure_enabled, kubernetes_enabled]):
logger.info("All providers are disabled. Core module execution complete.")
return# Initialize enabled providers (not part of this design document)# ...# Entry pointif__name__=="__main__":
main()
6. Testing Core Module Functionality
6.1 Unit Testing
Key unit tests for the Core Module will include:
Dynamic Module Discovery Tests:
Test module discovery mechanics
Test module loading with valid and invalid modules
Test handling of missing required hooks
Configuration Management Tests:
Test loading configuration from Pulumi stack
Test configuration validation
Test provider enablement detection
Metadata Management Tests:
Test metadata collection
Test Git metadata extraction with and without Git repository
Test compliance metadata processing
Test tag generation for resources
Logging System Tests:
Test logger creation and Pulumi logging integration
Test log level configuration (standard, verbose, debug)
Test log formatting
Provider Registry Tests:
Test provider registration
Test provider retrieval
Test cross-provider registration (e.g., AWS EKS registering Kubernetes providers)
Test enabled provider filtering
6.2 Integration Testing
Integration tests will verify the Core Module's functionality in an end-to-end scenario:
Core-Only Mode Test: Run Pulumi with all providers disabled and verify:
Configuration is loaded and validated
Metadata is collected and exported
Logs contain expected information
Stack outputs contain correct metadata
Provider Module Loading Test: Test the dynamic loading of provider modules:
Test discovery of standard provider modules
Test handling of invalid provider modules
Test loading provider modules with all resources disabled
Cross-Provider Integration Test: Test scenarios where provider modules register providers with other modules:
Test AWS EKS registering Kubernetes provider
Test Azure AKS registering Kubernetes provider
Test proper separation of credentials and configuration between providers
7. Next Steps
After implementing the Core Module, subsequent steps will include:
Provider Module Implementation: Implement AWS, Azure, and Kubernetes provider modules with proper hooks for registration
Provider-Specific Credentials Management: Implement credential loading and validation in each provider module
Resource and Component Implementation: Implement core resources and components for each provider
End-to-End Testing: Test the entire framework with real infrastructure
8. Conclusion
The Core Module design provides a solid foundation for our IaC framework, offering essential functionality that works even when all provider modules are disabled. Key enhancements in this design include:
Dynamic Provider Loading: Provider modules are discovered and loaded dynamically from src/providers/<provider_module_directory_name> without hardcoding in the core module or main entry point.
Standardized Provider Registration Hooks: Each provider module implements a standardized hook in its __init__.py to register with the core module.
Provider-Specific Credential Management: Each provider handles its own credential loading with a consistent fallback mechanism (config → environment variables → ambient credentials).
Always-Collected Git Metadata: Git metadata is always collected, with fallback values when Git information is unavailable.
Minimal Main Entry Point: The main entry point (__main__.py) contains minimal code, delegating all logic to the Core Module.
Named Stack Outputs: Stack outputs are named meaningfully with separate outputs for state and secrets.
Standardized Pulumi-Wrapped Logging: The logging system wraps Pulumi's native logging for consistent formatting and multiple log levels.
With this design, the Core Module will successfully execute in standalone mode, providing valuable metadata outputs and comprehensive logging while validating all configuration before any resources are provisioned. The modular architecture with dynamic provider loading ensures clean separation of concerns and allows for easy extension with new provider modules in the future.
9. Appendix: Sample Configuration
9.1 Pulumi Stack Configuration Example
Below is a sample Pulumi stack configuration file (Pulumi.<stack-name>.yaml) that demonstrates the expected format for configuring the core module and provider modules:
config:
# Core Module Configurationcore:
# Project metadataproject:
name: "example-iac-project"environment: "dev"owner: "platform-team"costCenter: "12345"# Logging configurationlogging:
level: "verbose"# Options: standard, verbose, debug# AWS Provider Configurationaws:
enabled: trueregion: "us-west-2"# Default regiontags: # Default tags for all AWS resourcesEnvironment: "dev"ManagedBy: "pulumi"# Multiple named providersproviders:
default: # Default provider (can be omitted, uses values above)region: "us-west-2"east:
region: "us-east-1"tags:
Region: "east"# Service-specific configurationsservices:
s3:
enabled: truedefaultBucketConfig:
versioning: trueencryption: trueec2:
enabled: truedefaultInstanceType: "t3.micro"# Azure Provider Configurationazure:
enabled: false # Provider is disabledlocation: "westus2"# Default regiontags: # Default tags for all Azure resourcesenvironment: "dev"managedBy: "pulumi"# Kubernetes Provider Configurationkubernetes:
enabled: true# Default provider configurationdefault:
namespace: "default"versionControlEnabled: trueversionControl:
repo: "https://github.com/org/k8s-configs"path: "env/dev"branch: "main"# Multiple named providers (e.g., for different clusters)providers:
dev-cluster:
namespace: "dev"prod-cluster:
enabled: false # This specific provider is disablednamespace: "prod"
9.2 Provider Module Directory Structure Example
Example directory structure for a provider module (AWS):
Below is an example implementation of a provider module's key files:
9.3.1 Provider Registration Hook (__init__.py)
# src/providers/aws/__init__.py"""AWS Provider Module for the IaC framework."""PROVIDER_TYPE="aws"PROVIDER_NAME="aws"# Default provider namePROVIDER_DISPLAY_NAME="AWS"# Human-readable namedefregister_provider(provider_registry, config_manager, log_manager):
"""Register the AWS provider with the provider registry. This is the standardized hook called by the Core Module during provider registration. Args: provider_registry: The provider registry instance config_manager: The configuration manager instance log_manager: The logging manager instance """from .providerimportcreate_aws_providerlogger=log_manager.get_logger("aws_provider")
logger.info(f"Registering AWS provider factory")
# Register the AWS provider factoryprovider_registry.register_provider_factory(PROVIDER_TYPE, create_aws_provider)
# Get AWS provider configurationsaws_config=config_manager.get_provider_config(PROVIDER_TYPE)
# Create default AWS provider if enabledifconfig_manager.is_provider_enabled(PROVIDER_TYPE):
# Create the default AWS providerprovider=provider_registry.create_provider(
PROVIDER_TYPE,
PROVIDER_NAME,
config=aws_config.get("default", {}),
config_manager=config_manager,
log_manager=log_manager
)
ifprovider:
logger.info(f"Created default AWS provider: {PROVIDER_NAME}")
# Create any additional named AWS providersnamed_providers=aws_config.get("providers", {})
forname, provider_configinnamed_providers.items():
ifname=="default":
continue# Skip default, already createdprovider=provider_registry.create_provider(
PROVIDER_TYPE,
name,
config=provider_config,
config_manager=config_manager,
log_manager=log_manager
)
ifprovider:
logger.info(f"Created named AWS provider: {name}")
else:
logger.info("AWS provider is disabled, skipping provider creation")
9.3.2 Provider Implementation (provider.py)
# src/providers/aws/provider.py"""AWS Provider implementation."""importpulumiimportpulumi_awsasawsimportosfromtypingimportDict, Any, Optionalfromcore.interfaces.providerimportProviderProtocolclassAwsProvider(ProviderProtocol):
"""AWS Provider implementation."""def__init__(self, provider_name: str, config: Dict[str, Any], config_manager, log_manager):
"""Initialize AWS provider. Args: provider_name: Name of the provider instance config: Provider-specific configuration config_manager: Configuration manager instance log_manager: Logging manager instance """self._name=provider_nameself._config=configself._config_manager=config_managerself._log_manager=log_managerself._logger=log_manager.get_logger(f"aws_provider_{provider_name}")
self._enabled=config.get("enabled", True) # Default to enabled if not specified# Load credentialsself._credentials=self._load_credentials()
# Initialize provider if enabledself._provider=Noneifself.is_enabled():
self._initialize_provider()
@propertydefname(self) ->str:
"""Get the provider name."""returnself._name@propertydefprovider_type(self) ->str:
"""Get the provider type."""return"aws"defis_enabled(self) ->bool:
"""Check if the provider is enabled."""returnself._enableddefget_config(self) ->Dict[str, Any]:
"""Get the provider configuration."""returnself._configdefget_provider(self) ->Optional[aws.Provider]:
"""Get the Pulumi AWS provider instance."""returnself._providerdef_load_credentials(self) ->Dict[str, Any]:
"""Load AWS credentials with fallback mechanism. Order of precedence: 1. Explicit credentials from Pulumi stack config 2. Environment variables 3. Ambient credentials (default profile or instance role) """# 1. Try to get from explicit configif"credentials"inself._config:
self._logger.info("Using AWS credentials from explicit configuration")
return {
"access_key": self._config["credentials"].get("accessKey"),
"secret_key": self._config["credentials"].get("secretKey"),
"session_token": self._config["credentials"].get("sessionToken"),
}
# 2. Try to get from environment variablesifos.environ.get("AWS_ACCESS_KEY_ID") andos.environ.get("AWS_SECRET_ACCESS_KEY"):
self._logger.info("Using AWS credentials from environment variables")
return {
"access_key": os.environ.get("AWS_ACCESS_KEY_ID"),
"secret_key": os.environ.get("AWS_SECRET_ACCESS_KEY"),
"session_token": os.environ.get("AWS_SESSION_TOKEN"),
}
# 3. Use ambient credentials (default profile or instance role)# Let Pulumi/AWS handle this automaticallyself._logger.info("Using ambient AWS credentials (default profile or instance role)")
return {}
def_initialize_provider(self) ->None:
"""Initialize the AWS provider."""try:
# Create provider optionsprovider_args= {}
# Region is requiredif"region"inself._config:
provider_args["region"] =self._config["region"]
else:
# Default to us-east-1 if not specifiedprovider_args["region"] ="us-east-1"self._logger.warning(f"No region specified for AWS provider {self._name}, using default: us-east-1")
# Add credentials if availableifself._credentials:
if"access_key"inself._credentialsandself._credentials["access_key"]:
provider_args["access_key"] =self._credentials["access_key"]
if"secret_key"inself._credentialsandself._credentials["secret_key"]:
provider_args["secret_key"] =self._credentials["secret_key"]
if"session_token"inself._credentialsandself._credentials["session_token"]:
provider_args["token"] =self._credentials["session_token"]
# Add other provider optionsif"profile"inself._config:
provider_args["profile"] =self._config["profile"]
# Initialize the providerself._provider=aws.Provider(
f"aws-{self._name}",
**provider_args
)
self._logger.info(f"Successfully initialized AWS provider: {self._name}")
exceptExceptionase:
self._logger.error(f"Failed to initialize AWS provider {self._name}: {str(e)}")
# Even though initialization failed, we don't want to crash the whole program# Set enabled to False to indicate the provider is not availableself._enabled=Falsedefcreate_aws_provider(provider_name: str, **kwargs) ->AwsProvider:
"""Create an AWS provider instance. Args: provider_name: Name of the provider instance **kwargs: Additional parameters for the provider Returns: An AWS provider instance """returnAwsProvider(provider_name, **kwargs)
9.3.3 Configuration Schema (config/schema.py)
# src/providers/aws/config/schema.py"""AWS Provider configuration schema."""AWS_CONFIG_SCHEMA= {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether the AWS provider is enabled",
"default": False
},
"region": {
"type": "string",
"description": "AWS region for the default provider",
"default": "us-east-1"
},
"profile": {
"type": "string",
"description": "AWS profile to use"
},
"tags": {
"type": "object",
"description": "Default tags for all AWS resources",
"additionalProperties": {
"type": "string"
}
},
"credentials": {
"type": "object",
"description": "AWS credentials (only used if environment variables are not set)",
"properties": {
"accessKey": {
"type": "string",
"description": "AWS access key ID"
},
"secretKey": {
"type": "string",
"description": "AWS secret access key"
},
"sessionToken": {
"type": "string",
"description": "AWS session token (optional)"
}
},
"required": ["accessKey", "secretKey"]
},
"providers": {
"type": "object",
"description": "Named AWS providers configuration",
"additionalProperties": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether this named provider is enabled",
"default": True
},
"region": {
"type": "string",
"description": "AWS region for this provider"
},
"profile": {
"type": "string",
"description": "AWS profile to use for this provider"
},
"tags": {
"type": "object",
"description": "Tags for resources created with this provider",
"additionalProperties": {
"type": "string"
}
}
}
}
},
"services": {
"type": "object",
"description": "Service-specific configurations",
"properties": {
"s3": {
"type": "object",
"description": "S3 service configuration",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether S3 resources are enabled",
"default": True
},
"defaultBucketConfig": {
"type": "object",
"description": "Default configuration for S3 buckets",
"properties": {
"versioning": {
"type": "boolean",
"description": "Whether versioning is enabled by default",
"default": False
},
"encryption": {
"type": "boolean",
"description": "Whether encryption is enabled by default",
"default": True
}
}
}
}
},
"ec2": {
"type": "object",
"description": "EC2 service configuration",
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether EC2 resources are enabled",
"default": True
},
"defaultInstanceType": {
"type": "string",
"description": "Default EC2 instance type",
"default": "t3.micro"
}
}
}
}
}
}
}
9.4 Testing the Core Module
Example test for provider discovery:
# tests/unit/core/providers/test_provider_discovery.pyimportunittestfromunittest.mockimportMagicMock, patchfrompathlibimportPathimportsysimportimportlib# Import the module to testfromcore.providers.provider_discoveryimportProviderDiscoveryclassTestProviderDiscovery(unittest.TestCase):
"""Tests for the ProviderDiscovery class."""defsetUp(self):
"""Set up test fixtures."""self.log_manager=MagicMock()
self.log_manager.get_logger.return_value=MagicMock()
self.discovery=ProviderDiscovery(self.log_manager)
@patch('pathlib.Path.exists')@patch('pathlib.Path.is_dir')@patch('pathlib.Path.iterdir')deftest_discover_provider_modules_empty(self, mock_iterdir, mock_is_dir, mock_exists):
"""Test discovering provider modules when no modules are present."""# Mock the directory exists and is a directorymock_exists.return_value=Truemock_is_dir.return_value=True# Mock no directories foundmock_iterdir.return_value= []
# Call the methodresult=self.discovery.discover_provider_modules()
# Assert no provider modules were foundself.assertEqual(result, [])
@patch('pathlib.Path.exists')@patch('pathlib.Path.is_dir')@patch('pathlib.Path.iterdir')@patch('importlib.import_module')deftest_discover_provider_modules_valid(self, mock_import, mock_iterdir, mock_is_dir, mock_exists):
"""Test discovering valid provider modules."""# Mock the directory exists and is a directorymock_exists.return_value=Truemock_is_dir.return_value=True# Create mock directories for AWS and Azure providersaws_dir=MagicMock(spec=Path)
aws_dir.name="aws"aws_dir.is_dir.return_value=Trueaws_dir.__truediv__.return_value.exists.return_value=Trueazure_dir=MagicMock(spec=Path)
azure_dir.name="azure"azure_dir.is_dir.return_value=Trueazure_dir.__truediv__.return_value.exists.return_value=True# Mock directories foundmock_iterdir.return_value= [aws_dir, azure_dir]
# Mock successful imports with PROVIDER_NAME attributeaws_module=MagicMock()
aws_module.PROVIDER_NAME="aws"azure_module=MagicMock()
azure_module.PROVIDER_NAME="azure"mock_import.side_effect= [aws_module, azure_module]
# Call the methodresult=self.discovery.discover_provider_modules()
# Assert correct provider modules were foundself.assertEqual(set(result), {"aws", "azure"})
# Assert the modules were storedself.assertEqual(self.discovery.get_provider_modules()["aws"], aws_module)
self.assertEqual(self.discovery.get_provider_modules()["azure"], azure_module)
if__name__=='__main__':
unittest.main()