def warmup(self):
        """ initialize variables necessary to perform a sprinter action """
        self.logger.debug("Warming up...")

        try:
            if not isinstance(self.source, Manifest) and self.source:
                self.source = load_manifest(self.source)
            if not isinstance(self.target, Manifest) and self.target:
                self.target = load_manifest(self.target)
            self.main_manifest = self.target or self.source
        except lib.BadCredentialsException:
            e = sys.exc_info()[1]
            self.logger.error(str(e))
            raise SprinterException(
                "Fatal error! Bad credentials to grab manifest!")

        if not getattr(self, 'namespace', None):
            if self.target:
                self.namespace = self.target.namespace
            elif not self.namespace and self.source:
                self.namespace = self.source.namespace
            else:
                raise SprinterException(
                    "No environment name has been specified!")

        self.directory_root = self.custom_directory_root

        if not self.directory:
            if not self.directory_root:
                self.directory_root = os.path.join(self.root, self.namespace)

            self.directory = Directory(self.directory_root,
                                       shell_util_path=self.shell_util_path)

        if not self.injections:
            self.injections = Injections(
                wrapper="%s_%s" %
                (self.sprinter_namespace.upper(), self.namespace),
                override="SPRINTER_OVERRIDES")
        if not self.global_injections:
            self.global_injections = Injections(
                wrapper="%s" % self.sprinter_namespace.upper() + "GLOBALS",
                override="SPRINTER_OVERRIDES")
        # append the bin, in the case sandboxes are necessary to
        # execute commands further down the sprinter lifecycle
        os.environ['PATH'] = self.directory.bin_path(
        ) + ":" + os.environ['PATH']
        self.warmed_up = True
Beispiel #2
0
    def warmup(self):
        """ initialize variables necessary to perform a sprinter action """
        self.logger.debug("Warming up...")

        try:
            if not isinstance(self.source, Manifest) and self.source:
                self.source = load_manifest(self.source)
            if not isinstance(self.target, Manifest) and self.target:
                self.target = load_manifest(self.target)
            self.main_manifest = self.target or self.source
        except lib.BadCredentialsException:
            e = sys.exc_info()[1]
            self.logger.error(str(e))
            raise SprinterException("Fatal error! Bad credentials to grab manifest!")

        if not getattr(self, 'namespace', None):
            if self.target:
                self.namespace = self.target.namespace
            elif not self.namespace and self.source:
                self.namespace = self.source.namespace
            else:
                raise SprinterException("No environment name has been specified!")

        self.directory_root = self.custom_directory_root

        if not self.directory:
            if not self.directory_root:
                self.directory_root = os.path.join(self.root, self.namespace)

            self.directory = Directory(self.directory_root,
                                       shell_util_path=self.shell_util_path)

        if not self.injections:
            self.injections = Injections(wrapper="%s_%s" % (self.sprinter_namespace.upper(),
                                                            self.namespace),
                                         override="SPRINTER_OVERRIDES")
        if not self.global_injections:
            self.global_injections = Injections(wrapper="%s" % self.sprinter_namespace.upper() + "GLOBALS",
                                                override="SPRINTER_OVERRIDES")
        # append the bin, in the case sandboxes are necessary to
        # execute commands further down the sprinter lifecycle
        os.environ['PATH'] = self.directory.bin_path() + ":" + os.environ['PATH']
        self.warmed_up = True
