summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorJoshua Harlow <harlowja@gmail.com>2016-06-14 14:56:51 -0700
committerJoshua Harlow <harlowja@gmail.com>2016-06-14 14:56:51 -0700
commit93afde09d89e60c29dfd20790e30a06f031c82e1 (patch)
tree4789cac6212e03ae917852df923807b2cff53c13 /bin
parent632d56a23a947a23e18fa234d675e34a1a119593 (diff)
downloadvyos-cloud-init-93afde09d89e60c29dfd20790e30a06f031c82e1.tar.gz
vyos-cloud-init-93afde09d89e60c29dfd20790e30a06f031c82e1.zip
Make the bin/cloud-init an actual console entrypoint
This allows for the test_cli test to be more sane.
Diffstat (limited to 'bin')
-rwxr-xr-xbin/cloud-init689
1 files changed, 0 insertions, 689 deletions
diff --git a/bin/cloud-init b/bin/cloud-init
deleted file mode 100755
index 21c3a684..00000000
--- a/bin/cloud-init
+++ /dev/null
@@ -1,689 +0,0 @@
-#!/usr/bin/python
-# vi: ts=4 expandtab
-#
-# Copyright (C) 2012 Canonical Ltd.
-# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
-# Copyright (C) 2012 Yahoo! Inc.
-#
-# Author: Scott Moser <scott.moser@canonical.com>
-# Author: Juerg Haefliger <juerg.haefliger@hp.com>
-# Author: Joshua Harlow <harlowja@yahoo-inc.com>
-#
-# 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 json
-import os
-import sys
-import time
-import tempfile
-import traceback
-
-# This is more just for running from the bin folder so that
-# cloud-init binary can find the cloudinit module
-possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
- sys.argv[0]), os.pardir, os.pardir))
-if os.path.exists(os.path.join(possible_topdir, "cloudinit", "__init__.py")):
- sys.path.insert(0, possible_topdir)
-
-from cloudinit import patcher
-patcher.patch()
-
-from cloudinit import log as logging
-from cloudinit import netinfo
-from cloudinit import signal_handler
-from cloudinit import sources
-from cloudinit import stages
-from cloudinit import templater
-from cloudinit import util
-from cloudinit import reporting
-from cloudinit.reporting import events
-from cloudinit import version
-
-from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, PER_ONCE,
- CLOUD_CONFIG)
-
-
-# Pretty little cheetah formatted welcome message template
-WELCOME_MSG_TPL = ("Cloud-init v. ${version} running '${action}' at "
- "${timestamp}. Up ${uptime} seconds.")
-
-# Module section template
-MOD_SECTION_TPL = "cloud_%s_modules"
-
-# Things u can query on
-QUERY_DATA_TYPES = [
- 'data',
- 'data_raw',
- 'instance_id',
-]
-
-# Frequency shortname to full name
-# (so users don't have to remember the full name...)
-FREQ_SHORT_NAMES = {
- 'instance': PER_INSTANCE,
- 'always': PER_ALWAYS,
- 'once': PER_ONCE,
-}
-
-LOG = logging.getLogger()
-
-
-# Used for when a logger may not be active
-# and we still want to print exceptions...
-def print_exc(msg=''):
- if msg:
- sys.stderr.write("%s\n" % (msg))
- sys.stderr.write('-' * 60)
- sys.stderr.write("\n")
- traceback.print_exc(file=sys.stderr)
- sys.stderr.write('-' * 60)
- sys.stderr.write("\n")
-
-
-def welcome(action, msg=None):
- if not msg:
- msg = welcome_format(action)
- util.multi_log("%s\n" % (msg),
- console=False, stderr=True, log=LOG)
- return msg
-
-
-def welcome_format(action):
- tpl_params = {
- 'version': version.version_string(),
- 'uptime': util.uptime(),
- 'timestamp': util.time_rfc2822(),
- 'action': action,
- }
- return templater.render_string(WELCOME_MSG_TPL, tpl_params)
-
-
-def extract_fns(args):
- # Files are already opened so lets just pass that along
- # since it would of broke if it couldn't have
- # read that file already...
- fn_cfgs = []
- if args.files:
- for fh in args.files:
- # The realpath is more useful in logging
- # so lets resolve to that...
- fn_cfgs.append(os.path.realpath(fh.name))
- return fn_cfgs
-
-
-def run_module_section(mods, action_name, section):
- full_section_name = MOD_SECTION_TPL % (section)
- (which_ran, failures) = mods.run_section(full_section_name)
- total_attempted = len(which_ran) + len(failures)
- if total_attempted == 0:
- msg = ("No '%s' modules to run"
- " under section '%s'") % (action_name, full_section_name)
- sys.stderr.write("%s\n" % (msg))
- LOG.debug(msg)
- return []
- else:
- LOG.debug("Ran %s modules with %s failures",
- len(which_ran), len(failures))
- return failures
-
-
-def apply_reporting_cfg(cfg):
- if cfg.get('reporting'):
- reporting.update_configuration(cfg.get('reporting'))
-
-
-def main_init(name, args):
- deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK]
- if args.local:
- deps = [sources.DEP_FILESYSTEM]
-
- if not args.local:
- # See doc/kernel-cmdline.txt
- #
- # This is used in maas datasource, in "ephemeral" (read-only root)
- # environment where the instance netboots to iscsi ro root.
- # and the entity that controls the pxe config has to configure
- # the maas datasource.
- #
- # Could be used elsewhere, only works on network based (not local).
- root_name = "%s.d" % (CLOUD_CONFIG)
- target_fn = os.path.join(root_name, "91_kernel_cmdline_url.cfg")
- util.read_write_cmdline_url(target_fn)
-
- # Cloud-init 'init' stage is broken up into the following sub-stages
- # 1. Ensure that the init object fetches its config without errors
- # 2. Setup logging/output redirections with resultant config (if any)
- # 3. Initialize the cloud-init filesystem
- # 4. Check if we can stop early by looking for various files
- # 5. Fetch the datasource
- # 6. Connect to the current instance location + update the cache
- # 7. Consume the userdata (handlers get activated here)
- # 8. Construct the modules object
- # 9. Adjust any subsequent logging/output redirections using the modules
- # objects config as it may be different from init object
- # 10. Run the modules for the 'init' stage
- # 11. Done!
- if not args.local:
- w_msg = welcome_format(name)
- else:
- w_msg = welcome_format("%s-local" % (name))
- init = stages.Init(ds_deps=deps, reporter=args.reporter)
- # Stage 1
- init.read_cfg(extract_fns(args))
- # Stage 2
- outfmt = None
- errfmt = None
- try:
- LOG.debug("Closing stdin")
- util.close_stdin()
- (outfmt, errfmt) = util.fixup_output(init.cfg, name)
- except:
- util.logexc(LOG, "Failed to setup output redirection!")
- print_exc("Failed to setup output redirection!")
- if args.debug:
- # Reset so that all the debug handlers are closed out
- LOG.debug(("Logging being reset, this logger may no"
- " longer be active shortly"))
- logging.resetLogging()
- logging.setupLogging(init.cfg)
- apply_reporting_cfg(init.cfg)
-
- # Any log usage prior to setupLogging above did not have local user log
- # config applied. We send the welcome message now, as stderr/out have
- # been redirected and log now configured.
- welcome(name, msg=w_msg)
-
- # Stage 3
- try:
- init.initialize()
- except Exception:
- util.logexc(LOG, "Failed to initialize, likely bad things to come!")
- # Stage 4
- path_helper = init.paths
- mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK
-
- if mode == sources.DSMODE_NETWORK:
- existing = "trust"
- sys.stderr.write("%s\n" % (netinfo.debug_info()))
- LOG.debug(("Checking to see if files that we need already"
- " exist from a previous run that would allow us"
- " to stop early."))
- # no-net is written by upstart cloud-init-nonet when network failed
- # to come up
- stop_files = [
- os.path.join(path_helper.get_cpath("data"), "no-net"),
- ]
- existing_files = []
- for fn in stop_files:
- if os.path.isfile(fn):
- existing_files.append(fn)
-
- if existing_files:
- LOG.debug("[%s] Exiting. stop file %s existed",
- mode, existing_files)
- return (None, [])
- else:
- LOG.debug("Execution continuing, no previous run detected that"
- " would allow us to stop early.")
- else:
- existing = "check"
- if util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False):
- existing = "trust"
-
- init.purge_cache()
- # Delete the non-net file as well
- util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net"))
-
- # Stage 5
- try:
- init.fetch(existing=existing)
- # if in network mode, and the datasource is local
- # then work was done at that stage.
- if mode == sources.DSMODE_NETWORK and init.datasource.dsmode != mode:
- LOG.debug("[%s] Exiting. datasource %s in local mode",
- mode, init.datasource)
- return (None, [])
- except sources.DataSourceNotFoundException:
- # In the case of 'cloud-init init' without '--local' it is a bit
- # more likely that the user would consider it failure if nothing was
- # found. When using upstart it will also mentions job failure
- # in console log if exit code is != 0.
- if mode == sources.DSMODE_LOCAL:
- LOG.debug("No local datasource found")
- else:
- util.logexc(LOG, ("No instance datasource found!"
- " Likely bad things to come!"))
- if not args.force:
- init.apply_network_config(bring_up=not args.local)
- LOG.debug("[%s] Exiting without datasource in local mode", mode)
- if mode == sources.DSMODE_LOCAL:
- return (None, [])
- else:
- return (None, ["No instance datasource found."])
- else:
- LOG.debug("[%s] barreling on in force mode without datasource",
- mode)
-
- # Stage 6
- iid = init.instancify()
- LOG.debug("[%s] %s will now be targeting instance id: %s. new=%s",
- mode, name, iid, init.is_new_instance())
-
- init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL))
-
- if mode == sources.DSMODE_LOCAL:
- if init.datasource.dsmode != mode:
- LOG.debug("[%s] Exiting. datasource %s not in local mode.",
- mode, init.datasource)
- return (init.datasource, [])
- else:
- LOG.debug("[%s] %s is in local mode, will apply init modules now.",
- mode, init.datasource)
-
- # update fully realizes user-data (pulling in #include if necessary)
- init.update()
- # Stage 7
- try:
- # Attempt to consume the data per instance.
- # This may run user-data handlers and/or perform
- # url downloads and such as needed.
- (ran, _results) = init.cloudify().run('consume_data',
- init.consume_data,
- args=[PER_INSTANCE],
- freq=PER_INSTANCE)
- if not ran:
- # Just consume anything that is set to run per-always
- # if nothing ran in the per-instance code
- #
- # See: https://bugs.launchpad.net/bugs/819507 for a little
- # reason behind this...
- init.consume_data(PER_ALWAYS)
- except Exception:
- util.logexc(LOG, "Consuming user data failed!")
- return (init.datasource, ["Consuming user data failed!"])
-
- apply_reporting_cfg(init.cfg)
-
- # Stage 8 - re-read and apply relevant cloud-config to include user-data
- mods = stages.Modules(init, extract_fns(args), reporter=args.reporter)
- # Stage 9
- try:
- outfmt_orig = outfmt
- errfmt_orig = errfmt
- (outfmt, errfmt) = util.get_output_cfg(mods.cfg, name)
- if outfmt_orig != outfmt or errfmt_orig != errfmt:
- LOG.warn("Stdout, stderr changing to (%s, %s)", outfmt, errfmt)
- (outfmt, errfmt) = util.fixup_output(mods.cfg, name)
- except:
- util.logexc(LOG, "Failed to re-adjust output redirection!")
- logging.setupLogging(mods.cfg)
-
- # Stage 10
- return (init.datasource, run_module_section(mods, name, name))
-
-
-def main_modules(action_name, args):
- name = args.mode
- # Cloud-init 'modules' stages are broken up into the following sub-stages
- # 1. Ensure that the init object fetches its config without errors
- # 2. Get the datasource from the init object, if it does
- # not exist then that means the main_init stage never
- # worked, and thus this stage can not run.
- # 3. Construct the modules object
- # 4. Adjust any subsequent logging/output redirections using
- # the modules objects configuration
- # 5. Run the modules for the given stage name
- # 6. Done!
- w_msg = welcome_format("%s:%s" % (action_name, name))
- init = stages.Init(ds_deps=[], reporter=args.reporter)
- # Stage 1
- init.read_cfg(extract_fns(args))
- # Stage 2
- try:
- init.fetch(existing="trust")
- except sources.DataSourceNotFoundException:
- # There was no datasource found, theres nothing to do
- msg = ('Can not apply stage %s, no datasource found! Likely bad '
- 'things to come!' % name)
- util.logexc(LOG, msg)
- print_exc(msg)
- if not args.force:
- return [(msg)]
- # Stage 3
- mods = stages.Modules(init, extract_fns(args), reporter=args.reporter)
- # Stage 4
- try:
- LOG.debug("Closing stdin")
- util.close_stdin()
- util.fixup_output(mods.cfg, name)
- except:
- util.logexc(LOG, "Failed to setup output redirection!")
- if args.debug:
- # Reset so that all the debug handlers are closed out
- LOG.debug(("Logging being reset, this logger may no"
- " longer be active shortly"))
- logging.resetLogging()
- logging.setupLogging(mods.cfg)
- apply_reporting_cfg(init.cfg)
-
- # now that logging is setup and stdout redirected, send welcome
- welcome(name, msg=w_msg)
-
- # Stage 5
- return run_module_section(mods, name, name)
-
-
-def main_query(name, _args):
- raise NotImplementedError(("Action '%s' is not"
- " currently implemented") % (name))
-
-
-def main_single(name, args):
- # Cloud-init single stage is broken up into the following sub-stages
- # 1. Ensure that the init object fetches its config without errors
- # 2. Attempt to fetch the datasource (warn if it doesn't work)
- # 3. Construct the modules object
- # 4. Adjust any subsequent logging/output redirections using
- # the modules objects configuration
- # 5. Run the single module
- # 6. Done!
- mod_name = args.name
- w_msg = welcome_format(name)
- init = stages.Init(ds_deps=[], reporter=args.reporter)
- # Stage 1
- init.read_cfg(extract_fns(args))
- # Stage 2
- try:
- init.fetch(existing="trust")
- except sources.DataSourceNotFoundException:
- # There was no datasource found,
- # that might be bad (or ok) depending on
- # the module being ran (so continue on)
- util.logexc(LOG, ("Failed to fetch your datasource,"
- " likely bad things to come!"))
- print_exc(("Failed to fetch your datasource,"
- " likely bad things to come!"))
- if not args.force:
- return 1
- # Stage 3
- mods = stages.Modules(init, extract_fns(args), reporter=args.reporter)
- mod_args = args.module_args
- if mod_args:
- LOG.debug("Using passed in arguments %s", mod_args)
- mod_freq = args.frequency
- if mod_freq:
- LOG.debug("Using passed in frequency %s", mod_freq)
- mod_freq = FREQ_SHORT_NAMES.get(mod_freq)
- # Stage 4
- try:
- LOG.debug("Closing stdin")
- util.close_stdin()
- util.fixup_output(mods.cfg, None)
- except:
- util.logexc(LOG, "Failed to setup output redirection!")
- if args.debug:
- # Reset so that all the debug handlers are closed out
- LOG.debug(("Logging being reset, this logger may no"
- " longer be active shortly"))
- logging.resetLogging()
- logging.setupLogging(mods.cfg)
- apply_reporting_cfg(init.cfg)
-
- # now that logging is setup and stdout redirected, send welcome
- welcome(name, msg=w_msg)
-
- # Stage 5
- (which_ran, failures) = mods.run_single(mod_name,
- mod_args,
- mod_freq)
- if failures:
- LOG.warn("Ran %s but it failed!", mod_name)
- return 1
- elif not which_ran:
- LOG.warn("Did not run %s, does it exist?", mod_name)
- return 1
- else:
- # Guess it worked
- return 0
-
-
-def atomic_write_file(path, content, mode='w'):
- tf = None
- try:
- tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(path),
- delete=False, mode=mode)
- tf.write(content)
- tf.close()
- os.rename(tf.name, path)
- except Exception as e:
- if tf is not None:
- os.unlink(tf.name)
- raise e
-
-
-def atomic_write_json(path, data):
- return atomic_write_file(path, json.dumps(data, indent=1) + "\n")
-
-
-def status_wrapper(name, args, data_d=None, link_d=None):
- if data_d is None:
- data_d = os.path.normpath("/var/lib/cloud/data")
- if link_d is None:
- link_d = os.path.normpath("/run/cloud-init")
-
- status_path = os.path.join(data_d, "status.json")
- status_link = os.path.join(link_d, "status.json")
- result_path = os.path.join(data_d, "result.json")
- result_link = os.path.join(link_d, "result.json")
-
- util.ensure_dirs((data_d, link_d,))
-
- (_name, functor) = args.action
-
- if name == "init":
- if args.local:
- mode = "init-local"
- else:
- mode = "init"
- elif name == "modules":
- mode = "modules-%s" % args.mode
- else:
- raise ValueError("unknown name: %s" % name)
-
- modes = ('init', 'init-local', 'modules-config', 'modules-final')
-
- status = None
- if mode == 'init-local':
- for f in (status_link, result_link, status_path, result_path):
- util.del_file(f)
- else:
- try:
- status = json.loads(util.load_file(status_path))
- except:
- pass
-
- if status is None:
- nullstatus = {
- 'errors': [],
- 'start': None,
- 'finished': None,
- }
- status = {'v1': {}}
- for m in modes:
- status['v1'][m] = nullstatus.copy()
- status['v1']['datasource'] = None
-
- v1 = status['v1']
- v1['stage'] = mode
- v1[mode]['start'] = time.time()
-
- atomic_write_json(status_path, status)
- util.sym_link(os.path.relpath(status_path, link_d), status_link,
- force=True)
-
- try:
- ret = functor(name, args)
- if mode in ('init', 'init-local'):
- (datasource, errors) = ret
- if datasource is not None:
- v1['datasource'] = str(datasource)
- else:
- errors = ret
-
- v1[mode]['errors'] = [str(e) for e in errors]
-
- except Exception as e:
- util.logexc(LOG, "failed stage %s", mode)
- print_exc("failed run of stage %s" % mode)
- v1[mode]['errors'] = [str(e)]
-
- v1[mode]['finished'] = time.time()
- v1['stage'] = None
-
- atomic_write_json(status_path, status)
-
- if mode == "modules-final":
- # write the 'finished' file
- errors = []
- for m in modes:
- if v1[m]['errors']:
- errors.extend(v1[m].get('errors', []))
-
- atomic_write_json(result_path,
- {'v1': {'datasource': v1['datasource'],
- 'errors': errors}})
- util.sym_link(os.path.relpath(result_path, link_d), result_link,
- force=True)
-
- return len(v1[mode]['errors'])
-
-
-def main():
- parser = argparse.ArgumentParser()
-
- # Top level args
- parser.add_argument('--version', '-v', action='version',
- version='%(prog)s ' + (version.version_string()))
- parser.add_argument('--file', '-f', action='append',
- dest='files',
- help=('additional yaml configuration'
- ' files to use'),
- type=argparse.FileType('rb'))
- parser.add_argument('--debug', '-d', action='store_true',
- help=('show additional pre-action'
- ' logging (default: %(default)s)'),
- default=False)
- parser.add_argument('--force', action='store_true',
- help=('force running even if no datasource is'
- ' found (use at your own risk)'),
- dest='force',
- default=False)
-
- parser.set_defaults(reporter=None)
- subparsers = parser.add_subparsers()
-
- # Each action and its sub-options (if any)
- parser_init = subparsers.add_parser('init',
- help=('initializes cloud-init and'
- ' performs initial modules'))
- parser_init.add_argument("--local", '-l', action='store_true',
- help="start in local mode (default: %(default)s)",
- default=False)
- # This is used so that we can know which action is selected +
- # the functor to use to run this subcommand
- parser_init.set_defaults(action=('init', main_init))
-
- # These settings are used for the 'config' and 'final' stages
- parser_mod = subparsers.add_parser('modules',
- help=('activates modules using '
- 'a given configuration key'))
- parser_mod.add_argument("--mode", '-m', action='store',
- help=("module configuration name "
- "to use (default: %(default)s)"),
- default='config',
- choices=('init', 'config', 'final'))
- parser_mod.set_defaults(action=('modules', main_modules))
-
- # These settings are used when you want to query information
- # stored in the cloud-init data objects/directories/files
- parser_query = subparsers.add_parser('query',
- help=('query information stored '
- 'in cloud-init'))
- parser_query.add_argument("--name", '-n', action="store",
- help="item name to query on",
- required=True,
- choices=QUERY_DATA_TYPES)
- parser_query.set_defaults(action=('query', main_query))
-
- # This subcommand allows you to run a single module
- parser_single = subparsers.add_parser('single',
- help=('run a single module '))
- parser_single.set_defaults(action=('single', main_single))
- parser_single.add_argument("--name", '-n', action="store",
- help="module name to run",
- required=True)
- parser_single.add_argument("--frequency", action="store",
- help=("frequency of the module"),
- required=False,
- choices=list(FREQ_SHORT_NAMES.keys()))
- parser_single.add_argument("--report", action="store_true",
- help="enable reporting",
- required=False)
- parser_single.add_argument("module_args", nargs="*",
- metavar='argument',
- help=('any additional arguments to'
- ' pass to this module'))
- parser_single.set_defaults(action=('single', main_single))
-
- args = parser.parse_args()
-
- # Setup basic logging to start (until reinitialized)
- # iff in debug mode...
- if args.debug:
- logging.setupBasicLogging()
-
- # Setup signal handlers before running
- signal_handler.attach_handlers()
-
- if not hasattr(args, 'action'):
- parser.error('too few arguments')
- (name, functor) = args.action
- if name in ("modules", "init"):
- functor = status_wrapper
-
- report_on = True
- if name == "init":
- if args.local:
- rname, rdesc = ("init-local", "searching for local datasources")
- else:
- rname, rdesc = ("init-network",
- "searching for network datasources")
- elif name == "modules":
- rname, rdesc = ("modules-%s" % args.mode,
- "running modules for %s" % args.mode)
- elif name == "single":
- rname, rdesc = ("single/%s" % args.name,
- "running single module %s" % args.name)
- report_on = args.report
-
- args.reporter = events.ReportEventStack(
- rname, rdesc, reporting_enabled=report_on)
- with args.reporter:
- return util.log_time(
- logfunc=LOG.debug, msg="cloud-init mode '%s'" % name,
- get_uptime=True, func=functor, args=(name, args))
-
-
-if __name__ == '__main__':
- sys.exit(main())