def test_verify_checksum_unsupported_algorithm(tmpdir): file = tmpdir.join("spells.txt") file.write("Beetlejuice! Beetlejuice! Beetlejuice!") expected_error = "Cannot perform checksum on the file spells.txt,.*bacon.*" with pytest.raises(CachitoError, match=expected_error): verify_checksum(str(file), ChecksumInfo("bacon", "spam"))
def test_verify_checksum_invalid_hexdigest(tmpdir): file = tmpdir.join("spells.txt") file.write("Beetlejuice! Beetlejuice! Beetlejuice!") expected_error = "The file spells.txt has an unexpected checksum value" with pytest.raises(CachitoError, match=expected_error): verify_checksum(str(file), ChecksumInfo("sha512", "spam"))
def test_verify_checksum(tmpdir): file = tmpdir.join("spells.txt") file.write("Beetlejuice! Beetlejuice! Beetlejuice!") expected = { "sha512": ( "da518fe8b800b3325fe35ca680085fe37626414d0916937a01a25ef8f5d7aa769b7233073235fce85ee" "c717e02bb9d72062656cf2d79223792a784910c267b54" ), "sha256": "ed1f8cd69bfacf0528744b6a7084f36e8841b6128de0217503e215612a0ee835", "md5": "308764bc995153f7d853827a675e6731", } for algorithm, checksum in expected.items(): verify_checksum(str(file), ChecksumInfo(algorithm, checksum))
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)