Skip to content

Commit c492dd7

Browse files
authored
Merge pull request #331 from ligangty/kflux
Merge from release branch for 1.4.0 release
2 parents 0df1166 + 6df053a commit c492dd7

21 files changed

+1593
-27
lines changed

charon.spec

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
%global owner Commonjava
22
%global modulename charon
33

4-
%global charon_version 1.3.3
4+
%global charon_version 1.4.0
55
%global sdist_tar_name %{modulename}-%{charon_version}
66

77
%global python3_pkgversion 3
@@ -64,6 +64,23 @@ export LANG=en_US.UTF-8 LANGUAGE=en_US.en LC_ALL=en_US.UTF-8
6464

6565

6666
%changelog
67+
* Fri Jun 27 2025 Gang Li <[email protected]>
68+
- 1.4.0 release
69+
- Add RADAS signature support
70+
71+
* Mon Jun 23 2025 Gang Li <[email protected]>
72+
- 1.3.4 release
73+
- Fix the sorting problem of index page items
74+
75+
* Mon Dec 16 2024 Gang Li <[email protected]>
76+
- 1.3.3 release
77+
- Fix npm del error when deleting a package which has overlapped name with others
78+
- Some code refinement
79+
80+
* Thu Jul 11 2024 Gang Li <[email protected]>
81+
- 1.3.2 release
82+
- Some updates in the Containerfile.
83+
6784
* Tue May 7 2024 Gang Li <[email protected]>
6885
- 1.3.1 release
6986
- Add checksum refresh command: refresh checksum files for maven artifacts

charon/cmd/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
"""
16-
from click import group
16+
from click import group, version_option, pass_context
1717
from charon.cmd.cmd_upload import upload
1818
from charon.cmd.cmd_delete import delete
1919
from charon.cmd.cmd_index import index
2020
from charon.cmd.cmd_checksum import init_checksum, checksum
2121
from charon.cmd.cmd_cache import init_cf, cf
22+
from charon.cmd.cmd_sign import sign
2223

2324

2425
@group()
25-
def cli():
26+
@version_option()
27+
@pass_context
28+
def cli(ctx):
2629
"""Charon is a tool to synchronize several types of
2730
artifacts repository data to Red Hat Ronda
2831
service (maven.repository.redhat.com).
@@ -41,3 +44,6 @@ def cli():
4144
# init checksum command
4245
init_checksum()
4346
cli.add_command(checksum)
47+
48+
# radas sign cmd
49+
cli.add_command(sign)

