Skip to content

Commit 06e2958

Browse files
authored
Bug 1889223 - Submit all files to notarization before polling (#957)
* Bug 1889223 - Submit all files to notarization before polling This is the first behavior that consumes all files at once, so it's a bit odd how it's being handled in script.py * ci(signingscript): PR changes + Prevent mixed formats Removed noop sign sign in tasks.py since we don't allow for mixed formats anymore * nit(signingscript): Add reference issue comments for stacked behavior * nit(signingscript): Lint fix * nit(signingscript): Revert utils.mkdir parameter for deleting before creating * fix(signingscript): Check all files before submitting for notarization * chore(signingscript): Add tests for apple_notarize_stacked
1 parent e990950 commit 06e2958

File tree

7 files changed

+199
-6
lines changed

7 files changed

+199
-6
lines changed

signingscript/src/signingscript/script.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import scriptworker.client
1111

1212
from signingscript.exceptions import SigningScriptError
13-
from signingscript.task import build_filelist_dict, sign, task_cert_type, task_signing_formats
13+
from signingscript.task import apple_notarize_stacked, build_filelist_dict, sign, task_cert_type, task_signing_formats
1414
from signingscript.utils import copy_to_dir, load_apple_notarization_configs, load_autograph_configs
1515

1616
log = logging.getLogger(__name__)
@@ -24,6 +24,7 @@ async def async_main(context):
2424
context (Context): the signing context.
2525
2626
"""
27+
work_dir = context.config["work_dir"]
2728
async with aiohttp.ClientSession() as session:
2829
all_signing_formats = task_signing_formats(context)
2930
if "gpg" in all_signing_formats or "autograph_gpg" in all_signing_formats:
@@ -36,17 +37,24 @@ async def async_main(context):
3637
if not context.config.get("widevine_cert"):
3738
raise Exception("Widevine format is enabled, but widevine_cert is not defined")
3839

39-
if "apple_notarization" in all_signing_formats or "apple_notarization_geckodriver" in all_signing_formats:
40+
if {"apple_notarization", "apple_notarization_geckodriver", "apple_notarization_stacked"}.intersection(all_signing_formats):
4041
if not context.config.get("apple_notarization_configs", False):
4142
raise Exception("Apple notarization is enabled but apple_notarization_configs is not defined")
4243
setup_apple_notarization_credentials(context)
4344

4445
context.session = session
4546
context.autograph_configs = load_autograph_configs(context.config["autograph_configs"])
4647

47-
work_dir = context.config["work_dir"]
48+
# TODO: Make task.sign take in the whole filelist_dict and return a dict of output files.
49+
# That would likely mean changing all behaviors to accept and deal with multiple files at once.
50+
4851
filelist_dict = build_filelist_dict(context)
4952
for path, path_dict in filelist_dict.items():
53+
if path_dict["formats"] == ["apple_notarization_stacked"]:
54+
# Skip if only format is notarization_stacked - handled below
55+
continue
56+
if "apple_notarization_stacked" in path_dict["formats"]:
57+
raise SigningScriptError("apple_notarization_stacked cannot be mixed with other signing types")
5058
copy_to_dir(path_dict["full_path"], context.config["work_dir"], target=path)
5159
log.info("signing %s", path)
5260
output_files = await sign(context, os.path.join(work_dir, path), path_dict["formats"], authenticode_comment=path_dict.get("comment"))
@@ -55,6 +63,16 @@ async def async_main(context):
5563
copy_to_dir(os.path.join(work_dir, source), context.config["artifact_dir"], target=source)
5664
if "gpg" in path_dict["formats"] or "autograph_gpg" in path_dict["formats"]:
5765
copy_to_dir(context.config["gpg_pubkey"], context.config["artifact_dir"], target="public/build/KEY")
66+
67+
# notarization_stacked is a special format that takes in all files at once instead of sequentially like other formats
68+
# Should be fixed in https://github.com/mozilla-releng/scriptworker-scripts/issues/980
69+
notarization_dict = {path: path_dict for path, path_dict in filelist_dict.items() if "apple_notarization_stacked" in path_dict["formats"]}
70+
if notarization_dict:
71+
output_files = await apple_notarize_stacked(context, notarization_dict)
72+
for source in output_files:
73+
source = os.path.relpath(source, work_dir)
74+
copy_to_dir(os.path.join(work_dir, source), context.config["artifact_dir"], target=source)
75+
5876
log.info("Done!")
5977

6078

signingscript/src/signingscript/sign.py

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,7 +1604,11 @@ async def _notarize_geckodriver(context, path, workdir):
16041604

16051605

16061606
async def _notarize_all(context, path, workdir):
1607-
"""Notarizes all files in a tarball"""
1607+
"""
1608+
Notarizes all files in a tarball
1609+
1610+
@Deprecated: This function is deprecated and will be removed in the future. Use apple_notarize_stacked instead.
1611+
"""
16081612
_, extension = os.path.splitext(path)
16091613
# Attempt extracting
16101614
await _extract_tarfile(context, path, extension, tmp_dir=workdir)
@@ -1634,10 +1638,11 @@ async def _notarize_all(context, path, workdir):
16341638
async def apple_notarize(context, path, *args, **kwargs):
16351639
"""
16361640
Notarizes given package(s) using rcodesign.
1641+
1642+
@Deprecated: This function is deprecated and will be removed in the future. Use apple_notarize_stacked instead.
16371643
"""
16381644
# Setup workdir
16391645
notarization_workdir = os.path.join(context.config["work_dir"], "apple_notarize")
1640-
shutil.rmtree(notarization_workdir, ignore_errors=True)
16411646
utils.mkdir(notarization_workdir)
16421647

16431648
_, extension = os.path.splitext(path)
@@ -1658,3 +1663,88 @@ async def apple_notarize_geckodriver(context, path, *args, **kwargs):
16581663
utils.mkdir(notarization_workdir)
16591664

16601665
return await _notarize_geckodriver(context, path, notarization_workdir)
1666+
1667+
1668+
@time_async_function
1669+
async def apple_notarize_stacked(context, filelist_dict):
1670+
"""
1671+
Notarizes multiple packages using rcodesign.
1672+
Submits everything before polling for status.
1673+
"""
1674+
ATTEMPTS = 5
1675+
1676+
relpath_index_map = {}
1677+
paths_to_notarize = []
1678+
task_index = 0
1679+
1680+
# Create list of files to be notarized + check for potential problems
1681+
for relpath, path_dict in filelist_dict.items():
1682+
task_index += 1
1683+
relpath_index_map[relpath] = task_index
1684+
notarization_workdir = os.path.join(context.config["work_dir"], f"apple_notarize-{task_index}")
1685+
shutil.rmtree(notarization_workdir, ignore_errors=True)
1686+
utils.mkdir(notarization_workdir)
1687+
_, extension = os.path.splitext(relpath)
1688+
if extension == ".pkg":
1689+
path = os.path.join(notarization_workdir, relpath)
1690+
utils.copy_to_dir(path_dict["full_path"], notarization_workdir, target=relpath)
1691+
paths_to_notarize.append(path)
1692+
elif extension == ".gz":
1693+
await _extract_tarfile(context, path_dict["full_path"], extension, notarization_workdir)
1694+
workdir_files = os.listdir(notarization_workdir)
1695+
supported_files = [filename for filename in workdir_files if _can_notarize(filename, (".app", ".pkg"))]
1696+
if not supported_files:
1697+
raise SigningScriptError(f"No supported files found for file {relpath}")
1698+
for file in supported_files:
1699+
path = os.path.join(notarization_workdir, file)
1700+
paths_to_notarize.append(path)
1701+
else:
1702+
raise SigningScriptError(f"Unsupported file extension: {extension} for file {relpath}")
1703+
1704+
# notarization submissions map (path -> submission_id)
1705+
submissions_map = {}
1706+
# Submit to notarization one by one
1707+
for path in paths_to_notarize:
1708+
submissions_map[path] = await retry_async(
1709+
func=rcodesign_notarize,
1710+
args=(path, context.apple_credentials_path),
1711+
attempts=ATTEMPTS,
1712+
retry_exceptions=RCodesignError,
1713+
)
1714+
1715+
# Notary wait all files
1716+
for path, submission_id in submissions_map.items():
1717+
await retry_async(
1718+
func=rcodesign_notary_wait,
1719+
args=(submission_id, context.apple_credentials_path),
1720+
attempts=ATTEMPTS,
1721+
retry_exceptions=RCodesignError,
1722+
)
1723+
1724+
# Staple files
1725+
for path in submissions_map.keys():
1726+
await retry_async(
1727+
func=rcodesign_staple,
1728+
args=[path],
1729+
attempts=ATTEMPTS,
1730+
retry_exceptions=RCodesignError,
1731+
)
1732+
1733+
# Wrap up
1734+
stapled_files = []
1735+
for relpath, path_dict in filelist_dict.items():
1736+
task_index = relpath_index_map[relpath]
1737+
notarization_workdir = os.path.join(context.config["work_dir"], f"apple_notarize-{task_index}")
1738+
target_path = os.path.join(context.config["work_dir"], relpath)
1739+
_, extension = os.path.splitext(relpath)
1740+
# Pkgs don't need to be tarred
1741+
if extension == ".pkg":
1742+
utils.copy_to_dir(os.path.join(notarization_workdir, relpath), os.path.dirname(target_path))
1743+
else:
1744+
all_files = []
1745+
for root, _, files in os.walk(notarization_workdir):
1746+
for f in files:
1747+
all_files.append(os.path.join(root, f))
1748+
await _create_tarfile(context, target_path, all_files, extension, notarization_workdir)
1749+
stapled_files.append(target_path)
1750+
return stapled_files

signingscript/src/signingscript/task.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from signingscript.sign import (
1919
apple_notarize,
2020
apple_notarize_geckodriver,
21+
apple_notarize_stacked, # noqa: F401
2122
sign_authenticode,
2223
sign_debian_pkg,
2324
sign_file,
@@ -33,6 +34,7 @@
3334

3435
log = logging.getLogger(__name__)
3536

37+
3638
FORMAT_TO_SIGNING_FUNCTION = immutabledict(
3739
{
3840
"autograph_hash_only_mar384": sign_mar384_with_autograph_hash,
@@ -58,6 +60,9 @@
5860
"autograph_rsa": sign_file_detached,
5961
"apple_notarization": apple_notarize,
6062
"apple_notarization_geckodriver": apple_notarize_geckodriver,
63+
# This format is handled in script.py
64+
# Should be refactored in https://github.com/mozilla-releng/scriptworker-scripts/issues/980
65+
# "apple_notarization_stacked": apple_notarize_stacked,
6166
"default": sign_file,
6267
}
6368
)

signingscript/src/signingscript/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ def mkdir(path):
4040
4141
Args:
4242
path (str): the path to mkdir
43-
4443
"""
4544
try:
4645
os.makedirs(path)

signingscript/tests/test_script.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ async def fake_sign(_, val, *args, authenticode_comment=None):
3030
assert authenticode_comment == "Some authenticode comment"
3131
return [val]
3232

33+
async def fake_notarize_stacked(_, filelist_dict, *args, **kwargs):
34+
return filelist_dict.keys()
35+
3336
mocker.patch.object(script, "load_autograph_configs", new=noop_sync)
3437
mocker.patch.object(script, "load_apple_notarization_configs", new=noop_sync)
3538
mocker.patch.object(script, "setup_apple_notarization_credentials", new=noop_sync)
3639
# mocker.patch.object(script, "task_cert_type", new=noop_sync)
3740
mocker.patch.object(script, "task_signing_formats", return_value=formats)
3841
mocker.patch.object(script, "build_filelist_dict", new=fake_filelist_dict)
3942
mocker.patch.object(script, "sign", new=fake_sign)
43+
mocker.patch.object(script, "apple_notarize_stacked", new=fake_notarize_stacked)
4044
context = mock.MagicMock()
4145
context.config = {"work_dir": tmpdir, "artifact_dir": tmpdir, "autograph_configs": {}, "apple_notarization_configs": "fake"}
4246
context.config.update(extra_config)
@@ -99,6 +103,21 @@ async def test_async_main_apple_notarization(tmpdir, mocker):
99103
await async_main_helper(tmpdir, mocker, formats)
100104

101105

106+
@pytest.mark.asyncio
107+
async def test_async_main_apple_notarization_stacked(tmpdir, mocker):
108+
formats = ["apple_notarization_stacked"]
109+
mocker.patch.object(script, "copy_to_dir", new=noop_sync)
110+
await async_main_helper(tmpdir, mocker, formats)
111+
112+
113+
@pytest.mark.asyncio
114+
async def test_async_main_apple_notarization_stacked_mixed_fail(tmpdir, mocker):
115+
formats = ["autograph_mar", "apple_notarization_stacked"]
116+
mocker.patch.object(script, "copy_to_dir", new=noop_sync)
117+
with pytest.raises(SigningScriptError):
118+
await async_main_helper(tmpdir, mocker, formats)
119+
120+
102121
@pytest.mark.asyncio
103122
async def test_async_main_apple_notarization_no_config(tmpdir, mocker):
104123
formats = ["apple_notarization"]

signingscript/tests/test_sign.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,3 +1481,64 @@ async def test_apple_notarize_geckodriver(mocker, context):
14811481

14821482
await sign.apple_notarize_geckodriver(context, "/foo/bar.pkg")
14831483
notarize_geckodriver.assert_awaited_once()
1484+
1485+
1486+
@pytest.mark.asyncio
1487+
async def test_apple_notarize_stacked(mocker, context):
1488+
notarize = mock.AsyncMock()
1489+
mocker.patch.object(sign, "rcodesign_notarize", notarize)
1490+
wait = mock.AsyncMock()
1491+
mocker.patch.object(sign, "rcodesign_notary_wait", wait)
1492+
staple = mock.AsyncMock()
1493+
mocker.patch.object(sign, "rcodesign_staple", staple)
1494+
1495+
mocker.patch.object(sign, "_extract_tarfile", noop_async)
1496+
mocker.patch.object(sign, "_create_tarfile", noop_async)
1497+
mocker.patch.object(sign.os, "listdir", lambda *_: ["/foo.pkg", "/baz.app", "/foobar"])
1498+
mocker.patch.object(sign.os, "walk", lambda *_: [("/", None, ["foo.pkg", "baz.app"])])
1499+
mocker.patch.object(sign.shutil, "rmtree", noop_sync)
1500+
mocker.patch.object(sign.utils, "mkdir", noop_sync)
1501+
mocker.patch.object(sign.utils, "copy_to_dir", noop_sync)
1502+
1503+
await sign.apple_notarize_stacked(
1504+
context,
1505+
{
1506+
"/app.tar.gz": {"full_path": "/app.tar.gz", "formats": ["apple_notarize_stacked"]},
1507+
"/app2.pkg": {"full_path": "/app2.pkg", "formats": ["apple_notarize_stacked"]},
1508+
},
1509+
)
1510+
# one for each file format
1511+
assert notarize.await_count == 3
1512+
assert wait.await_count == 3
1513+
assert staple.await_count == 3
1514+
1515+
1516+
@pytest.mark.asyncio
1517+
async def test_apple_notarize_stacked_unsupported(mocker, context):
1518+
"""Test unsupported file extensions"""
1519+
1520+
mocker.patch.object(sign, "_extract_tarfile", noop_async)
1521+
mocker.patch.object(sign.shutil, "rmtree", noop_sync)
1522+
mocker.patch.object(sign.utils, "mkdir", noop_sync)
1523+
mocker.patch.object(sign.utils, "copy_to_dir", noop_sync)
1524+
1525+
# Returns unsupported file formats
1526+
mocker.patch.object(sign.os, "listdir", lambda *_: ["/foo.aaa", "/baz.bbb", "/foobar"])
1527+
1528+
with pytest.raises(SigningScriptError):
1529+
# Main file is supported, contents uses the above os.listdir
1530+
await sign.apple_notarize_stacked(
1531+
context,
1532+
{
1533+
"/app.tar.gz": {"full_path": "/app.tar.gz", "formats": ["apple_notarize_stacked"]},
1534+
},
1535+
)
1536+
1537+
with pytest.raises(SigningScriptError):
1538+
# Main file extension is unsupported
1539+
await sign.apple_notarize_stacked(
1540+
context,
1541+
{
1542+
"/app.bbb": {"full_path": "/app.bbb", "formats": ["apple_notarize_stacked"]},
1543+
},
1544+
)

signingscript/tests/test_task.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def fake_log(context, new_files, *args):
151151
("widevine", stask.sign_widevine),
152152
("autograph_authenticode", stask.sign_authenticode),
153153
("autograph_authenticode_stub", stask.sign_authenticode),
154+
("apple_notarization", stask.apple_notarize),
154155
("default", stask.sign_file),
155156
# Key id cases
156157
("autograph_hash_only_mar384:firefox_20190321_dev", stask.sign_mar384_with_autograph_hash),

0 commit comments

Comments
 (0)