Merge ~ltrager/maas-images:version_remove_copy into maas-images:master

Proposed by Lee Trager
Status: Merged
Merge reported by: Lee Trager
Merged at revision: 0c3ba47197755743bd936360f60c07c43ce41624
Proposed branch: ~ltrager/maas-images:version_remove_copy
Merge into: maas-images:master
Diff against target: 268 lines (+184/-9)
5 files modified
doc/copying-product-version.txt (+36/-0)
doc/removing-product-version.txt (+51/-0)
meph2/commands/flags.py (+23/-4)
meph2/commands/meph2_util.py (+65/-0)
meph2/util.py (+9/-5)
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
Review via email: mp+374577@code.launchpad.net

Commit message

Add the ability to remove or copy versions from a stream.

Description of the change

Example - remove a version from all Bionic images
./maas-images/bin/meph2-util remove-version ~/remove-version/ 20191004 release=bionic

Example - copy a version for all amd64 Bionic images
/maas-images/bin/meph2-util copy-version ~/copy-version/ 20191004 20191022 release=bionic arch=amd64

To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

LGTM

review: Approve
0c3ba47... by Lee Trager

Add documentation

Revision history for this message
Robert C Jennings (rcj) wrote :

Lee, the docs look good, thank you. Should we copy an image forward from $serial to $serial.1 to test this out? Or at least, how did you test this?

Revision history for this message
Lee Trager (ltrager) wrote :

