Ejemplo n.º 1
0
    def load(self, repository):
        """Load an anod specification.

        :param repository: the anod spec repository of the spec file
        :type repository: AnodSpecRepository
        :raise SandBoxError: in case of failure
        """
        if self.is_loaded:
            return self.anod_class

        logger.debug('loading anod spec: %s', self.name)

        # Create a new module
        mod_name = 'anod_' + self.name
        anod_module = imp.new_module(mod_name)

        try:
            with open(self.path) as fd:
                # Inject the prolog into the new module dict
                anod_module.__dict__.update(repository.prolog_dict)

                # Exec spec code
                code = compile(fd.read(), self.path, 'exec')
                exec(code, anod_module.__dict__)
        except Exception as e:
            logger.error('exception: %s', e)
            logger.error('cannot load code of %s', self.name)
            raise SandBoxError(
                origin='load',
                message='invalid spec code for %s' % self.name), \
                None, sys.exc_traceback

        # At this stage we have loaded completely the module. Now we need to
        # look for a subclass of Anod. Use python inspection features to
        # achieve this.

        for members in inspect.getmembers(anod_module):
            _, value = members
            # Return the first subclass of Anod defined in this module
            if inspect.isclass(value) and value.__module__ == mod_name \
                    and 'Anod' in [k.__name__ for k in value.__mro__]:
                # This class is a child of Anod so register it.
                # Note that even if we won't use directly the
                # module we need to keep a reference on it in order
                # to avoid garbage collector issues.
                value.spec_checksum = self.checksum

                # Give a name to our Anod class: the basename of the
                # anod spec file (without the .anod extension)
                value.name = self.name
                self.anod_class = value
                self.module = anod_module
                self.anod_class.data_files = self.data
                self.anod_class.spec_dir = os.path.dirname(self.path)
                return self.anod_class

        logger.error('spec %s does not contains an Anod subclass', self.name)
        raise SandBoxError('cannot find Anod subclass in %s' % self.path,
                           'load')
Ejemplo n.º 2
0
    def __init__(self, spec_dir, spec_config=None):
        """Initialize an AnodSpecRepository.

        :param spec_dir: directory containing the anod specs.
        :type spec_dir: str
        :param spec_config: dictionary containing the configuration for this
            AnodSpecRepository
        :type spec_config: dict
        """
        logger.debug('initialize spec repository (%s)', spec_dir)

        if not os.path.isdir(spec_dir):
            raise SandBoxError('spec directory %s does not exist' % spec_dir)
        self.spec_dir = spec_dir
        self.api_version = None
        self.specs = {}
        self.repos = {}

        # Look for all spec files and data files
        spec_list = {
            os.path.basename(k)[:-5]: {
                'path': k,
                'data': []
            }
            for k in ls(os.path.join(self.spec_dir, '*.anod'),
                        emit_log_record=False)
        }
        logger.debug('found %s specs', len(spec_list))

        data_list = [
            os.path.basename(k)[:-5]
            for k in ls(os.path.join(self.spec_dir, '*.yaml'),
                        emit_log_record=False)
        ]
        logger.debug('found %s yaml files', len(data_list))

        # Match yaml files with associated specifications
        for data in data_list:
            candidate_specs = [
                spec_file for spec_file in spec_list
                if data.startswith(spec_file)
            ]
            # We pick the longuest spec name
            candidate_specs.sort(key=lambda x: len(x))
            if candidate_specs:
                spec_list[candidate_specs[-1]]['data'].append(data)

        # Create AnodModule objects
        for name, value in spec_list.iteritems():
            self.specs[name] = AnodModule(name, **value)

        # Declare spec prolog
        prolog_file = os.path.join(spec_dir, 'prolog.py')
        self.prolog_dict = {
            'spec_config': spec_config,
            '__spec_repository': self
        }
        if os.path.exists(prolog_file):
            with open(prolog_file) as f:
                exec(compile(f.read(), prolog_file, 'exec'), self.prolog_dict)
