Skip to content
Open
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
176 changes: 176 additions & 0 deletions .github/scripts/resolve_layer_publish_targets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
from __future__ import annotations

import argparse
import json
from dataclasses import dataclass


DEFAULT_AUDIENCE = "sts.amazonaws.com"
CHINA_AUDIENCE = "sts.amazonaws.com.cn"


@dataclass(frozen=True)
class CredentialGroup:
name: str
regions: tuple[str, ...]
role_secret: str
aws_region: str
audience: str = DEFAULT_AUDIENCE


@dataclass(frozen=True)
class PublishTarget:
name: str
regions: tuple[str, ...]
role_secret: str
aws_region: str
audience: str

def to_workflow_matrix_entry(self) -> dict[str, str]:
return {
"name": self.name,
"regions": ",".join(self.regions),
"role_secret": self.role_secret,
"aws_region": self.aws_region,
"audience": self.audience,
}


DEFAULT_COMMERCIAL_REGIONS = (
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
)

OPT_IN_REGIONS = (
"af-south-1",
"ap-east-1",
"ap-east-2",
"ap-south-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-west-1",
"eu-central-2",
"eu-south-1",
"eu-south-2",
"il-central-1",
"me-central-1",
"me-south-1",
"mx-central-1",
)

CHINA_REGIONS = ("cn-north-1", "cn-northwest-1")
US_GOV_REGIONS = ("us-gov-east-1", "us-gov-west-1")


def _region_role_secret(region: str) -> str:
return f"LAYER_PUBLISH_ROLE_ARN_{region.upper().replace('-', '_')}"


CREDENTIAL_GROUPS = (
CredentialGroup(
name="commercial-default",
regions=DEFAULT_COMMERCIAL_REGIONS,
role_secret="LAYER_PUBLISH_ROLE_ARN",
aws_region="us-east-1",
),
*(
CredentialGroup(
name=region,
regions=(region,),
role_secret=_region_role_secret(region),
aws_region=region,
)
for region in OPT_IN_REGIONS
),
CredentialGroup(
name="china",
regions=CHINA_REGIONS,
role_secret="LAYER_PUBLISH_ROLE_ARN_CHINA",
aws_region="cn-north-1",
audience=CHINA_AUDIENCE,
),
CredentialGroup(
name="us-gov",
regions=US_GOV_REGIONS,
role_secret="LAYER_PUBLISH_ROLE_ARN_US_GOV",
aws_region="us-gov-east-1",
),
)

SUPPORTED_REGIONS = frozenset(
region for group in CREDENTIAL_GROUPS for region in group.regions
)


def resolve_publish_targets(regions: str | None) -> tuple[PublishTarget, ...]:
requested_regions = {
region.strip() for region in (regions or "").split(",") if region.strip()
}
unknown_regions = requested_regions - SUPPORTED_REGIONS
if unknown_regions:
unknown_list = ", ".join(sorted(unknown_regions))
raise ValueError(f"Unsupported layer publish regions: {unknown_list}")

targets: list[PublishTarget] = []
for group in CREDENTIAL_GROUPS:
selected_regions = (
tuple(region for region in group.regions if region in requested_regions)
if requested_regions
else group.regions
)
if selected_regions:
targets.append(
PublishTarget(
name=group.name,
regions=selected_regions,
role_secret=group.role_secret,
aws_region=group.aws_region,
audience=group.audience,
)
)
return tuple(targets)


def serialize_publish_targets(targets: tuple[PublishTarget, ...]) -> str:
entries = [target.to_workflow_matrix_entry() for target in targets]
return json.dumps(entries, separators=(",", ":"))


def main() -> None:
parser = argparse.ArgumentParser(
description="Resolve credential-scoped Lambda layer publish targets."
)
parser.add_argument(
"--regions",
default="",
help="Optional comma-separated AWS Regions; defaults to every supported Region.",
)
args = parser.parse_args()

try:
targets = resolve_publish_targets(args.regions)
except ValueError as error:
parser.error(str(error))
print(serialize_publish_targets(targets))


if __name__ == "__main__":
main()
51 changes: 51 additions & 0 deletions .github/scripts/tests/test_lambda_layer_publish_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from pathlib import Path

import yaml


WORKFLOW_PATH = Path(__file__).parents[2] / "workflows" / "lambda-layer-publish.yml"


def _step_by_name(steps: list[dict[str, object]], name: str) -> dict[str, object]:
return next(step for step in steps if step.get("name") == name)


def test_publish_workflow_uses_resolved_credential_matrix() -> None:
workflow = yaml.safe_load(WORKFLOW_PATH.read_text())
build_job = workflow["jobs"]["build-distributions"]
publish_job = workflow["jobs"]["publish-layer"]

assert build_job["outputs"]["publish_targets"] == (
"${{ steps.publish-targets.outputs.publish_targets }}"
)
resolve_step = _step_by_name(build_job["steps"], "Resolve publish targets")
assert resolve_step["env"]["LAYER_REGIONS"] == (
"${{ inputs.regions || vars.LAYER_PUBLISH_REGIONS }}"
)
assert ".github/scripts/resolve_layer_publish_targets.py" in resolve_step["run"]

