Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 47 additions & 47 deletions .github/scripts/normalize_signing_key_id.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
import re
import sys
SHORT_OR_LONG_KEY_ID = re.compile(r"^(0x)?[0-9A-Fa-f]{8,16}$")
FINGERPRINT = re.compile(r"^[0-9A-Fa-f]{40}$")
def normalize_signing_key_id(value):
key_id = value.strip()
if not key_id:
return ""
if SHORT_OR_LONG_KEY_ID.fullmatch(key_id):
return key_id
if FINGERPRINT.fullmatch(key_id):
return key_id[-16:]
return ""
def shell_exports(value):
key_id = normalize_signing_key_id(value)
if not key_id:
return "\n".join(
(
"unset SIGNING_KEY_ID",
"unset SIGNING_IN_MEMORY_KEY_ID",
"unset ORG_GRADLE_PROJECT_signingInMemoryKeyId",
)
)
escaped = key_id.replace("'", "'\"'\"'")
return "\n".join(
(
f"export SIGNING_KEY_ID='{escaped}'",
f"export SIGNING_IN_MEMORY_KEY_ID='{escaped}'",
f"export ORG_GRADLE_PROJECT_signingInMemoryKeyId='{escaped}'",
)
)
def main():
value = sys.argv[1] if len(sys.argv) > 1 else ""
print(shell_exports(value))
if __name__ == "__main__":
main()
import re
import sys


SHORT_OR_LONG_KEY_ID = re.compile(r"^(0x)?[0-9A-Fa-f]{8,16}$")
FINGERPRINT = re.compile(r"^[0-9A-Fa-f]{40}$")


def normalize_signing_key_id(value):
key_id = value.strip()
if not key_id:
return ""
if SHORT_OR_LONG_KEY_ID.fullmatch(key_id):
return key_id
if FINGERPRINT.fullmatch(key_id):
return key_id[-16:]
return ""


def shell_exports(value):
key_id = normalize_signing_key_id(value)
if not key_id:
return "\n".join(
(
"unset SIGNING_KEY_ID",
"unset SIGNING_IN_MEMORY_KEY_ID",
"unset ORG_GRADLE_PROJECT_signingInMemoryKeyId",
)
)

escaped = key_id.replace("'", "'\"'\"'")
return "\n".join(
(
f"export SIGNING_KEY_ID='{escaped}'",
f"export SIGNING_IN_MEMORY_KEY_ID='{escaped}'",
f"export ORG_GRADLE_PROJECT_signingInMemoryKeyId='{escaped}'",
)
)


def main():
value = sys.argv[1] if len(sys.argv) > 1 else ""
print(shell_exports(value))


if __name__ == "__main__":
main()
74 changes: 37 additions & 37 deletions .github/scripts/normalize_signing_key_id_test.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import unittest
from normalize_signing_key_id import normalize_signing_key_id, shell_exports
class NormalizeSigningKeyIdTest(unittest.TestCase):
def test_keeps_short_and_long_hex_key_ids(self):
self.assertEqual(normalize_signing_key_id("00B5050F"), "00B5050F")
self.assertEqual(normalize_signing_key_id("0x00B5050F"), "0x00B5050F")
self.assertEqual(normalize_signing_key_id("2BA16C9B594CE0E6"), "2BA16C9B594CE0E6")
def test_converts_fingerprint_to_long_key_id(self):
self.assertEqual(
normalize_signing_key_id("672F6476219EAB8C55C127A32BA16C9B594CE0E6"),
"2BA16C9B594CE0E6",
)
def test_rejects_non_hex_values(self):
self.assertEqual(normalize_signing_key_id("wk1995 <[email protected]>"), "")
self.assertEqual(normalize_signing_key_id("not-a-key-id"), "")
def test_shell_exports_clear_all_gradle_key_id_aliases_for_invalid_value(self):
output = shell_exports("not-a-key-id")
self.assertIn("unset SIGNING_KEY_ID", output)
self.assertIn("unset SIGNING_IN_MEMORY_KEY_ID", output)
self.assertIn("unset ORG_GRADLE_PROJECT_signingInMemoryKeyId", output)
def test_shell_exports_sets_gradle_alias_for_fingerprint(self):
output = shell_exports("672F6476219EAB8C55C127A32BA16C9B594CE0E6")
self.assertIn("export SIGNING_KEY_ID='2BA16C9B594CE0E6'", output)
self.assertIn("export ORG_GRADLE_PROJECT_signingInMemoryKeyId='2BA16C9B594CE0E6'", output)
if __name__ == "__main__":
unittest.main()
import unittest

