cs-image-system-packer-plugin 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cs_image_system/packer_plugin/__init__.py +3 -0
- cs_image_system/packer_plugin/main.py +36 -0
- cs_image_system/packer_plugin/packer_builder.py +253 -0
- cs_image_system/packer_plugin/packer_ebs_builder.py +743 -0
- cs_image_system/packer_plugin/packer_models.py +42 -0
- cs_image_system/packer_plugin/v2_local_mods.py +146 -0
- cs_image_system/packer_plugin/v2_provisioners.py +161 -0
- cs_image_system/py.typed +0 -0
- cs_image_system_packer_plugin-0.1.1.dist-info/METADATA +932 -0
- cs_image_system_packer_plugin-0.1.1.dist-info/RECORD +13 -0
- cs_image_system_packer_plugin-0.1.1.dist-info/WHEEL +4 -0
- cs_image_system_packer_plugin-0.1.1.dist-info/entry_points.txt +2 -0
- cs_image_system_packer_plugin-0.1.1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from cs_image_system.base.basic.abstract_plugin_metadata import AbstractPluginMetadata
|
|
7
|
+
from cs_image_system.base.protocols.plugin_metadata import PluginMetadataProtocol
|
|
8
|
+
from cs_image_system.packer_plugin.packer_ebs_builder import PackerEbsImageBuilder, PackerGceImageBuilder
|
|
9
|
+
from .packer_builder import PACKER, PackerVersionChecker
|
|
10
|
+
from .packer_models import PACKER_EBS, PACKER_GCE, PackerEbsImageBuilderModel, PackerGceImageBuilderModel
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class PackerEbsTypes(AbstractPluginMetadata, PluginMetadataProtocol):
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
super().__init__( "1", "3.13",
|
|
17
|
+
{PACKER_EBS: [
|
|
18
|
+
PackerEbsImageBuilderModel,
|
|
19
|
+
PackerEbsImageBuilder,
|
|
20
|
+
],
|
|
21
|
+
PACKER_GCE: [
|
|
22
|
+
PackerGceImageBuilderModel,
|
|
23
|
+
PackerGceImageBuilder,
|
|
24
|
+
],
|
|
25
|
+
PACKER: [
|
|
26
|
+
PackerVersionChecker,
|
|
27
|
+
]},
|
|
28
|
+
{PackerEbsImageBuilderModel: PackerEbsImageBuilder,
|
|
29
|
+
PackerGceImageBuilderModel: PackerGceImageBuilder}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def initialize():
|
|
34
|
+
log.debug("Initializing Packer Provider")
|
|
35
|
+
return PackerEbsTypes()
|
|
36
|
+
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, TypeVar
|
|
10
|
+
from warnings import deprecated
|
|
11
|
+
|
|
12
|
+
from cs_image_system.base import utils
|
|
13
|
+
from cs_image_system.base.basic.abstract_version_checker import AbstractVersionChecker
|
|
14
|
+
from cs_image_system.base.basic.asset import AssetSet
|
|
15
|
+
from cs_image_system.base.basic.builder_base_image import ImageBuilderBase
|
|
16
|
+
from cs_image_system.base.basic.builder_base_runtime import RuntimeBuilderBase
|
|
17
|
+
from cs_image_system.base.constants import IMAGES
|
|
18
|
+
from cs_image_system.base.lifecycle import ExecutionLifecyclePhase
|
|
19
|
+
from cs_image_system.base.models.executable import ExecutableModel
|
|
20
|
+
from cs_image_system.base.models.executable_adds import CFExecutables
|
|
21
|
+
from cs_image_system.base.models.base_image import BaseImage
|
|
22
|
+
from cs_image_system.base.models.image import Image
|
|
23
|
+
from cs_image_system.base.basic.builder_base_os import OsBuilderBase
|
|
24
|
+
|
|
25
|
+
from cs_image_system.hashicorp_utils.collector import PackerCollector, PackerVariableDecl
|
|
26
|
+
|
|
27
|
+
from .packer_models import PACKER, PackerImageBuilderModel
|
|
28
|
+
|
|
29
|
+
log = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
T = TypeVar("T", bound=PackerImageBuilderModel)
|
|
33
|
+
|
|
34
|
+
class PackerImageBuilder(ImageBuilderBase[T]):
|
|
35
|
+
"""Packer image builder implementation."""
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def csis_name(cls) -> str:
|
|
39
|
+
return PACKER
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def model(self) -> PackerImageBuilderModel:
|
|
43
|
+
return self._model # type: ignore # FIXME: Type check on aisle 4!
|
|
44
|
+
|
|
45
|
+
def get_packer_source_type(self) -> str:
|
|
46
|
+
"""The packer source type: the runtime plugin's say (EXPLORE GCP
|
|
47
|
+
increment 1); ``amazon-ebs`` only for a runtime with no opinion."""
|
|
48
|
+
from cs_image_system.base.global_context import GlobalTypeContext
|
|
49
|
+
rtb = GlobalTypeContext().runtime_builders.get(self.model.get_runtime_provider())
|
|
50
|
+
if rtb is not None:
|
|
51
|
+
try:
|
|
52
|
+
return rtb.packer_source_type()
|
|
53
|
+
except NotImplementedError:
|
|
54
|
+
log.debug(f"Runtime {rtb.get_name()} declares no packer source type; assuming amazon-ebs")
|
|
55
|
+
return "amazon-ebs"
|
|
56
|
+
|
|
57
|
+
def build_target_label(self, image: Image | BaseImage) -> str:
|
|
58
|
+
return f"{self.get_packer_source_type()}.{image.get_name()}"
|
|
59
|
+
|
|
60
|
+
def generate_items_before(
|
|
61
|
+
self, phase: ExecutionLifecyclePhase
|
|
62
|
+
) -> AssetSet: # list[tuple[Path, str]]:
|
|
63
|
+
"""Generate a list of items to create before a specific execution lifecycle
|
|
64
|
+
event."""
|
|
65
|
+
items: AssetSet = AssetSet()
|
|
66
|
+
if phase == ExecutionLifecyclePhase.IMAGE_GENERATION:
|
|
67
|
+
col = PackerCollector()
|
|
68
|
+
ws = self.get_discriminator()
|
|
69
|
+
for plugin in self.model.required_plugins:
|
|
70
|
+
col.require_plugin(ws, plugin.name,
|
|
71
|
+
source=getattr(plugin, "source", None),
|
|
72
|
+
version=getattr(plugin, "version", None))
|
|
73
|
+
col.declare_variable(ws, PackerVariableDecl(
|
|
74
|
+
name="release", type="bool", default=True,
|
|
75
|
+
description="Determines if the build is meant to be immutable"))
|
|
76
|
+
col.declare_variable(ws, PackerVariableDecl(
|
|
77
|
+
name="base_image_version", type="string",
|
|
78
|
+
env_var="BASE_IMAGE_VERSION",
|
|
79
|
+
description="The version of the base image to build on top of, used for tagging and versioning the image. Read from the BASE_IMAGE_VERSION environment variable at build time; empty when it is not set."))
|
|
80
|
+
# stage 63 (decided 2026-09-25): an image's `variables:` become packer
|
|
81
|
+
# variables of this builder's roots, each with its declared value as
|
|
82
|
+
# the default (they were read by nothing but a dead generator). The
|
|
83
|
+
# collector refuses one name declared twice with different values.
|
|
84
|
+
for image in self.get_images(os_builder=True) + self.get_images():
|
|
85
|
+
for key, value in sorted((getattr(image, "variables", None) or {}).items()):
|
|
86
|
+
col.declare_variable(ws, image_packer_variable(image.get_name(), key, value))
|
|
87
|
+
|
|
88
|
+
setup_path = self.get_path_for_phase(phase, suffix="-setup.pkr.hcl")
|
|
89
|
+
items.add(setup_path,
|
|
90
|
+
f"# Packer Image Builders generated by {self.__class__.__name__} "
|
|
91
|
+
f"({self.name})")
|
|
92
|
+
items.add(setup_path, "# This file contains the setup configuration for the Packer image builder ")
|
|
93
|
+
items.add(setup_path, "# Packer configuration")
|
|
94
|
+
items.add(setup_path, f"# Generated by {self.model.__class__.__name__}")
|
|
95
|
+
items.add_list(setup_path, col.generate_packer_block(ws))
|
|
96
|
+
|
|
97
|
+
vpath = self.get_path_for_phase(phase, suffix="-vars.pkr.hcl")
|
|
98
|
+
items.add(vpath, "# Packer variables configuration")
|
|
99
|
+
items.add(vpath, f"# Generated by {ws} ({self.model.__class__.__name__})")
|
|
100
|
+
items.add_list(vpath, col.generate_variable_blocks(ws))
|
|
101
|
+
return items
|
|
102
|
+
|
|
103
|
+
def generate_items_during(
|
|
104
|
+
self, phase: ExecutionLifecyclePhase
|
|
105
|
+
) -> list[tuple[Path, str]]:
|
|
106
|
+
"""Generate a list of items to create during a specific execution lifecycle
|
|
107
|
+
event."""
|
|
108
|
+
items: list[tuple[Path, str]] = []
|
|
109
|
+
if phase == ExecutionLifecyclePhase.IMAGE_GENERATION:
|
|
110
|
+
for image in self.get_images(os_builder=True):
|
|
111
|
+
rpath = self.get_path_for_phase(
|
|
112
|
+
phase, f"image-{image.name}", suffix=".pkr.hcl"
|
|
113
|
+
)
|
|
114
|
+
items.append(
|
|
115
|
+
(
|
|
116
|
+
rpath,
|
|
117
|
+
f"# Packer configuration for BASE image {image.name} generated by "
|
|
118
|
+
f"{self.__class__.__name__} ({self.name})",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
for image in self.get_images():
|
|
122
|
+
rpath = self.get_path_for_phase(
|
|
123
|
+
phase, f"image-{image.name}", suffix=".pkr.hcl"
|
|
124
|
+
)
|
|
125
|
+
items.append(
|
|
126
|
+
(
|
|
127
|
+
rpath,
|
|
128
|
+
f"# Packer configuration for image {image.name} generated by "
|
|
129
|
+
f"{self.__class__.__name__} ({self.name})",
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
return items
|
|
133
|
+
|
|
134
|
+
def get_commands_to_run_after(
|
|
135
|
+
self, phase: ExecutionLifecyclePhase
|
|
136
|
+
) -> CFExecutables:
|
|
137
|
+
"""Get a list of commands to run after a specific execution lifecycle event."""
|
|
138
|
+
commands_now: list[ExecutableModel] = []
|
|
139
|
+
commands_final: list[ExecutableModel] = []
|
|
140
|
+
if phase == ExecutionLifecyclePhase.IMAGE_GENERATION:
|
|
141
|
+
wd = self.get_path_for_phase(phase, suffix=".tf").parent
|
|
142
|
+
assert wd is not None, (
|
|
143
|
+
f"Working directory for commands to run after {phase.value} cannot be "
|
|
144
|
+
f"None for builder {self.__class__.__name__}"
|
|
145
|
+
)
|
|
146
|
+
for cmd in [["fmt", "."], ["init", "."], ["validate","."]]:
|
|
147
|
+
e = self.get_executable_copy()
|
|
148
|
+
if not e:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"Executable for {self.__class__.__name__} is not available "
|
|
151
|
+
f"but is required to run commands after {phase.value}"
|
|
152
|
+
)
|
|
153
|
+
e.args = cmd
|
|
154
|
+
e.working_directory = wd
|
|
155
|
+
commands_now.append(e)
|
|
156
|
+
e = self.get_executable_copy()
|
|
157
|
+
e.args = [
|
|
158
|
+
"build",
|
|
159
|
+
".",
|
|
160
|
+
] # This is the command that actually does the image building, so we put
|
|
161
|
+
# it in the "after" list to run after all the setup and validation commands.
|
|
162
|
+
e.working_directory = wd
|
|
163
|
+
commands_final.append(e)
|
|
164
|
+
return CFExecutables(commands_now, commands_final)
|
|
165
|
+
|
|
166
|
+
@deprecated("This method is deprecated and will be removed in a future version. Performed in a setup phase")
|
|
167
|
+
def generate_osbuilder_images(
|
|
168
|
+
self,
|
|
169
|
+
phase: ExecutionLifecyclePhase,
|
|
170
|
+
rbb: RuntimeBuilderBase,
|
|
171
|
+
images: list[Image]
|
|
172
|
+
) -> tuple[list[tuple[Path, str]], list[ExecutableModel]]:
|
|
173
|
+
from cs_image_system.base.global_context import GlobalTypeContext
|
|
174
|
+
|
|
175
|
+
genfiles: list[tuple[Path, str]] = []
|
|
176
|
+
gencommands: list[ExecutableModel] = []
|
|
177
|
+
ctx = GlobalTypeContext()
|
|
178
|
+
log.debug(f"Generating images for runtime provider: {rbb.get_display_name()}")
|
|
179
|
+
gen_path: Path = ctx.generation_path
|
|
180
|
+
gen_path.parent.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
qv = self.generate_items_during(phase)
|
|
182
|
+
genfiles.extend(qv)
|
|
183
|
+
qc= self.get_commands_to_run_during(phase)
|
|
184
|
+
gencommands.extend(qc.build_executables)
|
|
185
|
+
ctx.extend_finalization_phase(phase, qc.finalize_executables)
|
|
186
|
+
return (genfiles, gencommands)
|
|
187
|
+
|
|
188
|
+
@deprecated("This method is deprecated and will be removed in a future version. Performed in a setup phase")
|
|
189
|
+
def get_sources(self, images: list[Image]) -> list[dict[str, Any]]:
|
|
190
|
+
from cs_image_system.base.global_context import GlobalTypeContext
|
|
191
|
+
|
|
192
|
+
ctx = GlobalTypeContext()
|
|
193
|
+
configured_os_builders: dict[str, OsBuilderBase] = ctx.os_builders
|
|
194
|
+
sources: list[dict[str, Any]] = []
|
|
195
|
+
for image in images:
|
|
196
|
+
# osb = configured_os_builders.get(image.os) if image.os else None
|
|
197
|
+
# assert osb is not None, (
|
|
198
|
+
# f"No OS builder configured for image {image.os} in {image.name}."
|
|
199
|
+
# )
|
|
200
|
+
|
|
201
|
+
s = {}
|
|
202
|
+
s.update(image.__dict__)
|
|
203
|
+
s["packer_source_type"] = "amazon-ebs"
|
|
204
|
+
sources.append(s)
|
|
205
|
+
return sources
|
|
206
|
+
|
|
207
|
+
def ib_generated_path_name(self) -> Path:
|
|
208
|
+
assert self.model.runtime is not None, (
|
|
209
|
+
"Runtime provider must be specified in the builder configuration to "
|
|
210
|
+
"determine generated path name."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return Path(utils.safe_name(self.model.runtime)) / IMAGES
|
|
214
|
+
|
|
215
|
+
class PackerVersionChecker(AbstractVersionChecker):
|
|
216
|
+
"""Version checker for Packer."""
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def csis_name(cls) -> str:
|
|
220
|
+
return PACKER
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_version_params(self) -> list[str]:
|
|
224
|
+
return ["version", "-machine-readable"]
|
|
225
|
+
|
|
226
|
+
def get_extracted_string(self, res: subprocess.CompletedProcess[str]) -> str | None:
|
|
227
|
+
return res.stdout.strip().splitlines()[0].split(",")[3].strip('"')
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
_VARIABLE_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_-]*$")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def image_packer_variable(image: str, key: str, value: Any) -> PackerVariableDecl:
|
|
234
|
+
"""One `variables:` entry of an image as a packer variable (stage 63):
|
|
235
|
+
a string, number or bool, whose type follows the value; a list, a mapping
|
|
236
|
+
or a name packer cannot take is refused naming the image and the key."""
|
|
237
|
+
name = str(key)
|
|
238
|
+
if not _VARIABLE_NAME.match(name):
|
|
239
|
+
raise ValueError(f"image '{image}': `variables.{name}` is not a packer variable name "
|
|
240
|
+
"(a letter or `_` first, then letters, digits, `_` or `-`)")
|
|
241
|
+
if isinstance(value, bool):
|
|
242
|
+
kind = "bool"
|
|
243
|
+
elif isinstance(value, (int, float)):
|
|
244
|
+
kind = "number"
|
|
245
|
+
elif isinstance(value, str):
|
|
246
|
+
kind = "string"
|
|
247
|
+
else:
|
|
248
|
+
raise ValueError(f"image '{image}': `variables.{name}` must be a string, number or bool, "
|
|
249
|
+
f"not {type(value).__name__}")
|
|
250
|
+
# the description names no image, so two images that give one name the SAME
|
|
251
|
+
# value declare one variable; different values are refused by the collector
|
|
252
|
+
return PackerVariableDecl(name=name, type=kind, default=value,
|
|
253
|
+
description="declared by an image's `variables:`")
|