Skip to content

Commit 4370430

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 4370430

File tree

7 files changed

+242
-18
lines changed

7 files changed

+242
-18
lines changed

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