Skip to content

Commit e4429fa

Browse files
PeaceRebeljlebon
andcommitted
Add cosa import
This command takes as argument a `containers-transport(5)`-style pullspec and creates a new cosa build dir from it. It essentially bridges the gap between coreos/fedora-coreos-config#3348 and the rest of the cosa pipeline. Co-authored-by: Jonathan Lebon <jonathan@jlebon.com>
1 parent 7c01f86 commit e4429fa

7 files changed

Lines changed: 245 additions & 18 deletions

File tree

cmd/coreos-assembler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
// commands we'd expect to use in the local dev path
1515
var buildCommands = []string{"init", "fetch", "build", "osbuild", "run", "prune", "clean", "list"}
16-
var advancedBuildCommands = []string{"buildfetch", "buildupload", "oc-adm-release", "push-container"}
16+
var advancedBuildCommands = []string{"import", "buildfetch", "buildupload", "oc-adm-release", "push-container"}
1717
var buildextendCommands = []string{"aliyun", "applehv", "aws", "azure", "digitalocean", "exoscale", "extensions-container", "gcp", "hyperv", "ibmcloud", "kubevirt", "live", "metal", "metal4k", "nutanix", "openstack", "oraclecloud", "qemu", "secex", "virtualbox", "vmware", "vultr"}
1818

1919
var utilityCommands = []string{"aws-replicate", "coreos-prune", "compress", "copy-container", "diff", "koji-upload", "kola", "push-container-manifest", "remote-build-container", "remote-session", "sign", "tag", "update-variant"}

pkg/builds/cosa_v1.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package builds
22

33
// generated by 'make schema'
4-
// source hash: 4bb5641ee8c32b122c412cbaefe3d068685ea6cbffcf3162e600a01268c92146
4+
// source hash: 445150ada0fe019c7bb33c793185b312111ed7538a59e1a0b424c10c6c2dbc0d
55

66
type AdvisoryDiff []AdvisoryDiffItems
77

