forked from openedx-unsupported/devstack
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsnapshot.py
More file actions
executable file
·127 lines (113 loc) · 4.94 KB
/
Copy pathsnapshot.py
File metadata and controls
executable file
·127 lines (113 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
#!/usr/bin/env python
"""
Script to capture a snapshot of the current devstack images, repositories,
and volume content to tarballs for no-network installation. To be run while
devstack is running (otherwise volume content can't be accessed).
"""
from __future__ import absolute_import, print_function, unicode_literals
import argparse
import json
import os
import re
from shutil import copyfile
from subprocess import STDOUT, CalledProcessError, check_output
import yaml
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEVSTACK_WORKSPACE = os.path.dirname(REPO_ROOT)
REPO_SCRIPT = os.path.join(REPO_ROOT, 'repo.sh')
# Use this minimal container image to fetch volume content
BACKUP_IMAGE = 'alpine:latest'
def make_directories(output_dir):
"""
Create any of the output directories that don't already exist.
"""
if not os.path.exists(output_dir):
os.mkdir(output_dir)
for dir_name in ('images', 'repositories', 'volumes'):
path = os.path.join(output_dir, dir_name)
if not os.path.exists(path):
os.mkdir(path)
def archive_repos(output_dir):
"""
Create tarballs for each of the relevant repositories in DEVSTACK_WORKSPACE
"""
with open('repo.sh', 'r') as f:
script = f.read()
prefix = r'https://github\.com/edx/'
suffix = r'\.git'
repos = re.findall(r'{}[^\.]+{}'.format(prefix, suffix), script)
dirs = [repo[len(prefix) - 1:1 - len(suffix)] for repo in repos if 'edx-themes' not in repo]
dirs.append('devstack')
repositories_dir = os.path.join(output_dir, 'repositories')
cwd = os.getcwd()
os.chdir(DEVSTACK_WORKSPACE)
for directory in dirs:
print('Archiving {}'.format(directory))
output = os.path.join(repositories_dir, '{}.tar.gz'.format(directory))
check_output(['tar', 'czf', output, directory], stderr=STDOUT)
os.chdir(cwd)
def process_compose_file(filename, output_dir):
"""
Go through the given docker-compose YAML file and save any of the
referenced Docker images and data volumes to tarballs.
"""
images_dir = os.path.join(output_dir, 'images')
volumes_dir = os.path.join(output_dir, 'volumes')
compose_path = os.path.join(REPO_ROOT, filename)
with open(compose_path, 'r') as f:
devstack = yaml.safe_load(f.read())
volume_list = []
services = devstack['services']
saved_images = set()
for service_name in services:
service = services[service_name]
image = service['image']
image = re.sub(r'\$.*', 'latest', image)
container_name = service['container_name']
# Don't save the same image twice, like edxapp for lms and studio
if image not in saved_images:
output = os.path.join(images_dir, '{}.tar'.format(service_name))
print('Saving image {}'.format(service_name))
check_output(['docker', 'save', '--output', output, image],
stderr=STDOUT)
check_output(['gzip', output], stderr=STDOUT)
saved_images.add(image)
if 'volumes' in service:
volumes = service['volumes']
for volume in volumes:
if volume[0] == '.':
# Mount of a host directory, skip it
continue
parts = volume.split(':')
volume_name = parts[0]
volume_path = parts[1]
tarball = '{}.tar.gz'.format(volume_name)
volume_list.append({'container': container_name,
'path': volume_path, 'tarball': tarball})
print('Saving volume {}'.format(volume_name))
check_output(['docker', 'run', '--rm', '--volumes-from', container_name, '-v',
'{}:/backup'.format(volumes_dir), BACKUP_IMAGE, 'tar', 'czf',
'/backup/{}'.format(tarball), volume_path], stderr=STDOUT)
print('Saving image alpine')
output = os.path.join(images_dir, 'alpine.tar')
check_output(['docker', 'save', '--output', output, BACKUP_IMAGE], stderr=STDOUT)
check_output(['gzip', output], stderr=STDOUT)
print('Saving volume metadata')
with open(os.path.join(volumes_dir, 'volumes.json'), 'w') as f:
f.write(json.dumps(volume_list))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('output_dir', help='The directory in which to create the devstack snapshot')
args = parser.parse_args()
output_dir = os.path.abspath(args.output_dir)
make_directories(output_dir)
try:
archive_repos(output_dir)
process_compose_file('docker-compose.yml', output_dir)
except CalledProcessError as e:
print(e.output)
raise
copyfile(os.path.join(REPO_ROOT, 'scripts', 'extract_snapshot_linux.sh'),
os.path.join(output_dir, 'linux.sh'))
copyfile(os.path.join(REPO_ROOT, 'scripts', 'extract_snapshot_mac.sh'),
os.path.join(output_dir, 'mac.sh'))