matrix = publish_job["strategy"]["matrix"]
assert matrix["publish_target"] == (
"${{ fromJSON(needs.build-distributions.outputs.publish_targets) }}"
)
assert set(matrix) == {"publish_target"}


def test_publish_workflow_resolves_role_and_partition_settings_from_matrix() -> None:
workflow = yaml.safe_load(WORKFLOW_PATH.read_text())
publish_steps = workflow["jobs"]["publish-layer"]["steps"]
credentials_step = _step_by_name(publish_steps, "Configure AWS credentials")
publish_step = _step_by_name(publish_steps, "Publish layer versions")

assert credentials_step["with"]["role-to-assume"] == (
"${{ secrets[matrix.publish_target.role_secret] }}"
)
assert credentials_step["with"]["aws-region"] == (
"${{ matrix.publish_target.aws_region }}"
)
assert credentials_step["with"]["audience"] == (
"${{ matrix.publish_target.audience }}"
)
assert publish_step["env"]["LAYER_REGIONS"] == (
"${{ matrix.publish_target.regions }}"
)
146 changes: 146 additions & 0 deletions .github/scripts/tests/test_resolve_layer_publish_targets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
from __future__ import annotations

import json
import os
import sys

import pytest


sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

from resolve_layer_publish_targets import (
CHINA_AUDIENCE,
CHINA_REGIONS,
CREDENTIAL_GROUPS,
DEFAULT_AUDIENCE,
DEFAULT_COMMERCIAL_REGIONS,
OPT_IN_REGIONS,
SUPPORTED_REGIONS,
US_GOV_REGIONS,
resolve_publish_targets,
serialize_publish_targets,
)


EXPECTED_DEFAULT_COMMERCIAL_REGIONS = {
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
}
EXPECTED_OPT_IN_REGIONS = {
"af-south-1",
"ap-east-1",
"ap-east-2",
"ap-south-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-west-1",
"eu-central-2",
"eu-south-1",
"eu-south-2",
"il-central-1",
"me-central-1",
"me-south-1",
"mx-central-1",
}


def test_region_sets_match_the_configured_aws_partitions() -> None:
assert set(DEFAULT_COMMERCIAL_REGIONS) == EXPECTED_DEFAULT_COMMERCIAL_REGIONS
assert set(OPT_IN_REGIONS) == EXPECTED_OPT_IN_REGIONS
assert CHINA_REGIONS == ("cn-north-1", "cn-northwest-1")
assert US_GOV_REGIONS == ("us-gov-east-1", "us-gov-west-1")


def test_default_targets_use_separate_credentials_for_each_region_scope() -> None:
targets = resolve_publish_targets(None)

assert len(targets) == len(OPT_IN_REGIONS) + 3
assert {
region for target in targets for region in target.regions
} == SUPPORTED_REGIONS

default_target = next(
target for target in targets if target.name == "commercial-default"
)
assert default_target.regions == DEFAULT_COMMERCIAL_REGIONS
assert default_target.role_secret == "LAYER_PUBLISH_ROLE_ARN"
assert default_target.audience == DEFAULT_AUDIENCE

for region in OPT_IN_REGIONS:
target = next(target for target in targets if target.name == region)
assert target.regions == (region,)
assert target.role_secret == (
f"LAYER_PUBLISH_ROLE_ARN_{region.upper().replace('-', '_')}"
)
assert target.aws_region == region
assert target.audience == DEFAULT_AUDIENCE

china_target = next(target for target in targets if target.name == "china")
assert china_target.regions == CHINA_REGIONS
assert china_target.role_secret == "LAYER_PUBLISH_ROLE_ARN_CHINA"
assert china_target.audience == CHINA_AUDIENCE

us_gov_target = next(target for target in targets if target.name == "us-gov")
assert us_gov_target.regions == US_GOV_REGIONS
assert us_gov_target.role_secret == "LAYER_PUBLISH_ROLE_ARN_US_GOV"
assert us_gov_target.audience == DEFAULT_AUDIENCE


def test_requested_regions_only_include_required_credential_groups() -> None:
targets = resolve_publish_targets(" cn-northwest-1,us-east-1,ap-east-1,cn-north-1 ")

assert [(target.name, target.regions) for target in targets] == [
("commercial-default", ("us-east-1",)),
("ap-east-1", ("ap-east-1",)),
("china", CHINA_REGIONS),
]


def test_requested_regions_reject_unsupported_regions() -> None:
with pytest.raises(
ValueError,
match="Unsupported layer publish regions: moon-west-1, test-east-1",
):
resolve_publish_targets("test-east-1,moon-west-1")


def test_serialized_targets_are_valid_workflow_matrix_entries() -> None:
targets = resolve_publish_targets("us-gov-west-1")

assert json.loads(serialize_publish_targets(targets)) == [
{
"name": "us-gov",
"regions": "us-gov-west-1",
"role_secret": "LAYER_PUBLISH_ROLE_ARN_US_GOV",
"aws_region": "us-gov-east-1",
"audience": DEFAULT_AUDIENCE,
}
]


def test_credential_groups_do_not_assign_a_region_more_than_once() -> None:
configured_regions = [
region for group in CREDENTIAL_GROUPS for region in group.regions
]

assert len(configured_regions) == len(set(configured_regions))
assert len(CREDENTIAL_GROUPS) <= 256
Loading
Loading