Providers

Three providers ship with atlantide.

localFile, SourceFile and Null. Needs no credentials, so it runs anywhere, including CI.

randomUuid, Password, Id and Timestamp. Each value is generated once at apply and then pinned in state, so re-applying never produces a new one.

aws — the resource types below, plus the AwsCallerIdentity and AwsAvailabilityZones data sources, the IAM policy helpers (allow, deny, assume_role, ServicePrincipal), and the SecureBucket component. Any resource can set provider_alias= to target an alternate account.

Area Types
Storage S3Bucket, S3BucketPolicy, S3Folder
Messaging SqsQueue, SnsTopic, SnsSubscription
Compute LambdaFunction
Identity IamRole, IamPolicy
Data DynamoDbTable
Observability CloudWatchLogGroup
Networking Vpc, Subnet, SecurityGroup, InternetGateway, ElasticIp, NatGateway, RouteTable
Edge and DNS CloudFrontDistribution, OriginAccessControl, AcmCertificate, Route53HostedZone, Route53Record

Defaults worth knowing

S3Bucket blocks public access and enables SSE-S3 encryption by default. A public or unencrypted bucket should be a decision you wrote down, not one you forgot.

Set force_destroy=True to empty a bucket — objects, versions and delete markers — before removing it. Without it, S3 refuses to delete a non-empty bucket and the teardown stalls.

SecurityGroup egress defaults to allow-all, matching the AWS default. Pass egress=[] to revoke it explicitly.

Subnet requires an availability_zone. Take one from AwsAvailabilityZones rather than writing a literal, because which zone letters exist differs between accounts.

CloudFrontDistribution accepts aliases and certificate_arn, and Route53Record accepts an alias target. Together these are what make an apex domain served from CloudFront expressible.

LambdaFunction requires a real package. Either point code_path= at a local zip or directory, or give s3_bucket= and s3_key= for a package already uploaded.

A directory is zipped with sorted entries and fixed timestamps, then fingerprinted into code_sha256 at config-evaluation time. Two checkouts of the same source therefore produce the same digest, and editing the source produces a planned update.

Data sources

A data source reads a fact about the account instead of hardcoding it, so one config can run against more than one account:

from atlantide.providers.aws import AwsAvailabilityZones, AwsCallerIdentity

me = AwsCallerIdentity("me")
zones = AwsAvailabilityZones("azs")

Each is read once at apply and then pinned in state. A plan therefore makes no provider call, and two runs of the same config still produce identical IR.

Data sources are never destroyed. destroy drops the state entry without calling the provider, because atlantide did not create the thing being read and must not remove it.

Writing a provider

A provider is an ordinary Python package that declares one entry point:

[project.entry-points."atlantide.providers"]
acme = "acme_atlantide:PLUGIN"
from atlantide.core.plugin import ProviderPlugin

PLUGIN = ProviderPlugin(
    name="acme",
    types={Gadget.type_name(): Gadget},
    factory=lambda settings: AcmeProvider(**settings),
    module="acme_atlantide",       # what config is allowed to import
)

Once it is installed, atlantide providers lists it and config can write from acme_atlantide import Gadget.

factory receives that provider's [provider.<name>] settings as a raw mapping, so a third-party provider can accept options atlantide knows nothing about.

A plugin that fails to load is reported and does not prevent unrelated commands from running. --no-plugins ignores installed plugins entirely, which is useful for reproducing a build or bisecting a plugin that has broken a run.

A plugin is trusted code

It runs in the same process, with the same access atlantide has. Atlas-lang sandboxes a malicious config; it does nothing about a malicious plugin. Installing one is the same decision as installing any other dependency.