Skip to content

Commit fac1ba6

Browse files
committed
hack: implement dump S3 publishing logic
1 parent 2d52501 commit fac1ba6

1 file changed

Lines changed: 119 additions & 52 deletions

File tree

src/gardenlinux/s3/s3_artifacts.py

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import yaml
2020

21-
from ..features import CName
2221
from .bucket import Bucket
2322

2423

@@ -120,26 +119,90 @@ def upload_from_directory(
120119

121120
release_file = artifacts_dir.joinpath(f"{base_name}.release")
122121

123-
cname_object = CName.new_from_release_file(release_file)
122+
if not release_file.exists():
123+
raise RuntimeError(f"Release file not found: {release_file}")
124124

125-
if cname_object.version_and_commit_id is None:
126-
raise RuntimeError(
127-
"Version information could not be determined from release file"
128-
)
125+
# RegEx for S3 supported characters
126+
re_object = re.compile("[^a-zA-Z0-9\\s+\\-=.\\_:/@]")
127+
128+
def _sanitize(value: Optional[str]) -> Optional[str]:
129+
if value is None:
130+
return None
131+
return re_object.sub("+", str(value))
132+
133+
def _read_kv_file(path: Path) -> ConfigParser:
134+
cfg = ConfigParser(allow_unnamed_section=True, interpolation=None)
135+
with path.open("r", encoding="utf-8") as fp:
136+
cfg.read_file(fp)
137+
return cfg
138+
139+
def _get_required(cfg: ConfigParser, key: str, what: Path) -> str:
140+
if not cfg.has_option(UNNAMED_SECTION, key):
141+
raise RuntimeError(f"Missing required field {key} in {what}")
142+
v = cfg.get(UNNAMED_SECTION, key).strip()
143+
if len(v) >= 2 and ((v[0] == '"' and v[-1] == '"') or (v[0] == "'" and v[-1] == "'")):
144+
v = v[1:-1]
145+
if v == "":
146+
raise RuntimeError(f"Empty required field {key} in {what}")
147+
return v
148+
149+
def _get_optional(cfg: ConfigParser, key: str) -> Optional[str]:
150+
if not cfg.has_option(UNNAMED_SECTION, key):
151+
return None
152+
v = cfg.get(UNNAMED_SECTION, key).strip()
153+
if len(v) >= 2 and ((v[0] == '"' and v[-1] == '"') or (v[0] == "'" and v[-1] == "'")):
154+
v = v[1:-1]
155+
if v == "":
156+
return None
157+
return v
158+
159+
release_config = _read_kv_file(release_file)
160+
161+
# Backwards compatibility: platform fallback
162+
cname = _get_optional(release_config, "GARDENLINUX_CNAME")
163+
platform = _get_optional(release_config, "GARDENLINUX_PLATFORM")
164+
if platform is None:
165+
if cname is None:
166+
cname = base_name
167+
platform = cname.split("-", 1)[0]
168+
169+
# Hard guard: frankenstein images must never be published
170+
if platform == "frankenstein":
171+
raise RuntimeError("frankenstein images must not be published")
172+
173+
version = _get_required(release_config, "GARDENLINUX_VERSION", release_file)
174+
commit_id_long = _get_required(
175+
release_config, "GARDENLINUX_COMMIT_ID_LONG", release_file
176+
)
177+
178+
platform_variant = _get_optional(
179+
release_config, "GARDENLINUX_PLATFORM_VARIANT"
180+
)
129181

130-
arch = cname_object.arch
131-
feature_set_list = cname_object.feature_set_list
132-
release_timestamp = stat(release_file).st_ctime
182+
features = _get_optional(release_config, "GARDENLINUX_FEATURES")
183+
feature_set_list: list[str] = []
184+
if features is not None:
185+
feature_set_list = [x.strip() for x in features.split(",")]
186+
feature_set_list = [x for x in feature_set_list if x]
187+
188+
gardenlinux_epoch = None
189+
try:
190+
gardenlinux_epoch = int(version.split(".", 1)[0])
191+
except Exception:
192+
gardenlinux_epoch = None
193+
194+
release_timestamp = stat(release_file).st_mtime
133195
requirements_file = artifacts_dir.joinpath(f"{base_name}.requirements")
134-
require_uefi = None
135-
secureboot = None
196+
require_uefi = False
197+
secureboot = False
198+
tpm2 = False
199+
arch = None
136200

137201
if requirements_file.exists():
138-
requirements_config = ConfigParser(allow_unnamed_section=True)
139-
requirements_config.read(requirements_file)
202+
requirements_config = _read_kv_file(requirements_file)
140203

141204
if requirements_config.has_option(UNNAMED_SECTION, "arch"):
142-
arch = requirements_config.get(UNNAMED_SECTION, "arch")
205+
arch = requirements_config.get(UNNAMED_SECTION, "arch").strip()
143206

144207
if requirements_config.has_option(UNNAMED_SECTION, "uefi"):
145208
require_uefi = requirements_config.getboolean(UNNAMED_SECTION, "uefi")
@@ -149,56 +212,55 @@ def upload_from_directory(
149212
UNNAMED_SECTION, "secureboot"
150213
)
151214

152-
if arch is None:
153-
raise RuntimeError(
154-
"Architecture could not be determined from release or requirements file"
155-
)
156-
157-
if require_uefi is None:
158-
require_uefi = "_usi" in feature_set_list
159-
160-
if secureboot is None:
161-
secureboot = "_trustedboot" in feature_set_list
162-
163-
# RegEx for S3 supported characters
164-
re_object = re.compile("[^a-zA-Z0-9\\s+\\-=.\\_:/@]")
165-
166-
arch = re_object.sub("+", arch)
167-
commit_id_or_hash = cname_object.commit_hash
215+
if requirements_config.has_option(UNNAMED_SECTION, "tpm2"):
216+
tpm2 = requirements_config.getboolean(UNNAMED_SECTION, "tpm2")
217+
218+
# Backwards compatibility: arch fallback
219+
if not arch:
220+
if cname is None:
221+
cname = base_name
222+
cname_parts = cname.split("-")
223+
if len(cname_parts) < 2:
224+
raise RuntimeError(
225+
"Architecture could not be determined from requirements file or cname"
226+
)
227+
arch = cname_parts[-2]
168228

169-
if commit_id_or_hash is None:
170-
commit_id_or_hash = cname_object.commit_id
229+
arch = _sanitize(arch)
171230

172231
metadata = {
173-
"platform": cname_object.feature_set_platform,
232+
"platform": platform,
233+
"platform_variant": platform_variant,
174234
"architecture": arch,
175-
"base_image": None,
176-
"build_committish": commit_id_or_hash,
235+
"version": version,
236+
"gardenlinux_epoch": gardenlinux_epoch,
237+
"build_committish": commit_id_long,
177238
"build_timestamp": datetime.fromtimestamp(release_timestamp),
178-
"logs": None,
179239
"modifiers": feature_set_list,
180240
"require_uefi": require_uefi,
181241
"secureboot": secureboot,
182-
"published_image_metadata": None,
242+
"tpm2": tpm2,
243+
"paths": [],
183244
"s3_bucket": self._bucket.name,
184245
"s3_key": f"meta/singles/{base_name}",
185-
"test_result": None,
186-
"version": cname_object.version,
187-
"paths": [],
188246
}
189247

190-
if cname_object.version_epoch is not None:
191-
metadata["gardenlinux_epoch"] = cname_object.version_epoch
248+
if metadata["platform_variant"] is None:
249+
del metadata["platform_variant"]
192250

193-
platform_variant = cname_object.platform_variant
194-
195-
if platform_variant is not None:
196-
metadata["platform_variant"] = platform_variant
251+
if metadata["gardenlinux_epoch"] is None:
252+
del metadata["gardenlinux_epoch"]
197253

198254
base_name_length = len(base_name)
199255

200256
for artifact in artifacts_dir.iterdir():
201-
if not artifact.match(f"{base_name}*"):
257+
if artifact.is_dir():
258+
continue
259+
260+
if artifact.name == f"{base_name}.release":
261+
continue
262+
263+
if artifact.name == f"{base_name}.requirements":
202264
continue
203265

204266
s3_key = f"objects/{base_name}/{artifact.name}"
@@ -207,20 +269,25 @@ def upload_from_directory(
207269
md5sum = file_digest(fp, "md5").hexdigest()
208270
sha256sum = file_digest(fp, "sha256").hexdigest()
209271

272+
if artifact.name.startswith(base_name):
273+
suffix = artifact.name[base_name_length:]
274+
else:
275+
suffix = artifact.suffix
276+
210277
artifact_metadata = {
211278
"name": artifact.name,
212279
"s3_bucket_name": self._bucket.name,
213280
"s3_key": s3_key,
214-
"suffix": re_object.sub("+", artifact.name[base_name_length:]),
281+
"suffix": _sanitize(suffix),
215282
"md5sum": md5sum,
216283
"sha256sum": sha256sum,
217284
}
218285

219286
s3_tags = {
220287
"architecture": arch,
221-
"platform": re_object.sub("+", cname_object.platform),
222-
"version": re_object.sub("+", cname_object.version), # type: ignore[arg-type]
223-
"committish": commit_id_or_hash,
288+
"platform": _sanitize(platform),
289+
"version": _sanitize(version),
290+
"committish": commit_id_long,
224291
"md5sum": md5sum,
225292
"sha256sum": sha256sum,
226293
}
@@ -246,7 +313,7 @@ def upload_from_directory(
246313
)
247314

248315
with TemporaryFile(mode="wb+") as fp:
249-
fp.write(yaml.dump(metadata).encode("utf-8"))
316+
fp.write(yaml.dump(metadata, sort_keys=False).encode("utf-8"))
250317
fp.seek(0)
251318

252319
self._bucket.upload_fileobj(

0 commit comments

Comments
 (0)