occystrap 0.1.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- occystrap/common.py +40 -0
- occystrap/constants.py +186 -0
- occystrap/docker_registry.py +118 -85
- occystrap/main.py +114 -9
- occystrap/output_directory.py +318 -0
- occystrap/output_mounts.py +155 -0
- occystrap/output_ocibundle.py +53 -0
- occystrap/output_tarfile.py +55 -0
- occystrap/util.py +9 -1
- occystrap-0.3.0.dist-info/METADATA +131 -0
- occystrap-0.3.0.dist-info/RECORD +20 -0
- {occystrap-0.1.1.dist-info → occystrap-0.3.0.dist-info}/WHEEL +1 -1
- occystrap-0.3.0.dist-info/pbr.json +1 -0
- occystrap-0.1.1.dist-info/METADATA +0 -33
- occystrap-0.1.1.dist-info/RECORD +0 -14
- occystrap-0.1.1.dist-info/pbr.json +0 -1
- {occystrap-0.1.1.dist-info → occystrap-0.3.0.dist-info}/AUTHORS +0 -0
- {occystrap-0.1.1.dist-info → occystrap-0.3.0.dist-info}/LICENSE +0 -0
- {occystrap-0.1.1.dist-info → occystrap-0.3.0.dist-info}/entry_points.txt +0 -0
- {occystrap-0.1.1.dist-info → occystrap-0.3.0.dist-info}/top_level.txt +0 -0
occystrap/common.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from occystrap.constants import RUNC_SPEC_TEMPLATE
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def write_container_config(container_config_filename, runtime_config_filename,
|
|
7
|
+
container_template=RUNC_SPEC_TEMPLATE,
|
|
8
|
+
container_values=None):
|
|
9
|
+
if not container_values:
|
|
10
|
+
container_values = {}
|
|
11
|
+
|
|
12
|
+
# Read the container config
|
|
13
|
+
with open(container_config_filename) as f:
|
|
14
|
+
image_conf = json.loads(f.read())
|
|
15
|
+
|
|
16
|
+
# Write a runc specification for the container
|
|
17
|
+
container_conf = json.loads(container_template)
|
|
18
|
+
|
|
19
|
+
container_conf['process']['terminal'] = True
|
|
20
|
+
cwd = image_conf['config']['WorkingDir']
|
|
21
|
+
if cwd == '':
|
|
22
|
+
cwd = '/'
|
|
23
|
+
container_conf['process']['cwd'] = cwd
|
|
24
|
+
|
|
25
|
+
entrypoint = image_conf['config'].get('Entrypoint', [])
|
|
26
|
+
if not entrypoint:
|
|
27
|
+
entrypoint = []
|
|
28
|
+
cmd = image_conf['config'].get('Cmd', [])
|
|
29
|
+
if cmd:
|
|
30
|
+
entrypoint.extend(cmd)
|
|
31
|
+
container_conf['process']['args'] = entrypoint
|
|
32
|
+
|
|
33
|
+
# terminal = false means "pass through existing file descriptors"
|
|
34
|
+
container_conf['process']['terminal'] = False
|
|
35
|
+
|
|
36
|
+
container_conf['hostname'] = container_values.get(
|
|
37
|
+
'hostname', 'occystrap')
|
|
38
|
+
|
|
39
|
+
with open(runtime_config_filename, 'w') as f:
|
|
40
|
+
f.write(json.dumps(container_conf, indent=4, sort_keys=True))
|
occystrap/constants.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
CONFIG_FILE = 'config_file'
|
|
2
|
+
IMAGE_LAYER = 'image_layer'
|
|
3
|
+
|
|
4
|
+
RUNC_SPEC_TEMPLATE = """{
|
|
5
|
+
"ociVersion": "1.0.2-dev",
|
|
6
|
+
"process": {
|
|
7
|
+
"terminal": false,
|
|
8
|
+
"user": {
|
|
9
|
+
"uid": 0,
|
|
10
|
+
"gid": 0
|
|
11
|
+
},
|
|
12
|
+
"args": [
|
|
13
|
+
"sh"
|
|
14
|
+
],
|
|
15
|
+
"env": [
|
|
16
|
+
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
17
|
+
"TERM=xterm"
|
|
18
|
+
],
|
|
19
|
+
"cwd": "/",
|
|
20
|
+
"capabilities": {
|
|
21
|
+
"bounding": [
|
|
22
|
+
"CAP_AUDIT_WRITE",
|
|
23
|
+
"CAP_KILL",
|
|
24
|
+
"CAP_NET_BIND_SERVICE"
|
|
25
|
+
],
|
|
26
|
+
"effective": [
|
|
27
|
+
"CAP_AUDIT_WRITE",
|
|
28
|
+
"CAP_KILL",
|
|
29
|
+
"CAP_NET_BIND_SERVICE"
|
|
30
|
+
],
|
|
31
|
+
"inheritable": [
|
|
32
|
+
"CAP_AUDIT_WRITE",
|
|
33
|
+
"CAP_KILL",
|
|
34
|
+
"CAP_NET_BIND_SERVICE"
|
|
35
|
+
],
|
|
36
|
+
"permitted": [
|
|
37
|
+
"CAP_AUDIT_WRITE",
|
|
38
|
+
"CAP_KILL",
|
|
39
|
+
"CAP_NET_BIND_SERVICE"
|
|
40
|
+
],
|
|
41
|
+
"ambient": [
|
|
42
|
+
"CAP_AUDIT_WRITE",
|
|
43
|
+
"CAP_KILL",
|
|
44
|
+
"CAP_NET_BIND_SERVICE"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"rlimits": [
|
|
48
|
+
{
|
|
49
|
+
"type": "RLIMIT_NOFILE",
|
|
50
|
+
"hard": 1024,
|
|
51
|
+
"soft": 1024
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"noNewPrivileges": true
|
|
55
|
+
},
|
|
56
|
+
"root": {
|
|
57
|
+
"path": "rootfs",
|
|
58
|
+
"readonly": true
|
|
59
|
+
},
|
|
60
|
+
"hostname": "runc",
|
|
61
|
+
"mounts": [
|
|
62
|
+
{
|
|
63
|
+
"destination": "/proc",
|
|
64
|
+
"type": "proc",
|
|
65
|
+
"source": "proc"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"destination": "/dev",
|
|
69
|
+
"type": "tmpfs",
|
|
70
|
+
"source": "tmpfs",
|
|
71
|
+
"options": [
|
|
72
|
+
"nosuid",
|
|
73
|
+
"strictatime",
|
|
74
|
+
"mode=755",
|
|
75
|
+
"size=65536k"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"destination": "/dev/pts",
|
|
80
|
+
"type": "devpts",
|
|
81
|
+
"source": "devpts",
|
|
82
|
+
"options": [
|
|
83
|
+
"nosuid",
|
|
84
|
+
"noexec",
|
|
85
|
+
"newinstance",
|
|
86
|
+
"ptmxmode=0666",
|
|
87
|
+
"mode=0620",
|
|
88
|
+
"gid=5"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"destination": "/dev/shm",
|
|
93
|
+
"type": "tmpfs",
|
|
94
|
+
"source": "shm",
|
|
95
|
+
"options": [
|
|
96
|
+
"nosuid",
|
|
97
|
+
"noexec",
|
|
98
|
+
"nodev",
|
|
99
|
+
"mode=1777",
|
|
100
|
+
"size=65536k"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"destination": "/dev/mqueue",
|
|
105
|
+
"type": "mqueue",
|
|
106
|
+
"source": "mqueue",
|
|
107
|
+
"options": [
|
|
108
|
+
"nosuid",
|
|
109
|
+
"noexec",
|
|
110
|
+
"nodev"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"destination": "/sys",
|
|
115
|
+
"type": "sysfs",
|
|
116
|
+
"source": "sysfs",
|
|
117
|
+
"options": [
|
|
118
|
+
"nosuid",
|
|
119
|
+
"noexec",
|
|
120
|
+
"nodev",
|
|
121
|
+
"ro"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"destination": "/sys/fs/cgroup",
|
|
126
|
+
"type": "cgroup",
|
|
127
|
+
"source": "cgroup",
|
|
128
|
+
"options": [
|
|
129
|
+
"nosuid",
|
|
130
|
+
"noexec",
|
|
131
|
+
"nodev",
|
|
132
|
+
"relatime",
|
|
133
|
+
"ro"
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"linux": {
|
|
138
|
+
"resources": {
|
|
139
|
+
"devices": [
|
|
140
|
+
{
|
|
141
|
+
"allow": false,
|
|
142
|
+
"access": "rwm"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"namespaces": [
|
|
147
|
+
{
|
|
148
|
+
"type": "pid"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"type": "network"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"type": "ipc"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"type": "uts"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"type": "mount"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"type": "cgroup"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"maskedPaths": [
|
|
167
|
+
"/proc/acpi",
|
|
168
|
+
"/proc/asound",
|
|
169
|
+
"/proc/kcore",
|
|
170
|
+
"/proc/keys",
|
|
171
|
+
"/proc/latency_stats",
|
|
172
|
+
"/proc/timer_list",
|
|
173
|
+
"/proc/timer_stats",
|
|
174
|
+
"/proc/sched_debug",
|
|
175
|
+
"/sys/firmware",
|
|
176
|
+
"/proc/scsi"
|
|
177
|
+
],
|
|
178
|
+
"readonlyPaths": [
|
|
179
|
+
"/proc/bus",
|
|
180
|
+
"/proc/fs",
|
|
181
|
+
"/proc/irq",
|
|
182
|
+
"/proc/sys",
|
|
183
|
+
"/proc/sysrq-trigger"
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
}"""
|
occystrap/docker_registry.py
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
# A simple implementation of a docker registry client. Fetches an image to a tarball.
|
|
2
2
|
# With a big nod to https://github.com/NotGlop/docker-drag/blob/master/docker_pull.py
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
# https://docs.docker.com/registry/spec/manifest-v2-2/ documents the image manifest
|
|
5
|
+
# format, noting that the response format you get back varies based on what you have
|
|
6
|
+
# in your accept header for the request.
|
|
7
|
+
|
|
5
8
|
import hashlib
|
|
6
9
|
import io
|
|
7
|
-
import json
|
|
8
10
|
import logging
|
|
9
11
|
import os
|
|
10
12
|
import re
|
|
11
13
|
import sys
|
|
12
|
-
import tarfile
|
|
13
14
|
import tempfile
|
|
14
15
|
import zlib
|
|
15
16
|
|
|
17
|
+
from occystrap import constants
|
|
16
18
|
from occystrap import util
|
|
17
19
|
|
|
18
20
|
LOG = logging.getLogger(__name__)
|
|
19
21
|
LOG.setLevel(logging.INFO)
|
|
20
22
|
|
|
23
|
+
DELETED_FILE_RE = re.compile(r'.*/\.wh\.(.*)$')
|
|
24
|
+
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
def always_fetch():
|
|
27
|
+
return True
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
class Image(object):
|
|
26
|
-
def __init__(self, registry, image, tag
|
|
31
|
+
def __init__(self, registry, image, tag, os='linux', architecture='amd64', variant='',
|
|
32
|
+
secure=True):
|
|
27
33
|
self.registry = registry
|
|
28
34
|
self.image = image
|
|
29
35
|
self.tag = tag
|
|
36
|
+
self.os = os
|
|
37
|
+
self.architecture = architecture
|
|
38
|
+
self.variant = variant
|
|
39
|
+
self.secure = secure
|
|
40
|
+
|
|
30
41
|
self._cached_auth = None
|
|
31
42
|
|
|
32
43
|
def request_url(self, method, url, headers=None, data=None, stream=False):
|
|
@@ -53,107 +64,129 @@ class Image(object):
|
|
|
53
64
|
return util.request_url(
|
|
54
65
|
method, url, headers=headers, data=data, stream=stream)
|
|
55
66
|
|
|
56
|
-
def fetch(self,
|
|
67
|
+
def fetch(self, fetch_callback=always_fetch):
|
|
57
68
|
LOG.info('Fetching manifest')
|
|
69
|
+
moniker = 'https'
|
|
70
|
+
if not self.secure:
|
|
71
|
+
moniker = 'http'
|
|
72
|
+
|
|
58
73
|
r = self.request_url(
|
|
59
74
|
'GET',
|
|
60
|
-
'
|
|
75
|
+
'%(moniker)s://%(registry)s/v2/%(image)s/manifests/%(tag)s'
|
|
61
76
|
% {
|
|
77
|
+
'moniker': moniker,
|
|
62
78
|
'registry': self.registry,
|
|
63
79
|
'image': self.image,
|
|
64
80
|
'tag': self.tag
|
|
65
81
|
},
|
|
66
|
-
headers={'Accept': 'application/vnd.docker.distribution.manifest.v2+json'
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
headers={'Accept': ('application/vnd.docker.distribution.manifest.v2+json,'
|
|
83
|
+
'application/vnd.docker.distribution.manifest.list.v2+json')})
|
|
84
|
+
|
|
85
|
+
config_digest = None
|
|
86
|
+
if r.headers['Content-Type'] == 'application/vnd.docker.distribution.manifest.v2+json':
|
|
87
|
+
manifest = r.json()
|
|
88
|
+
config_digest = manifest['config']['digest']
|
|
89
|
+
elif r.headers['Content-Type'] == 'application/vnd.docker.distribution.manifest.list.v2+json':
|
|
90
|
+
for m in r.json()['manifests']:
|
|
91
|
+
if 'variant' in m['platform']:
|
|
92
|
+
LOG.info('Found manifest for %s on %s %s'
|
|
93
|
+
% (m['platform']['os'], m['platform']['architecture'],
|
|
94
|
+
m['platform']['variant']))
|
|
95
|
+
else:
|
|
96
|
+
LOG.info('Found manifest for %s on %s'
|
|
97
|
+
% (m['platform']['os'], m['platform']['architecture']))
|
|
98
|
+
|
|
99
|
+
if (m['platform']['os'] == self.os and
|
|
100
|
+
m['platform']['architecture'] == self.architecture and
|
|
101
|
+
m['platform'].get('variant', '') == self.variant):
|
|
102
|
+
LOG.info('Fetching matching manifest')
|
|
103
|
+
r = self.request_url(
|
|
104
|
+
'GET',
|
|
105
|
+
'%(moniker)s://%(registry)s/v2/%(image)s/manifests/%(tag)s'
|
|
106
|
+
% {
|
|
107
|
+
'moniker': moniker,
|
|
108
|
+
'registry': self.registry,
|
|
109
|
+
'image': self.image,
|
|
110
|
+
'tag': m['digest']
|
|
111
|
+
},
|
|
112
|
+
headers={'Accept': ('application/vnd.docker.distribution.manifest.v2+json')})
|
|
113
|
+
manifest = r.json()
|
|
114
|
+
config_digest = manifest['config']['digest']
|
|
115
|
+
|
|
116
|
+
if not config_digest:
|
|
117
|
+
raise Exception('Could not find a matching manifest for this '
|
|
118
|
+
'os / architecture / variant')
|
|
119
|
+
else:
|
|
120
|
+
raise Exception('Unknown manifest content type %s!' %
|
|
121
|
+
r.headers['Content-Type'])
|
|
69
122
|
|
|
70
123
|
LOG.info('Fetching config file')
|
|
71
124
|
r = self.request_url(
|
|
72
125
|
'GET',
|
|
73
|
-
'
|
|
126
|
+
'%(moniker)s://%(registry)s/v2/%(image)s/blobs/%(config)s'
|
|
74
127
|
% {
|
|
128
|
+
'moniker': moniker,
|
|
75
129
|
'registry': self.registry,
|
|
76
130
|
'image': self.image,
|
|
77
|
-
'config':
|
|
131
|
+
'config': config_digest
|
|
78
132
|
})
|
|
79
133
|
config = r.content
|
|
80
134
|
h = hashlib.sha256()
|
|
81
135
|
h.update(config)
|
|
82
|
-
if h.hexdigest() !=
|
|
136
|
+
if h.hexdigest() != config_digest.split(':')[1]:
|
|
83
137
|
LOG.error('Hash verification failed for image config blob (%s vs %s)'
|
|
84
|
-
% (
|
|
138
|
+
% (config_digest.split(':')[1], h.hexdigest()))
|
|
85
139
|
sys.exit(1)
|
|
86
140
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
layer_filename += '/layer.tar'
|
|
139
|
-
image_tar.add(
|
|
140
|
-
tf.name, arcname=layer_filename)
|
|
141
|
-
tar_manifest[0]['Layers'].append(layer_filename)
|
|
142
|
-
|
|
143
|
-
with tarfile.open(tf.name) as layer:
|
|
144
|
-
for mem in layer.getmembers():
|
|
145
|
-
m = DELETED_FILE_RE.match(mem.name)
|
|
146
|
-
if m:
|
|
147
|
-
LOG.info('Layer tarball contains deleted file: %s'
|
|
148
|
-
% mem.name)
|
|
149
|
-
|
|
150
|
-
finally:
|
|
151
|
-
os.unlink(tf.name)
|
|
152
|
-
|
|
153
|
-
LOG.info('Writing manifest file to tarball')
|
|
154
|
-
encoded_manifest = json.dumps(tar_manifest).encode('utf-8')
|
|
155
|
-
ti = tarfile.TarInfo('manifest.json')
|
|
156
|
-
ti.size = len(encoded_manifest)
|
|
157
|
-
image_tar.addfile(ti, io.BytesIO(encoded_manifest))
|
|
141
|
+
config_filename = ('%s.json' % config_digest.split(':')[1])
|
|
142
|
+
yield (constants.CONFIG_FILE, config_filename,
|
|
143
|
+
io.BytesIO(config))
|
|
144
|
+
|
|
145
|
+
LOG.info('There are %d image layers' % len(manifest['layers']))
|
|
146
|
+
for layer in manifest['layers']:
|
|
147
|
+
layer_filename = layer['digest'].split(':')[1]
|
|
148
|
+
if not fetch_callback(layer_filename):
|
|
149
|
+
LOG.info('Fetch callback says skip layer %s' % layer['digest'])
|
|
150
|
+
yield (constants.IMAGE_LAYER, layer_filename, None)
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
LOG.info('Fetching layer %s (%d bytes)'
|
|
154
|
+
% (layer['digest'], layer['size']))
|
|
155
|
+
r = self.request_url(
|
|
156
|
+
'GET',
|
|
157
|
+
'%(moniker)s://%(registry)s/v2/%(image)s/blobs/%(layer)s'
|
|
158
|
+
% {
|
|
159
|
+
'moniker': moniker,
|
|
160
|
+
'registry': self.registry,
|
|
161
|
+
'image': self.image,
|
|
162
|
+
'layer': layer['digest']
|
|
163
|
+
},
|
|
164
|
+
stream=True)
|
|
165
|
+
|
|
166
|
+
# We can use zlib for streaming decompression, but we need to tell it
|
|
167
|
+
# to ignore the gzip header which it doesn't understand. Unfortunately
|
|
168
|
+
# tarfile doesn't do streaming writes (and we need to know the
|
|
169
|
+
# decompressed size before we can write to the tarfile), so we stream
|
|
170
|
+
# to a temporary file on disk.
|
|
171
|
+
try:
|
|
172
|
+
h = hashlib.sha256()
|
|
173
|
+
d = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
|
174
|
+
|
|
175
|
+
with tempfile.NamedTemporaryFile(delete=False) as tf:
|
|
176
|
+
LOG.info('Temporary file for layer is %s' % tf.name)
|
|
177
|
+
for chunk in r.iter_content(8192):
|
|
178
|
+
tf.write(d.decompress(chunk))
|
|
179
|
+
h.update(chunk)
|
|
180
|
+
|
|
181
|
+
if h.hexdigest() != layer_filename:
|
|
182
|
+
LOG.error('Hash verification failed for layer (%s vs %s)'
|
|
183
|
+
% (layer_filename, h.hexdigest()))
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
|
|
186
|
+
with open(tf.name, 'rb') as f:
|
|
187
|
+
yield (constants.IMAGE_LAYER, layer_filename, f)
|
|
188
|
+
|
|
189
|
+
finally:
|
|
190
|
+
os.unlink(tf.name)
|
|
158
191
|
|
|
159
192
|
LOG.info('Done')
|
occystrap/main.py
CHANGED
|
@@ -1,32 +1,137 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
from shakenfist_utilities import logs
|
|
5
|
+
import sys
|
|
3
6
|
|
|
4
7
|
from occystrap import docker_registry
|
|
8
|
+
from occystrap import output_directory
|
|
9
|
+
from occystrap import output_mounts
|
|
10
|
+
from occystrap import output_ocibundle
|
|
11
|
+
from occystrap import output_tarfile
|
|
5
12
|
|
|
6
|
-
logging.basicConfig(level=logging.INFO)
|
|
7
13
|
|
|
8
|
-
LOG =
|
|
9
|
-
LOG.setLevel(logging.INFO)
|
|
14
|
+
LOG = logs.setup_console(__name__)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
@click.group()
|
|
13
|
-
@click.option('--verbose
|
|
18
|
+
@click.option('--verbose', is_flag=True)
|
|
19
|
+
@click.option('--os', default='linux')
|
|
20
|
+
@click.option('--architecture', default='amd64')
|
|
21
|
+
@click.option('--variant', default='')
|
|
14
22
|
@click.pass_context
|
|
15
|
-
def cli(ctx, verbose=None):
|
|
23
|
+
def cli(ctx, verbose=None, os=None, architecture=None, variant=None):
|
|
16
24
|
if verbose:
|
|
17
25
|
logging.basicConfig(level=logging.DEBUG)
|
|
18
26
|
LOG.setLevel(logging.DEBUG)
|
|
19
27
|
|
|
28
|
+
if not ctx.obj:
|
|
29
|
+
ctx.obj = {}
|
|
30
|
+
ctx.obj['OS'] = os
|
|
31
|
+
ctx.obj['ARCHITECTURE'] = architecture
|
|
32
|
+
ctx.obj['VARIANT'] = variant
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _fetch(registry, image, tag, output, os, architecture, variant, secure=True):
|
|
36
|
+
img = docker_registry.Image(
|
|
37
|
+
registry, image, tag, os, architecture, variant, secure=secure)
|
|
38
|
+
for image_element in img.fetch(fetch_callback=output.fetch_callback):
|
|
39
|
+
output.process_image_element(*image_element)
|
|
40
|
+
output.finalize()
|
|
41
|
+
|
|
20
42
|
|
|
21
43
|
@click.command()
|
|
22
44
|
@click.argument('registry')
|
|
23
45
|
@click.argument('image')
|
|
24
46
|
@click.argument('tag')
|
|
47
|
+
@click.argument('path')
|
|
48
|
+
@click.option('--use-unique-names', is_flag=True)
|
|
49
|
+
@click.option('--expand', is_flag=True)
|
|
50
|
+
@click.option('--insecure', is_flag=True, default=False)
|
|
51
|
+
@click.pass_context
|
|
52
|
+
def fetch_to_extracted(ctx, registry, image, tag, path, use_unique_names,
|
|
53
|
+
expand, insecure):
|
|
54
|
+
d = output_directory.DirWriter(
|
|
55
|
+
image, tag, path, unique_names=use_unique_names, expand=expand)
|
|
56
|
+
_fetch(registry, image, tag, d, ctx.obj['OS'], ctx.obj['ARCHITECTURE'],
|
|
57
|
+
ctx.obj['VARIANT'], secure=(not insecure))
|
|
58
|
+
|
|
59
|
+
if expand:
|
|
60
|
+
d.write_bundle()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
cli.add_command(fetch_to_extracted)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@click.command()
|
|
67
|
+
@click.argument('registry')
|
|
68
|
+
@click.argument('image')
|
|
69
|
+
@click.argument('tag')
|
|
70
|
+
@click.argument('path')
|
|
71
|
+
@click.option('--insecure', is_flag=True, default=False)
|
|
72
|
+
@click.pass_context
|
|
73
|
+
def fetch_to_oci(ctx, registry, image, tag, path, insecure):
|
|
74
|
+
d = output_ocibundle.OCIBundleWriter(image, tag, path)
|
|
75
|
+
_fetch(registry, image, tag, d, ctx.obj['OS'], ctx.obj['ARCHITECTURE'],
|
|
76
|
+
ctx.obj['VARIANT'], secure=(not insecure))
|
|
77
|
+
d.write_bundle()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
cli.add_command(fetch_to_oci)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@click.command()
|
|
84
|
+
@click.argument('registry')
|
|
85
|
+
@click.argument('image')
|
|
86
|
+
@click.argument('tag')
|
|
87
|
+
@click.argument('tarfile')
|
|
88
|
+
@click.option('--insecure', is_flag=True, default=False)
|
|
89
|
+
@click.pass_context
|
|
90
|
+
def fetch_to_tarfile(ctx, registry, image, tag, tarfile, insecure):
|
|
91
|
+
tar = output_tarfile.TarWriter(image, tag, tarfile)
|
|
92
|
+
_fetch(registry, image, tag, tar, ctx.obj['OS'], ctx.obj['ARCHITECTURE'],
|
|
93
|
+
ctx.obj['VARIANT'], secure=(not insecure))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
cli.add_command(fetch_to_tarfile)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@click.command()
|
|
100
|
+
@click.argument('registry')
|
|
101
|
+
@click.argument('image')
|
|
102
|
+
@click.argument('tag')
|
|
103
|
+
@click.argument('path')
|
|
104
|
+
@click.option('--insecure', is_flag=True, default=False)
|
|
105
|
+
@click.pass_context
|
|
106
|
+
def fetch_to_mounts(ctx, registry, image, tag, path, insecure):
|
|
107
|
+
if not hasattr(os, 'setxattr'):
|
|
108
|
+
print('Sorry, your OS module implementation lacks setxattr')
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
if not hasattr(os, 'mknod'):
|
|
111
|
+
print('Sorry, your OS module implementation lacks mknod')
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
d = output_mounts.MountWriter(image, tag, path)
|
|
115
|
+
_fetch(registry, image, tag, d, ctx.obj['OS'], ctx.obj['ARCHITECTURE'],
|
|
116
|
+
ctx.obj['VARIANT'], secure=(not insecure))
|
|
117
|
+
d.write_bundle()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
cli.add_command(fetch_to_mounts)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@click.command()
|
|
124
|
+
@click.argument('path')
|
|
125
|
+
@click.argument('image')
|
|
126
|
+
@click.argument('tag')
|
|
25
127
|
@click.argument('tarfile')
|
|
26
128
|
@click.pass_context
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
129
|
+
def recreate_image(ctx, path, image, tag, tarfile):
|
|
130
|
+
d = output_directory.DirReader(path, image, tag)
|
|
131
|
+
tar = output_tarfile.TarWriter(image, tag, tarfile)
|
|
132
|
+
for image_element in d.fetch():
|
|
133
|
+
tar.process_image_element(*image_element)
|
|
134
|
+
tar.finalize()
|
|
30
135
|
|
|
31
136
|
|
|
32
|
-
cli.add_command(
|
|
137
|
+
cli.add_command(recreate_image)
|