charon/cmd/cmd_sign.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
Copyright (C) 2022 Red Hat, Inc. (https://github.com/Commonjava/charon)
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
from typing import List
17+
18+
from charon.config import get_config
19+
from charon.pkgs.radas_sign import sign_in_radas
20+
from charon.cmd.internal import _decide_mode
21+
from charon.constants import DEFAULT_RADAS_SIGN_IGNORES
22+
23+
from click import command, option, argument
24+
25+
import traceback
26+
import logging
27+
import sys
28+
import datetime
29+
30+
logger = logging.getLogger(__name__)
31+
32+
33+
@argument(
34+
"repo_url",
35+
type=str
36+
)
37+
@option(
38+
"--requester",
39+
"-r",
40+
help="""
41+
The requester who sends the signing request.
42+
""",
43+
required=True
44+
)
45+
@option(
46+
"--result_path",
47+
"-p",
48+
help="""
49+
The path which will save the sign result file.
50+
""",
51+
required=True
52+
)
53+
@option(
54+
"--ignore_patterns",
55+
"-i",
56+
multiple=True,
57+
help="""
58+
The regex patterns list to filter out the files which should
59+
not be allowed to upload to S3. Can accept more than one pattern.
60+
"""
61+
)
62+
@option(
63+
"--config",
64+
"-c",
65+
help="""
66+
The charon configuration yaml file path. Default is
67+
$HOME/.charon/charon.yaml
68+
"""
69+
)
70+
@option(
71+
"--sign_key",
72+
"-k",
73+
help="""
74+
rpm-sign key to be used, will replace {{ key }} in default configuration for signature.
75+
Does noting if detach_signature_command does not contain {{ key }} field.
76+
""",
77+
required=True
78+
)
79+
@option(
80+
"--debug",
81+
"-D",
82+
help="Debug mode, will print all debug logs for problem tracking.",
83+
is_flag=True,
84+
default=False
85+
)
86+
@option(
87+
"--quiet",
88+
"-q",
89+
help="Quiet mode, will shrink most of the logs except warning and errors.",
90+
is_flag=True,
91+
default=False
92+
)
93+
@command()
94+
def sign(
95+
repo_url: str,
96+
requester: str,
97+
result_path: str,
98+
sign_key: str,
99+
ignore_patterns: List[str] = None,
100+
config: str = None,
101+
debug=False,
102+
quiet=False
103+
):
104+
"""Do signing against files in the repo zip in repo_url through
105+
radas service. The repo_url points to the maven zip repository
106+
in quay.io, which will be sent as the source of the signing.
107+
"""
108+
logger.debug("%s", ignore_patterns)
109+
try:
110+
current = datetime.datetime.now().strftime("%Y%m%d%I%M")
111+
_decide_mode("radas_sign", current, is_quiet=quiet, is_debug=debug)
112+
conf = get_config(config)
113+
if not conf:
114+
logger.error("The charon configuration is not valid!")
115+
sys.exit(1)
116+
radas_conf = conf.get_radas_config()
117+
if not radas_conf or not radas_conf.validate():
118+
logger.error("The configuration for radas is not valid!")
119+
sys.exit(1)
120+
# All ignore files in global config should also be ignored in signing.
121+
ig_patterns = conf.get_ignore_patterns()
122+
ig_patterns.extend(DEFAULT_RADAS_SIGN_IGNORES)
123+
if ignore_patterns:
124+
ig_patterns.extend(ignore_patterns)
125+
ig_patterns = list(set(ig_patterns))
126+
args = {
127+
"repo_url": repo_url,
128+
"requester": requester,
129+
"sign_key": sign_key,
130+
"result_path": result_path,
131+
"ignore_patterns": ig_patterns,
132+
"radas_config": radas_conf
133+
}
134+
sign_in_radas(**args) # type: ignore
135+
except Exception:
136+
print(traceback.format_exc())
137+
sys.exit(2)

charon/cmd/cmd_upload.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@
136136
default=False
137137
)
138138
@option("--dryrun", "-n", is_flag=True, default=False)
139+
@option(
140+
"--sign_result_file",
141+
"-l",
142+
help="""
143+
The path of the file which contains radas signature result.
144+
Upload will use the file to generate the corresponding .asc files
145+
""",
146+
)
139147
@command()
140148
def upload(
141149
repo: str,
@@ -150,7 +158,8 @@ def upload(
150158
sign_key: str = "redhatdevel",
151159
debug=False,
152160
quiet=False,
153-
dryrun=False
161+
dryrun=False,
162+
sign_result_file=None,
154163
):
155164
"""Upload all files from a released product REPO to Ronda
156165
Service. The REPO points to a product released tarball which
@@ -221,7 +230,8 @@ def upload(
221230
key=sign_key,
222231
dry_run=dryrun,
223232
manifest_bucket_name=manifest_bucket_name,
224-
config=config
233+
config=config,
234+
sign_result_file=sign_result_file
225235
)
226236
if not succeeded:
227237
sys.exit(1)

charon/config.py

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
"""
16+
1617
import logging
1718
import os
1819
from typing import Dict, List, Optional
@@ -24,6 +25,104 @@
2425
logger = logging.getLogger(__name__)
2526

2627

28+
class RadasConfig(object):
29+
def __init__(self, data: Dict):
30+
self.__umb_host: str = data.get("umb_host", None)
31+
self.__umb_host_port: str = data.get("umb_host_port", "5671")
32+
self.__result_queue: str = data.get("result_queue", None)
33+
self.__request_chan: str = data.get("request_channel", None)
34+
self.__client_ca: str = data.get("client_ca", None)
35+
self.__client_key: str = data.get("client_key", None)
36+
self.__client_key_pass_file: str = data.get("client_key_pass_file", None)
37+
self.__root_ca: str = data.get("root_ca", "/etc/pki/tls/certs/ca-bundle.crt")
38+
self.__quay_radas_registry_config: Optional[str] = data.get(
39+
"quay_radas_registry_config", None
40+
)
41+
self.__radas_sign_timeout_retry_count: int = data.get("radas_sign_timeout_retry_count", 10)
42+
self.__radas_sign_timeout_retry_interval: int = data.get(
43+
"radas_sign_timeout_retry_interval", 60
44+
)
45+
self.__radas_receiver_timeout: int = int(data.get("radas_receiver_timeout", 1800))
46+
47+
def validate(self) -> bool:
48+
if not self.__umb_host:
49+
logger.error("Missing host name setting for UMB!")
50+
return False
51+
if not self.__result_queue:
52+
logger.error("Missing the queue setting to receive signing result in UMB!")
53+
return False
54+
if not self.__request_chan:
55+
logger.error("Missing the queue setting to send signing request in UMB!")
56+
return False
57+
if self.__client_ca and not os.access(self.__client_ca, os.R_OK):
58+
logger.error("The client CA file is not valid!")
59+
return False
60+
if self.__client_key and not os.access(self.__client_key, os.R_OK):
61+
logger.error("The client key file is not valid!")
62+
return False
63+
if self.__client_key_pass_file and not os.access(self.__client_key_pass_file, os.R_OK):
64+
logger.error("The client key password file is not valid!")
65+
return False
66+
if self.__root_ca and not os.access(self.__root_ca, os.R_OK):
67+
logger.error("The root ca file is not valid!")
68+
return False
69+
if self.__quay_radas_registry_config and not os.access(
70+
self.__quay_radas_registry_config, os.R_OK
71+
):
72+
self.__quay_radas_registry_config = None
73+
logger.warning(
74+
"The quay registry config for oras is not valid, will ignore the registry config!"
75+
)
76+
return True
77+
78+
def umb_target(self) -> str:
79+
if self.ssl_enabled():
80+
return f"amqps://{self.__umb_host.strip()}:{self.__umb_host_port}"
81+
else:
82+
return f"amqp://{self.__umb_host.strip()}:{self.__umb_host_port}"
83+
84+
def result_queue(self) -> str:
85+
return self.__result_queue.strip()
86+
87+
def request_channel(self) -> str:
88+
return self.__request_chan.strip()
89+
90+
def client_ca(self) -> str:
91+
return self.__client_ca.strip()
92+
93+
def client_key(self) -> str:
94+
return self.__client_key.strip()
95+
96+
def client_key_password(self) -> str:
97+
pass_file = self.__client_key_pass_file
98+
if os.access(pass_file, os.R_OK):
99+
with open(pass_file, "r") as f:
100+
return f.read().strip()
101+
elif pass_file:
102+
logger.warning("The key password file is not accessible. Will ignore the password.")
103+
return ""
104+
105+
def root_ca(self) -> str:
106+
return self.__root_ca.strip()
107+
108+
def ssl_enabled(self) -> bool:
109+
return bool(self.__client_ca and self.__client_key and self.__root_ca)
110+
111+
def quay_radas_registry_config(self) -> Optional[str]:
112+
if self.__quay_radas_registry_config:
113+
return self.__quay_radas_registry_config.strip()
114+
return None
115+
116+
def radas_sign_timeout_retry_count(self) -> int:
117+
return self.__radas_sign_timeout_retry_count
118+
119+
def radas_sign_timeout_retry_interval(self) -> int:
120+
return self.__radas_sign_timeout_retry_interval
121+
122+
def receiver_timeout(self) -> int:
123+
return self.__radas_receiver_timeout
124+
125+
27126
class CharonConfig(object):
28127
"""CharonConfig is used to store all configurations for charon
29128
tools.
@@ -39,6 +138,13 @@ def __init__(self, data: Dict):
39138
self.__ignore_signature_suffix: Dict = data.get("ignore_signature_suffix", None)
40139
self.__signature_command: str = data.get("detach_signature_command", None)
41140
self.__aws_cf_enable: bool = data.get("aws_cf_enable", False)
141+
radas_config: Dict = data.get("radas", None)
142+
self.__radas_config: Optional[RadasConfig] = None
143+
if radas_config:
144+
self.__radas_config = RadasConfig(radas_config)
145+
self.__radas_enabled = bool(self.__radas_config and self.__radas_config.validate())
146+
else:
147+
self.__radas_enabled = False
42148

43149
def get_ignore_patterns(self) -> List[str]:
44150
return self.__ignore_patterns
@@ -67,19 +173,23 @@ def get_detach_signature_command(self) -> str:
67173
def is_aws_cf_enable(self) -> bool:
68174
return self.__aws_cf_enable
69175

176+
def is_radas_enabled(self) -> bool:
177+
return self.__radas_enabled
178+
179+
def get_radas_config(self) -> Optional[RadasConfig]:
180+
return self.__radas_config
181+
70182

71183
def get_config(cfgPath=None) -> CharonConfig:
72184
config_file_path = cfgPath
73185
if not config_file_path or not os.path.isfile(config_file_path):
74186
config_file_path = os.path.join(os.getenv("HOME", ""), ".charon", CONFIG_FILE)
75-
data = read_yaml_from_file_path(config_file_path, 'schemas/charon.json')
187+
data = read_yaml_from_file_path(config_file_path, "schemas/charon.json")
76188
return CharonConfig(data)
77189

78190

79191
def get_template(template_file: str) -> str:
80-
template = os.path.join(
81-
os.getenv("HOME", ''), ".charon/template", template_file
82-
)
192+
template = os.path.join(os.getenv("HOME", ""), ".charon/template", template_file)
83193
if os.path.isfile(template):
84194
with open(template, encoding="utf-8") as file_:
85195
return file_.read()

0 commit comments

Comments
 (0)