Ejemplo n.º 3
0
    def root_dir(self) -> str:
        """Root path of the sandbox.

        :raise SandBoxError: when the sandbox is not initialized
        """
        if self.__root_dir is None:
            raise SandBoxError(
                origin="root_dir",
                message="sandbox not loaded. Please call load()")
        return self.__root_dir
Ejemplo n.º 4
0
    def root_dir(self):
        """Root path of the sandbox.

        :raise SandBoxError: when the sandbox is not initialized
        :rtype: str
        """
        if self.__root_dir is None:
            raise SandBoxError(
                origin='root_dir',
                message='sandbox not loaded. Please call load()')
        return self.__root_dir
Ejemplo n.º 5
0
def main(get_argument_parser=False):
    """Manipulate an Anod sandbox.

    This function creates the main code for the entry-point e3-sandbox. To
    create new actions it is possible to create new sandbox plugins. e.g. to
    add a new plugin ``foo`` from a package ``e3-contrib``, derives the
    class :class:`SandBoxAction` and register the extension by adding in
    :file:`e3-contrib/setup.py`::

        entry_points={
            'e3.anod.sandbox.sandbox_action': [
                'foo = e3_contrib.sandbox_actions.SandBoxFoo']
        }

    :param get_argument_parser: return e3.main.Main argument_parser instead
        of running the action.
    :type get_argument_parser: bool
    """
    m = Main()
    m.parse_args(known_args_only=True)

    subparsers = m.argument_parser.add_subparsers(title="action",
                                                  description="valid actions")

    # Load all sandbox actions plugins
    ext = stevedore.ExtensionManager(
        namespace="e3.anod.sandbox.sandbox_action",
        invoke_on_load=True,
        invoke_args=(subparsers, ),
    )

    if len(ext.names()) != len(ext.entry_points_names()):
        raise SandBoxError(
            "an error occured when loading sandbox_action entry points %s" %
            ",".join(ext.entry_points_names()))  # defensive code

    if get_argument_parser:
        return m.argument_parser

    args = m.argument_parser.parse_args()

    e3.log.debug("sandbox action plugins loaded: %s", ",".join(ext.names()))

    # An action has been selected, run it
    try:
        ext[args.action].obj.run(args)
    except SandBoxError as err:
        logger.error(err)
        sys.exit(1)
Ejemplo n.º 6
0
    def __init__(self, spec_dir: str, spec_config: Optional[dict] = None):
        """Initialize an AnodSpecRepository.

        :param spec_dir: directory containing the anod specs.
        :param spec_config: dictionary containing the configuration for this
            AnodSpecRepository
        """
        logger.debug("initialize spec repository (%s)", spec_dir)

        if not os.path.isdir(spec_dir):
            raise SandBoxError("spec directory %s does not exist" % spec_dir)
        self.spec_dir = spec_dir
        self.api_version = __version__
        self.specs = {}
        self.repos: Dict[str, Dict[str, str]] = {}

        # Look for all spec files and data files
        spec_list = {
            os.path.basename(os.path.splitext(k)[0]): {
                "path": k,
                "data": []
            }
            for k in ls(os.path.join(self.spec_dir, "*.anod"),
                        emit_log_record=False)
        }
        logger.debug("found %s specs", len(spec_list))

        # API == 1.4
        yaml_files = ls(os.path.join(self.spec_dir, "*.yaml"),
                        emit_log_record=False)
        data_list = [os.path.basename(k)[:-5] for k in yaml_files]
        logger.debug("found %s yaml files API 1.4 compatible", len(data_list))

        # Match yaml files with associated specifications
        for data in data_list:
            candidate_specs = [
                spec_file for spec_file in spec_list
                if data.startswith(spec_file)
            ]
            # We pick the longuest spec name
            candidate_specs.sort(key=len)
            if candidate_specs:
                spec_list[candidate_specs[-1]]["data"].append(
                    data)  # type: ignore

        # Find yaml files that are API >= 1.5 compatible
        new_yaml_files = ls(os.path.join(self.spec_dir, "*", "*.yaml"),
                            emit_log_record=False)

        for yml_f in new_yaml_files:
            associated_spec = os.path.basename(os.path.dirname(yml_f))

            # Keep only the yaml files associated with an .anod file
            if associated_spec in spec_list:
                # We're recording the relative path without the extension
                suffix, _ = os.path.splitext(os.path.basename(yml_f))

                spec_list[associated_spec]["data"].append(  # type: ignore
                    os.path.join(associated_spec, suffix))

        # Create AnodModule objects
        for name, value in spec_list.items():
            self.specs[name] = AnodModule(name, **value)  # type: ignore

        # Load config/repositories.yaml
        repo_file = os.path.join(self.spec_dir, "config", "repositories.yaml")
        if os.path.isfile(repo_file):
            with open(repo_file) as fd:
                self.repos = yaml.safe_load(fd)

        # Declare spec prolog
        prolog_file = os.path.join(spec_dir, "prolog.py")
        self.prolog_dict = {
            "spec_config": spec_config,
            "__spec_repository": self
        }
        if os.path.exists(prolog_file):
            with open(prolog_file) as f:
                exec(compile(f.read(), prolog_file, "exec"), self.prolog_dict)