Beispiel #3
0
def parse_args(argv, Environment=Environment):
    options = docopt(
        __doc__,
        argv=argv,
        version=pkg_resources.get_distribution('sprinter').version)
    logging_level = logging.DEBUG if options['--verbose'] else logging.INFO
    # start processing commands
    env = Environment(logging_level=logging_level,
                      ignore_errors=options['--ignore-errors'])
    try:
        if options['install']:
            target = options['<environment_source>']

            def handle_install_shutdown(signal, frame):
                if env.phase == PHASE.INSTALL:
                    print("Removing install...")
                    env.directory.remove()
                    env.clear_all()
                signal_handler(signal, frame)

            signal.signal(signal.SIGINT, handle_install_shutdown)
            if options['--username'] or options['--auth']:
                options = get_credentials(options, parse_domain(target))
                target = manifest.load_manifest(
                    target,
                    username=options['<username>'],
                    password=options['<password>'],
                    verify_certificate=(
                        not options['--allow-bad-certificate']))
            else:
                target = manifest.load_manifest(
                    target,
                    verify_certificate=(
                        not options['--allow-bad-certificate']))
            env.target = target
            if options['--namespace']:
                env.namespace = options['--namespace']
            if options['--local']:
                env.do_inject_environment_config = False
                env.custom_directory_root = os.path.abspath(
                    os.path.expanduser(options['--local']))
            env.install()

        elif options['update']:
            target = options['<environment_name>']
            env.directory = Directory(os.path.join(env.root, target),
                                      shell_util_path=env.shell_util_path)
            env.source = manifest.load_manifest(env.directory.manifest_path,
                                                do_inherit=False)
            use_auth = options['--username'] or options['--auth']
            if use_auth:
                options = get_credentials(options, target)
            env.target = manifest.load_manifest(
                env.source.source(),
                username=options['<username>'] if use_auth else None,
                password=options['<password>'] if use_auth else None,
                verify_certificate=(not options['--allow-bad-certificate']))
            env.update(reconfigure=options['--reconfigure'])

        elif options["remove"]:
            env.directory = Directory(os.path.join(
                env.root, options['<environment_name>']),
                                      shell_util_path=env.shell_util_path)
            env.source = manifest.load_manifest(
                env.directory.manifest_path,
                namespace=options['<environment_name>'],
                do_inherit=False)
            env.remove()

        elif options['deactivate']:
            env.directory = Directory(os.path.join(
                env.root, options['<environment_name>']),
                                      shell_util_path=env.shell_util_path)
            env.source = manifest.load_manifest(
                env.directory.manifest_path,
                namespace=options['<environment_name>'],
                do_inherit=False)
            env.deactivate()

        elif options['activate']:
            env.directory = Directory(os.path.join(
                env.root, options['<environment_name>']),
                                      shell_util_path=env.shell_util_path)
            env.source = manifest.load_manifest(
                env.directory.manifest_path,
                namespace=options['<environment_name>'],
                do_inherit=False)
            env.activate()

        elif options['list']:
            for _env in os.listdir(env.root):
                if _env != ".global":
                    print(_env)

        elif options['validate']:
            if options['--username'] or options['--auth']:
                options = get_credentials(options, parse_domain(target))
                target = manifest.load_manifest(
                    options['<environment_source>'],
                    username=options['<username>'],
                    password=options['<password>'],
                    verify_certificate=(
                        not options['--allow-bad-certificate']))
            env.target = options['<environment_source>']
            env.validate()
            if not env.error_occured:
                print("No errors! Manifest is valid!")
            else:
                "Manifest is invalid! Please see errors above."
        elif options['globals']:
            if options['--reconfigure']:
                configure_config(env.global_config, reconfigure=True)
                write_config(env.global_config, env.global_config_path)
            else:
                print_global_config(env.global_config)
    except BadCredentialsException:
        e = sys.exc_info()[1]
        raise e
    except ManifestException:
        e = sys.exc_info()[1]
        env.log_error(str(e))
        env.logger.info("Error occured when attempting to load manifest!")
        env.logger.info("Writing debug output to /tmp/sprinter.log")
        env.write_debug_log("/tmp/sprinter.log")
    except Exception:
        e = sys.exc_info()[1]
        env.log_error(str(e))
        env.logger.info("""
=====================================================================
the sprinter action failed! Writing debug output to /tmp/sprinter.log
        """)
        env.write_debug_log("/tmp/sprinter.log")
        if env.message_failure():
            env.logger.info(env.message_failure())
        env.logger.info("""
=====================================================================
        """.strip())
        raise
