You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
import hashlib
|
|
import argparse
|
|
import os
|
|
import tempfile
|
|
import gzip
|
|
import tarfile
|
|
import concurrent.futures
|
|
|
|
from docker_image import reference
|
|
|
|
from common import *
|
|
|
|
def tar_filter(p: Platform):
|
|
def f(info: tarfile.TarInfo):
|
|
# Ensure there's no directory called '/'
|
|
if info.name == '':
|
|
info.name = '.'
|
|
|
|
# buildx messes up symlink targets...
|
|
platform_prefix = f'/{p.dirname}'
|
|
if info.issym() and info.linkname.startswith(platform_prefix):
|
|
info.linkname = info.linkname[len(platform_prefix):]
|
|
|
|
return info
|
|
return f
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Construct and push a multiarch Docker plugin')
|
|
parser.add_argument('config', help='plugin config.json')
|
|
parser.add_argument('rootfs', help='buildx rootfs parent directory')
|
|
parser.add_argument('image', help='target image (registry/image:tag)')
|
|
parser.add_argument('-p', '--platforms', default='linux/amd64', help='buildx platforms')
|
|
|
|
args = parser.parse_args()
|
|
|
|
platforms = [Platform(p) for p in args.platforms.split(',')]
|
|
|
|
ref = reference.Reference.parse(args.image)
|
|
hostname, repo = ref.split_hostname()
|
|
tag = ref['tag']
|
|
|
|
reg = DXF(hostname, repo, auth=dxf_auth)
|
|
|
|
print(f'Pushing config file `{args.config}`')
|
|
config_size = os.path.getsize(args.config)
|
|
config_digest = reg.push_blob(filename=args.config, check_exists=True)
|
|
print(f'Pushed config as {config_digest}')
|
|
|
|
def push_platform(p):
|
|
#with open(f'/tmp/{p.dirname}.tar.gz', mode='w+b') as f:
|
|
with tempfile.TemporaryFile(mode='w+b', suffix='.tar.gz') as f:
|
|
tar_name = f'{p.dirname}.tar'
|
|
# Use gzip separately to force mtime=0 (deterministic gzipping!)
|
|
with gzip.GzipFile(filename=tar_name, mode='w', fileobj=f, mtime=0) as gz:
|
|
with tarfile.open(name=tar_name, mode='w', fileobj=gz) as tar:
|
|
path = os.path.join(args.rootfs, p.dirname)
|
|
print(f"tar'ing and gzip'ing {path}")
|
|
tar.add(path, arcname='', filter=tar_filter(p))
|
|
f.seek(0, os.SEEK_SET)
|
|
|
|
sha256 = hashlib.sha256()
|
|
for chunk in iter(lambda: f.read(8192), b''):
|
|
sha256.update(chunk)
|
|
h = 'sha256:' + sha256.hexdigest()
|
|
|
|
layer_size = f.tell()
|
|
f.seek(0, os.SEEK_SET)
|
|
|
|
print(f'Pushing {p.buildx} layer')
|
|
layer_digest = reg.push_blob(data=f, digest=h, check_exists=True)
|
|
print(f'Pushed {p.buildx} layer as {layer_digest}')
|
|
|
|
platform_tag = p.tag(tag)
|
|
print(f'Pushing {p.buildx} manifest with tag {platform_tag}')
|
|
size, digest = reg.push_manifest({
|
|
'schemaVersion': 2,
|
|
'mediaType': MTYPE_MANIFEST,
|
|
'config': {
|
|
'mediaType': MTYPE_PLUGIN_CONFIG,
|
|
'size': config_size,
|
|
'digest': config_digest,
|
|
},
|
|
'layers': [
|
|
{
|
|
'mediaType': MTYPE_LAYER,
|
|
'size': layer_size,
|
|
'digest': layer_digest,
|
|
}
|
|
],
|
|
}, ref=platform_tag)
|
|
print(f'Pushed {p.buildx} manifest with digest {digest}')
|
|
|
|
return size, digest
|
|
|
|
mf_list = {
|
|
'schemaVersion': 2,
|
|
'mediaType': MTYPE_MANIFEST_LIST,
|
|
'manifests': [],
|
|
}
|
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
fs = {executor.submit(push_platform, p): p for p in platforms}
|
|
|
|
for f in concurrent.futures.as_completed(fs):
|
|
p = fs[f]
|
|
try:
|
|
size, digest = f.result()
|
|
except Exception as ex:
|
|
print(f'Exception pushing `{p.buildx}`: {ex}')
|
|
continue
|
|
|
|
mf_list['manifests'].append({
|
|
'mediaType': MTYPE_MANIFEST,
|
|
'size': size,
|
|
'digest': digest,
|
|
'platform': p.manifest,
|
|
})
|
|
|
|
print(f'Pushing {args.image} manifest list')
|
|
reg.push_manifest(mf_list, ref=tag, mime=MTYPE_MANIFEST_LIST)
|
|
print(f'Pushed {args.image}')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|