Ejemplo n.º 7
0
    def load(self, repository: AnodSpecRepository) -> Callable[..., Anod]:
        """Load an anod specification and return the corresponding Anod class.

        :param repository: the anod spec repository of the spec file
        :raise SandBoxError: in case of failure
        """
        if self.is_loaded:
            if TYPE_CHECKING:
                assert self.anod_class is not None
            return self.anod_class

        logger.debug("loading anod spec: %s", self.name)

        # Create a new module
        mod_name = "anod_" + self.name
        anod_module = types.ModuleType(mod_name)

        try:
            with open(self.path) as fd:
                # Inject the prolog into the new module dict
                anod_module.__dict__.update(repository.prolog_dict)

                # Exec spec code
                code = compile(fd.read(), self.path, "exec")
                exec(code, anod_module.__dict__)
        except Exception as e:
            logger.error("exception: %s", e)
            logger.error("cannot load code of %s", self.name)
            raise SandBoxError(origin="load",
                               message="invalid spec code for %s" %
                               self.name).with_traceback(sys.exc_info()[2])

        # At this stage we have loaded completely the module. Now we need to
        # look for a subclass of Anod. Use python inspection features to
        # achieve this.

        for members in inspect.getmembers(anod_module):
            _, value = members
            # Return the first subclass of Anod defined in this module
            if (inspect.isclass(value) and value.__module__ == mod_name
                    and "Anod" in (k.__name__ for k in value.__mro__)):
                # This class is a child of Anod so register it.
                # Note that even if we won't use directly the
                # module we need to keep a reference on it in order
                # to avoid garbage collector issues.
                value.spec_checksum = self.checksum

                # Give a name to our Anod class: the basename of the
                # anod spec file (without the .anod extension)
                value.name = self.name
                self.anod_class = value
                self.module = anod_module
                self.anod_class.data_files = self.data  # type: ignore
                self.anod_class.spec_dir = os.path.dirname(
                    self.path)  # type: ignore
                self.anod_class.api_version = repository.api_version  # type: ignore
                return value

        logger.error("spec %s does not contains an Anod subclass", self.name)
        raise SandBoxError("cannot find Anod subclass in %s" % self.path,
                           "load")