class Environment(object):

    source = None  # the path to the source handle, the handle itself, or a manifest instance
    target = None  # the path to the target handle, the handle itself, or a manifest instance
    namespace = None  # the namespace of the environment
    custom_directory_root = None  # the root to install directories too
    do_inject_environment_config = True  # inject configuration into shells
    sprinter_namespace = None  # the namespace to make installs with. this affects:
    phase = None  # the phase currently running
    # the prefix added to injections
    # the libraries that environment utilizes
    directory = None  # handles interactions with the environment directory
    injections = None  # handles injections
    global_injections = None  # handles injections for the global sprinter configuration
    # variables typically populated programatically
    warmed_up = False  # returns true if the environment is ready for environments
    shell_util_path = None  # the path to the shell utils file
    error_occured = False
    _errors = []  # list to keep all the errors
    sandboxes = []  # a list of package managers to sandbox (brew)
    # specifies where to get the global sprinter root
    global_config = None  # configuration file, which defaults to loading from SPRINTER_ROOT/.global/config.cfg
    ignore_errors = False  # ignore errors in features

    def __init__(self,
                 logger=None,
                 logging_level=logging.INFO,
                 root=None,
                 sprinter_namespace=None,
                 global_config=None,
                 ignore_errors=False):

        # base logging object to log instances
        self.logger = logger or self._build_logger(level=logging_level)
        if logging_level == logging.DEBUG:
            self.logger.info("Starting in debug mode...")

        # the sprinter namespace
        self.sprinter_namespace = sprinter_namespace or 'sprinter'

        # the root directory which sprinter installs sandboxable files too
        self.root = root or os.path.expanduser(
            os.path.join("~", ".%s" % self.sprinter_namespace))

        self.ignore_errors = ignore_errors

        # path to the directory to install global files
        self.global_path = os.path.join(self.root, ".global")
        self.global_config_path = os.path.join(self.global_path, "config.cfg")
        self.global_config = global_config or load_global_config(
            self.global_config_path)

        self.shell_util_path = os.path.join(self.global_path, "utils.sh")
        self.main_manifest = None

        # a dictionary of the errors associated with features.
        # The key is a tuple of feature name and formula, while the value is an instance.
        self._error_dict = defaultdict(list)

    @warmup
    def install(self):
        """ Install the environment """
        self.phase = PHASE.INSTALL
        if not self.directory.new:
            self.logger.info("Namespace %s directory already exists!" %
                             self.namespace)
            self.source = load_manifest(self.directory.manifest_path)
            return self.update()
        try:
            self.logger.info("Installing environment %s..." % self.namespace)
            self.directory.initialize()
            self.install_sandboxes()
            self.instantiate_features()
            self.grab_inputs()
            self._specialize()
            for feature in self.features.run_order:
                self.run_action(feature, 'sync')
            self.inject_environment_config()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            self.logger.info("An error occured during installation!")
            if not self.ignore_errors:
                self.clear_all()
                self.logger.info("Removing installation %s..." %
                                 self.namespace)
                self.directory.remove()
                et, ei, tb = sys.exc_info()
                reraise(et, ei, tb)

    @warmup
    @install_required
    def update(self, reconfigure=False):
        """ update the environment """
        try:
            self.phase = PHASE.UPDATE
            self.logger.info("Updating environment %s..." % self.namespace)
            self.install_sandboxes()
            self.instantiate_features()
            # We don't grab inputs, only on install
            # updates inputs are grabbed on demand
            # self.grab_inputs(reconfigure=reconfigure)
            if reconfigure:
                self.grab_inputs(reconfigure=True)
            else:
                self._copy_source_to_target()
            self._specialize(reconfigure=reconfigure)
            for feature in self.features.run_order:
                self.run_action(feature, 'sync')
            self.inject_environment_config()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    @install_required
    def remove(self):
        """ remove the environment """
        try:
            self.phase = PHASE.REMOVE
            self.logger.info("Removing environment %s..." % self.namespace)
            self.instantiate_features()
            self._specialize()
            for feature in self.features.run_order:
                try:
                    self.run_action(feature, 'sync')
                except FormulaException:
                    # continue trying to removal any remaining features.
                    pass
            self.clear_all()
            self.directory.remove()
            self.injections.commit()
            if self.error_occured:
                self.logger.error(warning_template)
                self.logger.error(REMOVE_WARNING)
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    @install_required
    def deactivate(self):
        """ deactivate the environment """
        try:
            self.phase = PHASE.DEACTIVATE
            self.logger.info("Deactivating environment %s..." % self.namespace)
            self.directory.rewrite_config = False
            self.instantiate_features()
            self._specialize()
            for feature in self.features.run_order:
                self.logger.info("Deactivating %s..." % feature[0])
                self.run_action(feature, 'deactivate')
            self.clear_all()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    @install_required
    def activate(self):
        """ activate the environment """
        try:
            self.phase = PHASE.ACTIVATE
            self.logger.info("Activating environment %s..." % self.namespace)
            self.directory.rewrite_config = False
            self.instantiate_features()
            self._specialize()
            for feature in self.features.run_order:
                self.logger.info("Activating %s..." % feature[0])
                self.run_action(feature, 'activate')
            self.inject_environment_config()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    def validate(self):
        """ Validate the target environment """
        self.phase = PHASE.VALIDATE
        self.logger.info("Validating %s..." % self.namespace)
        self.instantiate_features()
        context_dict = {}
        if self.target:
            for s in self.target.formula_sections():
                context_dict["%s:root_dir" %
                             s] = self.directory.install_directory(s)
                context_dict['config:root_dir'] = self.directory.root_dir
                context_dict['config:node'] = system.NODE
                self.target.add_additional_context(context_dict)
        for feature in self.features.run_order:
            self.run_action(feature, 'validate', run_if_error=True)

    @warmup
    def inject_environment_config(self):
        if not self.do_inject_environment_config:
            return

        for shell in SHELL_CONFIG:
            if shell == 'gui':
                if system.is_debian():
                    self._inject_config_source(".gui",
                                               SHELL_CONFIG['gui']['debian'])
            else:
                if (self.global_config.has_option('shell', shell)
                        and lib.is_affirmative(
                            self.global_config.get('shell', shell))):

                    rc_file, rc_path = self._inject_config_source(
                        ".rc", SHELL_CONFIG[shell]['rc'])
                    env_file, env_path = self._inject_config_source(
                        ".env", SHELL_CONFIG[shell]['env'])
                    # If an rc file is sourced by an env file, we should alert the user.
                    if (self.phase is PHASE.INSTALL
                            and self.injections.in_noninjected_file(
                                env_path, rc_file)
                            and self.global_injections.in_noninjected_file(
                                env_path, rc_file)):
                        self.logger.info(
                            "You appear to be sourcing %s from inside %s." %
                            (rc_file, env_file))
                        self.logger.info(
                            "Please ensure it is wrapped in a #SPRINTER_OVERRIDES block "
                            + "to avoid repetitious operations!")
                    full_rc_path = os.path.expanduser(
                        os.path.join("~", rc_file))
                    full_env_path = os.path.expanduser(
                        os.path.join("~", env_file))
                    if lib.is_affirmative(
                            self.global_config.get('global', 'env_source_rc')):
                        self.global_injections.inject(
                            full_env_path,
                            source_template % (full_rc_path, full_rc_path))
                    else:
                        self.global_injections.inject(full_env_path, '')
                    if system.is_osx(
                    ) and not self.injections.in_noninjected_file(
                            env_path, rc_file):
                        if self.phase is PHASE.INSTALL:
                            self.logger.info(
                                "On OSX, login shell are the default, which only source config files"
                            )

    @warmup
    def clear_all(self):
        """ clear all files that were to be injected """
        self.injections.clear_all()
        for config_file in CONFIG_FILES:
            self.injections.clear(os.path.join("~", config_file))

    def install_sandboxes(self):
        if self.target:
            if system.is_osx():
                if not self.target.is_affirmative(
                        'config', 'use_global_packagemanagers'):
                    self._install_sandbox('brew', brew.install_brew)
                elif lib.which('brew') is None:
                    install_brew = lib.prompt(
                        "Looks like you don't have brew, " +
                        "which is sprinter's package manager of choice for OSX.\n"
                        "Would you like sprinter to install brew for you?",
                        default="yes",
                        boolean=True)
                    if install_brew:
                        lib.call("sudo mkdir -p /usr/local/",
                                 stdout=None,
                                 output_log_level=logging.DEBUG)
                        lib.call("sudo chown -R %s /usr/local/" %
                                 getpass.getuser(),
                                 output_log_level=logging.DEBUG,
                                 stdout=None)
                        brew.install_brew('/usr/local')

    def instantiate_features(self):
        if hasattr(self, 'features') and self.features:
            return
        self.features = FeatureDict(self, self.source, self.target,
                                    self.global_path)

    def run_feature(self, feature, action):
        for k in self.features.run_order:
            if feature in k:
                self.run_action(k, action, run_if_error=True)

    def write_debug_log(self, file_path):
        """ Write the debug log to a file """
        with open(file_path, "wb+") as fh:
            fh.write(system.get_system_info().encode('utf-8'))
            # writing to debug stream
            self._debug_stream.seek(0)
            fh.write(self._debug_stream.read().encode('utf-8'))
            fh.write("The following errors occured:\n".encode('utf-8'))
            for error in self._errors:
                fh.write((error + "\n").encode('utf-8'))
            for k, v in self._error_dict.items():
                if len(v) > 0:
                    fh.write(("Error(s) in %s with formula %s:\n" %
                              k).encode('utf-8'))
                    for error in v:
                        fh.write((error + "\n").encode('utf-8'))

    def write_manifest(self):
        """ Write the manifest to the file """
        if os.path.exists(self.directory.manifest_path):
            self.main_manifest.write(open(self.directory.manifest_path, "w+"))

    def message_failure(self):
        """ return a failure message, if one exists """
        if not isinstance(self.main_manifest, Manifest):
            return None
        return self.main_manifest.get('config',
                                      'message_failure',
                                      default=None)

    def message_success(self):
        """ return a success message, if one exists """
        return self.main_manifest.get('config',
                                      'message_success',
                                      default=None)

    def warmup(self):
        """ initialize variables necessary to perform a sprinter action """
        self.logger.debug("Warming up...")

        try:
            if not isinstance(self.source, Manifest) and self.source:
                self.source = load_manifest(self.source)
            if not isinstance(self.target, Manifest) and self.target:
                self.target = load_manifest(self.target)
            self.main_manifest = self.target or self.source
        except lib.BadCredentialsException:
            e = sys.exc_info()[1]
            self.logger.error(str(e))
            raise SprinterException(
                "Fatal error! Bad credentials to grab manifest!")

        if not getattr(self, 'namespace', None):
            if self.target:
                self.namespace = self.target.namespace
            elif not self.namespace and self.source:
                self.namespace = self.source.namespace
            else:
                raise SprinterException(
                    "No environment name has been specified!")

        self.directory_root = self.custom_directory_root

        if not self.directory:
            if not self.directory_root:
                self.directory_root = os.path.join(self.root, self.namespace)

            self.directory = Directory(self.directory_root,
                                       shell_util_path=self.shell_util_path)

        if not self.injections:
            self.injections = Injections(
                wrapper="%s_%s" %
                (self.sprinter_namespace.upper(), self.namespace),
                override="SPRINTER_OVERRIDES")
        if not self.global_injections:
            self.global_injections = Injections(
                wrapper="%s" % self.sprinter_namespace.upper() + "GLOBALS",
                override="SPRINTER_OVERRIDES")
        # append the bin, in the case sandboxes are necessary to
        # execute commands further down the sprinter lifecycle
        os.environ['PATH'] = self.directory.bin_path(
        ) + ":" + os.environ['PATH']
        self.warmed_up = True

    def _inject_config_source(self, source_filename, files_to_inject):
        """
        Inject existing environmental config with namespace sourcing.
        Returns a tuple of the first file name and path found.
        """
        # src_path = os.path.join(self.directory.root_dir, source_filename)
        # src_exec = "[ -r %s ] && . %s" % (src_path, src_path)
        src_exec = "[ -r {0} ] && . {0}".format(
            os.path.join(self.directory.root_dir, source_filename))
        # The ridiculous construction above is necessary to avoid failing tests(!)

        for config_file in files_to_inject:
            config_path = os.path.expanduser(os.path.join("~", config_file))
            if os.path.exists(config_path):
                self.injections.inject(config_path, src_exec)
                break
        else:
            config_file = files_to_inject[0]
            config_path = os.path.expanduser(os.path.join("~", config_file))
            self.logger.info(
                "No config files found to source %s, creating ~/%s!" %
                (source_filename, config_file))
            self.injections.inject(config_path, src_exec)

        return (config_file, config_path)

    def _finalize(self):
        """ command to run at the end of sprinter's run """
        self.logger.info("Finalizing...")
        self.write_manifest()

        if self.directory.rewrite_config:
            # always ensure .rc is written (sourcing .env)
            self.directory.add_to_rc('')
            # prepend brew for global installs
            if system.is_osx() and self.main_manifest.is_affirmative(
                    'config', 'use_global_packagemanagers'):
                self.directory.add_to_env('__sprinter_prepend_path "%s" PATH' %
                                          '/usr/local/bin')
            self.directory.add_to_env('__sprinter_prepend_path "%s" PATH' %
                                      self.directory.bin_path())
            self.directory.add_to_env(
                '__sprinter_prepend_path "%s" LIBRARY_PATH' %
                self.directory.lib_path())
            self.directory.add_to_env(
                '__sprinter_prepend_path "%s" C_INCLUDE_PATH' %
                self.directory.include_path())
            self.directory.finalize()

        self.injections.commit()
        self.global_injections.commit()

        if not os.path.exists(os.path.join(self.root, ".global")):
            self.logger.debug("Global directory doesn't exist! creating...")
            os.makedirs(os.path.join(self.root, ".global"))

        self.logger.debug("Writing shell util file...")
        with open(self.shell_util_path, 'w+') as fh:
            fh.write(shell_utils_template)

        if self.error_occured:
            raise SprinterException("Error occured!")

        if self.message_success():
            self.logger.info(self.message_success())

        self.logger.info("Done!")
        self.logger.info(
            "NOTE: Please remember to open new shells/terminals to use the modified environment"
        )

    def _install_sandbox(self, name, call, kwargs={}):
        if (self.target.is_affirmative('config', name)
                and (not self.source
                     or not self.source.is_affirmative('config', name))):
            self.logger.info("Installing %s..." % name)
            call(self.directory.root_dir, **kwargs)

    def _build_logger(self, level=logging.INFO):
        """ return a logger. if logger is none, generate a logger from stdout """
        self._debug_stream = StringIO()
        logger = logging.getLogger('sprinter')
        # stdout log
        out_hdlr = logging.StreamHandler(sys.stdout)
        out_hdlr.setLevel(level)
        logger.addHandler(out_hdlr)
        # debug log
        debug_hdlr = logging.StreamHandler(self._debug_stream)
        debug_hdlr.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
        debug_hdlr.setLevel(logging.DEBUG)
        logger.addHandler(debug_hdlr)
        logger.setLevel(logging.DEBUG)
        return logger

    def log_error(self, error_message):
        self.error_occured = True
        self._errors += [error_message]
        self.logger.error(error_message)

    def log_feature_error(self, feature, error_message):
        if type(error_message) != list:
            error_message = "Error occured! %s" % str(error_message)
            self._error_dict[feature] += [error_message]
        else:
            self._error_dict[feature] += error_message
        self.log_error(error_message)

    def get_error_value(self, feature):
        """ get the error value for a feature """
        if feature not in self._error_dict:
            self._error_dict1  #

    def run_action(self,
                   feature,
                   action,
                   run_if_error=False,
                   raise_exception=True):
        """ Run an action, and log it's output in case of errors """
        if len(self._error_dict[feature]) > 0 and not run_if_error:
            return

        error = None
        instance = self.features[feature]
        try:
            getattr(instance, action)()
        # catch a generic exception within a feature
        except Exception:
            e = sys.exc_info()[1]
            self.logger.info(
                "An exception occurred with action %s in feature %s!" %
                (action, feature))
            self.logger.debug("Exception", exc_info=sys.exc_info())
            error = str(e)
            self.log_feature_error(feature, str(e))
        # any error in a feature should fail immediately - unless it occurred
        # from the remove() method in which case continue the rest of the
        # feature removal from there
        if error is not None and raise_exception:
            exception_msg = "%s action failed for feature %s: %s" % (
                action, feature, error)
            if self.phase == PHASE.REMOVE:
                raise FormulaException(exception_msg)
            else:
                raise SprinterException(exception_msg)
        return error

    def _validate_manifest(self):
        errors = {}
        for feature in self.features.run_order:
            error = self.run_action(feature,
                                    'validate',
                                    run_if_error=True,
                                    raise_exception=False)
            if error:
                errors[feature] = error
        if errors:
            message = INVALID_MANIFEST + "\n\n"
            for feature, error in errors.items():
                message += "* {0}: {1}\n".format(error)
            self.log.error(message)
            raise SprinterException("invalid manifest!")

    def _specialize(self, reconfigure=False):
        """ Add variables and specialize contexts """
        # add in the 'root_dir' directories to the context dictionaries
        for manifest in [self.source, self.target]:
            context_dict = {}
            if manifest:
                for s in manifest.formula_sections():
                    context_dict["%s:root_dir" %
                                 s] = self.directory.install_directory(s)
                    context_dict['config:root_dir'] = self.directory.root_dir
                    context_dict['config:node'] = system.NODE
                manifest.add_additional_context(context_dict)
        self._validate_manifest()
        for feature in self.features.run_order:
            if not reconfigure:
                self.run_action(feature, 'resolve')
            # if a target doesn't exist, no need to prompt.
            instance = self.features[feature]
            if instance.target:
                self.run_action(feature, 'prompt')

    def _copy_source_to_target(self):
        """ copy source user configuration to target """
        if self.source and self.target:
            for k, v in self.source.items('config'):
                # always have source override target.
                self.target.set_input(k, v)

    def grab_inputs(self, reconfigure=False):
        """ Resolve the source and target config section """
        self._copy_source_to_target()
        if self.target:
            self.target.grab_inputs(force=reconfigure)
