Skip to content

Commit 0512d85

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 <[email protected]>
1 parent 625b0a9 commit 0512d85

File tree

7 files changed

+224
-15
lines changed

7 files changed

+224
-15
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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/python3
2+
3+
'''
4+
This command takes a containers-transports(5) ref to an OCI image and converts
5+
it into a `cosa build`, as if one did `cosa build ostree`. One can then e.g.
6+
`cosa buildextend-qemu` right away.
7+
'''
8+
9+
import argparse
10+
import datetime
11+
import json
12+
import os
13+
import subprocess
14+
import tempfile
15+
import shutil
16+
import sys
17+
from stat import (
18+
S_IREAD,
19+
S_IRGRP,
20+
S_IROTH)
21+
from cosalib.builds import Builds
22+
from cosalib.cmdlib import (
23+
rfc3339_time,
24+
get_basearch,
25+
sha256sum_file,
26+
import_oci_archive)
27+
28+
29+
def main():
30+
args = parse_args()
31+
32+
# immediate inspect to error out early if it doesn't exists/we don't have ACLs and to do some upfront checks
33+
metadata = skopeo_inspect(args.srcimg)
34+
35+
# let raise if missing
36+
assert metadata['Labels']['containers.bootc'] == '1'
37+
buildid = metadata['Labels']['org.opencontainers.image.version']
38+
39+
builds = Builds()
40+
if builds.has(buildid):
41+
print(f"ERROR: Build ID {buildid} already exists!")
42+
sys.exit(1)
43+
44+
with tempfile.TemporaryDirectory(prefix='cosa-import-', dir='tmp') as tmpd:
45+
# create the OCI archive and manifest
46+
tmp_oci_archive = generate_oci_archive(args, tmpd)
47+
tmp_oci_manifest = generate_oci_manifest(args, tmpd)
48+
49+
# import into the tmp/repo to get the ostree-commit but also so it's cached
50+
ostree_commit = import_oci_archive(tmpd, tmp_oci_archive, buildid)
51+
52+
# create meta.json
53+
build_meta = generate_build_meta(tmp_oci_archive, tmp_oci_manifest, metadata, ostree_commit)
54+
55+
# move into official location
56+
finalize_build(builds, build_meta, tmp_oci_archive, tmp_oci_manifest)
57+
58+
59+
def parse_args():
60+
parser = argparse.ArgumentParser(prog='cosa import')
61+
parser.add_argument("srcimg", metavar='IMAGE',
62+
help="image to import (containers-transports(5) format)")
63+
return parser.parse_args()
64+
65+
66+
def generate_oci_archive(args, tmpd):
67+
tmpf = os.path.join(tmpd, 'out.ociarchive')
68+
subprocess.check_call(['skopeo', 'copy', '--preserve-digests', args.srcimg,
69+
f"oci-archive:{tmpf}"])
70+
return tmpf
71+
72+
73+
def generate_oci_manifest(args, tmpd):
74+
tmpf = os.path.join(tmpd, 'oci-manifest.json')
75+
with open(tmpf, 'wb') as f:
76+
f.write(subprocess.check_output(["skopeo", "inspect", "--raw", args.srcimg]))
77+
os.fchmod(f.fileno(), S_IREAD | S_IRGRP | S_IROTH)
78+
return tmpf
79+
80+
81+
def generate_build_meta(tmp_oci_archive, tmp_oci_manifest, metadata, ostree_commit):
82+
name = metadata['Labels']['com.coreos.osname']
83+
buildid = metadata['Labels']['org.opencontainers.image.version']
84+
created_timestamp = parse_timestamp(metadata['Created'])
85+
arch = get_basearch()
86+
87+
return {
88+
'ostree-commit': ostree_commit,
89+
'ostree-version': buildid,
90+
'buildid': buildid,
91+
'name': name,
92+
'coreos-assembler.basearch': arch,
93+
'coreos-assembler.build-timestamp': created_timestamp,
94+
'coreos-assembler.oci-imported': True,
95+
'ostree-timestamp': created_timestamp,
96+
'images': {
97+
'ostree': {
98+
"path": f"{name}-{buildid}-ostree.{arch}.ociarchive",
99+
"sha256": sha256sum_file(tmp_oci_archive),
100+
'size': os.path.getsize(tmp_oci_archive),
101+
"skip-compression": True
102+
},
103+
'oci-manifest': {
104+
'path': f'{name}-{buildid}-ostree.{arch}-manifest.json',
105+
'sha256': sha256sum_file(tmp_oci_manifest),
106+
'size': os.path.getsize(tmp_oci_manifest),
107+
"skip-compression": True,
108+
},
109+
},
110+
}
111+
112+
113+
def finalize_build(builds, build_meta, tmp_oci_archive, tmp_oci_manifest):
114+
buildid = build_meta['buildid']
115+
arch = build_meta['coreos-assembler.basearch']
116+
117+
destdir = f'builds/{buildid}/{arch}'
118+
os.makedirs(destdir)
119+
120+
shutil.move(tmp_oci_archive, f'{destdir}/{build_meta['images']['ostree']['path']}')
121+
shutil.move(tmp_oci_manifest, f'{destdir}/{build_meta['images']['oci-manifest']['path']}')
122+
123+
with open(f'{destdir}/meta.json', 'w') as f:
124+
json.dump(build_meta, f, indent=4)
125+
126+
# and finally the real deal: insert the build and bump latest symlink
127+
builds.insert_build(buildid, arch)
128+
builds.bump_timestamp()
129+
130+
if os.path.exists('builds/latest'):
131+
os.remove('builds/latest')
132+
os.symlink(f'{buildid}', 'builds/latest', target_is_directory=True)
133+
134+
print(f'Imported OCI image as build {buildid}')
135+
136+
137+
def skopeo_inspect(image):
138+
return json.loads(subprocess.check_output(['skopeo', 'inspect', '-n', image]))
139+
140+
141+
def parse_timestamp(timestamp):
142+
# datetime's doesn't support nanoseconds.
143+
# So trim it.
144+
if len(timestamp) > 26 and timestamp[19] == '.':
145+
timestamp = timestamp[:26] + "Z"
146+
147+
timestamp = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%fZ')
148+
return rfc3339_time(timestamp.replace(tzinfo=datetime.timezone.utc))
149+
150+
151+
if __name__ == '__main__':
152+
main()