from normalize_signing_key_id import normalize_signing_key_id, shell_exports


class NormalizeSigningKeyIdTest(unittest.TestCase):
def test_keeps_short_and_long_hex_key_ids(self):
self.assertEqual(normalize_signing_key_id("00B5050F"), "00B5050F")
self.assertEqual(normalize_signing_key_id("0x00B5050F"), "0x00B5050F")
self.assertEqual(normalize_signing_key_id("2BA16C9B594CE0E6"), "2BA16C9B594CE0E6")

def test_converts_fingerprint_to_long_key_id(self):
self.assertEqual(
normalize_signing_key_id("672F6476219EAB8C55C127A32BA16C9B594CE0E6"),
"2BA16C9B594CE0E6",
)

def test_rejects_non_hex_values(self):
self.assertEqual(normalize_signing_key_id("wk1995 <[email protected]>"), "")
self.assertEqual(normalize_signing_key_id("not-a-key-id"), "")

def test_shell_exports_clear_all_gradle_key_id_aliases_for_invalid_value(self):
output = shell_exports("not-a-key-id")

self.assertIn("unset SIGNING_KEY_ID", output)
self.assertIn("unset SIGNING_IN_MEMORY_KEY_ID", output)
self.assertIn("unset ORG_GRADLE_PROJECT_signingInMemoryKeyId", output)

def test_shell_exports_sets_gradle_alias_for_fingerprint(self):
output = shell_exports("672F6476219EAB8C55C127A32BA16C9B594CE0E6")

self.assertIn("export SIGNING_KEY_ID='2BA16C9B594CE0E6'", output)
self.assertIn("export ORG_GRADLE_PROJECT_signingInMemoryKeyId='2BA16C9B594CE0E6'", output)


