Providers¶
Three providers ship with atlantide.
local — File, SourceFile and Null. Needs no credentials, so it runs
anywhere, including CI.
random — Uuid, 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.