Merge lp:~jamesodhunt/cloud-init/add-ci-tool into lp:~cloud-init-dev/cloud-init/trunk

Proposed by James Hunt
Status: Rejected
Rejected by: Scott Moser
Proposed branch: lp:~jamesodhunt/cloud-init/add-ci-tool
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 271 lines (+256/-0)
2 files modified
setup.py (+1/-0)
tools/ci-tool (+255/-0)
To merge this branch: bzr merge lp:~jamesodhunt/cloud-init/add-ci-tool
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+240288@code.launchpad.net

Description of the change

* Add ci-tool from lp:~smoser/cloud-init/ci-tool.

To post a comment you must log in.
Revision history for this message
James Hunt (jamesodhunt) wrote :

Hi Scott,

My plan is to split out a cloud-init-tools (or cloud-init-utils - keep the package name plural to be future-proof?) debian package once ci-tool is in the source package.

Revision history for this message
James Hunt (jamesodhunt) wrote :

I've just come across cloud-utils, so maybe ci-tool should live in that package?

Revision history for this message
Scott Moser (smoser) wrote :

Hello,
Thank you for taking the time to contribute to cloud-init. Cloud-init has moved its revision control system to git. As a result, we are marking all bzr merge proposals as 'rejected'. If you would like to re-submit this proposal for review, please do so by following the current HACKING documentation at http://cloudinit.readthedocs.io/en/latest/topics/hacking.html .

Unmerged revisions

1033. By James Hunt