@@ -56,6 +56,7 @@ type Build struct {
5656
CosaDelayedMetaMerge bool `json:"coreos-assembler.delayed-meta-merge,omitempty"`
5757
CosaImageChecksum string `json:"coreos-assembler.image-config-checksum,omitempty"`
5858
CosaImageVersion int `json:"coreos-assembler.image-genver,omitempty"`
59+
CosaImportedOciImage bool `json:"coreos-assembler.oci-imported,omitempty"`
5960
Extensions *Extensions `json:"extensions,omitempty"`
6061
ExtensionsContainer *PrimaryImage `json:"extensions-container,omitempty"`
6162
FedoraCoreOsParentCommit string `json:"fedora-coreos.parent-commit,omitempty"`
@@ -64,15 +65,15 @@ type Build struct {
6465
GitDirty string `json:"coreos-assembler.config-dirty,omitempty"`
6566
IbmCloud []Cloudartifact `json:"ibmcloud,omitempty"`
6667
ImageInputChecksum string `json:"coreos-assembler.image-input-checksum,omitempty"`
67-
InputHashOfTheRpmOstree string `json:"rpm-ostree-inputhash"`
68+
InputHashOfTheRpmOstree string `json:"rpm-ostree-inputhash,omitempty"`
6869
Koji *Koji `json:"koji,omitempty"`
6970
KubevirtContainer *PrimaryImage `json:"kubevirt,omitempty"`
7071
MetaStamp float64 `json:"coreos-assembler.meta-stamp,omitempty"`
7172
Name string `json:"name"`
7273
Oscontainer *PrimaryImage `json:"oscontainer,omitempty"`
7374
OstreeCommit string `json:"ostree-commit"`
7475
OstreeContentBytesWritten int `json:"ostree-content-bytes-written,omitempty"`
75-
OstreeContentChecksum string `json:"ostree-content-checksum"`
76+
OstreeContentChecksum string `json:"ostree-content-checksum,omitempty"`
7677
OstreeNCacheHits int `json:"ostree-n-cache-hits,omitempty"`
7778
OstreeNContentTotal int `json:"ostree-n-content-total,omitempty"`
7879
OstreeNContentWritten int `json:"ostree-n-content-written,omitempty"`

pkg/builds/schema_doc.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Generated by ./generate-schema.sh
2-
// Source hash: 4bb5641ee8c32b122c412cbaefe3d068685ea6cbffcf3162e600a01268c92146
2+
// Source hash: 445150ada0fe019c7bb33c793185b312111ed7538a59e1a0b424c10c6c2dbc0d
33
// DO NOT EDIT
44

55
package builds
@@ -255,10 +255,8 @@ var generatedSchemaJSON = `{
255255
"buildid",
256256
"name",
257257
"ostree-commit",
258-
"ostree-content-checksum",
259258
"ostree-timestamp",
260-
"ostree-version",
261-
"rpm-ostree-inputhash"
259+
"ostree-version"
262260
],
263261
"optional": [
264262
"aliyun",
@@ -273,13 +271,15 @@ var generatedSchemaJSON = `{
273271
"images",
274272
"koji",
275273
"oscontainer",
274+
"ostree-content-checksum",
276275
"extensions",
277276
"extensions-container",
278277
"parent-pkgdiff",
279278
"pkgdiff",
280279
"parent-advisories-diff",
281280
"advisories-diff",
282281
"release-payload",
282+
"rpm-ostree-inputhash",
283283
"summary",
284284
"s3",
285285
"coreos-assembler.basearch",
@@ -297,6 +297,7 @@ var generatedSchemaJSON = `{
297297
"coreos-assembler.meta-stamp",
298298
"coreos-assembler.overrides-active",
299299
"coreos-assembler.yumrepos-git",
300+
"coreos-assembeler.oci-imported",
300301
"fedora-coreos.parent-commit",
301302
"fedora-coreos.parent-version",
302303
"ref"
@@ -378,6 +379,12 @@ var generatedSchemaJSON = `{
378379
"default": "",
379380
"minLength": 1
380381
},
382+
"coreos-assembler.oci-imported": {
383+
"$id": "#/properties/coreos-assembler.oci-imported",
384+
"type": "boolean",
385+
"title": "COSA imported OCI image",
386+
"default": "False"
387+
},
381388
"coreos-assembler.code-source": {
382389
"$id": "#/properties/coreos-assembler.code-source",
383390
"type": "string",

src/cmd-coreos-prune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Build = collections.namedtuple("Build", ["id", "images", "arch", "meta_json"])
5555
# set metadata caching to 5m
5656
CACHE_MAX_AGE_METADATA = 60 * 5
5757
# These lists are up to date as of schema hash
58-
# 4bb5641ee8c32b122c412cbaefe3d068685ea6cbffcf3162e600a01268c92146. If changing
58+
# 445150ada0fe019c7bb33c793185b312111ed7538a59e1a0b424c10c6c2dbc0d. If changing
5959
# this hash, ensure that the list of SUPPORTED and UNSUPPORTED artifacts below
6060
# is up to date.
6161
SUPPORTED = ["amis", "aws-winli", "gcp"]

src/cmd-import

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/python3
2+
3+
import argparse
4+
import datetime
5+
import json
6+
import os
7+
import subprocess
8+
import tempfile
9+
import shutil
10+
from stat import (
11+
S_IREAD,
12+
S_IRGRP,
13+
S_IROTH)
14+
from cosalib.builds import Builds
15+
from cosalib.cmdlib import (
16+
rfc3339_time,
17+
get_basearch,
18+
sha256sum_file)
19+
20+
VALID_NAMES = ["fedora-coreos", "rhcos", "scos"]
21+
22+
23+
def main():
24+
args = parse_args()
25+
with tempfile.TemporaryDirectory(prefix='cosa-import-', dir='tmp') as tmpd:
26+
tmp_ociarchive = os.path.join(tmpd, "out.ociarchive")
27+
28+
metadata = prepare_build(args, tmp_ociarchive)
29+
30+
name = metadata['Labels']['com.coreos.osname']
31+
buildid = metadata['Labels']['org.opencontainers.image.version']
32+
33+
builds = Builds()
34+
if builds.has(buildid):
35+
raise Exception(f"Build ID {buildid} already exists!")
36+
37+
arch = get_basearch()
38+
39+
manifest = generate_manifest_json(tmpd, tmp_ociarchive, name, buildid, arch)
40+
meta_json = generate_meta_json(tmp_ociarchive, metadata, manifest['metadata'], name)
41+
42+
# import into the tmp/repo not only so it's cached, but also to get the ostree-commit
43+
meta_json['ostree-commit'] = ostree_import(tmpd, tmp_ociarchive, buildid)
44+
45+
finalize_build(builds, tmp_ociarchive, meta_json, manifest, buildid, arch)
46+
47+
48+
def parse_args():
49+
parser = argparse.ArgumentParser(prog='cosa import')
50+
parser.add_argument("srcimg", metavar='IMAGE',
51+
help="image to import (containers-transports(5) format)")
52+
return parser.parse_args()
53+
54+
55+
def prepare_build(args, target_ociarchive):
56+
import_oci_archive(args, target_ociarchive)
57+
return inspect_oci_archive(target_ociarchive)
58+
59+
60+
def finalize_build(builds, source_ociarchive, meta_json, manifest, buildid, arch):
61+
os.makedirs(f'builds/{buildid}/{arch}/', exist_ok=True)
62+
63+
archive_name = meta_json['images']['ostree']['path']
64+
archive_path = f'builds/{buildid}/{arch}/{archive_name}'
65+
# Move ociarchive to build dir
66+
shutil.move(source_ociarchive, archive_path)
67+
68+
# move manifest file
69+
manifest_fname = manifest['metadata']['path']
70+
shutil.move(manifest['src_path'], f'builds/{buildid}/{arch}/{manifest_fname}')
71+
72+
with open(f'builds/{buildid}/{arch}/meta.json', 'w') as meta_file:
73+
json.dump(meta_json, meta_file, indent=4)
74+
75+
# Symlink build to latest
76+
if os.path.exists('builds/latest'):
77+
os.remove('builds/latest')
78+
os.symlink(f'{buildid}', 'builds/latest', target_is_directory=True)
79+
80+
update_builds_json(builds, buildid, arch)
81+
82+
print(f'Imported OCI image as build {buildid}')
83+
84+
85+
def update_builds_json(builds, buildid, arch):
86+
builds.insert_build(buildid, arch)
87+
builds.bump_timestamp()
88+
builds.flush()
89+
90+
91+
def import_oci_archive(args, target):
92+
subprocess.check_call(['skopeo', 'copy', args.srcimg,
93+
f"oci-archive:{target}"])
94+
95+
96+
def inspect_oci_archive(image):
97+
out = subprocess.check_output(['skopeo', 'inspect',
98+
f'oci-archive:{image}'])
99+
return json.loads(out)
100+
101+
102+
def generate_manifest_json(tmpd, ociarchive, name, buildid, arch):
103+
manifest = subprocess.check_output(["skopeo", "inspect", "--raw", f"oci-archive:{ociarchive}"])
104+
105+
ostree_oci_manifest_path = f"{name}-{buildid}-ostree.{arch}-manifest.json"
106+
manifest_json_dest = f'{tmpd}/manifest.json'
107+
108+
manifest_json_sha256 = None
109+
manifest_json_size = None
110+
with open(manifest_json_dest, 'wb') as manifest_json:
111+
manifest_json.write(manifest)
112+
os.fchmod(manifest_json.fileno(), S_IREAD | S_IRGRP | S_IROTH)
113+
114+
manifest_json_sha256 = sha256sum_file(manifest_json_dest)
115+
manifest_json_size = os.path.getsize(manifest_json_dest)
116+
117+
manifest_metadata = {
118+
'path': ostree_oci_manifest_path,
119+
'sha256': manifest_json_sha256,
120+
'size': manifest_json_size,
121+
"skip-compression": True,
122+
}
123+
124+
return {
125+
'metadata': manifest_metadata,
126+
'src_path': manifest_json_dest
127+
}
128+
129+
130+
def parse_timestamp(timestamp):
131+
# datetime's doesn't support nanoseconds.
132+
# So trim it.
133+
if len(timestamp) > 26 and timestamp[19] == '.':
134+
timestamp = timestamp[:26] + "Z"
135+
136+
timestamp = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%fZ')
137+
return rfc3339_time(timestamp.replace(tzinfo=datetime.timezone.utc))
138+
139+
140+
def generate_meta_json(ociarchive, metadata, oci_manifest, name):
141+
archive_sha256sum = sha256sum_file(ociarchive)
142+
143+
# let raise if missing
144+
assert metadata['Labels']['containers.bootc'] == '1'
145+
146+
buildid = metadata['Labels']['org.opencontainers.image.version']
147+
arch = get_basearch()
148+
created_timestamp = parse_timestamp(metadata['Created'])
149+
150+
meta_json = {
151+
'ostree-version': buildid, # proxy version label
152+
'buildid': buildid, # also version label
153+
'coreos-assembler.build-timestamp': created_timestamp, # proxy OCI build timestamp
154+
'coreos-assembler.oci-imported': True,
155+
'name': name,
156+
'ostree-timestamp': created_timestamp,
157+
'images': {
158+
'ostree': {
159+
"path": f"{name}-{buildid}-ostree.{arch}.ociarchive",
160+
"sha256": archive_sha256sum,
161+
"skip-compression": True
162+
},
163+
'oci-manifest': oci_manifest,
164+
},
165+
'coreos-assembler.basearch': arch,
166+
}
167+
168+
return meta_json
169+
170+
171+
def ostree_import(parent_tmpd, ociarchive, buildid):
172+
with tempfile.TemporaryDirectory(dir=parent_tmpd) as tmpd:
173+
subprocess.check_call(['ostree', 'init', '--repo', tmpd, '--mode=bare-user'])
174+
175+
# import all the blob refs for more efficient import into bare-user repo
176+
blob_refs = subprocess.check_output(['ostree', 'refs', '--repo', 'tmp/repo',
177+
'--list', 'ostree/container/blob'],
178+
encoding='utf-8').splitlines()
179+
subprocess.check_call(['ostree', 'pull-local', '--repo', tmpd, 'tmp/repo'] + blob_refs)
180+
181+
subprocess.check_call(['ostree', 'container', 'image', 'pull', tmpd,
182+
f'ostree-unverified-image:oci-archive:{ociarchive}'])
183+
184+
# awkwardly work around the fact that there is no --write-ref equivalent
185+
refs = subprocess.check_output(['ostree', 'refs', '--repo', tmpd,
186+
'--list', 'ostree/container/image'],
187+
encoding='utf-8').splitlines()
188+
assert len(refs) == 1
189+
subprocess.check_call(['ostree', 'refs', '--repo', tmpd, refs[0], '--create', buildid])
190+
subprocess.check_call(['ostree', 'refs', '--repo', 'tmp/repo', buildid, '--delete'])
191+
subprocess.check_call(['ostree', 'pull-local', '--repo', 'tmp/repo', tmpd, buildid])
192+
193+
# export back all the blob refs for more efficient imports of next builds
194+
blob_refs = subprocess.check_output(['ostree', 'refs', '--repo', tmpd,
195+
'--list', 'ostree/container/blob'],
196+
encoding='utf-8').splitlines()
197+
subprocess.check_call(['ostree', 'pull-local', '--repo', 'tmp/repo', tmpd] + blob_refs)
198+
199+
return subprocess.check_output(['ostree', 'rev-parse', '--repo', 'tmp/repo', buildid], encoding='utf-8').strip()
200+
201+
202+
if __name__ == '__main__':
203+
main()

src/cosalib/cmdlib.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,14 @@ def import_ostree_commit(workdir, buildpath, buildmeta, extract_json=True, parti
295295
lifetime=LOCK_DEFAULT_LIFETIME):
296296
repo = os.path.join(tmpdir, 'repo')
297297
commit = buildmeta['ostree-commit']
298+
is_oci_imported = buildmeta.get('coreos-assembler.oci-imported', False)
298299
tarfile = os.path.join(buildpath, buildmeta['images']['ostree']['path'])
299300
# create repo in case e.g. tmp/ was cleared out; idempotent
300301
subprocess.check_call(['ostree', 'init', '--repo', repo, '--mode=archive'])
301302

302-
# in the common case where we're operating on a recent build, the OSTree
303-
# commit should already be in the tmprepo
303+
# in the common case where we're operating on a recent build (or
304+
# recently imported OCI image), the OSTree commit should already be in
305+
# the tmprepo
304306
commitpartial = os.path.join(repo, f'state/{commit}.commitpartial')
305307
if (subprocess.call(['ostree', 'show', '--repo', repo, commit],
306308
stdout=subprocess.DEVNULL,
@@ -332,8 +334,10 @@ def import_ostree_commit(workdir, buildpath, buildmeta, extract_json=True, parti
332334
# We do this in two stages, because right now ex-container only writes to
333335
# non-archive repos. Also, in the privileged case we need sudo to write
334336
# to `repo-build`, though it might be good to change this by default.
335-
if os.environ.get('COSA_PRIVILEGED', '') == '1':
337+
if not is_oci_imported and os.environ.get('COSA_PRIVILEGED', '') == '1':
336338
build_repo = os.path.join(repo, '../../cache/repo-build')
339+
# note: this actually is the same as `container unencapsulate` and
340+
# so only works with "pure OSTree OCI" encapsulated commits (legacy path)
337341
subprocess.check_call(['sudo', 'ostree', 'container', 'import', '--repo', build_repo,
338342
'--write-ref', buildmeta['buildid'],
339343
'ostree-unverified-image:oci-archive:' + tarfile])
@@ -344,9 +348,14 @@ def import_ostree_commit(workdir, buildpath, buildmeta, extract_json=True, parti
344348
else:
345349
with tempfile.TemporaryDirectory(dir=tmpdir) as tmpd:
346350
subprocess.check_call(['ostree', 'init', '--repo', tmpd, '--mode=bare-user'])
347-
subprocess.check_call(['ostree', 'container', 'import', '--repo', tmpd,
348-
'--write-ref', buildmeta['buildid'],
349-
'ostree-unverified-image:oci-archive:' + tarfile])
351+
if not is_oci_imported:
352+
subprocess.check_call(['ostree', 'container', 'import', '--repo', tmpd,
353+
'--write-ref', buildmeta['buildid'],
354+
'ostree-unverified-image:oci-archive:' + tarfile])
355+
else:
356+
subprocess.check_call(['ostree', 'container', 'image', 'pull', tmpd,
357+
f'ostree-unverified-image:oci-archive:{tarfile}'])
358+
subprocess.check_call(['ostree', 'refs', '--repo', tmpd, commit, '--create', buildmeta['buildid']])
350359
subprocess.check_call(['ostree', f'--repo={repo}', 'pull-local', tmpd, buildmeta['buildid']])
351360

352361
# Also extract image.json since it's commonly needed by image builds

0 commit comments

Comments
 (0)