def unprovide(self, requirement, environ, local_state_file, overrides, requirement_status=None): """Override superclass to delete the downloaded file.""" project_dir = environ['PROJECT_DIR'] filename = os.path.abspath( os.path.join(project_dir, requirement.filename)) try: if os.path.isdir(filename): shutil.rmtree(filename) elif os.path.isfile(filename): os.remove(filename) else: return SimpleStatus( success=True, description=( "No need to remove %s which wasn't downloaded." % filename)) return SimpleStatus(success=True, description=("Removed downloaded file %s." % filename)) except Exception as e: return SimpleStatus(success=False, description=("Failed to remove %s: %s." % (filename, str(e))))
def build_image(path, tag, command, builder_image=DEFAULT_BUILDER_IMAGE, build_args=None): """Run s2i build.""" cmd = [ 's2i', 'build', '--copy', path, builder_image, tag, '-e', 'CMD={}'.format(command) ] if build_args is not None: cmd.extend(build_args) start_msg = '''*** {} image build starting.'''.format(tag) print(start_msg) print(' '.join(cmd)) try: _ = subprocess.check_call(cmd) msg = '''\nDocker image {} build successful.'''.format(tag) return SimpleStatus(success=True, description=msg) except subprocess.CalledProcessError as e: error_msg = '''\nAn error was encountered building this docker image.''' return SimpleStatus(success=False, description=error_msg, errors=[str(e)]) except FileNotFoundError as e: error_msg = """\nERROR: The source-to-image (s2i) executable was not found. It can be installed using conda install -c ctools source-to-image""" return SimpleStatus(success=False, description=error_msg, errors=[str(e)])
def _upload(project, archive_filename, uploaded_basename, site=None, username=None, token=None, log_level=None): assert not project.problems client = _Client(site=site, username=username, token=token, log_level=log_level) try: json = client.upload(project.publication_info(), archive_filename, uploaded_basename) return _UploadedStatus(json) except Unauthorized as e: return SimpleStatus( success=False, description='Please log in with the "anaconda login" command.', errors=["Not logged in."]) except BinstarError as e: return SimpleStatus(success=False, description="Upload failed.", errors=[str(e)])
def set_variables(project, vars_and_values, env_spec_name=None): """Set variables' values in anaconda-project-local.yml. Returns a ``Status`` instance which evaluates to True on success and has an ``errors`` property (with a list of error strings) on failure. Args: project (Project): the project vars_and_values (list of tuple): key-value pairs env_spec_name (str): name of env spec to use Returns: ``Status`` instance """ (env_prefix, status) = _prepare_env_prefix(project, env_spec_name) if env_prefix is None: return status local_state = LocalStateFile.load_for_directory(project.directory_path) var_reqs = dict() for req in project.find_requirements(klass=EnvVarRequirement): var_reqs[req.env_var] = req present_vars = set(var_reqs.keys()) errors = [] local_state_count = 0 keyring_count = 0 for varname, value in vars_and_values: if varname in present_vars: if var_reqs[varname].encrypted: # import keyring locally because it's an optional dependency # that prints a warning when it's needed but not found. from anaconda_project.internal import keyring keyring.set(env_prefix, varname, value) keyring_count = keyring_count + 1 else: local_state.set_value(['variables', varname], value) local_state_count = local_state_count + 1 else: errors.append("Variable %s does not exist in the project." % varname) if errors: return SimpleStatus(success=False, description="Could not set variables.", errors=errors) else: if local_state_count > 0: local_state.save() if keyring_count == 0: description = ("Values saved in %s." % local_state.filename) elif local_state_count == 0: description = ("Values saved in the system keychain.") else: description = ( "%d values saved in %s, %d values saved in the system keychain." % (local_state_count, local_state.filename, keyring_count)) return SimpleStatus(success=True, description=description)
def test_simple_status_properties(): good_status = SimpleStatus(success=True, description="quick brown fox", errors=["bar"]) assert good_status assert good_status.status_description == "quick brown fox" assert good_status.errors == ["bar"] bad_status = SimpleStatus(success=False, description="quick brown fox", errors=["bar"]) assert not bad_status assert bad_status.status_description == "quick brown fox" assert bad_status.errors == ["bar"]
def _remove_env_path(env_path): """Also used by project_ops.py to delete environment files.""" if os.path.exists(env_path): try: shutil.rmtree(env_path) return SimpleStatus(success=True, description=("Deleted environment files in %s." % env_path)) except Exception as e: problem = "Failed to remove environment files in {}: {}.".format(env_path, str(e)) return SimpleStatus(success=False, description=problem) else: return SimpleStatus(success=True, description=("Nothing to clean up for environment '%s'." % os.path.basename(env_path)))
def remove_service(project, prepare_result, variable_name): """Remove a service to anaconda-project.yml. Returns a ``Status`` instance which evaluates to True on success and has an ``errors`` property (with a list of error strings) on failure. Args: project (Project): the project prepare_result (PrepareResult): result of a previous prepare variable_name (str): environment variable name for the service requirement Returns: ``Status`` instance """ failed = project.problems_status() if failed is not None: return failed requirements = [ req for req in project.find_requirements(klass=ServiceRequirement) if req.service_type == variable_name or req.env_var == variable_name ] if not requirements: return SimpleStatus( success=False, description="Service '{}' not found in the project file.".format( variable_name)) if len(requirements) > 1: return SimpleStatus( success=False, description=( "Conflicting results, found {} matches, use list-services" " to identify which service you want to remove").format( len(requirements))) env_var = requirements[0].env_var status = prepare.unprepare(project, prepare_result, whitelist=[env_var]) if not status: return status project.project_file.unset_value(['services', env_var]) project.project_file.use_changes_without_saving() assert project.problems == [] project.project_file.save() return SimpleStatus( success=True, description="Removed service '{}' from the project file.".format( variable_name))
def clean(project, prepare_result): """Blow away auto-provided state for the project. This should not remove any potential "user data" such as anaconda-project-local.yml. This includes a call to ``anaconda_project.prepare.unprepare`` but also removes the entire services/ and envs/ directories even if they contain leftovers that we didn't prepare in the most recent prepare() call. Args: project (Project): the project instance prepare_result (PrepareResult): result of a previous prepare Returns: a ``Status`` instance """ status = prepare.unprepare(project, prepare_result) logs = status.logs errors = status.errors if status: logs = logs + [status.status_description] else: errors = errors + [status.status_description] # we also nuke any "debris" from non-current choices, like old # environments or services def cleanup_dir(dirname): if os.path.isdir(dirname): logs.append("Removing %s." % dirname) try: shutil.rmtree(dirname) except Exception as e: errors.append("Error removing %s: %s." % (dirname, str(e))) cleanup_dir(os.path.join(project.directory_path, "services")) cleanup_dir(os.path.join(project.directory_path, "envs")) if status and len(errors) == 0: return SimpleStatus(success=True, description="Cleaned.", logs=logs, errors=errors) else: return SimpleStatus(success=False, description="Failed to clean everything up.", logs=logs, errors=errors)
def remove_env_spec(project, name): """Remove the environment spec from project directory and remove from anaconda-project.yml. Returns a ``Status`` subtype (it won't be a ``RequirementStatus`` as with some other functions, just a plain status). Args: project (Project): the project name (str): environment spec name Returns: ``Status`` instance """ assert name is not None failed = project.problems_status() if failed is not None: return failed if name not in project.env_specs: problem = "Environment spec {} doesn't exist.".format(name) return SimpleStatus(success=False, description=problem) if len(project.env_specs) == 1: problem = "At least one environment spec is required; '{}' is the only one left.".format( name) return SimpleStatus(success=False, description=problem) env_path = project.env_specs[name].path(project.directory_path) # For remove_service and remove_download, we use unprepare() # to do the cleanup; for the environment, it's awkward to do # that because the env we want to remove may not be the one # that was prepared. So instead we share some code with the # CondaEnvProvider but don't try to go through the unprepare # machinery. status = _remove_env_path(env_path) if status: project.project_file.unset_value(['env_specs', name]) project.project_file.use_changes_without_saving() if project.problems_status() is None: project.project_file.save() else: # revert and return the problems status = project.problems_status() project.project_file.load() return status
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) status = SimpleStatus(success=True, description='Service added.') status.requirement = RedisRequirement(RequirementsRegistry(), env_var='REDIS_URL', options=dict(type='redis')) _monkeypatch_add_service(monkeypatch, status) code = _parse_args_and_run_subcommand(['anaconda-project', 'add-service', 'redis']) assert code == 0 out, err = capsys.readouterr() assert ( 'Service added.\n' + 'Added service redis to the project file, its address will be in REDIS_URL.\n') == out assert '' == err
def remove_download(project, prepare_result, env_var): """Remove file or directory referenced by ``env_var`` from file system and the project. The returned ``Status`` will be an instance of ``SimpleStatus``. A False status will have an ``errors`` property with a list of error strings. Args: project (Project): the project prepare_result (PrepareResult): result of a previous prepare env_var (str): env var to store the local filename Returns: ``Status`` instance """ failed = project.problems_status() if failed is not None: return failed # Modify the project file _in memory only_, do not save requirement = project.find_requirements(env_var, klass=DownloadRequirement) if not requirement: return SimpleStatus( success=False, description="Download requirement: {} not found.".format(env_var)) assert len(requirement) == 1 # duplicate env vars aren't allowed requirement = requirement[0] status = prepare.unprepare(project, prepare_result, whitelist=[env_var]) if status: project.project_file.unset_value(['downloads', env_var]) project.project_file.use_changes_without_saving() assert project.problems == [] project.project_file.save() return status
def remove_variables(project, vars_to_remove, env_spec_name=None): """Remove variables from anaconda-project.yml and unset their values in local project state. Returns a ``Status`` instance which evaluates to True on success and has an ``errors`` property (with a list of error strings) on failure. Args: project (Project): the project vars_to_remove (list of str): variable names env_spec_name (str): name of env spec to use Returns: ``Status`` instance """ (env_prefix, status) = _prepare_env_prefix(project, env_spec_name) if env_prefix is None: return status local_state = LocalStateFile.load_for_directory(project.directory_path) for varname in vars_to_remove: _unset_variable(project, env_prefix, varname, local_state) project.project_file.unset_value(['variables', varname]) project.project_file.save() local_state.save() return SimpleStatus(success=True, description="Variables removed from the project file.")
def unset_variables(project, vars_to_unset, env_spec_name=None): """Unset variables' values in anaconda-project-local.yml. Returns a ``Status`` instance which evaluates to True on success and has an ``errors`` property (with a list of error strings) on failure. Args: project (Project): the project vars_to_unset (list of str): variable names env_spec_name (str): name of env spec to use Returns: ``Status`` instance """ (env_prefix, status) = _prepare_env_prefix(project, env_spec_name) if env_prefix is None: return status local_state = LocalStateFile.load_for_directory(project.directory_path) for varname in vars_to_unset: _unset_variable(project, env_prefix, varname, local_state) local_state.save() return SimpleStatus(success=True, description=("Variables were unset."))
def unprovide(self, requirement, environ, local_state_file, overrides, requirement_status=None): """Override superclass to delete project-scoped envs directory.""" config = self.read_config( requirement, environ, local_state_file, # future: pass in this default_env_spec_name default_env_spec_name='default', overrides=overrides) env_path = config.get('value', None) assert env_path is not None project_dir = environ['PROJECT_DIR'] if not env_path.startswith(project_dir): return SimpleStatus( success=True, description=( "Current environment is not in %s, no need to delete it." % project_dir)) return _remove_env_path(env_path)
def remove_command(project, name): """Remove a command from anaconda-project.yml. Returns a ``Status`` subtype (it won't be a ``RequirementStatus`` as with some other functions, just a plain status). Args: project (Project): the project name (string): name of the command to be removed Returns: a ``Status`` instance """ failed = project.problems_status() if failed is not None: return failed if name not in project.commands: return SimpleStatus( success=False, description="Command: '{}' not found in project file.".format( name)) command = project.commands[name] # if we remove a notebook, it's an error normally, we have to mark it skipped # TODO share this code with the no_fix function in project.py if command.notebook is not None: skipped_notebooks = project.project_file.get_value( ['skip_imports', 'notebooks'], default=[]) if isinstance(skipped_notebooks, list) and \ command.notebook not in skipped_notebooks: skipped_notebooks.append(command.notebook) project.project_file.set_value(['skip_imports', 'notebooks'], skipped_notebooks) project.project_file.unset_value(['commands', name]) project.project_file.use_changes_without_saving() assert project.problems == [] project.project_file.save() return SimpleStatus( success=True, description="Command: '{}' removed from project file.".format(name))
def set_properties(project, name=None, icon=None, description=None): """Set simple properties on a project. This doesn't support properties which require prepare() actions to check their effects; see other calls such as ``add_packages()`` for those. This will fail if project.problems is non-empty. Args: project (``Project``): the project instance name (str): Name of the project or None to leave unmodified icon (str): Icon for the project or None to leave unmodified description (str): description for the project or None to leave unmodified Returns: a ``Status`` instance indicating success or failure """ failed = project.problems_status() if failed is not None: return failed if name is not None: project.project_file.set_value('name', name) if icon is not None: project.project_file.set_value('icon', icon) if description is not None: project.project_file.set_value('description', description) project.project_file.use_changes_without_saving() if len(project.problems) == 0: # write out the anaconda-project.yml if it looks like we're safe. project.project_file.save() return SimpleStatus(success=True, description="Project properties updated.") else: # revert to previous state (after extracting project.problems) status = SimpleStatus(success=False, description="Failed to set project properties.", errors=list(project.problems)) project.project_file.load() return status
def mock_unarchive(filename, project_dir, parent_dir=None, frontend=None): assert frontend is not None frontend.info("a") frontend.info("b") frontend.error("c") frontend.error("d") return SimpleStatus(success=False, description="DESC", errors=['c', 'd'])
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) _monkeypatch_add_service(monkeypatch, SimpleStatus(success=False, description='Service add FAIL.')) code = _parse_args_and_run_subcommand(['anaconda-project', 'add-service', 'redis']) assert code == 1 out, err = capsys.readouterr() assert '' == out assert 'Service add FAIL.\n' == err
def unprovide(self, requirement, environ, local_state_file, overrides, requirement_status=None): """Override superclass to return success always.""" return SimpleStatus(success=True, description=("Nothing to clean up for %s." % requirement.env_var))
def shutdown_service_run_state(local_state_file, service_name): """Run any shutdown commands from the local state file for the given service. Also remove the shutdown commands from the file. Args: local_state_file (LocalStateFile): local state service_name (str): the name of the service, usually a variable name, should be specific enough to uniquely identify the provider Returns: a `Status` instance potentially containing errors """ run_states = local_state_file.get_all_service_run_states() if service_name not in run_states: return SimpleStatus(success=True, description=("Nothing to do to shut down %s." % service_name)) errors = [] state = run_states[service_name] if 'shutdown_commands' in state: commands = state['shutdown_commands'] for command in commands: code = logged_subprocess.call(command) if code != 0: errors.append( "Shutting down %s, command %s failed with code %d." % (service_name, repr(command), code)) # clear out the run state once we try to shut it down local_state_file.set_service_run_state(service_name, dict()) local_state_file.save() if errors: return SimpleStatus(success=False, description=("Shutdown commands failed for %s." % service_name), errors=errors) else: return SimpleStatus(success=True, description=("Successfully shut down %s." % service_name))
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) _monkeypatch_add_env_spec(monkeypatch, SimpleStatus(success=True, description='Environment looks good.')) code = _parse_args_and_run_subcommand(['anaconda-project', 'add-env-spec', '--name', 'foo']) assert code == 0 out, err = capsys.readouterr() assert ('Environment looks good.\n' + 'Added environment foo to the project file.\n') == out assert '' == err
def export_env_spec(project, name, filename): """Export the environment spec as an environment.yml-type file. Returns a ``Status`` subtype (it won't be a ``RequirementStatus`` as with some other functions, just a plain status). Args: project (Project): the project name (str): environment spec name or None for default filename (str): file to export to Returns: ``Status`` instance """ failed = project.problems_status() if failed is not None: return failed if name is None: name = project.default_env_spec_name assert name is not None if name not in project.env_specs: problem = "Environment spec {} doesn't exist.".format(name) return SimpleStatus(success=False, description=problem) spec = project.env_specs[name] try: spec.save_environment_yml(filename) except Exception as e: return SimpleStatus(success=False, description="Failed to save {}: {}.".format( filename, str(e))) return SimpleStatus( success=True, description="Exported environment spec {} to {}.".format( name, filename))
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) params = _monkeypatch_remove_packages(monkeypatch, SimpleStatus(success=True, description='Installed ok.')) code = _parse_args_and_run_subcommand(['anaconda-project', 'remove-packages', '--env-spec', 'foo', 'bar']) assert code == 0 out, err = capsys.readouterr() assert ('Installed ok.\n' + 'Removed packages from environment foo in project file: bar.\n') == out assert '' == err assert 1 == len(params['args']) assert dict(env_spec_name='foo', packages=['bar']) == params['kwargs']
def _remove_env_path(env_path, project_dir): """Also used by project_ops.py to delete environment files.""" if not os.path.isdir(env_path): return SimpleStatus( success=True, description=("Nothing to clean up for environment '%s'." % os.path.basename(env_path))) if not env_path.startswith(project_dir + os.sep): return SimpleStatus( success=True, description=( "Current environment is not in %s, no need to delete it." % project_dir)) try: shutil.rmtree(env_path) return SimpleStatus(success=True, description=("Deleted environment files in %s." % env_path)) except Exception as e: problem = "Failed to remove environment files in {}: {}.".format( env_path, str(e)) return SimpleStatus(success=False, description=problem)
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) _monkeypatch_add_env_spec(monkeypatch, SimpleStatus(success=False, description='Environment variable MYDATA is not set.', logs=['This is a log message.'], errors=['This is an error message.'])) code = _parse_args_and_run_subcommand(['anaconda-project', 'add-env-spec', '--name', 'foo']) assert code == 1 out, err = capsys.readouterr() assert '' == out assert 'This is a log message.\nThis is an error message.\nEnvironment variable MYDATA is not set.\n' == err
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) params = _monkeypatch_update( monkeypatch, SimpleStatus(success=True, description='Updated.')) code = _parse_args_and_run_subcommand(['anaconda-project', 'update']) assert code == 0 out, err = capsys.readouterr() assert ('Updated.\n') == out assert '' == err assert 1 == len(params['args']) assert dict(env_spec_name=None) == params['kwargs']
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) params = _monkeypatch_add_packages(monkeypatch, SimpleStatus(success=True, description='Installed ok.')) code = _parse_args_and_run_subcommand(['anaconda-project', 'add-packages', '--env-spec', 'foo', '--channel', 'c1', '--channel=c2', 'a', 'b']) assert code == 0 out, err = capsys.readouterr() assert ('Installed ok.\n' + 'Added packages to environment foo in project file: a, b.\n') == out assert '' == err assert 1 == len(params['args']) assert dict(env_spec_name='foo', packages=['a', 'b'], channels=['c1', 'c2']) == params['kwargs']
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) params = _monkeypatch_add_env_spec(monkeypatch, SimpleStatus(success=True, description='Environment looks good.')) code = _parse_args_and_run_subcommand(['anaconda-project', 'add-env-spec', '--name', 'foo', '--channel', 'c1', '--channel=c2', 'a', 'b']) assert code == 0 out, err = capsys.readouterr() assert ('Environment looks good.\n' + 'Added environment foo to the project file.\n') == out assert '' == err assert 1 == len(params['args']) assert dict(name='foo', packages=['a', 'b'], channels=['c1', 'c2']) == params['kwargs']
def add_variables(project, vars_to_add, defaults=None): """Add variables in anaconda-project.yml, optionally setting their defaults. Returns a ``Status`` instance which evaluates to True on success and has an ``errors`` property (with a list of error strings) on failure. Args: project (Project): the project vars_to_add (list of str): variable names defaults (dict): dictionary from keys to defaults, can be empty Returns: ``Status`` instance """ failed = project.problems_status() if failed is not None: return failed if defaults is None: defaults = dict() present_vars = { req.env_var for req in project.requirements if isinstance(req, EnvVarRequirement) } for varname in vars_to_add: if varname in defaults: # we need to update the default even if var already exists new_default = defaults.get(varname) variable_value = project.project_file.get_value( ['variables', varname]) if variable_value is None or not isinstance(variable_value, dict): variable_value = new_default else: variable_value['default'] = new_default project.project_file.set_value(['variables', varname], variable_value) elif varname not in present_vars: # we are only adding the var if nonexistent and should leave # the default alone if it's already set project.project_file.set_value(['variables', varname], None) project.project_file.save() return SimpleStatus(success=True, description="Variables added to the project file.")
def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) params = _monkeypatch_add_platforms( monkeypatch, SimpleStatus(success=True, description='Installed ok.')) code = _parse_args_and_run_subcommand( ['anaconda-project', 'add-platforms', 'a', 'b']) assert code == 0 out, err = capsys.readouterr() assert ('Installed ok.\n' + 'Added platforms to project file: a, b.\n') == out assert '' == err assert 1 == len(params['args']) assert dict(env_spec_name=None, platforms=['a', 'b']) == params['kwargs']