State, Credentials & Multi-Cloud
The AWS implementation has been fully tested in a project, however the Azure and GCP while implemented have not been fully tested.
Three cross-cutting concerns run through every phase: the deployment state manifest (what has been deployed), the credentials abstraction (how to authenticate), and the cloud provider strategy (how to make provider calls). This page also covers the verify and undeploy phases.
Domain Model
Everything hinges on a sealed Component interface. Because the set of component types is closed, the parser, validator, planner, deployer, verifier, and undeployer all pattern-match exhaustively over the same types — adding a component type surfaces every place that must handle it as a compile error.
sealed interface Component
permits Website, Storage, ApiGateway, Function, Database,
Authentication, Email, Sms, EventBus, Waf {
String name();
Map<String, Object> providerSettings();
List<ComponentLink> links();
}
Components are held in an insertion-ordered ComponentRegistry; relationships are ComponentLink records carrying a LinkType (18 values) and optional properties. Validation and permission derivation both key off LinkType.
Credentials Abstraction
Authentication config is a sealed hierarchy, so the provider is determined by the config type itself — there is no string-based provider dispatch at the deployment layer.
sealed interface CredentialConfig
permits AwsCredentialConfig, AzureCredentialConfig, GcpCredentialConfig { }
A Mojo builds the right CredentialConfig from the serverless.provider parameter plus credentialMethod / profileName (falling back to a credentials block in the config JSON). AWS supports environment variables, named profiles, static access keys, and assumed roles. The same config object then selects the state store, the state lock, the cloud state reader, and the provider strategy — all by pattern matching on its subtype.
Cloud Provider Strategy
All provider-specific work sits behind one interface:
interface CloudProviderStrategy {
DeploymentResult deploy(Component component,
List<PermissionGrant> grants,
CredentialConfig credentials);
}
AwsCloudProviderStrategy pattern-matches the component and dispatches to a per-component deployer (Lambda, DynamoDB, API Gateway v2, S3 + CloudFront, Cognito, SES, SNS, EventBridge, WAFv2), obtaining SDK clients through an AwsClientFactory and wrapping calls in a RetryExecutor that classifies transient vs permanent errors.
AzureCloudProviderStrategy and GcpCloudProviderStrategy follow the same shape: each pattern-matches the component and dispatches to its own per-component deployer (Azure: Function App, Cosmos DB, API Management, Storage / Static Web App, AD B2C, Communication Services, Event Grid, Front Door WAF; GCP: Cloud Functions, Firestore, API Gateway, Cloud Storage / Cloud CDN, Identity Platform, SendGrid/Twilio via Secret Manager, Pub/Sub, Cloud Armor). Clients come from an AzureClientFactory / GcpClientFactory and SDK calls are wrapped in the same RetryExecutor. A small number of resources (Azure APIM service creation, Azure AD B2C user flows, Azure Front Door WAF policies, GCP API Gateway service management, GCP Identity Platform tenant config and Cloud Armor policies) currently record the deterministic resource ID and log the intended action while the stable provider SDK modules are wired — every other Azure and GCP resource is provisioned for real.
State Manifest & Locking
Deployment state is persisted in a manifest so successive runs can diff against what already exists. Each deployed component contributes a StateEntry (name, type, resource id, and a SHA-256 configuration hash). The hash is computed by ConfigurationHashComputer over the component's type, name, sorted provider settings, and sorted links — deliberately excluding timestamps and resource IDs — so the planner can tell UPDATE from UNCHANGED.
The manifest is stored through a CompositeManifestStore that combines a remote store, a local cache, and a lock:
- Remote — S3, Azure Blob, or GCS (chosen by credential type), in a bucket named {application}-serverless-state. This is the source of truth for team use.
- Local — a file under the project directory, refreshed on read and always written, acting as a cache and offline fallback.
- Lock — a provider-native state lock (S3 / Azure Blob lease / GCS) acquired before deploy and released in a finally block, preventing concurrent deploys from corrupting shared state.
Cross-phase data (registry, change plan) lives in memory in the PhaseSession; cross-run data lives in the persisted state manifest.
Verify & Undeploy
Verify (Phase 4) derives the expected permission grants from the configuration, reads the live grants from the cloud, and reports any missing or unexpected grants without failing the build; separate health checkers probe deployed resources.
Undeploy (Phase 5) reads the state manifest, orders components in reverse, revokes permissions, and deletes each resource through a ComponentDeleter. The AWS deleter is complete (it empties and deletes buckets, removes Lambdas and roles, disables and deletes CloudFront distributions, deletes WAF ACLs in both scopes, and treats "not found" as success). The Azure deleter tears down the deployment by deleting its resource group in one call (all components live in the derived {application}-rg group), and the GCP deleter dispatches per component type through a resource-deleter seam (Cloud Functions, Cloud Storage buckets, Firestore data, Pub/Sub topics, Cloud Run/Cloud Endpoints resources, IAM grants). Both Azure and GCP deleters are idempotent — a "not found" outcome is treated as success — matching AWS.
Documentation Generation
The plugin-docgen module can generate a baseline documentation set (HTML pages, the JSON Schema, and llms.txt) into target/docs. The published docs/ site in this repository is hand-maintained and richer than the generated baseline.