Config Reference¶
API reference for everything you put in config.py.
ProjectConfig¶
Abstract base class. Subclass it, set default_env, implement project(), and expose an instance as CONFIG.
| Attribute / Method | Type | Description |
|---|---|---|
default_env |
str |
Environment used when --env is not passed. Default: "dev" |
project(env: str) → Project |
abstract | Return the Project for the given env name |
class AppConfig(ProjectConfig):
default_env = "dev"
def project(self, env: str) -> Project:
return Project(name="my-app", env=env, components=[...])
CONFIG = AppConfig()
Project¶
Dataclass holding the full description of a stack for one environment.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Stack name — used in lock names |
env |
str |
required | Active environment (passed in from project()) |
components |
list[BaseComponent] |
required | Ordered or unordered list of components |
tests |
list[HttpTest] |
[] |
Smoke tests run after sc build |
lock |
str |
"local" |
Lock strategy: "local" or "git-tag" |
TerraformComponent¶
Runs terraform init + apply/plan/destroy. Captures state via terraform show -json and outputs via terraform output -json.
Inherits all base parameters.
Default steps by action:
action |
Commands |
|---|---|
"create" (default) |
terraform init, terraform apply -auto-approve |
"plan" |
terraform init, terraform plan |
"validate" |
terraform init, terraform validate |
"destroy" |
terraform init, terraform destroy -auto-approve |
You rarely set action directly — the CLI commands (build, destroy, plan) set it for you.
Refs from Terraform¶
# Named Terraform output
url = infra.output("ip")
url_with_transform = infra.output("ip", transform=lambda ip: f"http://{ip}:8000")
# Value from terraform show -json via JMESPath
zone = infra.state(jmes="values.root_module.resources[0].values.zone")
# Value via Python function
name = infra.state(fn=lambda s: s["values"]["root_module"]["resources"][0]["values"]["name"])
AnsibleComponent¶
Runs ansible-playbook. No state capture.
| Parameter | Type | Default | Description |
|---|---|---|---|
playbook |
str |
"main.yml" |
Playbook filename (relative to path) |
Plus all base parameters.
AnsibleComponent(
name="configure",
path="./ansible",
playbook="site.yml",
vars={"host": infra.output("ip")},
depends_on=[infra],
)
ShellComponent¶
Escape hatch for anything that doesn't fit Terraform or Ansible: gcloud, kubectl, custom scripts, etc. No state capture.
| Parameter | Type | Default | Description |
|---|---|---|---|
commands |
list[str] |
required | Shell commands to run in order |
Plus all base parameters.
ShellComponent(
name="register",
path="./scripts",
commands=["./register.sh {{ vars.ip }}"],
vars={"ip": infra.output("ip")},
depends_on=[infra],
)
Base component parameters¶
All component types share these fields:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Unique identifier within a Project |
path |
str \| None |
./{name} |
Directory containing the component's files; omit to use ./{name}. An empty string resolves the same way but logs a warning |
action |
str |
"create" |
Lifecycle verb; usually set by the CLI |
vars |
dict[str, Any] |
{} |
Template variables; values may be Ref objects |
steps |
list[str] \| None |
None |
Override default commands entirely |
depends_on |
list[BaseComponent] |
[] |
Components that must run first |
tests |
list[Any] |
[] |
Component-scoped HttpTest entries (often with SelfOutputRef); see Concepts — Tests on components |
When the on-disk directory is not ./{name} (for example name="configure" but files live under ./ansible), pass path explicitly.
vars and templates¶
Values in vars are injected into .j2 Jinja2 templates before commands run. Refs are resolved first:
vars={
"project_id": "my-project", # literal
"api_key": sc.secret("api_key"), # SecretRef → SC_SECRET_API_KEY
"db_host": db.output("host"), # OutputRef → from prior component
}
In a Jinja2 template file:
HttpTest¶
HTTP smoke test. Retries on transport errors; resolves Ref URLs at test time.
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
str \| Ref |
required | URL to GET — string, OutputRef, StateRef, or component-scoped SelfOutputRef |
name |
str \| None |
None |
Label shown in result table; auto-generated if unset |
expect_status |
int |
200 |
Expected HTTP status code |
expect_body_contains |
str \| None |
None |
Substring that must appear in the response body |
attempts |
int |
6 |
Max attempts before failing |
retry_delay_s |
float |
5.0 |
Seconds between retries (transport errors only) |
trust_env |
bool |
False |
If False, ignores HTTP_PROXY / ALL_PROXY. Set True only if you intentionally need a proxy. |
timeout_s |
float |
30.0 |
Per-attempt connect + read timeout |
component |
BaseComponent \| None |
None |
Owning component for this test; also set automatically for tests listed in TerraformComponent(..., tests=[...]) or when using with TerraformComponent(...) as infra |
HttpTest(
name="api health",
url=infra.output("ip", transform=lambda ip: f"http://{ip}:8000"),
expect_status=200,
expect_body_contains="Hello",
attempts=10,
retry_delay_s=10.0,
)
Component-local URL without naming the component twice (see Concepts):
from small_config import HttpTest, SelfOutputRef
HttpTest(
name="api health",
url=SelfOutputRef("ip", transform=lambda ip: f"http://{ip}:8000"),
expect_status=200,
)
Refs¶
sc.secret(name) → SecretRef¶
The environment variable name is SC_SECRET_<NAME_UPPER>. The value is never written to disk.
component.output(key, *, transform=None) → OutputRef¶
Reads a named Terraform output (from terraform output -json) after the component is applied. The ref includes the component name (use everywhere: vars, project-level tests, cross-stack refs).
SelfOutputRef¶
Holds only the Terraform output key (and optional transform). Resolution uses the owning HttpTest’s component — for component-scoped tests only (tests=[...] on the component, with TerraformComponent(...) as infra, or HttpTest(component=...)).
Invalid in vars= and invalid for resolve() outside an HttpTest (raises TypeError). For project-level or explicit targets, use OutputRef or component.output(...).
component.state(*, jmes=None, fn=None, strict=True) → StateRef¶
Reads a value from terraform show -json via JMESPath or a Python function.
zone = infra.state(jmes="values.root_module.resources[0].values.zone")
name = infra.state(fn=lambda s: s["values"]["root_module"]["resources"][0]["values"]["name"])
| Parameter | Type | Default | Description |
|---|---|---|---|
jmes |
str \| None |
— | JMESPath expression |
fn |
callable \| None |
— | Python function receiving the raw state dict |
strict |
bool |
True |
Raise if the result is None |
Exactly one of jmes or fn must be provided.