arnmatch 0.1.0__tar.gz → 0.3.0__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.
- {arnmatch-0.1.0 → arnmatch-0.3.0}/.claude/settings.local.json +3 -1
- arnmatch-0.3.0/CLAUDE.md +58 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/Makefile +9 -1
- arnmatch-0.3.0/PKG-INFO +108 -0
- arnmatch-0.3.0/README.md +100 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/pyproject.toml +1 -1
- {arnmatch-0.1.0 → arnmatch-0.3.0}/src/arnmatch/__init__.py +28 -28
- arnmatch-0.3.0/tests/test_arnmatch.py +403 -0
- arnmatch-0.1.0/CLAUDE.md +0 -0
- arnmatch-0.1.0/PKG-INFO +0 -6
- arnmatch-0.1.0/README.md +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/.gitignore +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/.python-version +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/codegen/.gitignore +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/codegen/codegen.py +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/codegen/scraper.py +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/src/arnmatch/arn_patterns.py +0 -0
- {arnmatch-0.1.0 → arnmatch-0.3.0}/uv.lock +0 -0
arnmatch-0.3.0/CLAUDE.md
ADDED
|
@@ -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/
|
arnmatch-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arnmatch
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: arnmatch - 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
|
+

|
|
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
|
+
$ 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
|
+
```
|
arnmatch-0.3.0/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# arnmatch
|
|
2
|
+
|
|
3
|
+
Parse AWS ARNs into structured data.
|
|
4
|
+
|
|
5
|
+

|
|
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
|
+
$ 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 = "
|
|
4
|
+
description = "arnmatch - 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.
|
|
3
|
+
__version__ = "0.3.0"
|
|
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
|
|
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
|
|
23
|
-
"""
|
|
22
|
+
class ARN:
|
|
23
|
+
"""Parsed ARN with structured data and captured groups."""
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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.
|
|
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.
|
|
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) ->
|
|
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
|
|
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
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
107
|
+
resource_types=type_names, # all known names
|
|
108
|
+
attributes=match.groupdict(),
|
|
109
109
|
)
|
|
110
110
|
|
|
111
|
-
raise
|
|
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"
|
|
124
|
-
print(f"
|
|
125
|
-
print(f"
|
|
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
|
|
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"
|
arnmatch-0.1.0/CLAUDE.md
DELETED
|
File without changes
|
arnmatch-0.1.0/PKG-INFO
DELETED
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
|
|
File without changes
|