Ejemplo n.º 8
0
    def __init__(
        self,
        spec_dir: str,
        spec_config: Any = None,
        # Ideally should be spec_config: Optional[SpecConfig] = None,
        # We keep it to Any to avoid mypy issues on other projects
        extra_repositories_config: Optional[dict] = None,
    ):
        """Initialize an AnodSpecRepository.

        :param spec_dir: directory containing the anod specs.
        :param spec_config: dictionary containing the configuration for this
            AnodSpecRepository
        :param extra_repositories_config: first read the configuration from
            <spec_dir>/config/repositories.yaml and update the result with
            extra_repositories_config
        """
        logger.debug("initialize spec repository (%s)", spec_dir)

        if not os.path.isdir(spec_dir):
            raise SandBoxError(f"spec directory {spec_dir} does not exist")
        self.spec_dir = spec_dir
        self.api_version = __version__
        self.specs = {}
        self.repos: Dict[str, Dict[str, str]] = {}

        # Look for all spec files and data files
        spec_list = {
            os.path.basename(os.path.splitext(k)[0]): {
                "path": k,
                "data": []
            }
            for k in ls(os.path.join(self.spec_dir, "*.anod"),
                        emit_log_record=False)
        }
        logger.debug("found %s specs", len(spec_list))

        # API == 1.4
        yaml_files = ls(os.path.join(self.spec_dir, "*.yaml"),
                        emit_log_record=False)
        data_list = [os.path.basename(k)[:-5] for k in yaml_files]
        logger.debug("found %s yaml files API 1.4 compatible", len(data_list))

        # Match yaml files with associated specifications
        for data in data_list:
            candidate_specs = [
                spec_file for spec_file in spec_list
                if data.startswith(spec_file)
            ]
            # We pick the longuest spec name
            candidate_specs.sort(key=len)
            if candidate_specs:
                spec_list[candidate_specs[-1]]["data"].append(
                    data)  # type: ignore

        # Find yaml files that are API >= 1.5 compatible
        new_yaml_files = ls(os.path.join(self.spec_dir, "*", "*.yaml"),
                            emit_log_record=False)

        for yml_f in new_yaml_files:
            associated_spec = os.path.basename(os.path.dirname(yml_f))

            # Keep only the yaml files associated with an .anod file
            if associated_spec in spec_list:
                # We're recording the relative path without the extension
                suffix, _ = os.path.splitext(os.path.basename(yml_f))

                spec_list[associated_spec]["data"].append(  # type: ignore
                    os.path.join(associated_spec, suffix))

        # Create AnodModule objects
        for name, value in spec_list.items():
            self.specs[name] = AnodModule(name, **value)  # type: ignore

        # Load config/repositories.yaml
        repo_file = os.path.join(self.spec_dir, "config", "repositories.yaml")
        if os.path.isfile(repo_file):
            with open(repo_file) as fd:
                self.repos = yaml.safe_load(fd)

        if extra_repositories_config:
            for repo_name, repo_data in extra_repositories_config.items():
                if repo_name in self.repos:
                    self.repos[repo_name].update(repo_data)
                else:
                    self.repos[repo_name] = repo_data

        # Make sure that all revision are strings and not floats
        for repo_conf in self.repos.values():
            if "revision" in repo_conf:
                repo_conf["revision"] = str(repo_conf["revision"])

        if spec_config is None:
            spec_config = SpecConfig()
        spec_config.spec_dir = self.spec_dir
        spec_config.repositories = self.repos

        # Declare spec prolog
        prolog_file = os.path.join(spec_dir, "prolog.py")
        self.prolog_dict = {
            "spec_config": spec_config,
            "__spec_repository": self
        }
        if os.path.exists(prolog_file):
            with open(prolog_file) as f:
                exec(compile(f.read(), prolog_file, "exec"), self.prolog_dict)
Ejemplo n.º 9
0
    def run(self, args):
        sandbox = SandBox(root_dir=args.sandbox)

        if args.specs_dir:
            sandbox.specs_dir = args.specs_dir

        if args.create_sandbox:
            sandbox.create_dirs()

        if args.create_sandbox and args.spec_git_url:
            mkdir(sandbox.specs_dir)
            g = GitRepository(sandbox.specs_dir)
            if e3.log.default_output_stream is not None:
                g.log_stream = e3.log.default_output_stream
            g.init()
            g.update(args.spec_git_url, args.spec_git_branch, force=True)

        sandbox.dump_configuration()
        sandbox.write_scripts()

        asr = AnodSpecRepository(sandbox.specs_dir)
        check_api_version(asr.api_version)

        # Load plan content if needed
        if args.plan:
            if not os.path.isfile(args.plan):
                raise SandBoxError(f"plan file {args.plan} does not exist",
                                   origin="SandBoxExec.run")
            with open(args.plan) as plan_fd:
                plan_content = ["def main_entry_point():"]
                plan_content += [
                    f"    {line}" for line in plan_fd.read().splitlines()
                ]
                plan_content = "\n".join(plan_content)

            env = BaseEnv()
            cm = PlanContext(server=env)
            store = None
            resolver = getattr(
                AnodContext,
                str(args.resolver),
                AnodContext.always_create_source_resolver,
            )
            logger.debug("Using resolver %s", resolver.__name__)

            # Declare available actions and their signature
            def anod_action(module,
                            build=None,
                            host=None,
                            target=None,
                            qualifier=None):
                pass  # all: no cover

            for a in ("anod_install", "anod_build", "anod_test"):
                cm.register_action(a, anod_action)

            # Load the plan and execute
            plan = Plan(data={})
            plan.load_chunk(plan_content)
            actions = cm.execute(plan, "main_entry_point")

            ac = AnodContext(asr, default_env=env)
            for action in actions:
                ac.add_anod_action(
                    action.module,
                    action,
                    action.action.replace("anod_", "", 1),
                    action.qualifier,
                )

            # Check if machine plan is locally schedulable
            action_list = ac.schedule(resolver)
            e = ElectrolytJobFactory(sandbox, asr, store, dry_run=args.dry_run)
            e.run(action_list)