Beispiel #5
0
class Environment(object):

    source = None  # the path to the source handle, the handle itself, or a manifest instance
    target = None  # the path to the target handle, the handle itself, or a manifest instance
    namespace = None  # the namespace of the environment
    custom_directory_root = None  # the root to install directories too
    do_inject_environment_config = True  # inject configuration into shells
    sprinter_namespace = None  # the namespace to make installs with. this affects:
    phase = None  # the phase currently running
    # the prefix added to injections
    # the libraries that environment utilizes
    directory = None  # handles interactions with the environment directory
    injections = None  # handles injections
    global_injections = None  # handles injections for the global sprinter configuration
    # variables typically populated programatically
    warmed_up = False  # returns true if the environment is ready for environments
    shell_util_path = None  # the path to the shell utils file
    error_occured = False
    _errors = []  # list to keep all the errors
    sandboxes = []  # a list of package managers to sandbox (brew)
    # specifies where to get the global sprinter root
    global_config = None  # configuration file, which defaults to loading from SPRINTER_ROOT/.global/config.cfg
    ignore_errors = False  # ignore errors in features

    def __init__(self,
                 logger=None,
                 logging_level=logging.INFO,
                 root=None,
                 sprinter_namespace=None,
                 global_config=None,
                 ignore_errors=False):

        # base logging object to log instances
        self.logger = logger or self._build_logger(level=logging_level)
        if logging_level == logging.DEBUG:
            self.logger.info("Starting in debug mode...")

        # the sprinter namespace
        self.sprinter_namespace = sprinter_namespace or 'sprinter'

        # the root directory which sprinter installs sandboxable files too
        self.root = root or os.path.expanduser(os.path.join("~", ".%s" % self.sprinter_namespace))

        self.ignore_errors = ignore_errors

        # path to the directory to install global files
        self.global_path = os.path.join(self.root, ".global")
        self.global_config_path = os.path.join(self.global_path, "config.cfg")
        self.global_config = global_config or load_global_config(self.global_config_path)
        
        self.shell_util_path = os.path.join(self.global_path, "utils.sh")
        self.main_manifest = None

        # a dictionary of the errors associated with features.
        # The key is a tuple of feature name and formula, while the value is an instance.
        self._error_dict = defaultdict(list)
        
    @warmup
    def install(self):
        """ Install the environment """
        self.phase = PHASE.INSTALL
        if not self.directory.new:
            self.logger.info("Namespace %s directory already exists!" % self.namespace)
            self.source = load_manifest(self.directory.manifest_path)
            return self.update()
        try:
            self.logger.info("Installing environment %s..." % self.namespace)
            self.directory.initialize()
            self.install_sandboxes()
            self.instantiate_features()
            self.grab_inputs()
            self._specialize()
            for feature in self.features.run_order:
                self.run_action(feature, 'sync')
            self.inject_environment_config()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            self.logger.info("An error occured during installation!")
            if not self.ignore_errors:
                self.clear_all()
                self.logger.info("Removing installation %s..." % self.namespace)
                self.directory.remove()
                et, ei, tb = sys.exc_info()
                reraise(et, ei, tb)
        
    @warmup
    @install_required
    def update(self, reconfigure=False):
        """ update the environment """
        try:
            self.phase = PHASE.UPDATE
            self.logger.info("Updating environment %s..." % self.namespace)
            self.install_sandboxes()
            self.instantiate_features()
            # We don't grab inputs, only on install
            # updates inputs are grabbed on demand
            # self.grab_inputs(reconfigure=reconfigure)
            if reconfigure:
                self.grab_inputs(reconfigure=True)
            else:
                self._copy_source_to_target()
            self._specialize(reconfigure=reconfigure)
            for feature in self.features.run_order:
                self.run_action(feature, 'sync')
            self.inject_environment_config()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    @install_required
    def remove(self):
        """ remove the environment """
        try:
            self.phase = PHASE.REMOVE
            self.logger.info("Removing environment %s..." % self.namespace)
            self.instantiate_features()
            self._specialize()
            for feature in self.features.run_order:
                self.run_action(feature, 'sync')
            self.clear_all()
            self.directory.remove()
            self.injections.commit()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    @install_required
    def deactivate(self):
        """ deactivate the environment """
        try:
            self.phase = PHASE.DEACTIVATE
            self.logger.info("Deactivating environment %s..." % self.namespace)
            self.directory.rewrite_config = False
            self.instantiate_features()
            self._specialize()
            for feature in self.features.run_order:
                self.logger.info("Deactivating %s..." % feature[0])
                self.run_action(feature, 'deactivate')
            self.clear_all()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    @install_required
    def activate(self):
        """ activate the environment """
        try:
            self.phase = PHASE.ACTIVATE
            self.logger.info("Activating environment %s..." % self.namespace)
            self.directory.rewrite_config = False
            self.instantiate_features()
            self._specialize()
            for feature in self.features.run_order:
                self.logger.info("Activating %s..." % feature[0])
                self.run_action(feature, 'activate')
            self.inject_environment_config()
            self._finalize()
        except Exception:
            self.logger.debug("", exc_info=sys.exc_info())
            et, ei, tb = sys.exc_info()
            reraise(et, ei, tb)

    @warmup
    def validate(self):
        """ Validate the target environment """
        self.phase = PHASE.VALIDATE
        self.logger.info("Validating %s..." % self.namespace)
        self.instantiate_features()
        context_dict = {}
        if self.target:
            for s in self.target.formula_sections():
                context_dict["%s:root_dir" % s] = self.directory.install_directory(s)
                context_dict['config:root_dir'] = self.directory.root_dir
                context_dict['config:node'] = system.NODE
                self.target.add_additional_context(context_dict)
        for feature in self.features.run_order:
            self.run_action(feature, 'validate', run_if_error=True)

    @warmup
    def inject_environment_config(self):
        if not self.do_inject_environment_config:
            return

        for shell in SHELL_CONFIG:
            if shell == 'gui':
                if system.is_debian():
                    self._inject_config_source(".env", SHELL_CONFIG['gui']['debian'])
            else:
                if (self.global_config.has_option('shell', shell)
                   and lib.is_affirmative(self.global_config.get('shell', shell))):

                    rc_file, rc_path = self._inject_config_source(".rc", SHELL_CONFIG[shell]['rc'])
                    env_file, env_path = self._inject_config_source(".env", SHELL_CONFIG[shell]['env'])
                    # If an rc file is sourced by an env file, we should alert the user.
                    if (self.phase is PHASE.INSTALL
                       and self.injections.in_noninjected_file(env_path, rc_file)
                       and self.global_injections.in_noninjected_file(env_path, rc_file)):
                        self.logger.info("You appear to be sourcing %s from inside %s." % (rc_file, env_file))
                        self.logger.info("Please ensure it is wrapped in a #SPRINTER_OVERRIDES block " +
                                         "to avoid repetitious operations!")
                    full_rc_path = os.path.expanduser(os.path.join("~", rc_file))
                    full_env_path = os.path.expanduser(os.path.join("~", env_file))
                    if lib.is_affirmative(self.global_config.get('global', 'env_source_rc')):
                        self.global_injections.inject(
                            full_env_path,
                            source_template % (full_rc_path, full_rc_path))
                    else:
                        self.global_injections.inject(full_env_path, '')
                    if system.is_osx() and not self.injections.in_noninjected_file(env_path, rc_file):
                        if self.phase is PHASE.INSTALL:
                            self.logger.info("On OSX, login shell are the default, which only source config files")

    @warmup
    def clear_all(self):
        """ clear all files that were to be injected """
        self.injections.clear_all()
        for config_file in CONFIG_FILES:
            self.injections.clear(os.path.join("~", config_file))

    def install_sandboxes(self):
        if self.target:
            if system.is_osx():
                if not self.target.is_affirmative('config', 'use_global_packagemanagers'):
                    self._install_sandbox('brew', brew.install_brew)
                elif lib.which('brew') is None:
                    install_brew = lib.prompt(
                        "Looks like you don't have brew, " +
                        "which is sprinter's package manager of choice for OSX.\n"
                        "Would you like sprinter to install brew for you?",
                        default="yes", boolean=True)
                    if install_brew:
                        lib.call("sudo mkdir -p /usr/local/", stdout=None,
                                 output_log_level=logging.DEBUG)
                        lib.call("sudo chown -R %s /usr/local/" % getpass.getuser(),
                                 output_log_level=logging.DEBUG, stdout=None)
                        brew.install_brew('/usr/local')

    def instantiate_features(self):
        if hasattr(self, 'features') and self.features:
            return
        self.features = FeatureDict(self,
                                    self.source, self.target,
                                    self.global_path)

    def run_feature(self, feature, action):
        for k in self.features.run_order:
            if feature in k:
                self.run_action(k, action, run_if_error=True)

    def write_debug_log(self, file_path):
        """ Write the debug log to a file """
        with open(file_path, "w+") as fh:
            fh.write(system.get_system_info())
            # writing to debug stream
            self._debug_stream.seek(0)
            fh.write(self._debug_stream.read())
            fh.write("The following errors occured:\n")
            for error in self._errors:
                fh.write(error + "\n")
            for k, v in self._error_dict.items():
                if len(v) > 0:
                    fh.write("Error(s) in %s with formula %s:\n" % k)
                    for error in v:
                        fh.write(error + "\n")

    def write_manifest(self):
        """ Write the manifest to the file """
        if os.path.exists(self.directory.manifest_path):
            self.main_manifest.write(open(self.directory.manifest_path, "w+"))

    def message_failure(self):
        """ return a failure message, if one exists """
        if not isinstance(self.main_manifest, Manifest):
            return None
        return self.main_manifest.get('config', 'message_failure', default=None)

    def message_success(self):
        """ return a success message, if one exists """
        return self.main_manifest.get('config', 'message_success', default=None)

    def warmup(self):
        """ initialize variables necessary to perform a sprinter action """
        self.logger.debug("Warming up...")

        try:
            if not isinstance(self.source, Manifest) and self.source:
                self.source = load_manifest(self.source)
            if not isinstance(self.target, Manifest) and self.target:
                self.target = load_manifest(self.target)
            self.main_manifest = self.target or self.source
        except lib.BadCredentialsException:
            e = sys.exc_info()[1]
            self.logger.error(str(e))
            raise SprinterException("Fatal error! Bad credentials to grab manifest!")

        if not getattr(self, 'namespace', None):
            if self.target:
                self.namespace = self.target.namespace
            elif not self.namespace and self.source:
                self.namespace = self.source.namespace
            else:
                raise SprinterException("No environment name has been specified!")

        self.directory_root = self.custom_directory_root

        if not self.directory:
            if not self.directory_root:
                self.directory_root = os.path.join(self.root, self.namespace)

            self.directory = Directory(self.directory_root,
                                       shell_util_path=self.shell_util_path)

        if not self.injections:
            self.injections = Injections(wrapper="%s_%s" % (self.sprinter_namespace.upper(),
                                                            self.namespace),
                                         override="SPRINTER_OVERRIDES")
        if not self.global_injections:
            self.global_injections = Injections(wrapper="%s" % self.sprinter_namespace.upper() + "GLOBALS",
                                                override="SPRINTER_OVERRIDES")
        # append the bin, in the case sandboxes are necessary to
        # execute commands further down the sprinter lifecycle
        os.environ['PATH'] = self.directory.bin_path() + ":" + os.environ['PATH']
        self.warmed_up = True

    def _inject_config_source(self, source_filename, files_to_inject):
        """
        Inject existing environmental config with namespace sourcing.
        Returns a tuple of the first file name and path found.
        """
        # src_path = os.path.join(self.directory.root_dir, source_filename)
        # src_exec = "[ -r %s ] && . %s" % (src_path, src_path)
        src_exec = "[ -r %s/%s ] && . %s/%s" % (self.directory.root_dir, source_filename,
                                                self.directory.root_dir, source_filename)
        # The ridiculous construction above is necessary to avoid failing tests(!)

        for config_file in files_to_inject:
            config_path = os.path.expanduser(os.path.join("~", config_file))
            if os.path.exists(config_path):
                self.injections.inject(config_path, src_exec)
                break
        else:
            config_file = files_to_inject[0]
            config_path = os.path.expanduser(os.path.join("~", config_file))
            self.logger.info("No config files found to source %s, creating ~/%s!" % (source_filename, config_file))
            self.injections.inject(config_path, src_exec)

        return (config_file, config_path)

    def _finalize(self):
        """ command to run at the end of sprinter's run """
        self.logger.info("Finalizing...")
        self.write_manifest()

        if self.directory.rewrite_config:
            # always ensure .rc is written (sourcing .env)
            self.directory.add_to_rc('')
            # prepend brew for global installs
            if system.is_osx() and self.main_manifest.is_affirmative('config', 'use_global_packagemanagers'):
                self.directory.add_to_env('__sprinter_prepend_path "%s" PATH' % '/usr/local/bin')
            self.directory.add_to_env('__sprinter_prepend_path "%s" PATH' % self.directory.bin_path())
            self.directory.add_to_env('__sprinter_prepend_path "%s" LIBRARY_PATH' % self.directory.lib_path())
            self.directory.add_to_env('__sprinter_prepend_path "%s" C_INCLUDE_PATH' % self.directory.include_path())

        self.injections.commit()
        self.global_injections.commit()

        if not os.path.exists(os.path.join(self.root, ".global")):
            self.logger.debug("Global directory doesn't exist! creating...")
            os.makedirs(os.path.join(self.root, ".global"))

        self.logger.debug("Writing global config...")
        self.global_config.write(open(self.global_config_path, 'w+'))

        self.logger.debug("Writing shell util file...")
        with open(self.shell_util_path, 'w+') as fh:
            fh.write(shell_utils_template)

        if self.error_occured:
            raise SprinterException("Error occured!")

        if self.message_success():
            self.logger.info(self.message_success())

        self.logger.info("NOTE: Please remember to open new shells/terminals to use the modified environment")

    def _install_sandbox(self, name, call, kwargs={}):
        if (self.target.is_affirmative('config', name) and
           (not self.source or not self.source.is_affirmative('config', name))):
            self.logger.info("Installing %s..." % name)
            call(self.directory.root_dir, **kwargs)

    def _build_logger(self, level=logging.INFO):
        """ return a logger. if logger is none, generate a logger from stdout """
        self._debug_stream = StringIO()
        logger = logging.getLogger('sprinter')
        # stdout log
        out_hdlr = logging.StreamHandler(sys.stdout)
        out_hdlr.setLevel(level)
        logger.addHandler(out_hdlr)
        # debug log
        debug_hdlr = logging.StreamHandler(self._debug_stream)
        debug_hdlr.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
        debug_hdlr.setLevel(logging.DEBUG)
        logger.addHandler(debug_hdlr)
        logger.setLevel(logging.DEBUG)
        return logger

    def log_error(self, error_message):
        self.error_occured = True
        self._errors += [error_message]
        self.logger.error(error_message)

    def get_error_value(self, feature):
        """ get the error value for a feature """
        if feature not in self._error_dict:
            self._error_dict1

    def log_feature_error(self, feature, error_message):
        self.error_occured = True
        self._error_dict[feature] += [error_message]
        self.logger.error(error_message)
            
    def run_action(self, feature, action, run_if_error=False):
        """ Run an action, and log it's output in case of errors """
        if len(self._error_dict[feature]) > 0 and not run_if_error:
            return
        instance = self.features[feature]
        try:
            result = getattr(instance, action)()
            if result:
                if type(result) != list:
                    self.log_feature_error(feature,
                                           "Error occurred! %s" % str(result))
                else:
                    self._error_dict[feature] += result
            if len(self._error_dict[feature]) > 0:
                self.error_occured = True
        # catch a generic exception within a feature
        except Exception:
            e = sys.exc_info()[1]
            self.logger.info("An exception occurred with action %s in feature %s!" %
                             (action, feature))
            self.logger.debug("Exception", exc_info=sys.exc_info())
            self.log_feature_error(feature, str(e))

    def _specialize(self, reconfigure=False):
        """ Add variables and specialize contexts """
        # add in the 'root_dir' directories to the context dictionaries
        for manifest in [self.source, self.target]:
            context_dict = {}
            if manifest:
                for s in manifest.formula_sections():
                    context_dict["%s:root_dir" % s] = self.directory.install_directory(s)
                    context_dict['config:root_dir'] = self.directory.root_dir
                    context_dict['config:node'] = system.NODE
                manifest.add_additional_context(context_dict)
        for feature in self.features.run_order:
            self.run_action(feature, 'validate', run_if_error=True)
            if not reconfigure:
                self.run_action(feature, 'resolve')
            self.run_action(feature, 'prompt')

    def _copy_source_to_target(self):
        """ copy source user configuration to target """
        if self.source and self.target:
            for k, v in self.source.items('config'):
                # always have source override target.
                self.target.set_input(k, v)

    def grab_inputs(self, reconfigure=False):
        """ Resolve the source and target config section """
        self._copy_source_to_target()
        if self.target:
            self.target.grab_inputs(force=reconfigure)