if __name__ == "__main__":
unittest.main()
220 changes: 110 additions & 110 deletions .github/scripts/publish_plugin_central_workflow_test.py
Original file line number Diff line number Diff line change
@@ -1,110 +1,110 @@
import re
import unittest
from pathlib import Path
WORKFLOWS = Path(__file__).resolve().parents[1] / "workflows"
CENTRAL_WORKFLOW = WORKFLOWS / "publish-plugin-central.yml"
PR_CHECK_WORKFLOW = WORKFLOWS / "publish-plugin-pr-check.yml"
SECRET_ENV_NAMES = (
"CENTRAL_USERNAME",
"CENTRAL_PASSWORD",
"GPG_KEY_CONTENTS",
"SIGNING_KEY_ID",
"SIGNING_PASSWORD",
)
def workflow_text(path=CENTRAL_WORKFLOW):
return path.read_text(encoding="utf-8")
def step_block(name, path=CENTRAL_WORKFLOW):
text = workflow_text(path)
match = re.search(
rf"(?ms)^ - name: {re.escape(name)}\n(?P<body>.*?)(?=^ - name: |\Z)",
text,
)
if match is None:
raise AssertionError(f"Missing workflow step: {name}")
return match.group("body")
class PublishPluginCentralWorkflowTest(unittest.TestCase):
def test_local_metadata_validation_does_not_inherit_publish_secrets(self):
text = workflow_text()
job_header, _, _ = text.partition(" steps:")
validation = step_block("Validate local publication metadata")
for name in SECRET_ENV_NAMES:
self.assertNotIn(f"{name}:", job_header)
self.assertNotIn(f"{name}:", validation)
def test_central_publish_steps_receive_required_publish_env(self):
central_publish = step_block("Publish to Central staging")
central_upload = step_block("Create Central Portal deployment")
for name in SECRET_ENV_NAMES:
self.assertIn(f"{name}:", central_publish)
for name in ("CENTRAL_NAMESPACE", "CENTRAL_PUBLISHING_TYPE", "CENTRAL_USERNAME", "CENTRAL_PASSWORD"):
self.assertIn(f"{name}:", central_upload)
def test_central_publish_falls_back_when_signing_key_id_is_invalid(self):
central_publish = step_block("Publish to Central staging")
self.assertIn("SIGNING_KEY_ID", central_publish)
self.assertIn("normalize_signing_key_id.py", central_publish)
self.assertIn("original_signing_key_id", central_publish)
self.assertIn("falling back to infer it from GPG_KEY_CONTENTS", central_publish)
self.assertIn("normalized to a Gradle-compatible long key id", central_publish)
def test_central_publish_is_gated_by_pre_publish_vs_main_version(self):
release = step_block("Resolve release version")
check_tag = step_block("Check release tag")
publish = step_block("Publish to Central staging")
merge = step_block("Merge pre_publish to main")
self.assertIn("MAIN_VERSION=", release)
self.assertIn("publish_required=true", release)
self.assertIn("publish_required=false", release)
self.assertIn("must not be lower than main version", release)
publish_condition = "steps.release.outputs.publish_required == 'true'"
self.assertIn(publish_condition, check_tag)
self.assertIn(publish_condition, publish)
self.assertIn("steps.release.outputs.publish_required", merge)
self.assertIn("== \"true\"", merge)
self.assertIn("does not require Central publish; merging directly", merge)
class PublishPluginPrCheckWorkflowTest(unittest.TestCase):
def test_version_bump_only_runs_when_plugin_base_changed(self):
detect_changes = step_block("Detect plugin_base changes", PR_CHECK_WORKFLOW)
ensure_version = step_block("Ensure publish plugin version", PR_CHECK_WORKFLOW)
sync_readme = step_block("Sync README publish plugin version", PR_CHECK_WORKFLOW)
commit_bump = step_block("Commit version bump", PR_CHECK_WORKFLOW)
self.assertIn("git diff --quiet", detect_changes)
self.assertIn("FETCH_HEAD...HEAD", detect_changes)
self.assertIn("plugin_base/", detect_changes)
self.assertIn("changed=true", detect_changes)
self.assertIn("changed=false", detect_changes)
expected_condition = "steps.plugin_base_changes.outputs.changed == 'true'"
self.assertIn(expected_condition, ensure_version)
self.assertIn(expected_condition, sync_readme)
self.assertIn(expected_condition, commit_bump)
def test_workflow_only_changes_do_not_need_bot_version_commit(self):
text = workflow_text(PR_CHECK_WORKFLOW)
self.assertIn("id: plugin_base_changes", text)
self.assertRegex(
text,
r"(?s)- name: Commit version bump.*if:.*steps\.plugin_base_changes\.outputs\.changed == 'true'",
)
if __name__ == "__main__":
unittest.main()
import re
import unittest
from pathlib import Path


WORKFLOWS = Path(__file__).resolve().parents[1] / "workflows"
CENTRAL_WORKFLOW = WORKFLOWS / "publish-plugin-central.yml"
PR_CHECK_WORKFLOW = WORKFLOWS / "publish-plugin-pr-check.yml"
SECRET_ENV_NAMES = (
"CENTRAL_USERNAME",
"CENTRAL_PASSWORD",
"GPG_KEY_CONTENTS",
"SIGNING_KEY_ID",
"SIGNING_PASSWORD",
)


def workflow_text(path=CENTRAL_WORKFLOW):
return path.read_text(encoding="utf-8")


