def test_build_artifact_from_path_no_version(collection_artifact, monkeypatch): mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) # a collection artifact should always contain a valid version manifest_path = os.path.join(collection_artifact[0], b'MANIFEST.json') manifest_value = json.dumps({ 'collection_info': { 'namespace': 'namespace', 'name': 'name', 'version': '', 'dependencies': {} } }) with open(manifest_path, 'wb') as manifest_obj: manifest_obj.write(to_bytes(manifest_value)) tmp_path = os.path.join(os.path.split(collection_artifact[1])[0], b'temp') concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager( tmp_path, validate_certs=False) expected = ( '^Collection metadata file `.*` at `.*` is expected to have a valid SemVer ' 'version value but got {empty_unicode_string!r}$'.format( empty_unicode_string=u'')) with pytest.raises(AnsibleError, match=expected): Requirement.from_dir_path_as_unknown(collection_artifact[0], concrete_artifact_cm)
def test_build_requirement_from_tar_no_manifest(tmp_path_factory): test_dir = to_bytes( tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input')) json_data = to_bytes(json.dumps({ 'files': [], 'format': 1, })) tar_path = os.path.join(test_dir, b'ansible-collections.tar.gz') with tarfile.open(tar_path, 'w:gz') as tfile: b_io = BytesIO(json_data) tar_info = tarfile.TarInfo('FILES.json') tar_info.size = len(json_data) tar_info.mode = 0o0644 tfile.addfile(tarinfo=tar_info, fileobj=b_io) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager( test_dir, validate_certs=False) expected = "Collection at '%s' does not contain the required file MANIFEST.json." % to_native( tar_path) with pytest.raises(AnsibleError, match=expected): Requirement.from_requirement_dict({'name': to_text(tar_path)}, concrete_artifact_cm)
def collection_objects(): collection_ham = Requirement('sandwiches.ham', '1.5.0', None, 'galaxy') collection_pbj = Requirement('sandwiches.pbj', '2.5', None, 'galaxy') collection_reuben = Requirement('sandwiches.reuben', '4', None, 'galaxy') return [collection_ham, collection_pbj, collection_reuben]
def test_build_requirement_from_tar_fail_not_tar(tmp_path_factory): test_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input')) test_file = os.path.join(test_dir, b'fake.tar.gz') with open(test_file, 'wb') as test_obj: test_obj.write(b"\x00\x01\x02\x03") concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(test_dir, validate_certs=False) expected = "Collection artifact at '%s' is not a valid tar file." % to_native(test_file) with pytest.raises(AnsibleError, match=expected): Requirement.from_requirement_dict({'name': to_text(test_file)}, concrete_artifact_cm)
def test_build_requirement_from_path_invalid_manifest(collection_artifact): manifest_path = os.path.join(collection_artifact[0], b'MANIFEST.json') with open(manifest_path, 'wb') as manifest_obj: manifest_obj.write(b"not json") tmp_path = os.path.join(os.path.split(collection_artifact[1])[0], b'temp') concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(tmp_path, validate_certs=False) expected = "Collection tar file member MANIFEST.json does not contain a valid json string." with pytest.raises(AnsibleError, match=expected): Requirement.from_dir_path_as_unknown(collection_artifact[0], concrete_artifact_cm)
def mock_collection_objects(mocker): mocker.patch('ansible.cli.galaxy.GalaxyCLI._resolve_path', side_effect=[ '/root/.ansible/collections', '/usr/share/ansible/collections' ]) mocker.patch('ansible.cli.galaxy.validate_collection_path', side_effect=[ '/root/.ansible/collections/ansible_collections', '/usr/share/ansible/collections/ansible_collections' ]) collection_args_1 = ( ( 'sandwiches.pbj', '1.5.0', None, 'dir', None, ), ( 'sandwiches.reuben', '2.5.0', None, 'dir', None, ), ) collection_args_2 = ( ( 'sandwiches.pbj', '1.0.0', None, 'dir', None, ), ( 'sandwiches.ham', '1.0.0', None, 'dir', None, ), ) collections_path_1 = [Requirement(*cargs) for cargs in collection_args_1] collections_path_2 = [Requirement(*cargs) for cargs in collection_args_2] mocker.patch('ansible.cli.galaxy.find_existing_collections', side_effect=[collections_path_1, collections_path_2])
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 _from_path(collection_name='pbj'): collection_args = { 'sandwiches.pbj': ( ( 'sandwiches.pbj', '1.5.0', None, 'dir', ), ( 'sandwiches.pbj', '1.0.0', None, 'dir', ), ), 'sandwiches.ham': (( 'sandwiches.ham', '1.0.0', None, 'dir', ), ), } from_path_objects = [ Requirement(*args) for args in collection_args[collection_name] ] mocker.patch('ansible.cli.galaxy.Requirement.from_dir_path_as_unknown', side_effect=from_path_objects)
def test_get_collection_widths_single_collection(mocker): mocked_collection = Requirement('sandwiches.club', '3.0.0', None, 'galaxy', None) # Make this look like it is not iterable mocker.patch('ansible.cli.galaxy.is_iterable', return_value=False) assert _get_collection_widths(mocked_collection) == (15, 5)
def test_build_requirement_from_path_no_version(collection_artifact, monkeypatch): mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) # version may be falsey/arbitrary strings for collections in development manifest_path = os.path.join(collection_artifact[0], b'galaxy.yml') metadata = { 'authors': ['Ansible'], 'readme': 'README.md', 'namespace': 'namespace', 'name': 'name', 'version': '', 'dependencies': {}, } with open(manifest_path, 'wb') as manifest_obj: manifest_obj.write(to_bytes(yaml.safe_dump(metadata))) tmp_path = os.path.join(os.path.split(collection_artifact[1])[0], b'temp') concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager( tmp_path, validate_certs=False) actual = Requirement.from_dir_path_as_unknown(collection_artifact[0], concrete_artifact_cm) # While the folder name suggests a different collection, we treat MANIFEST.json as the source of truth. assert actual.namespace == u'namespace' assert actual.name == u'name' assert actual.src == collection_artifact[0] assert actual.ver == u'*'
def test_build_requirement_from_path_with_manifest(version, collection_artifact): manifest_path = os.path.join(collection_artifact[0], b'MANIFEST.json') manifest_value = json.dumps({ 'collection_info': { 'namespace': 'namespace', 'name': 'name', 'version': version, 'dependencies': { 'ansible_namespace.collection': '*' } } }) with open(manifest_path, 'wb') as manifest_obj: manifest_obj.write(to_bytes(manifest_value)) tmp_path = os.path.join(os.path.split(collection_artifact[1])[0], b'temp') concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager( tmp_path, validate_certs=False) actual = Requirement.from_dir_path_as_unknown(collection_artifact[0], concrete_artifact_cm) # While the folder name suggests a different collection, we treat MANIFEST.json as the source of truth. assert actual.namespace == u'namespace' assert actual.name == u'name' assert actual.src == collection_artifact[0] assert actual.ver == to_text(version)
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_build_requirement_from_tar_invalid_manifest(tmp_path_factory): test_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input')) json_data = b"not a json" tar_path = os.path.join(test_dir, b'ansible-collections.tar.gz') with tarfile.open(tar_path, 'w:gz') as tfile: b_io = BytesIO(json_data) tar_info = tarfile.TarInfo('MANIFEST.json') tar_info.size = len(json_data) tar_info.mode = 0o0644 tfile.addfile(tarinfo=tar_info, fileobj=b_io) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(test_dir, validate_certs=False) expected = "Collection tar file member MANIFEST.json does not contain a valid json string." with pytest.raises(AnsibleError, match=expected): Requirement.from_requirement_dict({'name': to_text(tar_path)}, concrete_artifact_cm)
def test_build_requirement_from_path(collection_artifact): tmp_path = os.path.join(os.path.split(collection_artifact[1])[0], b'temp') concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(tmp_path, validate_certs=False) actual = Requirement.from_dir_path_as_unknown(collection_artifact[0], concrete_artifact_cm) assert actual.namespace == u'ansible_namespace' assert actual.name == u'collection' assert actual.src == collection_artifact[0] assert actual.ver == u'0.1.0'
def test_build_requirement_from_tar_no_files(tmp_path_factory): test_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input')) json_data = to_bytes(json.dumps( { 'collection_info': {}, } )) tar_path = os.path.join(test_dir, b'ansible-collections.tar.gz') with tarfile.open(tar_path, 'w:gz') as tfile: b_io = BytesIO(json_data) tar_info = tarfile.TarInfo('MANIFEST.json') tar_info.size = len(json_data) tar_info.mode = 0o0644 tfile.addfile(tarinfo=tar_info, fileobj=b_io) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(test_dir, validate_certs=False) with pytest.raises(KeyError, match='namespace'): Requirement.from_requirement_dict({'name': to_text(tar_path)}, concrete_artifact_cm)
def test_install_collection_with_download(galaxy_server, collection_artifact, monkeypatch): collection_path, collection_tar = collection_artifact shutil.rmtree(collection_path) collections_dir = ('%s' % os.path.sep).join( to_text(collection_path).split('%s' % os.path.sep)[:-2]) temp_path = os.path.join(os.path.split(collection_tar)[0], b'temp') os.makedirs(temp_path) mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager( temp_path, validate_certs=False) mock_download = MagicMock() mock_download.return_value = collection_tar monkeypatch.setattr(concrete_artifact_cm, 'get_galaxy_artifact_path', mock_download) req = Requirement('ansible_namespace.collection', '0.1.0', 'https://downloadme.com', 'galaxy') collection.install(req, to_text(collections_dir), concrete_artifact_cm) 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' ] assert mock_display.call_count == 2 assert mock_display.mock_calls[0][1][0] == "Installing 'ansible_namespace.collection:0.1.0' to '%s'" \ % to_text(collection_path) assert mock_display.mock_calls[1][1][ 0] == "ansible_namespace.collection:0.1.0 was installed successfully" assert mock_download.call_count == 1 assert mock_download.mock_calls[0][1][0].src == 'https://downloadme.com' assert mock_download.mock_calls[0][1][0].type == 'galaxy'
def _cobj(fqcn='sandwiches.ham'): return Requirement(fqcn, '1.5.0', None, 'galaxy')