Profiles and stages¶
A profile is a named connection: a provider plus its settings, written once in a config file and selected by name. One name, in code or at a prompt, instead of a block of coordinates repeated per script and per shell.
# storix.toml
[profiles.media]
provider = "azure"
account_name = "mediaaccount"
credential = "env:MEDIA_AZURE_CREDENTIAL"
container = "media"
The library and the CLI read the same file through the same loader, so a profile a pipeline uses is a profile you can stand inside and inspect.
Stages¶
A stage (an environment) overlays the settings that differ between deployments of the same connection. Name the provider and everything stable once, then list only what changes.
In practice what changes across deployments is not just the container: dev, staging and production are usually separate accounts with separate credentials. A stage carries both, so the layout of your infrastructure is what the file describes:
[profiles.ingest]
provider = "azure"
container = "raw"
default_environment = "dev"
[profiles.ingest.environments.dev]
account_name = "acmedevstorage"
credential = "env:ACME_DEV_CREDENTIAL"
[profiles.ingest.environments.stg]
account_name = "acmestgstorage"
credential = "env:ACME_STG_CREDENTIAL"
[profiles.ingest.environments.prod]
account_name = "acmeprdstorage"
credential = "env:ACME_PRD_CREDENTIAL"
One name, one flag, three accounts:
A separate credential per stage is the point. Each stage reads its own
variable, so the process running against dev never has the production
secret in its environment at all - the file describes which one to reach
for, and an unset variable fails loudly at load rather than silently
falling back to another stage's.
A stage can change settings but never the provider: stages of one profile share a backend by definition. A key that is not a setting of that provider is an error naming the provider and its fields, so a block meant for another backend cannot sit there unread.
default_environment pins the stage a profile usually runs on, so --env
is only needed to step off it - and pinning dev means the dangerous one is
the one you have to type.
A stage can also be as small as one key when that is genuinely all that differs:
[profiles.media]
provider = "azure"
account_name = "mediaaccount"
credential = "env:MEDIA_AZURE_CREDENTIAL"
[profiles.media.environments.dev]
container = "media-dev"
[profiles.media.environments.prod]
container = "media-prod"
Why this matters for a pipeline¶
The alternative to a profile is a function that assembles coordinates from scattered environment variables, once per entry point, slightly differently each time. That is where dev credentials reach prod buckets.
A profile makes the connection a name the code passes and the operator reads:
# one job, three deployments, one line that changes
fs = get_storage(profile="ingest", environment=os.environ["STAGE"])
That line is the whole difference between a dev run and a production run:
the account, the credential, and the container come from the stage. A job
scheduled per environment passes its own STAGE and nothing else about it
varies, so there is no second place where a deployment can be half-switched.
Everything else about the session - layers, transfer tuning, the typed errors - is unchanged. The profile decides only which store you are talking to.
Secrets stay out of the file
credential = "env:MEDIA_AZURE_CREDENTIAL" reads that variable at load
time, from the process environment or a .env beside the project. A
literal secret in a project file is refused outright, because project
files get committed. See Configure from
settings.
Selecting one¶
Strongest first:
| what | applies to | scope |
|---|---|---|
get_storage(profile=...) |
library | that call |
--profile |
sx |
that command |
STORIX_PROFILE |
sx |
that shell |
profile = "..." in the project file |
sx |
everyone on that project |
profile = "..." in your user file |
sx |
you, everywhere |
Typing --profile every time is not the intended workflow. Pin one.
A profile reaches the library only when a call asks for one. Neither
STORIX_PROFILE nor a pinned profile key steers get_storage(). Both are
a person's convenience at a prompt, and a library that honored them would
let a personal file point an application's session at another account - and
would break the shape every migration takes:
src = get_storage("azure", container="raw")
dst = get_storage("s3", bucket="archive") # neither is redirected by a pin
sx honors all of it, because there the convenience is the point.
A profile names its own provider, so sx --profile media -p s3 is an error:
you said two things. A profile that is merely pinned is a default, not a
lock, so sx -p s3 simply steps off it. --env without a profile, an
unknown profile, and an unknown stage each exit naming what is available.
Seeing which one is in force¶
$ sx --profile media --env prod whereami
backend: AzureBackend
profile: media (stage: prod)
root uri: abfss://[email protected]/
cwd: /
home: /
layers: cache ls/stat/du/cat via InMemoryCacheStore
sx config profiles lists every profile as a table, stages as rows, with
* on what the invocation would use. sx config show --effective goes
further and prints each field with the layer that supplied it, so a value
you did not write is still traceable:
$ sx --profile media --env prod config show --effective
effective
provider azure (profile 'media')
azure.account_name 'mediaaccount' <- profile
azure.container 'media-prod' <- environment
azure.credential '***' <- profile
azure.read_prefetch_size 8388608 (8.0MiB) <- default
Neither command opens a connection or resolves a credential, so both still answer when the connection is the thing that is broken.
Sharing settings between profiles¶
A profile layers on top of that provider's own table, so settings common to every profile on a backend are written once:
[s3]
region = "auto"
endpoint = "https://ACCOUNT.r2.cloudflarestorage.com"
access_key_id = "env:R2_ACCESS_KEY_ID"
secret_access_key = "env:R2_SECRET_ACCESS_KEY"
[profiles.media]
provider = "s3"
bucket = "media"
[profiles.archive]
provider = "s3"
bucket = "archive"
That is the right shape for different buckets: they are different
connections, so they are different profiles. Stages are for one connection
across deployments - dev, stg, prod - where the name carries meaning
about which deployment you are touching. Modelling unrelated buckets as
stages of one profile works mechanically and then reads as a lie:
default_environment = "storix" says a bucket is a deployment stage.
What a profile does not do¶
- It does not carry layers. Compose those per session
(
Storix(be, layers=[...]),--cache,--sandbox). - It does not inherit from another profile. Deliberate, until a real case for it turns up; repeat the two lines instead.
- It does not switch providers per stage.
The full schema, with every key, is in
storix.toml.example.