← Back to team overview

cloud-init-dev team mailing list archive

[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