def test_upload_asset_only_component_failed(mock_requests): mock_open = mock.mock_open(read_data=b"some tgz file") mock_requests.post.return_value.ok = False expected = "Failed to upload a component to Nexus" with mock.patch("cachito.workers.nexus.open", mock_open): with pytest.raises(CachitoError, match=expected): nexus.upload_asset_only_component("cachito-js-hosted", "npm", "/path/to/rxjs-6.5.5.tgz")
def test_upload_asset_only_component_connection_error(mock_requests): mock_open = mock.mock_open(read_data=b"some tgz file") mock_requests.post.side_effect = requests.ConnectionError() expected = "Could not connect to the Nexus instance to upload a component" with mock.patch("cachito.workers.nexus.open", mock_open): with pytest.raises(CachitoError, match=expected): nexus.upload_asset_only_component("cachito-js-hosted", "npm", "/path/to/rxjs-6.5.5.tgz")
def test_upload_asset_only_component(mock_requests, use_hoster): mock_open = mock.mock_open(read_data=b"some tgz file") mock_requests.post.return_value.ok = True with mock.patch("cachito.workers.nexus.open", mock_open): nexus.upload_asset_only_component("cachito-js-hosted", "npm", "/path/to/rxjs-6.5.5.tgz", use_hoster) expected_asset = {"npm.asset": ("rxjs-6.5.5.tgz", b"some tgz file")} assert mock_requests.post.call_args[1]["files"] == expected_asset assert mock_requests.post.call_args[1]["params"] == { "repository": "cachito-js-hosted" } assert mock_requests.post.call_args[1]["auth"].username == "cachito" assert mock_requests.post.call_args[1]["auth"].password == "cachito"
def test_upload_asset_only_component_wrong_type(): repo_type = "unsupported" expected = f"Type {repo_type!r} is not supported or requires additional params" with pytest.raises(ValueError, match=expected): nexus.upload_asset_only_component("cachito-js-hosted", repo_type, "/path/to/rxjs-6.5.5.tgz")
def upload_non_registry_dependency( dep_identifier, version_suffix, verify_scripts=False, checksum_info=None ): """ Upload the non-registry npm dependency to the Nexus hosted repository with a custom version. :param str dep_identifier: the identifier of the dependency to download :param str version_suffix: the suffix to append to the dependency's version in its package.json file :param bool verify_scripts: if ``True``, raise an exception if dangerous scripts are present in the ``package.json`` file and would have been executed by ``npm pack`` if ``ignore-scripts`` was set to ``false`` :param ChecksumInfo checksum_info: if not ``None``, the checksum of the downloaded artifact will be verified. :raise CachitoError: if the dependency cannot be download, uploaded, or is invalid """ # These are the scripts that should not be present if verify_scripts is True dangerous_scripts = {"prepare", "prepack"} with tempfile.TemporaryDirectory(prefix="cachito-") as temp_dir: env = { # This is set since the home directory must be determined by the HOME environment # variable or by looking at the /etc/passwd file. The latter does not always work # since some deployments (e.g. OpenShift) don't have an entry for the running user # in /etc/passwd. "HOME": os.environ.get("HOME", ""), "NPM_CONFIG_CACHE": os.path.join(temp_dir, "cache"), # This is important to avoid executing any dangerous scripts if it's a Git dependency "NPM_CONFIG_IGNORE_SCRIPTS": "true", "PATH": os.environ.get("PATH", ""), # Have `npm pack` fail without a prompt if the SSH key from a protected source such # as a private GitHub repo is not trusted "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=yes", } run_params = {"env": env, "cwd": temp_dir} npm_pack_args = ["npm", "pack", dep_identifier] log.info("Downloading the npm dependency %s to be uploaded to Nexus", dep_identifier) # An example of the command's stdout: # "reactivex-rxjs-6.5.5.tgz\n" stdout = run_cmd( npm_pack_args, run_params, f"Failed to download the npm dependency {dep_identifier}" ) dep_archive = os.path.join(temp_dir, stdout.strip()) if checksum_info: verify_checksum(dep_archive, checksum_info) package_json_rel_path = find_package_json(dep_archive) if not package_json_rel_path: msg = f"The dependency {dep_identifier} does not have a package.json file" log.error(msg) raise CachitoError(msg) modified_dep_archive = os.path.join( os.path.dirname(dep_archive), f"modified-{os.path.basename(dep_archive)}" ) with tarfile.open(dep_archive, mode="r:*") as dep_archive_file: with tarfile.open(modified_dep_archive, mode="x:gz") as modified_dep_archive_file: for member in dep_archive_file.getmembers(): # Add all the files except for the package.json file without any modifications if member.path != package_json_rel_path: modified_dep_archive_file.addfile( member, dep_archive_file.extractfile(member) ) continue # Modify the version in the package.json file try: package_json = json.load(dep_archive_file.extractfile(member)) except json.JSONDecodeError: msg = ( f"The dependency {dep_identifier} does not have a valid " "package.json file" ) log.exception(msg) raise CachitoError(msg) if verify_scripts: log.info( "Checking for dangerous scripts in the package.json of %s", dep_identifier, ) scripts = package_json.get("scripts", {}) if dangerous_scripts & scripts.keys(): msg = ( f"The dependency {dep_identifier} is not supported because Cachito " "cannot execute the following required scripts of Git " f"dependencies: {', '.join(sorted(dangerous_scripts))}" ) log.error(msg) raise CachitoError(msg) new_version = f"{package_json['version']}{version_suffix}" log.debug( "Modifying the version of %s from %s to %s", dep_identifier, package_json["version"], new_version, ) package_json["version"] = new_version package_json_bytes = json.dumps(package_json, indent=2).encode("utf-8") package_json_file_obj = io.BytesIO(package_json_bytes) member.size = len(package_json_bytes) modified_dep_archive_file.addfile(member, package_json_file_obj) repo_name = get_js_hosted_repo_name() nexus.upload_asset_only_component(repo_name, "npm", modified_dep_archive)