Skip to content

Commit 1fdc03d

Browse files
committed
Add npm dist gen supporting for multi-targets and integrity computing
* Add registry in target bucket configuration * Different registries dist-tarball gen for multi-targets * Tarball integrity computing for dist support * Add unit testings for dist gen computing against s3
1 parent fed9e9b commit 1fdc03d

28 files changed

+296
-143
lines changed

charon/cmd/command.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,15 @@ def delete(
345345
__safe_delete(tmp_dir)
346346

347347

348-
def __get_targets(target: List[str], conf: CharonConfig) -> List[Tuple[str, str, str]]:
348+
def __get_targets(target: List[str], conf: CharonConfig) -> List[Tuple[str, str, str, str]]:
349349
targets_ = []
350350
for tgt in target:
351351
aws_bucket = conf.get_aws_bucket(tgt)
352352
if not aws_bucket:
353353
continue
354354
prefix = conf.get_bucket_prefix(tgt)
355-
targets_.append([tgt, aws_bucket, prefix])
355+
registry = conf.get_bucket_registry(tgt)
356+
targets_.append([tgt, aws_bucket, prefix, registry])
356357
if len(targets_) == 0:
357358
logger.error(
358359
"All targets are not valid or configured, "

charon/config.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import logging
2121

2222
from charon.utils.strings import remove_prefix
23+
from charon.constants import DEFAULT_REGISTRY
2324

2425
CONFIG_FILE = "charon.yaml"
2526

@@ -53,8 +54,8 @@ def get_aws_bucket(self, target: str) -> str:
5354
return None
5455
bucket = target_.get("bucket", None)
5556
if not bucket:
56-
logger.error("The bucket %s is not found for target %s "
57-
"in charon configuration.")
57+
logger.error("The bucket is not found for target %s "
58+
"in charon configuration.", target)
5859
return bucket
5960

6061
def get_bucket_prefix(self, target: str) -> str:
@@ -73,6 +74,19 @@ def get_bucket_prefix(self, target: str) -> str:
7374
prefix = remove_prefix(prefix, "/")
7475
return prefix
7576

77+
def get_bucket_registry(self, target: str) -> str:
78+
target_: Dict = self.__targets.get(target, None)
79+
if not target_ or not isinstance(target_, Dict):
80+
logger.error("The target %s is not found in charon configuration.", target)
81+
return None
82+
registry = target_.get("registry", None)
83+
if not registry:
84+
registry = DEFAULT_REGISTRY
85+
logger.error("The registry is not found for target %s "
86+
"in charon configuration, so DEFAULT_REGISTRY(localhost) will be used.",
87+
target)
88+
return registry
89+
7690
def get_manifest_bucket(self) -> str:
7791
return self.__manifest_bucket
7892

charon/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,4 @@
174174
MANIFEST_SUFFIX = ".txt"
175175
DEFAULT_ERRORS_LOG = "errors.log"
176176

177-
NRRC_REGISTRY = "npm.registry.redhat.com"
177+
DEFAULT_REGISTRY = "localhost"

charon/pkgs/maven.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def handle_maven_uploading(
256256
prod_key: str,
257257
ignore_patterns=None,
258258
root="maven-repository",
259-
targets: List[Tuple[str, str, str]] = None,
259+
targets: List[Tuple[str, str, str, str]] = None,
260260
aws_profile=None,
261261
dir_=None,
262262
do_index=True,
@@ -418,7 +418,7 @@ def handle_maven_del(
418418
prod_key: str,
419419
ignore_patterns=None,
420420
root="maven-repository",
421-
targets: List[Tuple[str, str, str]] = None,
421+
targets: List[Tuple[str, str, str, str]] = None,
422422
aws_profile=None,
423423
dir_=None,
424424
do_index=True,

charon/pkgs/npm.py

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from charon.pkgs.pkg_utils import upload_post_process, rollback_post_process
3131
from charon.utils.strings import remove_prefix
3232
from charon.utils.files import write_manifest
33+
from charon.utils.map import del_none
3334

3435
logger = logging.getLogger(__name__)
3536

@@ -65,7 +66,7 @@ def __init__(self, metadata, is_version):
6566
def handle_npm_uploading(
6667
tarball_path: str,
6768
product: str,
68-
targets: List[Tuple[str, str, str]] = None,
69+
targets: List[Tuple[str, str, str, str]] = None,
6970
aws_profile=None,
7071
dir_=None,
7172
do_index=True,
@@ -86,48 +87,47 @@ def handle_npm_uploading(
8687
8788
Returns the directory used for archive processing and if uploading is successful
8889
"""
89-
target_dir, valid_paths, package_metadata = _scan_metadata_paths_from_archive(
90-
tarball_path, prod=product, dir__=dir_
91-
)
92-
if not os.path.isdir(target_dir):
93-
logger.error("Error: the extracted target_dir path %s does not exist.", target_dir)
94-
sys.exit(1)
90+
for target in targets:
91+
bucket_ = target[1]
92+
prefix__ = remove_prefix(target[2], "/")
93+
registry__ = target[3]
94+
target_dir, valid_paths, package_metadata = _scan_metadata_paths_from_archive(
95+
tarball_path, registry__, prod=product, dir__=dir_
96+
)
97+
if not os.path.isdir(target_dir):
98+
logger.error("Error: the extracted target_dir path %s does not exist.", target_dir)
99+
sys.exit(1)
100+
valid_dirs = __get_path_tree(valid_paths, target_dir)
101+
102+
# main_target = targets[0]
103+
client = S3Client(aws_profile=aws_profile, dry_run=dry_run)
104+
logger.info("Start uploading files to s3 buckets: %s", bucket_)
105+
failed_files = client.upload_files(
106+
file_paths=valid_paths,
107+
targets=[(bucket_, prefix__)],
108+
product=product,
109+
root=target_dir
110+
)
95111

96-
valid_dirs = __get_path_tree(valid_paths, target_dir)
112+
logger.info("Files uploading done\n")
97113

98-
# main_target = targets[0]
99-
client = S3Client(aws_profile=aws_profile, dry_run=dry_run)
100-
targets_ = [(target[1], remove_prefix(target[2], "/")) for target in targets]
101-
logger.info(
102-
"Start uploading files to s3 buckets: %s",
103-
[target[1] for target in targets]
104-
)
105-
failed_files = client.upload_files(
106-
file_paths=valid_paths,
107-
targets=targets_,
108-
product=product,
109-
root=target_dir
110-
)
111-
logger.info("Files uploading done\n")
114+
succeeded = True
112115

113-
succeeded = True
114-
for target in targets:
115116
if not manifest_bucket_name:
116117
logger.warning(
117118
'Warning: No manifest bucket is provided, will ignore the process of manifest '
118119
'uploading\n')
119120
else:
120121
logger.info("Start uploading manifest to s3 bucket %s", manifest_bucket_name)
121-
manifest_folder = target[1]
122+
manifest_folder = bucket_
122123
manifest_name, manifest_full_path = write_manifest(valid_paths, target_dir, product)
124+
123125
client.upload_manifest(
124126
manifest_name, manifest_full_path,
125127
manifest_folder, manifest_bucket_name
126128
)
127129
logger.info("Manifest uploading is done\n")
128130

129-
bucket_ = target[1]
130-
prefix__ = remove_prefix(target[2], "/")
131131
logger.info(
132132
"Start generating package.json for package: %s in s3 bucket %s",
133133
package_metadata.name, bucket_
@@ -178,7 +178,7 @@ def handle_npm_uploading(
178178
def handle_npm_del(
179179
tarball_path: str,
180180
product: str,
181-
targets: List[Tuple[str, str, str]] = None,
181+
targets: List[Tuple[str, str, str, str]] = None,
182182
aws_profile=None,
183183
dir_=None,
184184
do_index=True,
@@ -381,11 +381,11 @@ def _gen_npm_package_metadata_for_del(
381381
return meta_files
382382

383383

384-
def _scan_metadata_paths_from_archive(path: str, prod="", dir__=None) -> Tuple[str, list,
385-
NPMPackageMetadata]:
384+
def _scan_metadata_paths_from_archive(path: str, registry: str, prod="", dir__=None) ->\
385+
Tuple[str, list, NPMPackageMetadata]:
386386
tmp_root = mkdtemp(prefix=f"npm-charon-{prod}-", dir=dir__)
387387
try:
388-
_, valid_paths = extract_npm_tarball(path, tmp_root, True)
388+
_, valid_paths = extract_npm_tarball(path, tmp_root, True, registry)
389389
if len(valid_paths) > 1:
390390
version = _scan_for_version(valid_paths[1])
391391
package = NPMPackageMetadata(version, True)
@@ -502,23 +502,14 @@ def _write_package_metadata_to_file(package_metadata: NPMPackageMetadata, root='
502502
final_package_metadata_path = os.path.join(root, package_metadata.name, PACKAGE_JSON)
503503
try:
504504
with open(final_package_metadata_path, mode='w', encoding='utf-8') as f:
505-
dump(_del_none(package_metadata.__dict__.copy()), f)
505+
dump(del_none(package_metadata.__dict__.copy()), f)
506506
return final_package_metadata_path
507507
except FileNotFoundError:
508508
logger.error(
509509
'Can not create file %s because of some missing folders', final_package_metadata_path
510510
)
511511

512512

513-
def _del_none(d):
514-
for key, value in list(d.items()):
515-
if value is None:
516-
del d[key]
517-
elif isinstance(value, dict):
518-
_del_none(value)
519-
return d
520-
521-
522513
def __get_path_tree(paths: str, prefix: str) -> Set[str]:
523514
valid_dirs = set()
524515
for f in paths:

charon/utils/archive.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import requests
2121
import tempfile
2222
import shutil
23+
import subresource_integrity
2324
from enum import Enum
2425
from json import load, JSONDecodeError, dump
2526
from typing import Tuple
2627
from zipfile import ZipFile, is_zipfile
27-
from charon.constants import NRRC_REGISTRY
28+
from charon.constants import DEFAULT_REGISTRY
2829
from charon.utils.files import digest, HashType
30+
from charon.utils.map import del_none
2931

3032
logger = logging.getLogger(__name__)
3133

@@ -44,7 +46,8 @@ def extract_zip_with_files(zf: ZipFile, target_dir: str, file_suffix: str, debug
4446
zf.extractall(target_dir, members=filtered)
4547

4648

47-
def extract_npm_tarball(path: str, target_dir: str, is_for_upload: bool) -> Tuple[str, list]:
49+
def extract_npm_tarball(path: str, target_dir: str, is_for_upload: bool, registry=DEFAULT_REGISTRY)\
50+
-> Tuple[str, list]:
4851
""" Extract npm tarball will relocate the tgz file and metadata files.
4952
* Locate tar path ( e.g.: jquery/-/jquery-7.6.1.tgz or @types/jquery/-/jquery-2.2.3.tgz).
5053
* Locate version metadata path (e.g.: jquery/7.6.1 or @types/jquery/2.2.3).
@@ -68,7 +71,7 @@ def extract_npm_tarball(path: str, target_dir: str, is_for_upload: bool) -> Tupl
6871

6972
if is_for_upload:
7073
tgz_relative_path = "/".join([parse_paths[0], "-", _get_tgz_name(path)])
71-
__write_npm_version_dist(path, f.path, version_data, tgz_relative_path)
74+
__write_npm_version_dist(path, f.path, version_data, tgz_relative_path, registry)
7275

7376
os.makedirs(tarball_parent_path)
7477
target = os.path.join(tarball_parent_path, os.path.basename(path))
@@ -87,25 +90,18 @@ def _get_tgz_name(path: str):
8790
return ""
8891

8992

90-
def _del_none(d):
91-
for key, value in list(d.items()):
92-
if value is None:
93-
del d[key]
94-
elif isinstance(value, dict):
95-
_del_none(value)
96-
return d
97-
98-
9993
def __write_npm_version_dist(path: str, version_meta_extract_path: str, version_data: dict,
100-
tgz_relative_path: str):
101-
tarball_link = "".join(["https://", NRRC_REGISTRY, "/", tgz_relative_path])
102-
shasum = digest(path, HashType.SHA1)
94+
tgz_relative_path: str, registry: str):
10395
dist = dict()
104-
dist["tarball"] = tarball_link
105-
dist["shasum"] = shasum
96+
dist["tarball"] = "".join(["https://", registry, "/", tgz_relative_path])
97+
dist["shasum"] = digest(path, HashType.SHA1)
98+
with open(path, "rb") as tarball:
99+
tarball_data = tarball.read()
100+
integrity = subresource_integrity.render(tarball_data, ['sha512'])
101+
dist["integrity"] = integrity
106102
version_data["dist"] = dist
107103
with open(version_meta_extract_path, mode='w', encoding='utf-8') as f:
108-
dump(_del_none(version_data), f)
104+
dump(del_none(version_data), f)
109105

110106

111107
def __parse_npm_package_version_paths(path: str) -> Tuple[dict, list]:

charon/utils/map.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def del_none(d):
2+
for key, value in list(d.items()):
3+
if value is None:
4+
del d[key]
5+
elif isinstance(value, dict):
6+
del_none(value)
7+
return d

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ click==8.0.3
77
requests==2.27.1
88
ruamel.yaml==0.17.20
99
defusedxml==0.7.1
10+
subresource-integrity==0.2

tests/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def setUp(self):
5050
ea:
5151
bucket: "charon-test-ea"
5252
prefix: earlyaccess/all
53+
54+
npm:
55+
bucket: "charon-test-npm"
56+
registry: "npm1.registry.redhat.com"
5357
"""
5458
self.prepare_config(config_base, default_config_content)
5559

tests/commons.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
"@babel/code-frame/-/code-frame-7.15.8.tgz",
102102
]
103103
CODE_FRAME_META = "@babel/code-frame/package.json"
104+
105+
CODE_FRAME_7_14_5_META = "@babel/code-frame/7.14.5/package.json"
104106
# For npm indexes
105107
CODE_FRAME_7_14_5_INDEXES = [
106108
"@babel/code-frame/7.14.5/index.html",

0 commit comments

Comments
 (0)