Skip to content

Commit 71ca509

Browse files
move subprocess command to configuration, make subprocess to run async (#172)
* move subprocess command to configuration, make subprocess to run async * simplify template rendering of sign command * Remove unwanted file
1 parent a7f0598 commit 71ca509

File tree

9 files changed

+49
-162
lines changed

9 files changed

+49
-162
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ future. And Ronda service will be hosted in AWS S3.
1414

1515
See [AWS CLi V2 installation](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html#cliv2-linux-install)
1616

17-
### [Optional] GnuPG CLI tool or rpm-sign
17+
### [Optional] rpm-sign or GnuPG CLI tool
1818

19-
For artifact signing using keys already in GPG keystore, not required when using exported secret key file.
20-
Can be configured to use rpm-sign for release.
19+
Can be configured to use rpm-sign or any command to generate .asc file.
2120

2221
## Installation
2322

@@ -54,7 +53,7 @@ to configure AWS access credentials.
5453
### charon-upload: upload a repo to S3
5554

5655
```bash
57-
usage: charon upload $tarball --product/-p ${prod} --version/-v ${ver} [--root_path] [--ignore_patterns] [--debug] [--key_id] [--key_file] [--passphrase]
56+
usage: charon upload $tarball --product/-p ${prod} --version/-v ${ver} [--root_path] [--ignore_patterns] [--debug] [--contain_signature] [--key]
5857
```
5958

6059
This command will upload the repo in tarball to S3.

charon/cmd/command.py

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -97,33 +97,19 @@
9797
""",
9898
)
9999
@option(
100-
"--sign_keyid",
101-
"-k",
102-
help="""
103-
GPG key fingerprint to sign artifacts, cannot work with --sign_keyfile
104-
requires key already imported with gpg command --passphrase is needed.
105-
""",
106-
)
107-
@option(
108-
"--sign_keyfile",
109-
"-K",
110-
help="""
111-
GPG key file path to sign artifacts, --passphrase is needed.
112-
Will be ignored when using sign_method rpm-sign
113-
""",
114-
)
115-
@option(
116-
"--sign_method",
100+
"--contain_signature",
101+
"-s",
102+
is_flag=True,
117103
help="""
118-
Choose 'gpg' or 'rpm-sign' to warp the actuall command, default to use gpg.
119-
key_id will be used to set key for signature.
120-
""",
104+
Toggle signature generation and upload feature in charon.
105+
"""
121106
)
122107
@option(
123-
"--passphrase",
108+
"--sign_key",
109+
"-k",
124110
help="""
125-
The passphrase for GPG key, Necessary when using gpg as sign method.
126-
Will require input when it's not set
111+
rpm-sign key to be used, will replace {{ key }} in default configuration for signature.
112+
Does noting if detach_signature_command does not contain {{ key }} field.
127113
""",
128114
)
129115
@option(
@@ -150,10 +136,8 @@ def upload(
150136
root_path="maven-repository",
151137
ignore_patterns: List[str] = None,
152138
work_dir: str = None,
153-
sign_keyid: str = None,
154-
sign_keyfile: str = None,
155-
sign_method: str = "gpg",
156-
passphrase: str = None,
139+
contain_signature: bool = False,
140+
sign_key: str = "redhatdevel",
157141
debug=False,
158142
quiet=False,
159143
dryrun=False
@@ -179,10 +163,6 @@ def upload(
179163
logger.error("No AWS profile specified!")
180164
sys.exit(1)
181165

182-
if sign_method != 'gpg' and sign_method != 'rpm-sign':
183-
logger.error("Signature method must be gpg or rpm-sign")
184-
sys.exit(1)
185-
186166
archive_path = __get_local_repo(repo)
187167
npm_archive_type = detect_npm_archive(archive_path)
188168
product_key = f"{product}-{version}"
@@ -196,10 +176,8 @@ def upload(
196176
buckets=buckets,
197177
aws_profile=aws_profile,
198178
dir_=work_dir,
199-
key_id=sign_keyid,
200-
key_file=sign_keyfile,
201-
sign_method=sign_method,
202-
passphrase=passphrase,
179+
gen_sign=contain_signature,
180+
key=sign_key,
203181
dry_run=dryrun,
204182
manifest_bucket_name=manifest_bucket_name
205183
)
@@ -220,10 +198,8 @@ def upload(
220198
buckets=buckets,
221199
aws_profile=aws_profile,
222200
dir_=work_dir,
223-
key_id=sign_keyid,
224-
key_file=sign_keyfile,
225-
sign_method=sign_method,
226-
passphrase=passphrase,
201+
gen_sign=contain_signature,
202+
key=sign_key,
227203
dry_run=dryrun,
228204
manifest_bucket_name=manifest_bucket_name
229205
)

charon/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(self, data: Dict):
3737
self.__targets: Dict = data.get("targets", None)
3838
self.__manifest_bucket: str = data.get("manifest_bucket", None)
3939
self.__ignore_signature_suffix: Dict = data.get("ignore_signature_suffix", None)
40+
self.__signature_command: str = data.get("detach_signature_command", None)
4041

4142
def get_ignore_patterns(self) -> List[str]:
4243
return self.__ignore_patterns
@@ -59,6 +60,9 @@ def get_ignore_signature_suffix(self, package_type: str) -> List[str]:
5960
logger.error("package type %s does not have ignore artifact config.", package_type)
6061
return xartifact_list
6162

63+
def get_detach_signature_command(self) -> str:
64+
return self.__signature_command
65+
6266

6367
def get_config() -> Optional[CharonConfig]:
6468
config_file_path = os.path.join(os.getenv("HOME"), ".charon", CONFIG_FILE)

charon/pkgs/maven.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,8 @@ def handle_maven_uploading(
261261
aws_profile=None,
262262
dir_=None,
263263
do_index=True,
264-
key_id=None,
265-
key_file=None,
266-
sign_method=None,
267-
passphrase=None,
264+
gen_sign=False,
265+
key=None,
268266
dry_run=False,
269267
manifest_bucket_name=None
270268
) -> Tuple[str, bool]:
@@ -389,19 +387,20 @@ def handle_maven_uploading(
389387
failed_metas.extend(_failed_metas)
390388
logger.info("archetype-catalog.xml updating done in bucket %s\n", bucket_name)
391389

392-
# 10. Generate signature file if gpg ket is provided
393-
if key_id is not None or key_file is not None:
390+
# 10. Generate signature file if contain_signature is set to True
391+
if gen_sign:
394392
conf = get_config()
395393
if not conf:
396394
sys.exit(1)
397395
suffix_list = __get_suffix(PACKAGE_TYPE_MAVEN, conf)
396+
command = conf.get_detach_signature_command()
398397
artifacts = [s for s in valid_mvn_paths if not s.endswith(tuple(suffix_list))]
399398
logger.info("Start generating signature for s3 bucket %s\n", bucket_name)
400399
(_failed_metas, _generated_signs) = signature.generate_sign(
401400
PACKAGE_TYPE_MAVEN, artifacts,
402401
top_level, prefix,
403402
s3_client, bucket_name,
404-
key_id, key_file, sign_method, passphrase
403+
key, command
405404
)
406405
failed_metas.extend(_failed_metas)
407406
generated_signs.extend(_generated_signs)

charon/pkgs/npm.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,8 @@ def handle_npm_uploading(
7272
aws_profile=None,
7373
dir_=None,
7474
do_index=True,
75-
key_id=None,
76-
key_file=None,
77-
sign_method=None,
78-
passphrase=None,
75+
gen_sign=False,
76+
key=None,
7977
dry_run=False,
8078
manifest_bucket_name=None
8179
) -> Tuple[str, bool]:
@@ -167,11 +165,12 @@ def handle_npm_uploading(
167165
failed_metas.extend(_failed_metas)
168166
logger.info("package.json uploading done")
169167

170-
if key_id is not None or key_file is not None:
168+
if gen_sign:
171169
conf = get_config()
172170
if not conf:
173171
sys.exit(1)
174172
suffix_list = __get_suffix(PACKAGE_TYPE_NPM, conf)
173+
command = conf.get_detach_signature_command()
175174
artifacts = [s for s in valid_paths if not s.endswith(tuple(suffix_list))]
176175
if META_FILE_GEN_KEY in meta_files:
177176
artifacts.extend(meta_files[META_FILE_GEN_KEY])
@@ -180,7 +179,7 @@ def handle_npm_uploading(
180179
PACKAGE_TYPE_NPM, artifacts,
181180
target_dir, prefix,
182181
client, bucket_name,
183-
key_id, key_file, sign_method, passphrase
182+
key, command
184183
)
185184
failed_metas.extend(_failed_metas)
186185
generated_signs.extend(_generated_signs)

charon/pkgs/signature.py

Lines changed: 10 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818
import subprocess
1919
import asyncio
2020
import logging
21-
import gnupg
21+
from jinja2 import Template
2222
from typing import Awaitable, Callable, List, Tuple
2323
from charon.storage import S3Client
24-
from getpass import getpass
2524

2625
logger = logging.getLogger(__name__)
2726

@@ -33,34 +32,22 @@ def generate_sign(
3332
prefix: str,
3433
s3_client: S3Client,
3534
bucket: str,
36-
key_id: str = None,
37-
key_file: str = None,
38-
sign_method: str = None,
39-
passphrase: str = None
35+
key: str = None,
36+
command: str = None
4037
) -> Tuple[List[str], List[str]]:
4138
""" This Python function generates a digital signature for a list of metadata files using
4239
the GPG library for uploads to an Amazon S3 bucket.
4340
4441
* Does not regenerate the existing metadata files when existing
4542
* Returning all failed to generate signature files due to exceptions
46-
* key_id: A string representing the ID of the RSA key to use for signing,
47-
GPG command line tool is required when this parameter is not None.
48-
* key_file: A string representing the location of the private key file.
49-
* passphrase: A string containing the passphrase for the RSA key for key_id or key_file.
43+
* key: name of the sign key, using inside template to render correct command,
44+
replace {{ key }} field in command string.
45+
* command: A string representing the subprocess command to run.
5046
5147
It returns a tuple containing two lists: one with the successfully generated files
5248
and another with the failed to generate files due to exceptions.
5349
"""
5450

55-
gpg = None
56-
if key_file is not None:
57-
gnupg_home_path = os.path.join(os.getenv("HOME"), ".charon", ".gnupg")
58-
gpg = gnupg.GPG(gnupghome=gnupg_home_path)
59-
gpg.import_keys_file(key_file)
60-
61-
if sign_method == 'gpg' and passphrase is None:
62-
passphrase = getpass('Passphrase for your gpg key:')
63-
6451
async def sign_file(
6552
filename: str, failed_paths: List[str], generated_signs: List[str]
6653
):
@@ -93,18 +80,12 @@ async def sign_file(
9380
logger.debug(".asc file %s existed, skipping", remote)
9481
return
9582

96-
if sign_method == "rpm-sign":
97-
result = detach_rpm_sign_files(key_id, artifact)
98-
elif sign_method == "gpg":
99-
result = gpg_sign_files(
100-
gpg=gpg,
101-
artifact=artifact,
102-
key_id=key_id, key_file=key_file,
103-
passphrase=passphrase
104-
)
83+
run_command = Template(command)
84+
result = await __run_cmd_async(run_command.render(key=key, file=artifact).split())
10585

106-
if result == 0:
86+
if result.returncode == 0:
10787
generated_signs.append(local)
88+
logger.debug("Generated signature file: %s", local)
10889
else:
10990
failed_paths.append(local)
11091

@@ -115,82 +96,6 @@ async def sign_file(
11596
)
11697

11798

118-
def detach_rpm_sign_files(key: str, artifact: str) -> int:
119-
# Usage: detach_sign_files KEYNAME FILE ...
120-
121-
# let's make sure we can actually sign with the given key
122-
command = [
123-
'rpm-sign',
124-
'--list-keys',
125-
'|',
126-
'grep',
127-
key
128-
]
129-
result = subprocess.run(command, capture_output=True, text=True, check=True)
130-
if result.returncode != 0:
131-
logger.error("Key %s is not in list of allowed keys", key)
132-
return result.returncode
133-
134-
# okay, now let's actually sign this thing
135-
command = [
136-
'rpm-sign',
137-
'--detachsign',
138-
'--key',
139-
key,
140-
artifact
141-
]
142-
try:
143-
# result = await __run_cmd_async(command)
144-
result = subprocess.run(command, capture_output=True, text=True, check=True)
145-
except subprocess.CalledProcessError as e:
146-
logger.error(
147-
"Error: signature generation failed due to error: %s", e
148-
)
149-
return result.returncode
150-
151-
return 1
152-
153-
154-
def gpg_sign_files(
155-
artifact: str,
156-
gpg=None,
157-
key_id: str = None,
158-
key_file: str = None,
159-
passphrase: str = None
160-
) -> int:
161-
command = [
162-
'gpg',
163-
'--batch',
164-
'--armor',
165-
'-u', key_id,
166-
'--passphrase', passphrase,
167-
'--sign', artifact
168-
]
169-
170-
if key_file is None:
171-
# use GPG command line tool to sign artifact if key_id is passed
172-
try:
173-
# result = await __run_cmd_async(command)
174-
result = subprocess.run(command, capture_output=True, text=True, check=True)
175-
except subprocess.CalledProcessError as e:
176-
logger.error(
177-
"Error: signature generation failed due to error: %s", e
178-
)
179-
return result.returncode
180-
else:
181-
try:
182-
with open(artifact, "rb") as f:
183-
local = artifact + '.asc'
184-
gpg.sign_file(f, passphrase=passphrase, output=local, detach=True)
185-
return 0
186-
except ValueError as e:
187-
logger.error(
188-
"Error: signature generation failed due to error: %s", e
189-
)
190-
return 1
191-
return 1
192-
193-
19499
def __do_path_cut_and(
195100
file_paths: List[str],
196101
path_handler: Callable[[str, List[str], List[str], asyncio.Semaphore], Awaitable[bool]],

charon/schemas/charon.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
}
2727
}
2828
},
29+
"detach_signature_command": {
30+
"type": "string",
31+
"description": "signature command to be used for signature"
32+
},
2933
"targets": {
3034
"type": "object",
3135
"patternProperties": {

config/charon.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ ignore_signature_suffix:
1212
npm:
1313
- "package.json"
1414

15+
detach_signature_command: "rpm-sign --detach-sign --key {{ key }} {{ file }}"
16+
1517
targets:
1618
stage-ga:
1719
- bucket: "stage-maven-ga"

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ PyYAML==6.0
88
defusedxml==0.7.1
99
subresource-integrity==0.2
1010
jsonschema==3.2.0
11-
python-gnupg==0.5.0

0 commit comments

Comments
 (0)