2
"""Update the lxc 'download' template cache for hosts on closed networks."""
4
from __future__ import print_function
6
from argparse import ArgumentParser
7
from collections import namedtuple
17
SITE = 'https://images.linuxcontainers.org'
18
INDEX_PATH = 'meta/1.0'
19
INDEX = 'index-system'
20
ROOTFS = 'rootfs.tar.xz'
22
LXC_CACHE = '/var/cache/lxc/download'
26
'System', ['dist', 'release', 'arch', 'variant', 'version', 'path'])
30
scp {rootfs_path} {meta_path} {user_host}:~/
34
ssh {user_host} bash <<"EOT"
35
sudo mkdir -p {lxc_cache}
36
sudo mv ~/{rootfs} ~/{meta} {lxc_cache}
37
sudo chown -R root:root {lxc_cache}
38
sudo tar -C {lxc_cache} -xf {lxc_cache}/meta.tar.xz
44
"""Manage the LXC download template cache."""
46
def __init__(self, workspace, verbose=False, dry_run=False):
47
"""Set the workspace for the local cache."""
48
self.workspace = os.path.abspath(workspace)
49
self.verbose = verbose
50
self.dry_run = dry_run
51
local_path = os.path.join(self.workspace, INDEX_PATH, INDEX)
52
self.systems, ignore = self.init_systems(local_path)
54
def init_systems(self, location):
55
"""Return a tuple of the dict of lxc Systems and the source data.
57
A System has these attributes: 'dist', 'release', 'arch', 'variant',
58
'version', and 'path'. The dict keys are a tuple of
59
(dist, release, arch, variant).
62
if location.startswith('http'):
63
request = urllib2.Request(location)
64
response = urllib2.urlopen(request)
65
data = response.read()
68
with open(location) as f:
71
if e.errno == errno.ENOENT:
73
print('Local cache is empty.')
75
for line in data.splitlines():
76
system = System(*line.split(';'))
77
key = (system.dist, system.release, system.arch, system.variant)
81
def get_updates(self, dist, release, arch, variant):
82
"""Return a tuple of the new system and the source data that match.
84
The new system and source data will be None when there are
85
no updates. The dist, release, arch, and variant args identify the
88
key = (dist, release, arch, variant)
89
old_system = self.systems.get(key)
90
url = '%s/%s/%s' % (SITE, INDEX_PATH, INDEX)
91
new_systems, data = self.init_systems(url)
92
new_system = new_systems[key]
93
if not old_system or new_system.version > old_system.version:
95
print('Found new version for %s' % str(key))
96
print(new_system.version)
97
return new_system, data
99
print('Version is current for %s' % str(key))
100
print(old_system.version)
103
def get_lxc_data(self, system):
104
"""Download the system image and meta data.
106
Return a tuple of the image and meta data paths.
108
image_path = os.path.join(self.workspace, system.path[1:])
111
print('creating %s' % image_path)
112
if not os.path.isdir(image_path):
113
os.makedirs(image_path)
114
rootfs_path = os.path.join(image_path, ROOTFS)
115
rootfs_url = '%s%s%s' % (SITE, system.path, ROOTFS)
116
self.download(rootfs_url, rootfs_path)
117
meta_path = os.path.join(image_path, META)
118
meta_url = '%s%s%s' % (SITE, system.path, META)
119
self.download(meta_url, meta_path)
120
return rootfs_path, meta_path
122
def download(self, location, path):
123
"""Download a large binary from location to the specified path."""
126
request = urllib2.Request(location)
127
response = urllib2.urlopen(request)
128
if response.getcode() == 200:
129
with open(path, 'wb') as f:
130
shutil.copyfileobj(response, f, chunk)
132
print('Downloaded %s' % location)
134
def put_lxc_data(self, user_host, system, rootfs_path, meta_path):
135
"""Install the lxc image and meta data on the host.
137
The user on the host must have password-less sudo.
139
lxc_cache = os.path.join(
140
LXC_CACHE, system.dist, system.release, system.arch,
142
put_script = PUT_SCRIPT.format(
143
user_host=user_host, rootfs_path=rootfs_path, meta_path=meta_path)
145
subprocess.check_call([put_script], shell=True)
147
print("Uploaded %s and %s" % (ROOTFS, META))
148
install_script = INSTALL_SCRIPT.format(
149
user_host=user_host, lxc_cache=lxc_cache, rootfs=ROOTFS, meta=META)
151
subprocess.check_call([install_script], shell=True)
153
print("Installed %s and %s" % (ROOTFS, META))
155
def save_index(self, data):
156
"Save the (current) index data for future calls to get_updates()."
157
index_dir = os.path.join(self.workspace, INDEX_PATH)
158
if not os.path.isdir(index_dir):
159
os.makedirs(index_dir)
160
index_path = os.path.join(self.workspace, INDEX_PATH, INDEX)
161
with open(index_path, 'w') as f:
164
print('saved index: %s' % INDEX)
167
def parse_args(argv=None):
168
"""Return the argument parser for this program."""
169
parser = ArgumentParser(
170
"Update a remote host's download lxc template cache.")
172
'-d', '--dry-run', action='store_true', default=False,
173
help='Do not make changes.')
175
'-v', '--verbose', action='store_true', default=False,
176
help='Increase verbosity.')
178
'--dist', default="ubuntu", help="The distribution to update.")
180
'--variant', default="default", help="The variant to update.")
182
'user_host', help='The user@host to update.')
184
'release', help='The release to update.')
186
'arch', help='The architecture of the remote host')
188
'workspace', help='The path to the local dir to stage the update.')
189
args = parser.parse_args(argv)
194
"""Update the lxc download template cache for hosts on closed networks."""
195
args = parse_args(argv)
197
lxc_cache = LxcCache(
198
args.workspace, verbose=args.verbose, dry_run=args.dry_run)
199
new_system, data = lxc_cache.get_updates(
200
args.dist, args.release, args.arch, args.variant)
202
rootfs_path, meta_path = lxc_cache.get_lxc_data(new_system)
203
lxc_cache.put_lxc_data(
204
args.user_host, new_system, rootfs_path, meta_path)
205
lxc_cache.save_index(data)
206
except Exception as e:
208
print(getattr(e, 'output', ''))
210
traceback.print_tb(sys.exc_info()[2])
217
if __name__ == '__main__':
218
sys.exit(main(sys.argv[1:]))