For development and testing I created a local mirror of the images and ran remove-version and copy-version on it. I verified with diff that the modifications happened as expected.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/doc/copying-product-version.txt b/doc/copying-product-version.txt
2new file mode 100644
3index 0000000..6008d0b
4--- /dev/null
5+++ b/doc/copying-product-version.txt
6@@ -0,0 +1,36 @@
7+In the case that a bad image is published it may be quicker to copy a working
8+version to a new version than wait for a fixed version to appear at
9+cloud-images.ubuntu.com. This process is safer than just removing a broken
10+version as it mimics a new version being added to the stream. This guarantees
11+all clients, not just MAAS, will get the fixed image.
12+
13+It is also suggested to create a backup of the stream metadata in
14+maas-v3-images/streams/v1 so you can use diff to verify only versions you wish
15+to remove were removed.
16+
17+Basic usage:
18+meph2-util copy-version data_d from_version to_version
19+
20+data_d - The path to the directory containing the stream you wish to modify.
21+from_version - The version you wish to copy from
22+to_version - The version you wish to copy to
23+
24+Example - Copy 20191004 to 20191022 on all products
25+meph2-util copy-version /path/to/stream 20191004 20191022
26+
27+Filters:
28+You may also add filters to the copy-version command. Only products matching
29+those filters will have the specified version copied. Filters may be any field
30+described in the product.
31+
32+Example - Copy the version 20191004 to 20191022 on all AMD64 Bionic and Xenial
33+products.
34+meph2-util copy-version /path/to/stream 20191004 20191022 \
35+ 'release~(bionic|xenial)' arch=amd64
36+
37+Optional Arguments:
38+ -n, --dry-run - Only show what will be copied, do not modify the stream.
39+ -u, --no-sign - Do not sign the stream when done. A stream can be signed later
40+ with the meph2-util sign command.
41+ --keyring - Specify the keyring to use when verifying the stream. Defaults
42+ to /usr/share/keyrings/ubuntu-cloudimage-keyring.gpg
43diff --git a/doc/removing-product-version.txt b/doc/removing-product-version.txt
44new file mode 100644
45index 0000000..df8dfe6
46--- /dev/null
47+++ b/doc/removing-product-version.txt
48@@ -0,0 +1,51 @@
49+In the case that a bad image is published it may be quicker to remove it from
50+the stream than wait for a fixed version to appear at cloud-images.ubuntu.com.
51+MAAS will import the latest version available from the stream, if a version is
52+removed the previous version will be automatically retrieved.
53+
54+Before beginning make sure any import process which imports from
55+cloud-images.ubuntu.com is disabled so the image isn't recreated.
56+
57+It is also suggested to create a backup of the stream metadata in
58+maas-v3-images/streams/v1 so you can use diff to verify only versions you wish
59+to remove were removed.
60+
61+Basic usage:
62+meph2-util remove-version data_d version
63+
64+data_d - The path to the directory containing the stream you wish to modify.
65+version - The version of the product you wish to remove.
66+
67+Example - Remove the version 20191021 from all products
68+meph2-util remove-version /path/to/stream 20191021
69+
70+Filters:
71+You may also add filters to the remove-version command. Only products matching
72+those filters will have the specified version removed. Filters may be any field
73+described in the product.
74+
75+Example - Remove the version 20191021 from all AMD64 Bionic and Xenial
76+products.
77+meph2-util remove-version /path/to/stream 20191021 \
78+ 'release~(bionic|xenial)' arch=amd64
79+
80+Optional Arguments:
81+ -n, --dry-run - Only show what will be removed, do not modify the stream.
82+ -u, --no-sign - Do not sign the stream when done. A stream can be signed later
83+ with the meph2-util sign command.
84+ --keyring - Specify the keyring to use when verifying the stream. Defaults
85+ to /usr/share/keyrings/ubuntu-cloudimage-keyring.gpg
86+
87+Clean up:
88+meph2-util remove-version only modifies the stream metadata. The image files
89+themselves are left on the filesystem. To automatically remove them run the
90+following commands
91+
92+Find all orphaned files and write the list to /tmp/orphans:
93+meph2-util find-orphans /tmp/orphans /path/to/stream
94+
95+Delete all orphaned files:
96+meph2-util reap-orphans /tmp/orphans /path/to/stream --older 0
97+
98+Remove orphan file:
99+rm /tmp/orphans
100diff --git a/meph2/commands/flags.py b/meph2/commands/flags.py
101index e2cffa5..d0c3d61 100644
102--- a/meph2/commands/flags.py
103+++ b/meph2/commands/flags.py
104@@ -24,6 +24,8 @@ COMMON_FLAGS = {
105 'keyring': (('--keyring',),
106 {'help': 'gpg keyring to check sjson',
107 'default': DEF_KEYRING}),
108+ 'filters': ('filters', {'nargs': '*', 'default': []}),
109+ 'version': ('version', {'help': 'the version_id to promote.'}),
110 }
111
112 SUBCOMMANDS = {
113@@ -33,7 +35,7 @@ SUBCOMMANDS = {
114 COMMON_FLAGS['dry-run'], COMMON_FLAGS['no-sign'],
115 COMMON_FLAGS['keyring'],
116 COMMON_FLAGS['src'], COMMON_FLAGS['target'],
117- ('filters', {'nargs': '*', 'default': []}),
118+ COMMON_FLAGS['filters'],
119 ]
120 },
121 'import': {
122@@ -68,8 +70,7 @@ SUBCOMMANDS = {
123 {'help': 'do not copy files, only metadata [TEST_ONLY]',
124 'action': 'store_true', 'default': False}),
125 COMMON_FLAGS['src'], COMMON_FLAGS['target'],
126- ('version', {'help': 'the version_id to promote.'}),
127- ('filters', {'nargs': '+', 'default': []}),
128+ COMMON_FLAGS['version'], COMMON_FLAGS['filters'],
129 ]
130 },
131 'clean-md': {
132@@ -78,7 +79,7 @@ SUBCOMMANDS = {
133 COMMON_FLAGS['dry-run'], COMMON_FLAGS['no-sign'],
134 COMMON_FLAGS['keyring'],
135 ('max', {'type': int}), ('target', {}),
136- ('filters', {'nargs': '*', 'default': []}),
137+ COMMON_FLAGS['filters'],
138 ]
139 },
140 'find-orphans': {
141@@ -106,6 +107,24 @@ SUBCOMMANDS = {
142 COMMON_FLAGS['data_d'], COMMON_FLAGS['no-sign'],
143 ],
144 },
145+ 'remove-version': {
146+ 'help': 'Remove a version from a product',
147+ 'opts': [
148+ COMMON_FLAGS['dry-run'], COMMON_FLAGS['no-sign'],
149+ COMMON_FLAGS['keyring'], COMMON_FLAGS['data_d'],
150+ COMMON_FLAGS['version'], COMMON_FLAGS['filters'],
151+ ],
152+ },
153+ 'copy-version': {
154+ 'help': 'Copy a version of a product to a new version',
155+ 'opts': [
156+ COMMON_FLAGS['dry-run'], COMMON_FLAGS['no-sign'],
157+ COMMON_FLAGS['keyring'], COMMON_FLAGS['data_d'],
158+ ('from_version', {'help': 'the version_id to copy from.'}),
159+ ('to_version', {'help': 'the version_id to copy to.'}),
160+ COMMON_FLAGS['filters']
161+ ],
162+ },
163 }
164
165 # vi: ts=4 expandtab syntax=python
166diff --git a/meph2/commands/meph2_util.py b/meph2/commands/meph2_util.py
167index 4109634..0b114dc 100755
168--- a/meph2/commands/meph2_util.py
169+++ b/meph2/commands/meph2_util.py
170@@ -403,6 +403,71 @@ def main_sign(args):
171 return 0
172
173
174+def main_remove_version(args):
175+ filter_list = filters.get_filters(args.filters)
176+ product_streams = util.load_product_streams(args.data_d)
177+ resign = False
178+
179+ for product_stream in product_streams:
180+ product_stream_path = os.path.join(args.data_d, product_stream)
181+ content = util.load_content(product_stream_path)
182+ products = content['products']
183+ write_stream = False
184+ for product, data in products.items():
185+ if (
186+ filters.filter_dict(filter_list, data) and
187+ args.version in data['versions']):
188+ print('Removing %s from %s' % (args.version, product))
189+ if not args.dry_run:
190+ del data['versions'][args.version]
191+ resign = write_stream = True
192+ if write_stream:
193+ with open(product_stream_path, 'wb') as f:
194+ f.write(util.dump_data(content).strip())
195+ if resign:
196+ util.gen_index_and_sign(args.data_d, not args.no_sign)
197+ return 0
198+
199+
200+def main_copy_version(args):
201+ filter_list = filters.get_filters(args.filters)
202+ product_streams = util.load_product_streams(args.data_d)
203+ resign = False
204+
205+ for product_stream in product_streams:
206+ product_stream_path = os.path.join(args.data_d, product_stream)
207+ content = util.load_content(product_stream_path)
208+ products = content['products']
209+ write_stream = False
210+ for product, data in products.items():
211+ if (
212+ filters.filter_dict(filter_list, data) and
213+ args.from_version in data['versions']):
214+ print('Copying %s to %s in %s' % (
215+ args.from_version, args.to_version, product))
216+ if not args.dry_run:
217+ new_version = copy.deepcopy(
218+ data['versions'][args.from_version])
219+ for item in new_version['items'].values():
220+ old_path = os.path.join(args.data_d, item['path'])
221+ item['path'] = item['path'].replace(
222+ args.from_version, args.to_version)
223+ new_path = os.path.join(args.data_d, item['path'])
224+ if not os.path.exists(new_path):
225+ os.makedirs(
226+ os.path.dirname(new_path), exist_ok=True)
227+ shutil.copy(
228+ old_path, new_path, follow_symlinks=False)
229+ data['versions'][args.to_version] = new_version
230+ resign = write_stream = True
231+ if write_stream:
232+ with open(product_stream_path, 'wb') as f:
233+ f.write(util.dump_data(content).strip())
234+ if resign:
235+ util.gen_index_and_sign(args.data_d, not args.no_sign)
236+ return 0
237+
238+
239 def main_import(args):
240 """meph2-util import wraps the preferred command 'meph2-import'.
241
242diff --git a/meph2/util.py b/meph2/util.py
243index 017e5ef..f990eb9 100644
244--- a/meph2/util.py
245+++ b/meph2/util.py
246@@ -264,15 +264,19 @@ def dump_data(data, end_cr=True):
247 return bytestr
248
249
250+def load_content(path):
251+ if not os.path.exists(path):
252+ return {}
253+ with scontentsource.UrlContentSource(path) as tcs:
254+ return sutil.load_content(tcs.read())
255+
256+
257 def load_products(path, product_streams):
258 products = {}
259 for product_stream in product_streams:
260 product_stream_path = os.path.join(path, product_stream)
261- if os.path.exists(product_stream_path):
262- with scontentsource.UrlContentSource(
263- product_stream_path) as tcs:
264- product_listing = sutil.load_content(tcs.read())
265- products.update(product_listing['products'])
266+ product_listing = load_content(product_stream_path)
267+ products.update(product_listing['products'])
268 return products
269
270

Subscribers

People subscribed via source and target branches