* Add ci-tool from lp:~smoser/cloud-init/ci-tool.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'setup.py'
2--- setup.py 2014-09-02 16:18:21 +0000
3+++ setup.py 2014-10-31 15:42:24 +0000
4@@ -144,6 +144,7 @@
5 packages=setuptools.find_packages(exclude=['tests']),
6 scripts=['bin/cloud-init',
7 'tools/cloud-init-per',
8+ 'tools/ci-tool',
9 ],
10 license='GPLv3',
11 data_files=[(ETC + '/cloud', glob('config/*.cfg')),
12
13=== added file 'tools/ci-tool'
14--- tools/ci-tool 1970-01-01 00:00:00 +0000
15+++ tools/ci-tool 2014-10-31 15:42:24 +0000
16@@ -0,0 +1,255 @@
17+#!/usr/bin/python
18+# vi: ts=4 expandtab
19+#
20+# Copyright (C) 2012 Canonical Ltd.
21+#
22+# Author: Scott Moser <scott.moser@canonical.com>
23+#
24+# This program is free software: you can redistribute it and/or modify
25+# it under the terms of the GNU General Public License version 3, as
26+# published by the Free Software Foundation.
27+#
28+# This program is distributed in the hope that it will be useful,
29+# but WITHOUT ANY WARRANTY; without even the implied warranty of
30+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31+# GNU General Public License for more details.
32+#
33+# You should have received a copy of the GNU General Public License
34+# along with this program. If not, see <http://www.gnu.org/licenses/>.
35+import argparse
36+import os
37+import shutil
38+import sys
39+import yaml
40+
41+VERSION = "0.3.0"
42+SEED_DIR = "var/lib/cloud/seed"
43+SET_DS_CFG = 'etc/cloud/cloud.cfg.d/90_dpkg.cfg'
44+
45+VAR_LIB_CLOUD = "var/lib/cloud"
46+VAR_LOG = "var/log"
47+KNOWN_LOGS = ["cloud-init.log", "cloud-init-output.log"]
48+
49+KNOWN_DATASOURCES = [
50+ 'AltCloud',
51+ 'Azure',
52+ 'CloudStack',
53+ 'ConfigDrive',
54+ 'Ec2',
55+ 'MAAS',
56+ 'NoCloud',
57+ 'None',
58+ 'OpenNebula',
59+ 'OVF',
60+ 'SmartOS',
61+]
62+
63+DEFAULT_METADATA = "instance-id: nocloud-static\n"
64+DEFAULT_USERDATA = """
65+#cloud-config
66+password: passw0rd
67+chpasswd: { expire: False }
68+ssh_pwauth: True
69+""".lstrip()
70+
71+
72+def seed(args, seed_dir=SEED_DIR):
73+ sdir = os.path.join(args.target, seed_dir, args.seed)
74+ (userdata, metadata) = (DEFAULT_USERDATA, DEFAULT_METADATA)
75+
76+ if not os.path.isdir(sdir):
77+ os.makedirs(sdir)
78+ if args.userdata not in ("default", None):
79+ with open(args.userdata, "r") as fp:
80+ userdata = fp.read()
81+ if args.metadata:
82+ with open(args.metadata, "r") as fp:
83+ metadata = fp.read()
84+
85+ md = yaml.safe_load(metadata)
86+ if not args.no_set_hostname:
87+ hostname = md.get('local-hostname', md.get('hostname'))
88+ if hostname is not None:
89+ hfile = os.path.join(args.target, "etc/hostname")
90+ set_hostname(hostname, hostname_file=hfile)
91+
92+ with open(os.path.join(sdir, 'user-data'), "w") as fp:
93+ fp.write(userdata)
94+
95+ with open(os.path.join(sdir, 'meta-data'), "w") as fp:
96+ fp.write(metadata)
97+
98+
99+def set_hostname(hostname, hostname_file):
100+ with open(hostname_file, "w") as fp:
101+ fp.write(hostname + "\n")
102+
103+
104+def clean_lib(varlibcloud=VAR_LIB_CLOUD, full=False, purge=False):
105+ if purge:
106+ removes = [varlibcloud]
107+ else:
108+ bnames = ['instance', 'instances', 'seed', 'sem']
109+ if full:
110+ bnames.extend(('data', 'handlers', 'scripts',))
111+
112+ removes = [os.path.join(varlibcloud, f) for f in bnames]
113+
114+ for r in removes:
115+ rm_force(r)
116+
117+
118+def clean_logs(varlog=VAR_LOG, full=False):
119+ removes = [os.path.join(varlog, r) for r in KNOWN_LOGS]
120+
121+ if full:
122+ for bname in os.glob(os.path.join(varlog, 'cloud-init*')):
123+ removes.append(os.path.join(varlog, bname))
124+
125+ for r in removes:
126+ rm_force(r)
127+
128+
129+def reset(args):
130+ clean_lib(varlibcloud=os.path.join(args.target, VAR_LIB_CLOUD),
131+ full=args.full, purge=args.purge)
132+
133+ if args.logs:
134+ clean_logs(varlog=os.path.join(args.target, VAR_LOG),
135+ full=args.full)
136+
137+ return
138+
139+
140+def run(args):
141+ print(args)
142+
143+
144+def set_ds(args):
145+ # TODO: figure out the best way to handle target
146+ if args.config_file is None:
147+ cfg_path = os.path.join(args.target, SET_DS_CFG)
148+ elif args.config_file == "-":
149+ cfg_path = "-"
150+ elif args.target is not None:
151+ cfg_path = os.path.join(args.target, args.config_file)
152+ else:
153+ cfg_path = args.config_file
154+
155+ data = {'datasource_list': args.datasources}
156+ known_ds = [f.lower() for f in KNOWN_DATASOURCES]
157+ ds_list = []
158+ for ds in args.datasources:
159+ try:
160+ ds_list.append(KNOWN_DATASOURCES[known_ds.index(ds.lower())])
161+ except ValueError:
162+ # probably should warn here about unknown datasource
163+ ds_list.append(ds)
164+
165+ if cfg_path == "-":
166+ fp = sys.stdout
167+ else:
168+ fp = open(cfg_path, "w")
169+
170+ fp.write(yaml.dump({'datasource_list': ds_list}) + "\n")
171+
172+ if cfg_path != "-":
173+ fp.close()
174+
175+
176+def rm_force(path):
177+ if os.path.islink(path):
178+ os.unlink(path)
179+ elif os.path.isdir(path):
180+ shutil.rmtree(path)
181+ elif os.path.exists(path):
182+ os.unlink(path)
183+
184+
185+def main():
186+ parser = argparse.ArgumentParser()
187+
188+ # Top level args
189+ for (args, kwargs) in COMMON_ARGS:
190+ parser.add_argument(*args, **kwargs)
191+
192+ subparsers = parser.add_subparsers()
193+ for subcmd in sorted(SUBCOMMANDS.keys()):
194+ val = SUBCOMMANDS[subcmd]
195+ sparser = subparsers.add_parser(subcmd, help=val['help'])
196+ sparser.set_defaults(action=(val.get('func'), val['func']))
197+ for (args, kwargs) in val['opts']:
198+ sparser.add_argument(*args, **kwargs)
199+
200+ args = parser.parse_args()
201+ if not getattr(args, 'action', None):
202+ # http://bugs.python.org/issue16308
203+ parser.print_help()
204+ sys.exit(1)
205+
206+ (name, functor) = args.action
207+
208+ functor(args)
209+
210+
211+SUBCOMMANDS = {
212+ 'reset': {
213+ 'func': reset, 'opts': [],
214+ 'help': 'remove logs and state files',
215+ 'opts': [
216+ (('-F', '--full'),
217+ {'help': 'be more complete in cleanup',
218+ 'default': False, 'action': 'store_true'}),
219+ (('-P', '--purge'),
220+ {'help': 'remove all of state directory (/var/lib/cloud)',
221+ 'default': False, 'action': 'store_true'}),
222+ (('-l', '--logs'),
223+ {'help': 'remove log files', 'default': False,
224+ 'action': 'store_true'}),
225+ ]
226+ },
227+ 'run': {
228+ 'func': run, 'opts': [],
229+ 'help': 'execute cloud-init manually',
230+ },
231+ 'set-ds': {
232+ 'func': set_ds, 'opts': [],
233+ 'help': 'set the datasource',
234+ 'opts': [
235+ (('-f', '--config-file'),
236+ {'help': 'output to specified cloud-config file',
237+ 'default': None}),
238+ (('datasources',),
239+ {'nargs': '+', 'metavar': 'DataSource'}),
240+ ]
241+ },
242+ 'seed': {
243+ 'func': seed, 'help': 'populate the datasource',
244+ 'opts': [
245+ (('-s', '--seed'),
246+ {'action': 'store', 'default': 'nocloud-net',
247+ 'help': 'directory to populate with seed data',
248+ 'choices': ['nocloud-net', 'nocloud']}),
249+ (('--no-set-hostname',),
250+ {'action': 'store_true', 'default': False,
251+ 'help': 'do not attempt to set hostname based on metadata'}),
252+ (('userdata',),
253+ {'nargs': '?', 'default': None,
254+ 'help': 'use user-data from file', 'default': None}),
255+ (('metadata',),
256+ {'nargs': '?', 'default': None,
257+ 'help': 'use meta-data from file'}),
258+ ]
259+ },
260+}
261+
262+
263+COMMON_ARGS = [
264+ (('--version',), {'action': 'version', 'version': '%(prog)s ' + VERSION}),
265+ (('--verbose', '-v'), {'action': 'count', 'default': 0}),
266+ (('--target', '-t'), {'action': 'store', 'default': '/'}),
267+]
268+
269+
270+if __name__ == '__main__':
271+ sys.exit(main())