Skip to content

TerraformStack

laktory.models.stacks.TerraformStack ¤

Bases: BaseModel

A Terraform stack is terraform-specific flavor of the laktory.models.Stack. It re-structure the attributes to be aligned with a terraform json file.

It is generally not instantiated directly, but rather created using laktory.models.Stack.to_terraform().

References
PARAMETER DESCRIPTION
providers

TYPE: dict[str | VariableType, Any | VariableType] | VariableType DEFAULT: {}

resources

TYPE: dict[str | VariableType, Any | VariableType] | VariableType DEFAULT: {}

terraform

TYPE: TerraformConfig | VariableType DEFAULT: TerraformConfig(variables={}, required_providers=None, backend=None)

METHOD DESCRIPTION
apply

Runs terraform apply

destroy

Runs terraform destroy

init

Runs terraform init

model_dump

Serialize model to match the structure of a Terraform json file.

plan

Runs terraform plan

write

Write Terraform json configuration file

apply(flags=None) ¤

Runs terraform apply

PARAMETER DESCRIPTION
flags

List of flags / options for terraform apply

TYPE: list[str] DEFAULT: None

Source code in laktory/models/stacks/terraformstack.py
236
237
238
239
240
241
242
243
244
245
def apply(self, flags: list[str] = None):
    """
    Runs `terraform apply`

    Parameters
    ----------
    flags:
        List of flags / options for terraform apply
    """
    self._call("apply", flags=flags)

destroy(flags=None) ¤

Runs terraform destroy

PARAMETER DESCRIPTION
flags

List of flags / options for terraform destroy

TYPE: list[str] DEFAULT: None

Source code in laktory/models/stacks/terraformstack.py
247
248
249
250
251
252
253
254
255
256
def destroy(self, flags: list[str] = None):
    """
    Runs `terraform destroy`

    Parameters
    ----------
    flags:
        List of flags / options for terraform destroy
    """
    self._call("destroy", flags=flags)

init(flags=None) ¤

Runs terraform init

PARAMETER DESCRIPTION
flags

List of flags / options for terraform plan

TYPE: list[str] DEFAULT: None

Source code in laktory/models/stacks/terraformstack.py
214
215
216
217
218
219
220
221
222
223
def init(self, flags: list[str] = None) -> None:
    """
    Runs `terraform init`

    Parameters
    ----------
    flags:
        List of flags / options for terraform plan
    """
    self._call("init", flags=flags)

model_dump(*args, **kwargs) ¤

Serialize model to match the structure of a Terraform json file.

Source code in laktory/models/stacks/terraformstack.py
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def model_dump(self, *args, **kwargs) -> dict[str, Any]:
    """Serialize model to match the structure of a Terraform json file."""
    kwargs["exclude_none"] = kwargs.get("exclude_none", True)
    d = super().model_dump(*args, **kwargs)

    # Terraform uses singular top-level block names
    d["provider"] = d.pop("providers", {})
    d.pop("resources", None)

    # Special treatment of resources
    d["resource"] = defaultdict(lambda: {})
    d["data"] = defaultdict(lambda: {})
    for r in self.resources.values():
        _d = r.terraform_properties
        if r.lookup_existing:
            d["data"][r.terraform_resource_lookup_type][r.resource_name] = (
                r.lookup_existing.model_dump()
            )
            for k in r.resource_options.terraform_options:
                if k in _d:
                    d["data"][r.terraform_resource_lookup_type][r.resource_name][
                        k
                    ] = _d[k]
        else:
            d["resource"][r.terraform_resource_type][r.resource_name] = _d
    d["data"] = dict(d["data"])
    if len(d["data"]) == 0:
        del d["data"]
    d["resource"] = dict(d["resource"])

    # Special treatment of moved blocks
    i = -1
    for r in self.resources.values():
        _from = r.resource_options.moved_from
        if _from:
            i += 1
            d[f"moved_{i:05d}"] = {
                "from": f"{r.terraform_resource_type}.{_from}",
                "to": f"{r.terraform_resource_type}.{r.resource_name}",
            }

    # Special treatment of import
    i = -1
    for r in self.resources.values():
        import_ = r.resource_options.import_
        if import_:
            i += 1
            d[f"import_{i:05d}"] = {
                "id": import_,
                "to": f"{r.terraform_resource_type}.{r.resource_name}",
            }

    # Terraform JSON requires the keyword "resources." to be removed and the
    # resource_name to be replaced with resource_type.resource_name.
    _vars = {}
    for r in list(self.resources.values()) + list(self.providers.values()):
        k0 = r.resource_name

        k1 = f"{r.terraform_resource_type}.{r.resource_name}"
        # special treatment for data sources
        if r.lookup_existing:
            k1 = f"data.{r.terraform_resource_lookup_type}.{r.resource_name}"

        if isinstance(r, BaseProvider):
            pattern = r"\$\{resources." + k0 + "}"
            _vars[pattern] = k0

        else:
            # ${resources.resource_name} -> resource_type.resource_name
            pattern = r"\$\{resources\." + k0 + r"\}"
            _vars[pattern] = k1

            # ${resources.resource_name.property} -> ${resource_type.resource_name.property}
            pattern = r"\$\{resources\." + k0 + r"\.(.*?)\}"
            _vars[pattern] = rf"${{{k1}.\1}}"

    # Because all variables are mapped to a string, it is more efficient
    # (>10x) to convert the dict to string before substitution.
    d = json.loads(_resolve_values(json.dumps(d), vars=_vars, objs={}))

    return d

plan(flags=None) ¤

Runs terraform plan

PARAMETER DESCRIPTION
flags

List of flags / options for terraform plan

TYPE: list[str] DEFAULT: None

Source code in laktory/models/stacks/terraformstack.py
225
226
227
228
229
230
231
232
233
234
def plan(self, flags: list[str] = None) -> None:
    """
    Runs `terraform plan`

    Parameters
    ----------
    flags:
        List of flags / options for terraform plan
    """
    self._call("plan", flags=flags)

write() ¤

Write Terraform json configuration file

RETURNS DESCRIPTION
str

Filepath of the configuration file

Source code in laktory/models/stacks/terraformstack.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def write(self) -> str:
    """
    Write Terraform json configuration file

    Returns
    -------
    :
        Filepath of the configuration file
    """
    filepath = os.path.join(CACHE_ROOT, "stack.tf.json")
    logger.info(f"Writing terraform config at '{filepath}'")

    if not os.path.exists(CACHE_ROOT):
        os.makedirs(CACHE_ROOT)

    text = json.dumps(self.model_dump(), indent=4)

    # Terraform stack file is not a strict format. Some keys might be
    # repeated and require special treatment.

    # Special treatment of providers with aliases
    for _, p in self.providers.items():
        if p.alias is not None:
            text = text.replace(
                f'"{p.resource_name}":',
                f'"{p.resource_name_without_alias}":',
            )

    # Special treatment of moved
    text = re.sub(r'"moved_\d+": {', '"moved": {', text)

    # Special treatment of import
    text = re.sub(r'"import_\d+": {', '"import": {', text)

    # Output
    with open(filepath, "w") as fp:
        fp.write(text)

    return filepath