arnmatch 0.1.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v5
18
+
19
+ - name: Build package
20
+ run: uv build
21
+
22
+ - name: Publish to PyPI
23
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,58 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ arnmatch is a zero-dependency Python library that parses AWS ARNs into structured data. Supports 300+ AWS services and 2000+ resource types. ARN patterns are auto-generated by scraping AWS official documentation.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ make lint # Run ruff linter
13
+ make test # Run pytest tests
14
+ make check # Run lint and test
15
+ make build # Copy generated patterns to src + build wheel/tarball
16
+ make publish # Build and upload to PyPI
17
+ make clean # Remove build artifacts
18
+ ```
19
+
20
+ Run codegen to regenerate patterns from AWS docs:
21
+ ```bash
22
+ cd codegen && uv run codegen.py
23
+ ```
24
+
25
+ Test locally:
26
+ ```bash
27
+ uv run arnmatch <arn>
28
+ ```
29
+
30
+ ## Architecture
31
+
32
+ ### Core Library (`src/arnmatch/`)
33
+
34
+ - `__init__.py` - Main module with `arnmatch(arn)` function and `ARN` dataclass
35
+ - `arn_patterns.py` - Generated file containing compiled regex patterns indexed by service
36
+
37
+ The `arnmatch()` function splits the ARN, looks up patterns by service, and returns an `ARN` with partition, service, region, account, resource_type, and captured groups. The `resource_id` and `resource_name` properties use heuristics (prefer groups ending in "Id" or "Name").
38
+
39
+ ### Code Generation (`codegen/`)
40
+
41
+ - `scraper.py` - Scrapes AWS service authorization reference pages, caches results with joblib
42
+ - `codegen.py` - Processes resources and generates `arn_patterns.py`
43
+
44
+ Data flow: AWS docs → `scraper.py` → raw resources → `codegen.py` → `codegen/build/arn_patterns.py` → (copied by `make build`) → `src/arnmatch/arn_patterns.py`
45
+
46
+ ### Key Design Decisions
47
+
48
+ 1. **Pattern ordering**: Patterns sorted by specificity (more literal segments first) for correct matching
49
+ 2. **Service index**: O(1) lookup by service before pattern matching
50
+ 3. **Overrides in codegen.py**: `PATTERN_OVERRIDES` fixes AWS docs that use wildcards instead of capture groups; `PATTERN_INCLUDES` adds patterns not in docs (EKS k8s resources, Inspector legacy)
51
+ 4. **Zero runtime dependencies**: Only codegen has external deps (requests, beautifulsoup4, joblib)
52
+
53
+ ## Build Notes
54
+
55
+ - Uses `uv` for package management (not pip)
56
+ - Build system is hatchling with dynamic version from `src/arnmatch/__init__.py`
57
+ - Always run `make build` before publishing to ensure patterns are current
58
+ - Scraper cache lives in `.cache/` - delete if AWS docs change significantly
@@ -8,14 +8,22 @@ help: ## Show this help
8
8
  lint: ## Run linter
9
9
  uv run ruff check .
10
10
 
11
+ .PHONY: test
12
+ test: ## Run tests
13
+ uv run pytest tests/
14
+
11
15
  .PHONY: check
12
- check: lint ## Run lint and test
16
+ check: lint test ## Run lint and test
13
17
 
14
18
  .PHONY: build
15
19
  build: ## Build package
16
20
  cp codegen/build/arn_patterns.py src/arnmatch/arn_patterns.py
17
21
  uv build
18
22
 
23
+ .PHONY: publish
24
+ publish: build ## Publish package to PyPI
25
+ uv publish
26
+
19
27
  .PHONY: clean
20
28
  clean: ## Clean build artifacts
21
29
  rm -rf dist/ __pycache__/ .pytest_cache/ .ruff_cache/
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: arnmatch
3
+ Version: 0.3.1
4
+ Summary: Parse AWS ARNs into structured data
5
+ Author-email: Andrey Gubarev <andrey@andreygubarev.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+
9
+ # arnmatch
10
+
11
+ Parse AWS ARNs into structured data.
12
+
13
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
14
+
15
+ ## Features
16
+
17
+ - Zero runtime dependencies
18
+ - 300+ AWS services, 2000+ resource types supported
19
+ - Patterns auto-generated from AWS official documentation
20
+ - CLI and library interface
21
+ - Extracts resource type, ID, and name with smart heuristics
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install arnmatch
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### CLI
32
+
33
+ ```bash
34
+ $ uvx arnmatch "arn:aws:lambda:us-east-1:123456789012:function:my-function"
35
+ aws_service: lambda
36
+ aws_region: us-east-1
37
+ aws_account: 123456789012
38
+ resource_type: function
39
+ resource_id: my-function
40
+ resource_name: my-function
41
+ ```
42
+
43
+ ### Library
44
+
45
+ ```python
46
+ from arnmatch import arnmatch
47
+
48
+ arn = "arn:aws:lambda:us-east-1:123456789012:function:my-function"
49
+ result = arnmatch(arn)
50
+
51
+ print(result.aws_service) # lambda
52
+ print(result.aws_region) # us-east-1
53
+ print(result.aws_account) # 123456789012
54
+ print(result.resource_type) # function
55
+ print(result.resource_id) # my-function
56
+ print(result.resource_name) # my-function
57
+ print(result.attributes) # {'Partition': 'aws', 'Region': 'us-east-1', ...}
58
+ ```
59
+
60
+ ## API Reference
61
+
62
+ ### `arnmatch(arn: str) -> ARN`
63
+
64
+ Parse an ARN string and return structured data.
65
+
66
+ Raises `ARNError` if the ARN format is invalid or no pattern matches.
67
+
68
+ ### `ARN`
69
+
70
+ Dataclass with parsed ARN components:
71
+
72
+ | Field | Type | Description |
73
+ |-------|------|-------------|
74
+ | `aws_partition` | `str` | AWS partition (aws, aws-cn, aws-us-gov) |
75
+ | `aws_service` | `str` | AWS service name |
76
+ | `aws_region` | `str` | AWS region (may be empty for global resources) |
77
+ | `aws_account` | `str` | AWS account ID |
78
+ | `resource_type` | `str` | Canonical resource type from AWS docs |
79
+ | `resource_types` | `list[str]` | All known names for this resource type |
80
+ | `attributes` | `dict[str, str]` | All captured attributes from the pattern |
81
+
82
+ Properties:
83
+
84
+ | Property | Description |
85
+ |----------|-------------|
86
+ | `resource_id` | Resource identifier (prefers groups ending in `Id`, falls back to `Name`, then last group) |
87
+ | `resource_name` | Resource name (prefers groups ending in `Name`, falls back to `resource_id`) |
88
+
89
+ ### `ARNError`
90
+
91
+ Exception raised when ARN parsing fails. Inherits from `ValueError`.
92
+
93
+ ## Development
94
+
95
+ Prerequisites: [uv](https://github.com/astral-sh/uv)
96
+
97
+ ```bash
98
+ make lint # Run ruff linter
99
+ make build # Build wheel and tarball
100
+ make publish # Build and upload to PyPI
101
+ make clean # Remove build artifacts
102
+ ```
103
+
104
+ Regenerate patterns from AWS docs:
105
+
106
+ ```bash
107
+ cd codegen && uv run codegen.py
108
+ ```
@@ -0,0 +1,100 @@
1
+ # arnmatch
2
+
3
+ Parse AWS ARNs into structured data.
4
+
5
+ ![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
6
+
7
+ ## Features
8
+
9
+ - Zero runtime dependencies
10
+ - 300+ AWS services, 2000+ resource types supported
11
+ - Patterns auto-generated from AWS official documentation
12
+ - CLI and library interface
13
+ - Extracts resource type, ID, and name with smart heuristics
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install arnmatch
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### CLI
24
+
25
+ ```bash
26
+ $ uvx arnmatch "arn:aws:lambda:us-east-1:123456789012:function:my-function"
27
+ aws_service: lambda
28
+ aws_region: us-east-1
29
+ aws_account: 123456789012
30
+ resource_type: function
31
+ resource_id: my-function
32
+ resource_name: my-function
33
+ ```
34
+
35
+ ### Library
36
+
37
+ ```python
38
+ from arnmatch import arnmatch
39
+
40
+ arn = "arn:aws:lambda:us-east-1:123456789012:function:my-function"
41
+ result = arnmatch(arn)
42
+
43
+ print(result.aws_service) # lambda
44
+ print(result.aws_region) # us-east-1
45
+ print(result.aws_account) # 123456789012
46
+ print(result.resource_type) # function
47
+ print(result.resource_id) # my-function
48
+ print(result.resource_name) # my-function
49
+ print(result.attributes) # {'Partition': 'aws', 'Region': 'us-east-1', ...}
50
+ ```
51
+
52
+ ## API Reference
53
+
54
+ ### `arnmatch(arn: str) -> ARN`
55
+
56
+ Parse an ARN string and return structured data.
57
+
58
+ Raises `ARNError` if the ARN format is invalid or no pattern matches.
59
+
60
+ ### `ARN`
61
+
62
+ Dataclass with parsed ARN components:
63
+
64
+ | Field | Type | Description |
65
+ |-------|------|-------------|
66
+ | `aws_partition` | `str` | AWS partition (aws, aws-cn, aws-us-gov) |
67
+ | `aws_service` | `str` | AWS service name |
68
+ | `aws_region` | `str` | AWS region (may be empty for global resources) |
69
+ | `aws_account` | `str` | AWS account ID |
70
+ | `resource_type` | `str` | Canonical resource type from AWS docs |
71
+ | `resource_types` | `list[str]` | All known names for this resource type |
72
+ | `attributes` | `dict[str, str]` | All captured attributes from the pattern |
73
+
74
+ Properties:
75
+
76
+ | Property | Description |
77
+ |----------|-------------|
78
+ | `resource_id` | Resource identifier (prefers groups ending in `Id`, falls back to `Name`, then last group) |
79
+ | `resource_name` | Resource name (prefers groups ending in `Name`, falls back to `resource_id`) |
80
+
81
+ ### `ARNError`
82
+
83
+ Exception raised when ARN parsing fails. Inherits from `ValueError`.
84
+
85
+ ## Development
86
+
87
+ Prerequisites: [uv](https://github.com/astral-sh/uv)
88
+
89
+ ```bash
90
+ make lint # Run ruff linter
91
+ make build # Build wheel and tarball
92
+ make publish # Build and upload to PyPI
93
+ make clean # Remove build artifacts
94
+ ```
95
+
96
+ Regenerate patterns from AWS docs:
97
+
98
+ ```bash
99
+ cd codegen && uv run codegen.py
100
+ ```
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "arnmatch"
3
3
  dynamic = ["version"]
4
- description = "AWS ARN Matcher - parses and returns information about AWS ARNs"
4
+ description = "Parse AWS ARNs into structured data"
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Andrey Gubarev", email = "andrey@andreygubarev.com" }
@@ -1,6 +1,6 @@
1
1
  """ARN pattern matching using regex patterns."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.3.1"
4
4
 
5
5
  import sys
6
6
  from dataclasses import dataclass
@@ -12,23 +12,23 @@ from .arn_patterns import ARN_PATTERNS
12
12
  STANDARD_GROUPS = {"Partition", "Region", "Account"}
13
13
 
14
14
 
15
- class ARNMatchError(ValueError):
16
- """Raised when ARN cannot be matched."""
15
+ class ARNError(ValueError):
16
+ """Raised when ARN cannot be parsed or matched."""
17
17
 
18
18
  pass
19
19
 
20
20
 
21
21
  @dataclass(frozen=True)
22
- class ARNMatch:
23
- """Result of matching an ARN against patterns."""
22
+ class ARN:
23
+ """Parsed ARN with structured data and captured groups."""
24
24
 
25
- partition: str
26
- service: str
27
- region: str
28
- account: str
25
+ aws_partition: str
26
+ aws_service: str
27
+ aws_region: str
28
+ aws_account: str
29
29
  resource_type: str # canonical type (from AWS docs)
30
- resource_type_aliases: list[str] # all known names including Resource Explorer
31
- groups: dict[str, str]
30
+ resource_types: list[str] # all known names including Resource Explorer
31
+ attributes: dict[str, str]
32
32
 
33
33
  @cached_property
34
34
  def resource_id(self) -> str:
@@ -39,7 +39,7 @@ class ARNMatch:
39
39
  2. Group ending with 'Name' as fallback
40
40
  3. Last non-standard group
41
41
  """
42
- resource_groups = list(self.groups.items())
42
+ resource_groups = list(self.attributes.items())
43
43
  resource_groups = [(k, v) for k, v in resource_groups if k not in STANDARD_GROUPS]
44
44
 
45
45
  # Look for *Id (from end)
@@ -66,7 +66,7 @@ class ARNMatch:
66
66
  1. Group ending with 'Name' (FunctionName, BucketName, StackName)
67
67
  2. Falls back to resource_id
68
68
  """
69
- resource_groups = list(self.groups.items())
69
+ resource_groups = list(self.attributes.items())
70
70
  resource_groups = [(k, v) for k, v in resource_groups if k not in STANDARD_GROUPS]
71
71
 
72
72
  # Look for *Name (from end)
@@ -78,7 +78,7 @@ class ARNMatch:
78
78
  return self.resource_id
79
79
 
80
80
 
81
- def arnmatch(arn: str) -> ARNMatch:
81
+ def arnmatch(arn: str) -> ARN:
82
82
  """Match ARN against patterns.
83
83
 
84
84
  Returns ARNMatch with all captured groups.
@@ -88,27 +88,27 @@ def arnmatch(arn: str) -> ARNMatch:
88
88
  """
89
89
  parts = arn.split(":", 5)
90
90
  if len(parts) != 6 or parts[0] != "arn":
91
- raise ARNMatchError(f"Invalid ARN format: {arn}")
91
+ raise ARNError(f"Invalid ARN format: {arn}")
92
92
 
93
93
  _, partition, service, region, account, _ = parts
94
94
 
95
95
  if service not in ARN_PATTERNS:
96
- raise ARNMatchError(f"Unknown service: {service}")
96
+ raise ARNError(f"Unknown service: {service}")
97
97
 
98
98
  for regex, type_names in ARN_PATTERNS[service]:
99
99
  match = regex.match(arn)
100
100
  if match:
101
- return ARNMatch(
102
- partition=partition,
103
- service=service,
104
- region=region,
105
- account=account,
101
+ return ARN(
102
+ aws_partition=partition,
103
+ aws_service=service,
104
+ aws_region=region,
105
+ aws_account=account,
106
106
  resource_type=type_names[0], # canonical
107
- resource_type_aliases=type_names, # all known names
108
- groups=match.groupdict(),
107
+ resource_types=type_names, # all known names
108
+ attributes=match.groupdict(),
109
109
  )
110
110
 
111
- raise ARNMatchError(f"No pattern matched ARN: {arn}")
111
+ raise ARNError(f"No pattern matched ARN: {arn}")
112
112
 
113
113
 
114
114
  def main() -> None:
@@ -120,12 +120,12 @@ def main() -> None:
120
120
  arn = sys.argv[1]
121
121
  try:
122
122
  result = arnmatch(arn)
123
- print(f"service: {result.service}")
124
- print(f"region: {result.region}")
125
- print(f"account: {result.account}")
123
+ print(f"aws_service: {result.aws_service}")
124
+ print(f"aws_region: {result.aws_region}")
125
+ print(f"aws_account: {result.aws_account}")
126
126
  print(f"resource_type: {result.resource_type}")
127
127
  print(f"resource_id: {result.resource_id}")
128
128
  print(f"resource_name: {result.resource_name}")
129
- except ARNMatchError as e:
129
+ except ARNError as e:
130
130
  print(f"Error: {e}", file=sys.stderr)
131
131
  sys.exit(1)
@@ -0,0 +1,403 @@
1
+ """Tests for ARN pattern matching."""
2
+
3
+ from arnmatch import arnmatch
4
+
5
+
6
+ def test_acm():
7
+ result = arnmatch(
8
+ "arn:aws:acm:us-east-1:012345678901:certificate/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
9
+ )
10
+ assert result.resource_type == "certificate"
11
+ assert result.attributes["CertificateId"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
12
+
13
+
14
+ def test_apigateway():
15
+ result = arnmatch("arn:aws:apigateway:us-east-1::/restapis/abc123def4")
16
+ assert result.resource_type == "RestApi"
17
+ assert result.attributes["RestApiId"] == "abc123def4"
18
+
19
+
20
+ def test_athena():
21
+ result = arnmatch("arn:aws:athena:us-east-1:012345678901:workgroup/workgroup1")
22
+ assert result.resource_type == "workgroup"
23
+ assert result.attributes["WorkGroupName"] == "workgroup1"
24
+
25
+
26
+ def test_autoscaling():
27
+ result = arnmatch(
28
+ "arn:aws:autoscaling:us-east-1:012345678901:autoScalingGroup:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:autoScalingGroupName/asg1"
29
+ )
30
+ assert result.resource_type == "autoScalingGroup"
31
+ assert result.attributes["GroupId"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
32
+ assert result.attributes["GroupFriendlyName"] == "asg1"
33
+
34
+
35
+ def test_backup():
36
+ result = arnmatch("arn:aws:backup:us-east-1:012345678901:backup-vault:vault1")
37
+ assert result.resource_type == "backupVault"
38
+ assert result.attributes["BackupVaultName"] == "vault1"
39
+
40
+
41
+ def test_cloudformation():
42
+ result = arnmatch(
43
+ "arn:aws:cloudformation:us-east-1:012345678901:stack/stack1/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
44
+ )
45
+ assert result.resource_type == "stack"
46
+ assert result.attributes["StackName"] == "stack1"
47
+ assert result.attributes["Id"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
48
+
49
+
50
+ def test_cloudfront():
51
+ result = arnmatch("arn:aws:cloudfront::012345678901:distribution/ABCDEFGHIJKLMN")
52
+ assert result.resource_type == "distribution"
53
+ assert result.attributes["DistributionId"] == "ABCDEFGHIJKLMN"
54
+
55
+
56
+ def test_cloudtrail():
57
+ result = arnmatch("arn:aws:cloudtrail:us-east-1:012345678901:trail/trailname1")
58
+ assert result.resource_type == "trail"
59
+ assert result.attributes["TrailName"] == "trailname1"
60
+
61
+
62
+ def test_cloudwatch():
63
+ result = arnmatch(
64
+ "arn:aws:cloudwatch:us-east-1:012345678901:alarm:CPU Utilization - High Warning - service-production-1"
65
+ )
66
+ assert result.resource_type == "alarm"
67
+ assert (
68
+ result.attributes["AlarmName"]
69
+ == "CPU Utilization - High Warning - service-production-1"
70
+ )
71
+
72
+
73
+ def test_codebuild():
74
+ result = arnmatch("arn:aws:codebuild:us-east-1:012345678901:project/build_image")
75
+ assert result.resource_type == "project"
76
+ assert result.attributes["ProjectName"] == "build_image"
77
+
78
+
79
+ def test_codecommit():
80
+ result = arnmatch("arn:aws:codecommit:us-east-1:012345678901:repository1")
81
+ assert result.resource_type == "repository"
82
+ assert result.attributes["RepositoryName"] == "repository1"
83
+
84
+
85
+ def test_codepipeline():
86
+ result = arnmatch("arn:aws:codepipeline:us-east-1:012345678901:deploy_pipeline")
87
+ assert result.resource_type == "pipeline"
88
+ assert result.attributes["PipelineName"] == "deploy_pipeline"
89
+
90
+
91
+ def test_datasync():
92
+ result = arnmatch(
93
+ "arn:aws:datasync:us-east-1:012345678901:task/task-0123456789abcdef0"
94
+ )
95
+ assert result.resource_type == "task"
96
+ assert result.attributes["TaskId"] == "task-0123456789abcdef0"
97
+
98
+
99
+ def test_dynamodb():
100
+ result = arnmatch("arn:aws:dynamodb:us-east-1:012345678901:table/table1")
101
+ assert result.resource_type == "table"
102
+ assert result.attributes["TableName"] == "table1"
103
+
104
+
105
+ def test_ec2():
106
+ result = arnmatch("arn:aws:ec2:us-east-1:012345678901:instance/i-0123456789abcdef0")
107
+ assert result.resource_type == "instance"
108
+ assert result.attributes["InstanceId"] == "i-0123456789abcdef0"
109
+
110
+ result = arnmatch("arn:aws:ec2:us-east-1:012345678901:volume/vol-0123456789abcdef0")
111
+ assert result.resource_type == "volume"
112
+ assert result.attributes["VolumeId"] == "vol-0123456789abcdef0"
113
+
114
+ result = arnmatch(
115
+ "arn:aws:ec2:us-east-1:012345678901:snapshot/snap-0123456789abcdef0"
116
+ )
117
+ assert result.resource_type == "snapshot"
118
+ assert result.attributes["SnapshotId"] == "snap-0123456789abcdef0"
119
+
120
+ result = arnmatch("arn:aws:ec2:us-east-1:012345678901:image/ami-0123456789abcdef0")
121
+ assert result.resource_type == "image"
122
+ assert result.attributes["ImageId"] == "ami-0123456789abcdef0"
123
+
124
+ result = arnmatch(
125
+ "arn:aws:ec2:us-east-1:012345678901:elastic-ip/eipalloc-0123456789abcdef0"
126
+ )
127
+ assert result.resource_type == "elastic-ip"
128
+ assert result.attributes["AllocationId"] == "eipalloc-0123456789abcdef0"
129
+
130
+ result = arnmatch(
131
+ "arn:aws:ec2:us-east-1:012345678901:natgateway/nat-0123456789abcdef0"
132
+ )
133
+ assert result.resource_type == "natgateway"
134
+ assert result.attributes["NatGatewayId"] == "nat-0123456789abcdef0"
135
+
136
+ result = arnmatch(
137
+ "arn:aws:ec2:us-east-1:012345678901:vpc-endpoint/vpce-0123456789abcdef0"
138
+ )
139
+ assert result.resource_type == "vpc-endpoint"
140
+ assert result.attributes["VpcEndpointId"] == "vpce-0123456789abcdef0"
141
+
142
+ result = arnmatch(
143
+ "arn:aws:ec2:us-east-1:012345678901:launch-template/lt-0123456789abcdef0"
144
+ )
145
+ assert result.resource_type == "launch-template"
146
+ assert result.attributes["LaunchTemplateId"] == "lt-0123456789abcdef0"
147
+
148
+ result = arnmatch(
149
+ "arn:aws:ec2:us-east-1:012345678901:reserved-instances/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
150
+ )
151
+ assert result.resource_type == "reserved-instances"
152
+ assert result.attributes["ReservationId"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
153
+
154
+ result = arnmatch(
155
+ "arn:aws:ec2:us-east-1:012345678901:spot-instances-request/sir-abcd1234"
156
+ )
157
+ assert result.resource_type == "spot-instances-request"
158
+ assert result.attributes["SpotInstanceRequestId"] == "sir-abcd1234"
159
+
160
+
161
+ def test_ecr():
162
+ result = arnmatch("arn:aws:ecr:us-east-1:012345678901:repository/web-service")
163
+ assert result.resource_type == "repository"
164
+ assert result.attributes["RepositoryName"] == "web-service"
165
+
166
+
167
+ def test_eks():
168
+ result = arnmatch("arn:aws:eks:us-east-1:012345678901:cluster/c1")
169
+ assert result.resource_type == "cluster"
170
+ assert result.attributes["ClusterName"] == "c1"
171
+
172
+
173
+ def test_elasticache():
174
+ result = arnmatch(
175
+ "arn:aws:elasticache:us-east-1:012345678901:cluster:redis-cluster-node-001"
176
+ )
177
+ assert result.resource_type == "cluster"
178
+ assert result.attributes["CacheClusterId"] == "redis-cluster-node-001"
179
+
180
+ result = arnmatch(
181
+ "arn:aws:elasticache:us-east-1:012345678901:parametergroup:redis-cluster-params-redis7"
182
+ )
183
+ assert result.resource_type == "parametergroup"
184
+ assert result.attributes["CacheParameterGroupName"] == "redis-cluster-params-redis7"
185
+
186
+ result = arnmatch(
187
+ "arn:aws:elasticache:us-east-1:012345678901:replicationgroup:redis-repl-group1"
188
+ )
189
+ assert result.resource_type == "replicationgroup"
190
+ assert result.attributes["ReplicationGroupId"] == "redis-repl-group1"
191
+
192
+ result = arnmatch(
193
+ "arn:aws:elasticache:us-east-1:012345678901:reserved-instance:reserved-node-prod1"
194
+ )
195
+ assert result.resource_type == "reserved-instance"
196
+ assert result.attributes["ReservedCacheNodeId"] == "reserved-node-prod1"
197
+
198
+ result = arnmatch(
199
+ "arn:aws:elasticache:us-east-1:012345678901:subnetgroup:cache-subnet-group1"
200
+ )
201
+ assert result.resource_type == "subnetgroup"
202
+ assert result.attributes["CacheSubnetGroupName"] == "cache-subnet-group1"
203
+
204
+ result = arnmatch("arn:aws:elasticache:us-east-1:012345678901:user:default")
205
+ assert result.resource_type == "user"
206
+ assert result.attributes["UserId"] == "default"
207
+
208
+
209
+ def test_elasticfilesystem():
210
+ result = arnmatch(
211
+ "arn:aws:elasticfilesystem:us-east-1:012345678901:file-system/fs-01234567"
212
+ )
213
+ assert result.resource_type == "file-system"
214
+ assert result.attributes["FileSystemId"] == "fs-01234567"
215
+
216
+
217
+ def test_elasticloadbalancing():
218
+ # Classic LB
219
+ result = arnmatch(
220
+ "arn:aws:elasticloadbalancing:us-east-1:012345678901:loadbalancer/a0123456789abcdef0123456789abcde"
221
+ )
222
+ assert result.resource_type == "loadbalancer"
223
+ assert result.attributes["LoadBalancerName"] == "a0123456789abcdef0123456789abcde"
224
+
225
+ # ALB
226
+ result = arnmatch(
227
+ "arn:aws:elasticloadbalancing:us-east-1:012345678901:loadbalancer/app/alb-application-lb-name/0123456789abcdef"
228
+ )
229
+ assert result.resource_type == "loadbalancer/app/"
230
+ assert result.attributes["LoadBalancerName"] == "alb-application-lb-name"
231
+ assert result.attributes["LoadBalancerId"] == "0123456789abcdef"
232
+
233
+ # NLB
234
+ result = arnmatch(
235
+ "arn:aws:elasticloadbalancing:us-east-1:012345678901:loadbalancer/net/nlb-network-load-balancer/0123456789abcdef"
236
+ )
237
+ assert result.resource_type == "loadbalancer/net/"
238
+ assert result.attributes["LoadBalancerName"] == "nlb-network-load-balancer"
239
+ assert result.attributes["LoadBalancerId"] == "0123456789abcdef"
240
+
241
+ # Target group
242
+ result = arnmatch(
243
+ "arn:aws:elasticloadbalancing:us-east-1:012345678901:targetgroup/target-grp-1/0123456789abcdef"
244
+ )
245
+ assert result.resource_type == "targetgroup"
246
+ assert result.attributes["TargetGroupName"] == "target-grp-1"
247
+ assert result.attributes["TargetGroupId"] == "0123456789abcdef"
248
+
249
+
250
+ def test_es():
251
+ result = arnmatch("arn:aws:es:us-east-1:012345678901:domain/search-domain-prod")
252
+ assert result.resource_type == "domain"
253
+ assert result.attributes["DomainName"] == "search-domain-prod"
254
+
255
+
256
+ def test_events():
257
+ result = arnmatch("arn:aws:events:us-east-1:012345678901:event-bus/default")
258
+ assert result.resource_type == "event-bus"
259
+ assert result.attributes["EventBusName"] == "default"
260
+
261
+ result = arnmatch("arn:aws:events:us-east-1:012345678901:rule/ScheduledEventRule01")
262
+ assert result.resource_type == "rule-on-default-event-bus"
263
+ assert result.attributes["RuleName"] == "ScheduledEventRule01"
264
+
265
+
266
+ def test_guardduty():
267
+ result = arnmatch(
268
+ "arn:aws:guardduty:us-east-1:012345678901:detector/0123456789abcdef0123456789abcdef"
269
+ )
270
+ assert result.resource_type == "detector"
271
+ assert result.attributes["DetectorId"] == "0123456789abcdef0123456789abcdef"
272
+
273
+
274
+ def test_iam():
275
+ result = arnmatch("arn:aws:iam::012345678901:user/admin")
276
+ assert result.resource_type == "user"
277
+ assert result.attributes["AwsUserName"] == "admin"
278
+
279
+ result = arnmatch("arn:aws:iam::012345678901:role/lambda-execution-role")
280
+ assert result.resource_type == "role"
281
+ assert result.attributes["RoleNameWithPath"] == "lambda-execution-role"
282
+
283
+ result = arnmatch("arn:aws:iam::012345678901:policy/custom-policy")
284
+ assert result.resource_type == "policy"
285
+ assert result.attributes["PolicyNameWithPath"] == "custom-policy"
286
+
287
+ result = arnmatch("arn:aws:iam::012345678901:group/developers")
288
+ assert result.resource_type == "group"
289
+ assert result.attributes["GroupNameWithPath"] == "developers"
290
+
291
+ result = arnmatch("arn:aws:iam::012345678901:instance-profile/ec2-profile")
292
+ assert result.resource_type == "instance-profile"
293
+ assert result.attributes["InstanceProfileNameWithPath"] == "ec2-profile"
294
+
295
+
296
+ def test_kms():
297
+ result = arnmatch(
298
+ "arn:aws:kms:us-east-1:012345678901:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
299
+ )
300
+ assert result.resource_type == "key"
301
+ assert result.attributes["KeyId"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
302
+
303
+
304
+ def test_lambda():
305
+ result = arnmatch(
306
+ "arn:aws:lambda:us-east-1:012345678901:function:ProcessDataHandler"
307
+ )
308
+ assert result.resource_type == "function"
309
+ assert result.attributes["FunctionName"] == "ProcessDataHandler"
310
+
311
+
312
+ def test_logs():
313
+ result = arnmatch(
314
+ "arn:aws:logs:us-east-1:012345678901:log-group:/aws/lambda/function-logs"
315
+ )
316
+ assert result.resource_type == "log-group"
317
+ assert result.attributes["LogGroupName"] == "/aws/lambda/function-logs"
318
+
319
+
320
+ def test_rds():
321
+ result = arnmatch("arn:aws:rds:us-east-1:012345678901:cluster:database-cluster-1")
322
+ assert result.resource_type == "cluster"
323
+ assert result.attributes["DbClusterInstanceName"] == "database-cluster-1"
324
+
325
+ result = arnmatch("arn:aws:rds:us-east-1:012345678901:cluster-snapshot:snap001")
326
+ assert result.resource_type == "cluster-snapshot"
327
+ assert result.attributes["ClusterSnapshotName"] == "snap001"
328
+
329
+ result = arnmatch("arn:aws:rds:us-east-1:012345678901:db:database-instance-1")
330
+ assert result.resource_type == "db"
331
+ assert result.attributes["DbInstanceName"] == "database-instance-1"
332
+
333
+ result = arnmatch("arn:aws:rds:us-east-1:012345678901:ri:reserved01")
334
+ assert result.resource_type == "ri"
335
+ assert result.attributes["ReservedDbInstanceName"] == "reserved01"
336
+
337
+ result = arnmatch(
338
+ "arn:aws:rds:us-east-1:012345678901:snapshot:final-database-backup-01234567"
339
+ )
340
+ assert result.resource_type == "snapshot"
341
+ assert result.attributes["SnapshotName"] == "final-database-backup-01234567"
342
+
343
+
344
+ def test_route53():
345
+ result = arnmatch(
346
+ "arn:aws:route53:::healthcheck/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
347
+ )
348
+ assert result.resource_type == "healthcheck"
349
+ assert result.attributes["Id"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
350
+
351
+ result = arnmatch("arn:aws:route53:::hostedzone/Z0123456789ABCDEFGHIJ")
352
+ assert result.resource_type == "hostedzone"
353
+ assert result.attributes["Id"] == "Z0123456789ABCDEFGHIJ"
354
+
355
+
356
+ def test_s3():
357
+ result = arnmatch("arn:aws:s3:::example-bucket-01")
358
+ assert result.resource_type == "bucket"
359
+ assert result.attributes["BucketName"] == "example-bucket-01"
360
+
361
+
362
+ def test_secretsmanager():
363
+ result = arnmatch(
364
+ "arn:aws:secretsmanager:us-east-1:012345678901:secret:/app/secrets/service-name/production-AbCdEf"
365
+ )
366
+ assert result.resource_type == "Secret"
367
+ assert result.attributes["SecretId"] == "/app/secrets/service-name/production-AbCdEf"
368
+
369
+
370
+ def test_sns():
371
+ result = arnmatch("arn:aws:sns:us-east-1:012345678901:topic1")
372
+ assert result.resource_type == "topic"
373
+ assert result.attributes["TopicName"] == "topic1"
374
+
375
+
376
+ def test_sqs():
377
+ result = arnmatch("arn:aws:sqs:us-east-1:012345678901:processing-queue-1")
378
+ assert result.resource_type == "queue"
379
+ assert result.attributes["QueueName"] == "processing-queue-1"
380
+
381
+
382
+ def test_ssm():
383
+ result = arnmatch("arn:aws:ssm:us-east-1:012345678901:parameter/config_val")
384
+ assert result.resource_type == "parameter"
385
+ assert result.attributes["ParameterNameWithoutLeadingSlash"] == "config_val"
386
+
387
+
388
+ def test_wafv2():
389
+ result = arnmatch(
390
+ "arn:aws:wafv2:us-east-1:012345678901:global/webacl/WebACL-for-CloudFront-01/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
391
+ )
392
+ assert result.resource_type == "webacl"
393
+ assert result.attributes["Scope"] == "global"
394
+ assert result.attributes["Name"] == "WebACL-for-CloudFront-01"
395
+ assert result.attributes["Id"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
396
+
397
+ result = arnmatch(
398
+ "arn:aws:wafv2:us-east-1:012345678901:regional/webacl/webacl-production-acl/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
399
+ )
400
+ assert result.resource_type == "webacl"
401
+ assert result.attributes["Scope"] == "regional"
402
+ assert result.attributes["Name"] == "webacl-production-acl"
403
+ assert result.attributes["Id"] == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
@@ -1,16 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__plugin_adaptivegears_adaptivegears__search",
5
- "mcp__plugin_adaptivegears_adaptivegears__read",
6
- "Bash(uv init:*)",
7
- "Bash(uv venv:*)",
8
- "Bash(uv add:*)",
9
- "Bash(uv run:*)",
10
- "Bash(uv sync:*)"
11
- ]
12
- },
13
- "enabledPlugins": {
14
- "adaptivegears@adaptivegears-marketplace": true
15
- }
16
- }
arnmatch-0.1.0/CLAUDE.md DELETED
File without changes
arnmatch-0.1.0/PKG-INFO DELETED
@@ -1,6 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: arnmatch
3
- Version: 0.1.0
4
- Summary: AWS ARN Matcher - parses and returns information about AWS ARNs
5
- Author-email: Andrey Gubarev <andrey@andreygubarev.com>
6
- Requires-Python: >=3.10
arnmatch-0.1.0/README.md DELETED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes