def test_install_missing_metadata_warning(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) for file in [b'MANIFEST.json', b'galaxy.yml']: b_path = os.path.join(collection_path, file) if os.path.isfile(b_path): os.unlink(b_path) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager( temp_path, validate_certs=False) requirements = [ Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file') ] collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm) display_msgs = [ m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1 ] assert 'WARNING' in display_msgs[0]
def test_install_collections_existing_without_force(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) # If we don't delete collection_path it will think the original build skeleton is installed so we expect a skip collection.install_collections([( to_text(collection_tar), '*', None, )], to_text(temp_path), [u'https://galaxy.ansible.com'], True, False, False, False, False) assert os.path.isdir(collection_path) actual_files = os.listdir(collection_path) actual_files.sort() assert actual_files == [ b'README.md', b'docs', b'galaxy.yml', b'playbooks', b'plugins', b'roles' ] assert mock_display.call_count == 2 # Msg1 is the warning about not MANIFEST.json, cannot really check message as it has line breaks which varies based # on the path size assert mock_display.mock_calls[1][1][ 0] == "Skipping 'ansible_namespace.collection' as it is already installed"
def test_install_collection_with_circular_dependency(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] shutil.rmtree(collection_path) mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) collection.install_collections([(to_text(collection_tar), '*', None,)], to_text(temp_path), [u'https://galaxy.ansible.com'], True, False, False, False, False) assert os.path.isdir(collection_path) actual_files = os.listdir(collection_path) actual_files.sort() assert actual_files == [b'FILES.json', b'MANIFEST.json', b'README.md', b'docs', b'playbooks', b'plugins', b'roles'] with open(os.path.join(collection_path, b'MANIFEST.json'), 'rb') as manifest_obj: actual_manifest = json.loads(to_text(manifest_obj.read())) assert actual_manifest['collection_info']['namespace'] == 'ansible_namespace' assert actual_manifest['collection_info']['name'] == 'collection' assert actual_manifest['collection_info']['version'] == '0.1.0' # Filter out the progress cursor display calls. display_msgs = [m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1] assert len(display_msgs) == 3 assert display_msgs[0] == "Process install dependency map" assert display_msgs[1] == "Starting collection install process" assert display_msgs[2] == "Installing 'ansible_namespace.collection:0.1.0' to '%s'" % to_text(collection_path)
def test_install_collections_existing_without_force(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) # If we don't delete collection_path it will think the original build skeleton is installed so we expect a skip collection.install_collections([(to_text(collection_tar), '*', None,)], to_text(temp_path), [u'https://galaxy.ansible.com'], True, False, False, False, False) assert os.path.isdir(collection_path) actual_files = os.listdir(collection_path) actual_files.sort() assert actual_files == [b'README.md', b'docs', b'galaxy.yml', b'playbooks', b'plugins', b'roles'] # Filter out the progress cursor display calls. display_msgs = [m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1] assert len(display_msgs) == 4 # Msg1 is the warning about not MANIFEST.json, cannot really check message as it has line breaks which varies based # on the path size assert display_msgs[1] == "Process install dependency map" assert display_msgs[2] == "Starting collection install process" assert display_msgs[3] == "Skipping 'ansible_namespace.collection' as it is already installed"
def test_install_collection_with_circular_dependency(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] shutil.rmtree(collection_path) mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False) requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)] collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True) assert os.path.isdir(collection_path) actual_files = os.listdir(collection_path) actual_files.sort() assert actual_files == [b'FILES.json', b'MANIFEST.json', b'README.md', b'docs', b'playbooks', b'plugins', b'roles', b'runme.sh'] with open(os.path.join(collection_path, b'MANIFEST.json'), 'rb') as manifest_obj: actual_manifest = json.loads(to_text(manifest_obj.read())) assert actual_manifest['collection_info']['namespace'] == 'ansible_namespace' assert actual_manifest['collection_info']['name'] == 'collection' assert actual_manifest['collection_info']['version'] == '0.1.0' # Filter out the progress cursor display calls. display_msgs = [m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1] assert len(display_msgs) == 4 assert display_msgs[0] == "Process install dependency map" assert display_msgs[1] == "Starting collection install process" assert display_msgs[2] == "Installing 'ansible_namespace.collection:0.1.0' to '%s'" % to_text(collection_path) assert display_msgs[3] == "ansible_namespace.collection:0.1.0 was installed successfully"
def test_install_collections_existing_without_force(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False) assert os.path.isdir(collection_path) requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)] collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True) assert os.path.isdir(collection_path) actual_files = os.listdir(collection_path) actual_files.sort() assert actual_files == [b'README.md', b'docs', b'galaxy.yml', b'playbooks', b'plugins', b'roles', b'runme.sh'] # Filter out the progress cursor display calls. display_msgs = [m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1] assert len(display_msgs) == 1 assert display_msgs[0] == 'Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`.' for msg in display_msgs: assert 'WARNING' not in msg
def test_install_missing_metadata_warning(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) for file in [b'MANIFEST.json', b'galaxy.yml']: b_path = os.path.join(collection_path, file) if os.path.isfile(b_path): os.unlink(b_path) collection.install_collections([( to_text(collection_tar), '*', None, )], to_text(temp_path), [u'https://galaxy.ansible.com'], True, False, False, False, False) display_msgs = [ m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1 ] assert 'WARNING' in display_msgs[0]
def test_install_collection_with_circular_dependency(collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact temp_path = os.path.split(collection_tar)[0] shutil.rmtree(collection_path) mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) collection.install_collections([( to_text(collection_tar), '*', None, )], to_text(temp_path), [u'https://galaxy.ansible.com'], True, False, False, False, False) assert os.path.isdir(collection_path) actual_files = os.listdir(collection_path) actual_files.sort() assert actual_files == [ b'FILES.json', b'MANIFEST.json', b'README.md', b'docs', b'playbooks', b'plugins', b'roles' ] with open(os.path.join(collection_path, b'MANIFEST.json'), 'rb') as manifest_obj: actual_manifest = json.loads(to_text(manifest_obj.read())) assert actual_manifest['collection_info'][ 'namespace'] == 'ansible_namespace' assert actual_manifest['collection_info']['name'] == 'collection' assert actual_manifest['collection_info']['version'] == '0.1.0' assert mock_display.call_count == 1 assert mock_display.mock_calls[0][1][0] == "Installing 'ansible_namespace.collection:0.1.0' to '%s'" \ % to_text(collection_path)
def execute_install(self): """ uses the args list of roles to be installed, unless -f was specified. The list of roles can be a name (which will be downloaded via the galaxy API and github), or it can be a local tar archive file. """ if context.CLIARGS['type'] == 'collection': collections = context.CLIARGS['args'] force = context.CLIARGS['force'] output_path = context.CLIARGS['collections_path'] # TODO: use a list of server that have been configured in ~/.ansible_galaxy servers = [context.CLIARGS['api_server']] ignore_certs = context.CLIARGS['ignore_certs'] ignore_errors = context.CLIARGS['ignore_errors'] requirements_file = context.CLIARGS['requirements'] no_deps = context.CLIARGS['no_deps'] force_deps = context.CLIARGS['force_with_deps'] if collections and requirements_file: raise AnsibleError("The positional collection_name arg and --requirements-file are mutually exclusive.") elif not collections and not requirements_file: raise AnsibleError("You must specify a collection name or a requirements file.") if requirements_file: requirements_file = GalaxyCLI._resolve_path(requirements_file) collection_requirements = parse_collections_requirements_file(requirements_file) else: collection_requirements = [] for collection_input in collections: name, dummy, requirement = collection_input.partition(':') collection_requirements.append((name, requirement or '*', None)) output_path = GalaxyCLI._resolve_path(output_path) collections_path = C.COLLECTIONS_PATHS if len([p for p in collections_path if p.startswith(output_path)]) == 0: display.warning("The specified collections path '%s' is not part of the configured Ansible " "collections paths '%s'. The installed collection won't be picked up in an Ansible " "run." % (to_text(output_path), to_text(":".join(collections_path)))) if os.path.split(output_path)[1] != 'ansible_collections': output_path = os.path.join(output_path, 'ansible_collections') b_output_path = to_bytes(output_path, errors='surrogate_or_strict') if not os.path.exists(b_output_path): os.makedirs(b_output_path) install_collections(collection_requirements, output_path, servers, (not ignore_certs), ignore_errors, no_deps, force, force_deps) return 0 role_file = context.CLIARGS['role_file'] if not context.CLIARGS['args'] and role_file is None: # the user needs to specify one of either --role-file or specify a single user/role name raise AnsibleOptionsError("- you must specify a user/role name or a roles file") no_deps = context.CLIARGS['no_deps'] force_deps = context.CLIARGS['force_with_deps'] force = context.CLIARGS['force'] or force_deps roles_left = [] if role_file: try: f = open(role_file, 'r') if role_file.endswith('.yaml') or role_file.endswith('.yml'): try: required_roles = yaml.safe_load(f.read()) except Exception as e: raise AnsibleError( "Unable to load data from the requirements file (%s): %s" % (role_file, to_native(e)) ) if required_roles is None: raise AnsibleError("No roles found in file: %s" % role_file) for role in required_roles: if "include" not in role: role = RoleRequirement.role_yaml_parse(role) display.vvv("found role %s in yaml file" % str(role)) if "name" not in role and "scm" not in role: raise AnsibleError("Must specify name or src for role") roles_left.append(GalaxyRole(self.galaxy, **role)) else: with open(role["include"]) as f_include: try: roles_left += [ GalaxyRole(self.galaxy, **r) for r in (RoleRequirement.role_yaml_parse(i) for i in yaml.safe_load(f_include)) ] except Exception as e: msg = "Unable to load data from the include requirements file: %s %s" raise AnsibleError(msg % (role_file, e)) else: raise AnsibleError("Invalid role requirements file") f.close() except (IOError, OSError) as e: raise AnsibleError('Unable to open %s: %s' % (role_file, to_native(e))) else: # roles were specified directly, so we'll just go out grab them # (and their dependencies, unless the user doesn't want us to). for rname in context.CLIARGS['args']: role = RoleRequirement.role_yaml_parse(rname.strip()) roles_left.append(GalaxyRole(self.galaxy, **role)) for role in roles_left: # only process roles in roles files when names matches if given if role_file and context.CLIARGS['args'] and role.name not in context.CLIARGS['args']: display.vvv('Skipping role %s' % role.name) continue display.vvv('Processing role %s ' % role.name) # query the galaxy API for the role data if role.install_info is not None: if role.install_info['version'] != role.version or force: if force: display.display('- changing role %s from %s to %s' % (role.name, role.install_info['version'], role.version or "unspecified")) role.remove() else: display.warning('- %s (%s) is already installed - use --force to change version to %s' % (role.name, role.install_info['version'], role.version or "unspecified")) continue else: if not force: display.display('- %s is already installed, skipping.' % str(role)) continue try: installed = role.install() except AnsibleError as e: display.warning(u"- %s was NOT installed successfully: %s " % (role.name, to_text(e))) self.exit_without_ignore() continue # install dependencies, if we want them if not no_deps and installed: if not role.metadata: display.warning("Meta file %s is empty. Skipping dependencies." % role.path) else: role_dependencies = role.metadata.get('dependencies') or [] for dep in role_dependencies: display.debug('Installing dep %s' % dep) dep_req = RoleRequirement() dep_info = dep_req.role_yaml_parse(dep) dep_role = GalaxyRole(self.galaxy, **dep_info) if '.' not in dep_role.name and '.' not in dep_role.src and dep_role.scm is None: # we know we can skip this, as it's not going to # be found on galaxy.ansible.com continue if dep_role.install_info is None: if dep_role not in roles_left: display.display('- adding dependency: %s' % to_text(dep_role)) roles_left.append(dep_role) else: display.display('- dependency %s already pending installation.' % dep_role.name) else: if dep_role.install_info['version'] != dep_role.version: if force_deps: display.display('- changing dependant role %s from %s to %s' % (dep_role.name, dep_role.install_info['version'], dep_role.version or "unspecified")) dep_role.remove() roles_left.append(dep_role) else: display.warning('- dependency %s (%s) from role %s differs from already installed version (%s), skipping' % (to_text(dep_role), dep_role.version, role.name, dep_role.install_info['version'])) else: if force_deps: roles_left.append(dep_role) else: display.display('- dependency %s is already installed, skipping.' % dep_role.name) if not installed: display.warning("- %s was NOT installed successfully." % role.name) self.exit_without_ignore() return 0