Ejemplo n.º 10
0
 def error(message: str) -> NoReturn:
     raise SandBoxError(message)
Ejemplo n.º 11
0
 def run(self, args):
     if args.api_version != "1.5":
         raise SandBoxError("Only 1.5 is supported for now")
     cd(args.spec_dir)
     migrate_v1_5()
Ejemplo n.º 12
0
    def __init__(self, spec_dir, spec_config=None):
        """Initialize an AnodSpecRepository.

        :param spec_dir: directory containing the anod specs.
        :type spec_dir: str
        :param spec_config: dictionary containing the configuration for this
            AnodSpecRepository
        :type spec_config: dict | SandboxConfig
        """
        logger.debug('initialize spec repository (%s)', spec_dir)

        if not os.path.isdir(spec_dir):
            raise SandBoxError(
                'spec directory %s does not exist' % spec_dir)
        self.spec_dir = spec_dir
        self.api_version = __version__
        self.specs = {}
        self.repos = {}

        # Look for all spec files and data files
        spec_list = {os.path.basename(os.path.splitext(k)[0]): {'path': k,
                                                                'data': []}
                     for k in ls(os.path.join(self.spec_dir, '*.anod'),
                                 emit_log_record=False)}
        logger.debug('found %s specs', len(spec_list))

        # API == 1.4
        yaml_files = ls(os.path.join(self.spec_dir, '*.yaml'),
                        emit_log_record=False)
        data_list = [os.path.basename(k)[:-5] for k in yaml_files]
        logger.debug('found %s yaml files API 1.4 compatible', len(data_list))

        # Match yaml files with associated specifications
        for data in data_list:
            candidate_specs = [spec_file for spec_file in spec_list
                               if data.startswith(spec_file)]
            # We pick the longuest spec name
            candidate_specs.sort(key=len)
            if candidate_specs:
                spec_list[candidate_specs[-1]]['data'].append(data)

        # Find yaml files that are API >= 1.5 compatible
        new_yaml_files = ls(os.path.join(self.spec_dir, '*', '*.yaml'),
                            emit_log_record=False)

        for yml_f in new_yaml_files:
            associated_spec = os.path.basename(os.path.dirname(yml_f))

            # Keep only the yaml files associated with an .anod file
            if associated_spec in spec_list:
                # We're recording the relative path without the extension
                suffix, _ = os.path.splitext(os.path.basename(yml_f))

                spec_list[associated_spec]['data'].append(
                    os.path.join(associated_spec, suffix))

        # Create AnodModule objects
        for name, value in spec_list.iteritems():
            self.specs[name] = AnodModule(name, **value)

        # Declare spec prolog
        prolog_file = os.path.join(spec_dir, 'prolog.py')
        self.prolog_dict = {'spec_config': spec_config,
                            '__spec_repository': self}
        if os.path.exists(prolog_file):
            with open(prolog_file) as f:
                exec(compile(f.read(), prolog_file, 'exec'),
                     self.prolog_dict)