예제 #1
0
    async def run(self, msg_cb, event_name=None):
        # Define STEP_NAME for use in determining where to store
        # our step results,
        #  state set "conjure-up.$SPELL_NAME.$STEP_NAME.result" "val"
        app.env['CONJURE_UP_STEP'] = self.name

        step_path = Path(app.config['spell-dir']) / 'steps' / self.filename

        if not step_path.is_file():
            return

        step_path = str(step_path)

        msg = "Running step: {}.".format(self.name)
        app.log.info(msg)
        msg_cb(msg)
        if event_name is not None:
            track_event(event_name, "Started", "")

        if not os.access(step_path, os.X_OK):
            raise Exception("Step {} not executable".format(self.title))

        if is_linux() and self.needs_sudo and not await can_sudo():
            raise SudoError('Step "{}" requires sudo: {}'.format(
                self.title,
                'password failed'
                if app.sudo_pass else 'passwordless sudo required',
            ))

        cloud_types = juju.get_cloud_types_by_name()
        provider_type = cloud_types[app.provider.cloud]

        app.env['JUJU_PROVIDERTYPE'] = provider_type
        # not all providers have a credential, e.g., localhost
        app.env['JUJU_CREDENTIAL'] = app.provider.credential or ''
        app.env['JUJU_CONTROLLER'] = app.provider.controller
        app.env['JUJU_MODEL'] = app.provider.model
        app.env['JUJU_REGION'] = app.provider.region or ''
        app.env['CONJURE_UP_SPELLSDIR'] = app.argv.spells_dir

        if provider_type == "maas":
            app.env['MAAS_ENDPOINT'] = app.maas.endpoint
            app.env['MAAS_APIKEY'] = app.maas.api_key

        for step_name, step_data in app.steps_data.items():
            for key, value in step_data.items():
                app.env[key.upper()] = step_data[key]

        for key, value in app.env.items():
            if value is None:
                app.log.warning('Env {} is None; '
                                'replacing with empty string'.format(key))
                app.env[key] = ''

        app.log.debug("Storing environment")
        async with aiofiles.open(step_path + ".env", 'w') as outf:
            for k, v in app.env.items():
                if 'JUJU' in k or 'MAAS' in k or 'CONJURE' in k:
                    await outf.write("{}=\"{}\" ".format(k.upper(), v))

        app.log.debug("Executing script: {}".format(step_path))

        async with aiofiles.open(step_path + ".out", 'w') as outf:
            async with aiofiles.open(step_path + ".err", 'w') as errf:
                proc = await asyncio.create_subprocess_exec(step_path,
                                                            env=app.env,
                                                            stdout=outf,
                                                            stderr=errf)
                async with aiofiles.open(step_path + '.out', 'r') as f:
                    while proc.returncode is None:
                        async for line in f:
                            msg_cb(line)
                        await asyncio.sleep(0.01)

        out_log = Path(step_path + '.out').read_text()
        err_log = Path(step_path + '.err').read_text()

        if proc.returncode != 0:
            app.sentry.context.merge({
                'extra': {
                    'out_log_tail': out_log[-400:],
                    'err_log_tail': err_log[-400:],
                }
            })
            raise Exception("Failure in step {}".format(self.filename))

        # special case for 00_deploy-done to report masked
        # charm hook failures that were retried automatically
        if not app.noreport:
            failed_apps = set()  # only report each charm once
            for line in err_log.splitlines():
                if 'hook failure, will retry' in line:
                    log_leader = line.split()[0]
                    unit_name = log_leader.split(':')[-1]
                    app_name = unit_name.split('/')[0]
                    failed_apps.add(app_name)
            for app_name in failed_apps:
                # report each individually so that Sentry will give us a
                # breakdown of failures per-charm in addition to per-spell
                sentry_report('Retried hook failure',
                              tags={
                                  'app_name': app_name,
                              })

        if event_name is not None:
            track_event(event_name, "Done", "")

        result_key = "conjure-up.{}.{}.result".format(app.config['spell'],
                                                      self.name)
        result = app.state.get(result_key)
        return (result or '')