def step_block(name, path=CENTRAL_WORKFLOW):
text = workflow_text(path)
match = re.search(
rf"(?ms)^ - name: {re.escape(name)}\n(?P<body>.*?)(?=^ - name: |\Z)",
text,
)
if match is None:
raise AssertionError(f"Missing workflow step: {name}")
return match.group("body")


class PublishPluginCentralWorkflowTest(unittest.TestCase):
def test_local_metadata_validation_does_not_inherit_publish_secrets(self):
text = workflow_text()
job_header, _, _ = text.partition(" steps:")
validation = step_block("Validate local publication metadata")

for name in SECRET_ENV_NAMES:
self.assertNotIn(f"{name}:", job_header)
self.assertNotIn(f"{name}:", validation)

def test_central_publish_steps_receive_required_publish_env(self):
central_publish = step_block("Publish to Central staging")
central_upload = step_block("Create Central Portal deployment")

for name in SECRET_ENV_NAMES:
self.assertIn(f"{name}:", central_publish)

for name in ("CENTRAL_NAMESPACE", "CENTRAL_PUBLISHING_TYPE", "CENTRAL_USERNAME", "CENTRAL_PASSWORD"):
self.assertIn(f"{name}:", central_upload)

def test_central_publish_falls_back_when_signing_key_id_is_invalid(self):
central_publish = step_block("Publish to Central staging")

self.assertIn("SIGNING_KEY_ID", central_publish)
self.assertIn("normalize_signing_key_id.py", central_publish)
self.assertIn("original_signing_key_id", central_publish)
self.assertIn("falling back to infer it from GPG_KEY_CONTENTS", central_publish)
self.assertIn("normalized to a Gradle-compatible long key id", central_publish)

def test_central_publish_is_gated_by_pre_publish_vs_main_version(self):
release = step_block("Resolve release version")
check_tag = step_block("Check release tag")
publish = step_block("Publish to Central staging")
merge = step_block("Merge pre_publish to main")

self.assertIn("MAIN_VERSION=", release)
self.assertIn("publish_required=true", release)
self.assertIn("publish_required=false", release)
self.assertIn("must not be lower than main version", release)

publish_condition = "steps.release.outputs.publish_required == 'true'"
self.assertIn(publish_condition, check_tag)
self.assertIn(publish_condition, publish)
self.assertIn("steps.release.outputs.publish_required", merge)
self.assertIn("== \"true\"", merge)
self.assertIn("does not require Central publish; merging directly", merge)


class PublishPluginPrCheckWorkflowTest(unittest.TestCase):
def test_version_bump_only_runs_when_plugin_base_changed(self):
detect_changes = step_block("Detect plugin_base changes", PR_CHECK_WORKFLOW)
ensure_version = step_block("Ensure publish plugin version", PR_CHECK_WORKFLOW)
sync_readme = step_block("Sync README publish plugin version", PR_CHECK_WORKFLOW)
commit_bump = step_block("Commit version bump", PR_CHECK_WORKFLOW)

self.assertIn("git diff --quiet", detect_changes)
self.assertIn("FETCH_HEAD...HEAD", detect_changes)
self.assertIn("plugin_base/", detect_changes)
self.assertIn("changed=true", detect_changes)
self.assertIn("changed=false", detect_changes)

expected_condition = "steps.plugin_base_changes.outputs.changed == 'true'"
self.assertIn(expected_condition, ensure_version)
self.assertIn(expected_condition, sync_readme)
self.assertIn(expected_condition, commit_bump)

def test_workflow_only_changes_do_not_need_bot_version_commit(self):
text = workflow_text(PR_CHECK_WORKFLOW)

self.assertIn("id: plugin_base_changes", text)
self.assertRegex(
text,
r"(?s)- name: Commit version bump.*if:.*steps\.plugin_base_changes\.outputs\.changed == 'true'",
)


if __name__ == "__main__":
unittest.main()
Loading
Loading