cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #00561
[Merge] lp:~jamesodhunt/cloud-init/add-ci-tool into lp:cloud-init
James Hunt has proposed merging lp:~jamesodhunt/cloud-init/add-ci-tool into lp:cloud-init.
Requested reviews:
cloud init development team (cloud-init-dev)
For more details, see:
https://code.launchpad.net/~jamesodhunt/cloud-init/add-ci-tool/+merge/240288
* Add ci-tool from lp:~smoser/cloud-init/ci-tool.
--
https://code.launchpad.net/~jamesodhunt/cloud-init/add-ci-tool/+merge/240288
Your team cloud init development team is requested to review the proposed merge of lp:~jamesodhunt/cloud-init/add-ci-tool into lp:cloud-init.
=== modified file 'setup.py'
--- setup.py 2014-09-02 16:18:21 +0000
+++ setup.py 2014-10-31 15:36:04 +0000
@@ -144,6 +144,7 @@
packages=setuptools.find_packages(exclude=['tests']),
scripts=['bin/cloud-init',
'tools/cloud-init-per',
+ 'tools/ci-tool',
],
license='GPLv3',
data_files=[(ETC + '/cloud', glob('config/*.cfg')),
=== added file 'tools/ci-tool'
--- tools/ci-tool 1970-01-01 00:00:00 +0000
+++ tools/ci-tool 2014-10-31 15:36:04 +0000
@@ -0,0 +1,255 @@
+#!/usr/bin/python
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2012 Canonical Ltd.
+#
+# Author: Scott Moser <scott.moser@xxxxxxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3, as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import argparse
+import os
+import shutil
+import sys
+import yaml
+
+VERSION = "0.3.0"
+SEED_DIR = "var/lib/cloud/seed"
+SET_DS_CFG = 'etc/cloud/cloud.cfg.d/90_dpkg.cfg'
+
+VAR_LIB_CLOUD = "var/lib/cloud"
+VAR_LOG = "var/log"
+KNOWN_LOGS = ["cloud-init.log", "cloud-init-output.log"]
+
+KNOWN_DATASOURCES = [
+ 'AltCloud',
+ 'Azure',
+ 'CloudStack',
+ 'ConfigDrive',
+ 'Ec2',
+ 'MAAS',
+ 'NoCloud',
+ 'None',
+ 'OpenNebula',
+ 'OVF',
+ 'SmartOS',
+]
+
+DEFAULT_METADATA = "instance-id: nocloud-static\n"
+DEFAULT_USERDATA = """
+#cloud-config
+password: passw0rd
+chpasswd: { expire: False }
+ssh_pwauth: True
+""".lstrip()
+
+
+def seed(args, seed_dir=SEED_DIR):
+ sdir = os.path.join(args.target, seed_dir, args.seed)
+ (userdata, metadata) = (DEFAULT_USERDATA, DEFAULT_METADATA)
+
+ if not os.path.isdir(sdir):
+ os.makedirs(sdir)
+ if args.userdata not in ("default", None):
+ with open(args.userdata, "r") as fp:
+ userdata = fp.read()
+ if args.metadata:
+ with open(args.metadata, "r") as fp:
+ metadata = fp.read()
+
+ md = yaml.safe_load(metadata)
+ if not args.no_set_hostname:
+ hostname = md.get('local-hostname', md.get('hostname'))
+ if hostname is not None:
+ hfile = os.path.join(args.target, "etc/hostname")
+ set_hostname(hostname, hostname_file=hfile)
+
+ with open(os.path.join(sdir, 'user-data'), "w") as fp:
+ fp.write(userdata)
+
+ with open(os.path.join(sdir, 'meta-data'), "w") as fp:
+ fp.write(metadata)
+
+
+def set_hostname(hostname, hostname_file):
+ with open(hostname_file, "w") as fp:
+ fp.write(hostname + "\n")
+
+
+def clean_lib(varlibcloud=VAR_LIB_CLOUD, full=False, purge=False):
+ if purge:
+ removes = [varlibcloud]
+ else:
+ bnames = ['instance', 'instances', 'seed', 'sem']
+ if full:
+ bnames.extend(('data', 'handlers', 'scripts',))
+
+ removes = [os.path.join(varlibcloud, f) for f in bnames]
+
+ for r in removes:
+ rm_force(r)
+
+
+def clean_logs(varlog=VAR_LOG, full=False):
+ removes = [os.path.join(varlog, r) for r in KNOWN_LOGS]
+
+ if full:
+ for bname in os.glob(os.path.join(varlog, 'cloud-init*')):
+ removes.append(os.path.join(varlog, bname))
+
+ for r in removes:
+ rm_force(r)
+
+
+def reset(args):
+ clean_lib(varlibcloud=os.path.join(args.target, VAR_LIB_CLOUD),
+ full=args.full, purge=args.purge)
+
+ if args.logs:
+ clean_logs(varlog=os.path.join(args.target, VAR_LOG),
+ full=args.full)
+
+ return
+
+
+def run(args):
+ print(args)
+
+
+def set_ds(args):
+ # TODO: figure out the best way to handle target
+ if args.config_file is None:
+ cfg_path = os.path.join(args.target, SET_DS_CFG)
+ elif args.config_file == "-":
+ cfg_path = "-"
+ elif args.target is not None:
+ cfg_path = os.path.join(args.target, args.config_file)
+ else:
+ cfg_path = args.config_file
+
+ data = {'datasource_list': args.datasources}
+ known_ds = [f.lower() for f in KNOWN_DATASOURCES]
+ ds_list = []
+ for ds in args.datasources:
+ try:
+ ds_list.append(KNOWN_DATASOURCES[known_ds.index(ds.lower())])
+ except ValueError:
+ # probably should warn here about unknown datasource
+ ds_list.append(ds)
+
+ if cfg_path == "-":
+ fp = sys.stdout
+ else:
+ fp = open(cfg_path, "w")
+
+ fp.write(yaml.dump({'datasource_list': ds_list}) + "\n")
+
+ if cfg_path != "-":
+ fp.close()
+
+
+def rm_force(path):
+ if os.path.islink(path):
+ os.unlink(path)
+ elif os.path.isdir(path):
+ shutil.rmtree(path)
+ elif os.path.exists(path):
+ os.unlink(path)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ # Top level args
+ for (args, kwargs) in COMMON_ARGS:
+ parser.add_argument(*args, **kwargs)
+
+ subparsers = parser.add_subparsers()
+ for subcmd in sorted(SUBCOMMANDS.keys()):
+ val = SUBCOMMANDS[subcmd]
+ sparser = subparsers.add_parser(subcmd, help=val['help'])
+ sparser.set_defaults(action=(val.get('func'), val['func']))
+ for (args, kwargs) in val['opts']:
+ sparser.add_argument(*args, **kwargs)
+
+ args = parser.parse_args()
+ if not getattr(args, 'action', None):
+ # http://bugs.python.org/issue16308
+ parser.print_help()
+ sys.exit(1)
+
+ (name, functor) = args.action
+
+ functor(args)
+
+
+SUBCOMMANDS = {
+ 'reset': {
+ 'func': reset, 'opts': [],
+ 'help': 'remove logs and state files',
+ 'opts': [
+ (('-F', '--full'),
+ {'help': 'be more complete in cleanup',
+ 'default': False, 'action': 'store_true'}),
+ (('-P', '--purge'),
+ {'help': 'remove all of state directory (/var/lib/cloud)',
+ 'default': False, 'action': 'store_true'}),
+ (('-l', '--logs'),
+ {'help': 'remove log files', 'default': False,
+ 'action': 'store_true'}),
+ ]
+ },
+ 'run': {
+ 'func': run, 'opts': [],
+ 'help': 'execute cloud-init manually',
+ },
+ 'set-ds': {
+ 'func': set_ds, 'opts': [],
+ 'help': 'set the datasource',
+ 'opts': [
+ (('-f', '--config-file'),
+ {'help': 'output to specified cloud-config file',
+ 'default': None}),
+ (('datasources',),
+ {'nargs': '+', 'metavar': 'DataSource'}),
+ ]
+ },
+ 'seed': {
+ 'func': seed, 'help': 'populate the datasource',
+ 'opts': [
+ (('-s', '--seed'),
+ {'action': 'store', 'default': 'nocloud-net',
+ 'help': 'directory to populate with seed data',
+ 'choices': ['nocloud-net', 'nocloud']}),
+ (('--no-set-hostname',),
+ {'action': 'store_true', 'default': False,
+ 'help': 'do not attempt to set hostname based on metadata'}),
+ (('userdata',),
+ {'nargs': '?', 'default': None,
+ 'help': 'use user-data from file', 'default': None}),
+ (('metadata',),
+ {'nargs': '?', 'default': None,
+ 'help': 'use meta-data from file'}),
+ ]
+ },
+}
+
+
+COMMON_ARGS = [
+ (('--version',), {'action': 'version', 'version': '%(prog)s ' + VERSION}),
+ (('--verbose', '-v'), {'action': 'count', 'default': 0}),
+ (('--target', '-t'), {'action': 'store', 'default': '/'}),
+]
+
+
+if __name__ == '__main__':
+ sys.exit(main())
Follow ups