예제 #2
0
    async def run(self, phase, msg_cb, event_name=None):
        # Define STEP_NAME for use in determining where to store
        # our step results,
        #  state set "conjure-up.$SPELL_NAME.$STEP_NAME.result" "val"
        app.env['CONJURE_UP_STEP'] = self.name
        app.env['CONJURE_UP_PHASE'] = phase.value

        step_path = self._build_phase_path(phase)

        if not step_path.is_file():
            return

        if not os.access(str(step_path), os.X_OK):
            app.log.error(
                'Unable to run {} step {} {}, it is not executable'.format(
                    self.source, step_path.stem, phase.value))
            return

        step_path = str(step_path)

        msg = "Running {} step: {} {}.".format(self.source, self.name,
                                               phase.value)
        app.log.info(msg)
        msg_cb(msg)
        if event_name is not None:
            track_event(event_name, "Started", "")

        if is_linux() and self.needs_sudo and not await can_sudo():
            raise SudoError('The "{}" step "{}" requires sudo: {}'.format(
                self.source,
                self.title,
                'password failed'
                if app.sudo_pass else 'passwordless sudo required',
            ))

        app.env['CONJURE_UP_SPELLSDIR'] = app.conjurefile['spells-dir']
        app.env['CONJURE_UP_SESSION_ID'] = app.session_id

        if app.metadata.spell_type == spell_types.JUJU:
            cloud_types = juju.get_cloud_types_by_name()
            provider_type = cloud_types[app.provider.cloud]

            app.env['JUJU_PROVIDERTYPE'] = provider_type
            # not all providers have a credential, e.g., localhost
            app.env['JUJU_CREDENTIAL'] = app.provider.credential or ''
            app.env['JUJU_CONTROLLER'] = app.provider.controller
            app.env['JUJU_MODEL'] = app.provider.model
            app.env['JUJU_REGION'] = app.provider.region or ''

            if provider_type == "maas":
                app.env['MAAS_ENDPOINT'] = app.maas.endpoint
                app.env['MAAS_APIKEY'] = app.maas.api_key

        for step_name, step_data in app.steps_data.items():
            for key, value in step_data.items():
                app.env[key.upper()] = str(step_data[key])

        for key, value in app.env.items():
            if value is None:
                app.log.warning('Env {} is None; '
                                'replacing with empty string'.format(key))
                app.env[key] = ''

        app.log.debug("Storing environment")
        async with aiofiles.open(step_path + ".env", 'w') as outf:
            for k, v in app.env.items():
                if 'JUJU' in k or 'MAAS' in k or 'CONJURE' in k:
                    await outf.write("{}=\"{}\" ".format(k.upper(), v))

        app.log.debug("Executing script: {}".format(step_path))

        out_path = step_path + '.out'
        err_path = step_path + '.err'
        ret, out_log, err_log = await arun([step_path],
                                           stdout=out_path,
                                           stderr=err_path,
                                           cb_stdout=msg_cb)

        if ret != 0:
            app.sentry.context.merge({
                'extra': {
                    'out_log_tail': out_log[-400:],
                    'err_log_tail': err_log[-400:],
                }
            })
            raise Exception("Failure in step {} {}".format(
                self.name, phase.value))

        # special case for 00_deploy-done to report masked
        # charm hook failures that were retried automatically
        if not app.no_report and app.metadata.spell_type == spell_types.JUJU:
            failed_apps = set()  # only report each charm once
            for line in err_log.splitlines():
                if 'hook failure, will retry' in line:
                    log_leader = line.split()[0]
                    unit_name = log_leader.split(':')[-1]
                    app_name = unit_name.split('/')[0]
                    failed_apps.add(app_name)
            for app_name in failed_apps:
                # report each individually so that Sentry will give us a
                # breakdown of failures per-charm in addition to per-spell
                sentry_report('Retried hook failure',
                              tags={
                                  'app_name': app_name,
                              })

        if event_name is not None:
            track_event(event_name, "Done", "")

        return self.get_state('result', phase)