src/cosalib/cmdlib.py

Lines changed: 45 additions & 3 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,12 @@ 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 is_oci_imported:
338+
import_oci_archive(tmpdir, tarfile, buildmeta['buildid'])
339+
elif os.environ.get('COSA_PRIVILEGED', '') == '1':
336340
build_repo = os.path.join(repo, '../../cache/repo-build')
341+
# note: this actually is the same as `container unencapsulate` and
342+
# so only works with "pure OSTree OCI" encapsulated commits (legacy path)
337343
subprocess.check_call(['sudo', 'ostree', 'container', 'import', '--repo', build_repo,
338344
'--write-ref', buildmeta['buildid'],
339345
'ostree-unverified-image:oci-archive:' + tarfile])
@@ -354,6 +360,42 @@ def import_ostree_commit(workdir, buildpath, buildmeta, extract_json=True, parti
354360
extract_image_json(workdir, commit)
355361

356362

363+
def import_oci_archive(parent_tmpd, ociarchive, ref):
364+
with tempfile.TemporaryDirectory(dir=parent_tmpd) as tmpd:
365+
subprocess.check_call(['ostree', 'init', '--repo', tmpd, '--mode=bare-user'])
366+
367+
# Init tmp/repo in case it doesn't exist.
368+
# If it exists, no problem. It's idempotent
369+
subprocess.check_call(['ostree', 'init', '--repo', 'tmp/repo', '--mode=archive'])
370+
371+
# import all the blob refs for more efficient import into bare-user repo
372+
blob_refs = subprocess.check_output(['ostree', 'refs', '--repo', 'tmp/repo',
373+
'--list', 'ostree/container/blob'],
374+
encoding='utf-8').splitlines()
375+
if len(blob_refs) > 0:
376+
subprocess.check_call(['ostree', 'pull-local', '--repo', tmpd, 'tmp/repo'] + blob_refs)
377+
378+
subprocess.check_call(['ostree', 'container', 'image', 'pull', tmpd,
379+
f'ostree-unverified-image:oci-archive:{ociarchive}'])
380+
381+
# awkwardly work around the fact that there is no --write-ref equivalent
382+
refs = subprocess.check_output(['ostree', 'refs', '--repo', tmpd,
383+
'--list', 'ostree/container/image'],
384+
encoding='utf-8').splitlines()
385+
assert len(refs) == 1
386+
subprocess.check_call(['ostree', 'refs', '--repo', tmpd, refs[0], '--create', ref])
387+
subprocess.check_call(['ostree', 'refs', '--repo', 'tmp/repo', ref, '--delete'])
388+
subprocess.check_call(['ostree', 'pull-local', '--repo', 'tmp/repo', tmpd, ref])
389+
390+
# export back all the blob refs for more efficient imports of next builds
391+
blob_refs = subprocess.check_output(['ostree', 'refs', '--repo', tmpd,
392+
'--list', 'ostree/container/blob'],
393+
encoding='utf-8').splitlines()
394+
subprocess.check_call(['ostree', 'pull-local', '--repo', 'tmp/repo', tmpd] + blob_refs)
395+
396+
return subprocess.check_output(['ostree', 'rev-parse', '--repo', 'tmp/repo', ref], encoding='utf-8').strip()
397+
398+
357399
def get_basearch():
358400
try:
359401
return get_basearch.saved

src/v1.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,8 @@
249249
"buildid",
250250
"name",
251251
"ostree-commit",
252-
"ostree-content-checksum",
253252
"ostree-timestamp",
254-
"ostree-version",
255-
"rpm-ostree-inputhash"
253+
"ostree-version"
256254
],
257255
"optional": [
258256
"aliyun",
@@ -267,13 +265,15 @@
267265
"images",
268266
"koji",
269267
"oscontainer",
268+
"ostree-content-checksum",
270269
"extensions",
271270
"extensions-container",
272271
"parent-pkgdiff",
273272
"pkgdiff",
274273
"parent-advisories-diff",
275274
"advisories-diff",
276275
"release-payload",
276+
"rpm-ostree-inputhash",
277277
"summary",
278278
"s3",
279279
"coreos-assembler.basearch",
@@ -291,6 +291,7 @@
291291
"coreos-assembler.meta-stamp",
292292
"coreos-assembler.overrides-active",
293293
"coreos-assembler.yumrepos-git",
294+
"coreos-assembeler.oci-imported",
294295
"fedora-coreos.parent-commit",
295296
"fedora-coreos.parent-version",
296297
"ref"
@@ -372,6 +373,12 @@
372373
"default": "",
373374
"minLength": 1
374375
},
376+
"coreos-assembler.oci-imported": {
377+
"$id": "#/properties/coreos-assembler.oci-imported",
378+
"type": "boolean",
379+
"title": "COSA imported OCI image",
380+
"default": "False"
381+
},
375382
"coreos-assembler.code-source": {
376383
"$id": "#/properties/coreos-assembler.code-source",
377384
"type": "string",

0 commit comments

Comments
 (0)