awsquery 0.2.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.
- {awsquery-0.2.0 → awsquery-0.3.1}/.devcontainer/devcontainer.json +16 -15
- {awsquery-0.2.0 → awsquery-0.3.1}/.github/workflows/release.yml +20 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.github/workflows/test.yml +38 -5
- {awsquery-0.2.0 → awsquery-0.3.1}/Makefile +13 -4
- {awsquery-0.2.0 → awsquery-0.3.1}/PKG-INFO +3 -12
- {awsquery-0.2.0 → awsquery-0.3.1}/README.md +2 -11
- {awsquery-0.2.0 → awsquery-0.3.1}/pyproject.toml +1 -1
- awsquery-0.3.1/pytest.ini +10 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/cli.py +49 -42
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/default_filters.yaml +651 -228
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/filters.py +6 -3
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/formatters.py +7 -5
- awsquery-0.3.1/src/awsquery/security.py +120 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery.egg-info/PKG-INFO +3 -12
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery.egg-info/SOURCES.txt +1 -1
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/conftest.py +4 -4
- awsquery-0.3.1/tests/integration/test_autocomplete_resilience.py +363 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/test_end_to_end.py +56 -64
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/test_feature_combinations.py +16 -27
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/test_keys_behavior.py +4 -14
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/test_multi_level.py +2 -2
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/test_session_management.py +3 -13
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_cli_arg_processing.py +10 -17
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_cli_flags.py +12 -32
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_cli_parser.py +14 -36
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_cli_session_args.py +7 -16
- awsquery-0.3.1/tests/unit/test_config_loading_simple.py +55 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_critical_mutations.py +2 -54
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_default_column_filters.py +28 -61
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_filter_matching.py +1 -5
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_keys_display_fix.py +2 -6
- awsquery-0.3.1/tests/unit/test_security.py +192 -0
- awsquery-0.3.1/tests/unit/test_security_edge_cases.py +214 -0
- awsquery-0.2.0/pytest.ini +0 -13
- awsquery-0.2.0/src/awsquery/security.py +0 -108
- awsquery-0.2.0/tests/integration/test_autocomplete_resilience.py +0 -141
- awsquery-0.2.0/tests/unit/test_config_loading.py +0 -375
- awsquery-0.2.0/tests/unit/test_security.py +0 -525
- awsquery-0.2.0/tests/unit/test_security_edge_cases.py +0 -190
- {awsquery-0.2.0 → awsquery-0.3.1}/.claude/agents/code-reviewer.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.claude/agents/makefile-optimizer.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.claude/agents/python-infra-automator.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.claude/agents/test-writer.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.claude/settings.local.json +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.coveragerc +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.dockerignore +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.flake8 +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.github/workflows/quality.yml +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/.gitignore +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/CLAUDE.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/Dockerfile +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/Dockerfile.prod +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/LICENSE +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/Todo.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/docker-compose.yml +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/docs/DOCKER.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/docs/mutation-testing.md +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/policy.json +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/requirements.txt +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/setup.cfg +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/__init__.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/config.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/core.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/policy.json +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery/utils.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery.egg-info/dependency_links.txt +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery.egg-info/entry_points.txt +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery.egg-info/requires.txt +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/src/awsquery.egg-info/top_level.txt +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/__init__.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/fixtures/__init__.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/fixtures/aws_responses.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/fixtures/policy_samples.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/__init__.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/integration/test_multi_level_calls.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/__init__.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_column_ordering.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_core.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_core_inference.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_core_string_handling.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_filter_edge_cases.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_filter_implementation.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_filter_parameter_extraction.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_filters.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_filters_pattern_matching.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_formatter_edge_cases.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_formatters.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_formatters_data_processing.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_messaging_enhancements.py +0 -0
- {awsquery-0.2.0 → awsquery-0.3.1}/tests/unit/test_tags_transformation.py +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "AWSQuery Development",
|
|
3
3
|
"image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye",
|
|
4
|
-
|
|
5
4
|
"features": {
|
|
6
5
|
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0.5": {},
|
|
7
6
|
"ghcr.io/devcontainers/features/aws-cli:1": {
|
|
@@ -10,10 +9,8 @@
|
|
|
10
9
|
"ghcr.io/devcontainers/features/git:1": {
|
|
11
10
|
"ppa": true,
|
|
12
11
|
"version": "latest"
|
|
13
|
-
}
|
|
14
|
-
"ghcr.io/devcontainers-contrib/features/pre-commit:2": {}
|
|
12
|
+
}
|
|
15
13
|
},
|
|
16
|
-
|
|
17
14
|
"customizations": {
|
|
18
15
|
"vscode": {
|
|
19
16
|
"extensions": [
|
|
@@ -34,30 +31,37 @@
|
|
|
34
31
|
"python.linting.enabled": true,
|
|
35
32
|
"python.linting.pylintEnabled": true,
|
|
36
33
|
"python.formatting.provider": "black",
|
|
37
|
-
"python.formatting.blackArgs": [
|
|
34
|
+
"python.formatting.blackArgs": [
|
|
35
|
+
"--line-length=100"
|
|
36
|
+
],
|
|
38
37
|
"python.linting.mypyEnabled": true,
|
|
39
38
|
"python.testing.pytestEnabled": true,
|
|
40
39
|
"python.testing.unittestEnabled": false,
|
|
41
|
-
"python.testing.pytestArgs": [
|
|
40
|
+
"python.testing.pytestArgs": [
|
|
41
|
+
"tests"
|
|
42
|
+
],
|
|
42
43
|
"editor.formatOnSave": true,
|
|
43
|
-
"editor.rulers": [
|
|
44
|
+
"editor.rulers": [
|
|
45
|
+
100
|
|
46
|
+
],
|
|
44
47
|
"[python]": {
|
|
45
48
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
|
46
49
|
"editor.codeActionsOnSave": {
|
|
47
50
|
"source.organizeImports": "explicit"
|
|
48
51
|
}
|
|
49
52
|
},
|
|
50
|
-
"isort.args": [
|
|
53
|
+
"isort.args": [
|
|
54
|
+
"--profile",
|
|
55
|
+
"black",
|
|
56
|
+
"--line-length",
|
|
57
|
+
"100"
|
|
58
|
+
]
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
},
|
|
54
|
-
|
|
55
62
|
"postCreateCommand": "pip install -e '.[dev]' && pre-commit install",
|
|
56
|
-
|
|
57
63
|
"postStartCommand": "echo 'Welcome to AWSQuery development environment!'",
|
|
58
|
-
|
|
59
64
|
"remoteUser": "vscode",
|
|
60
|
-
|
|
61
65
|
"mounts": [
|
|
62
66
|
{
|
|
63
67
|
"source": "${localEnv:HOME}/.aws",
|
|
@@ -66,15 +70,12 @@
|
|
|
66
70
|
"consistency": "cached"
|
|
67
71
|
}
|
|
68
72
|
],
|
|
69
|
-
|
|
70
73
|
"containerEnv": {
|
|
71
74
|
"AWS_REGION": "${localEnv:AWS_REGION}",
|
|
72
75
|
"AWS_PROFILE": "${localEnv:AWS_PROFILE}",
|
|
73
76
|
"AWS_DEFAULT_REGION": "${localEnv:AWS_DEFAULT_REGION}"
|
|
74
77
|
},
|
|
75
|
-
|
|
76
78
|
"forwardPorts": [],
|
|
77
|
-
|
|
78
79
|
"remoteEnv": {
|
|
79
80
|
"PYTHONPATH": "/workspaces/awsquery/src:${PYTHONPATH}"
|
|
80
81
|
}
|
|
@@ -39,6 +39,26 @@ jobs:
|
|
|
39
39
|
python -m pip install --upgrade pip
|
|
40
40
|
pip install build twine
|
|
41
41
|
|
|
42
|
+
- name: Update version in pyproject.toml
|
|
43
|
+
run: |
|
|
44
|
+
# Extract version from tag (remove 'v' prefix if present)
|
|
45
|
+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
|
|
46
|
+
VERSION=${GITHUB_REF#refs/tags/v}
|
|
47
|
+
elif [[ "${{ github.event.inputs.version }}" != "" ]]; then
|
|
48
|
+
VERSION="${{ github.event.inputs.version }}"
|
|
49
|
+
else
|
|
50
|
+
echo "No version found"
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
echo "Updating version to $VERSION for build"
|
|
54
|
+
|
|
55
|
+
# Update version using sed
|
|
56
|
+
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
|
|
57
|
+
|
|
58
|
+
# Verify the change
|
|
59
|
+
echo "Updated pyproject.toml:"
|
|
60
|
+
grep "^version =" pyproject.toml
|
|
61
|
+
|
|
42
62
|
- name: Build distribution
|
|
43
63
|
run: |
|
|
44
64
|
python -m build
|
|
@@ -38,12 +38,46 @@ jobs:
|
|
|
38
38
|
python -m pip install --upgrade pip
|
|
39
39
|
pip install -e ".[dev]"
|
|
40
40
|
|
|
41
|
-
- name: Run tests
|
|
41
|
+
- name: Run tests (fast, no coverage)
|
|
42
42
|
run: |
|
|
43
|
-
|
|
43
|
+
# Run tests without coverage for speed on older Python versions
|
|
44
|
+
python -m pytest tests/ -n auto -q
|
|
45
|
+
|
|
46
|
+
- name: Archive test results
|
|
47
|
+
if: failure()
|
|
48
|
+
uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: test-results-${{ matrix.python-version }}
|
|
51
|
+
path: |
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
coverage:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
needs: test # Run after tests pass
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v5
|
|
59
|
+
|
|
60
|
+
- name: Set up Python 3.12 (latest)
|
|
61
|
+
uses: actions/setup-python@v5
|
|
62
|
+
with:
|
|
63
|
+
python-version: '3.12'
|
|
64
|
+
|
|
65
|
+
- name: Cache pip packages
|
|
66
|
+
uses: actions/cache@v4
|
|
67
|
+
with:
|
|
68
|
+
path: ~/.cache/pip
|
|
69
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
|
|
70
|
+
restore-keys: |
|
|
71
|
+
${{ runner.os }}-pip-
|
|
72
|
+
|
|
73
|
+
- name: Install dependencies
|
|
74
|
+
run: |
|
|
75
|
+
python -m pip install --upgrade pip
|
|
76
|
+
pip install -e ".[dev]"
|
|
44
77
|
|
|
45
78
|
- name: Generate coverage report
|
|
46
79
|
run: |
|
|
80
|
+
# Run with coverage on latest Python where it's fast
|
|
47
81
|
make coverage
|
|
48
82
|
|
|
49
83
|
- name: Upload coverage to Codecov
|
|
@@ -54,11 +88,10 @@ jobs:
|
|
|
54
88
|
name: codecov-umbrella
|
|
55
89
|
fail_ci_if_error: false
|
|
56
90
|
|
|
57
|
-
- name: Archive
|
|
58
|
-
if: always()
|
|
91
|
+
- name: Archive coverage results
|
|
59
92
|
uses: actions/upload-artifact@v4
|
|
60
93
|
with:
|
|
61
|
-
name:
|
|
94
|
+
name: coverage-report
|
|
62
95
|
path: |
|
|
63
96
|
htmlcov/
|
|
64
97
|
.coverage
|
|
@@ -11,10 +11,10 @@ NC := \033[0m
|
|
|
11
11
|
# Default target
|
|
12
12
|
.DEFAULT_GOAL := help
|
|
13
13
|
|
|
14
|
-
.PHONY: help clean install-dev test test-unit test-integration test-critical test-slow
|
|
15
|
-
coverage-report lint format format-check type-check security-check ci build
|
|
16
|
-
publish watch-tests version release update-policy validate-policy all ec2-instances s3-buckets
|
|
17
|
-
iam-roles lambda-functions cloudformation-stacks dynamodb-tables ec2-volumes \
|
|
14
|
+
.PHONY: help clean install-dev test test-unit test-integration test-critical test-slow test-fast test-unit-fast \
|
|
15
|
+
test-integration-fast coverage coverage-report lint format format-check type-check security-check ci build \
|
|
16
|
+
publish-test publish watch-tests version release update-policy validate-policy all ec2-instances s3-buckets \
|
|
17
|
+
iam-users iam-roles lambda-functions cloudformation-stacks dynamodb-tables ec2-volumes \
|
|
18
18
|
ec2-security-groups s3-bucket-versioning cloudwatch-alarms route53-zones shell \
|
|
19
19
|
docker-build docker-clean test-in-docker test-awsquery
|
|
20
20
|
|
|
@@ -48,6 +48,15 @@ test-integration: ## Run integration tests only
|
|
|
48
48
|
test-slow: ## Run slow tests
|
|
49
49
|
python3 -m pytest tests/ -v -m "slow"
|
|
50
50
|
|
|
51
|
+
test-fast: ## Run all tests with parallel execution (optimized for Python 3.8-3.10)
|
|
52
|
+
python3 -m pytest tests/ -n auto -q
|
|
53
|
+
|
|
54
|
+
test-unit-fast: ## Run unit tests with parallel execution (2-5 seconds)
|
|
55
|
+
python3 -m pytest tests/unit/ -n auto -q
|
|
56
|
+
|
|
57
|
+
test-integration-fast: ## Run integration tests with parallel execution
|
|
58
|
+
python3 -m pytest tests/integration/ -n auto -q
|
|
59
|
+
|
|
51
60
|
test-critical: ## Run critical path tests
|
|
52
61
|
python3 -m pytest tests/ -v -m "critical"
|
|
53
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awsquery
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Advanced CLI tool for querying AWS APIs with flexible filtering and automatic parameter resolution
|
|
5
5
|
Author-email: Florian Motlik <flo@flomotlik.me>
|
|
6
6
|
License: MIT
|
|
@@ -112,16 +112,7 @@ pip install -e ".[dev]"
|
|
|
112
112
|
|
|
113
113
|
awsquery supports tab completion for AWS services and actions through argcomplete.
|
|
114
114
|
|
|
115
|
-
####
|
|
116
|
-
```bash
|
|
117
|
-
# Run the setup script
|
|
118
|
-
./setup-autocomplete.sh
|
|
119
|
-
|
|
120
|
-
# Then reload your shell configuration
|
|
121
|
-
source ~/.bashrc # or ~/.zshrc, etc.
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
#### Manual Setup
|
|
115
|
+
#### Setup
|
|
125
116
|
|
|
126
117
|
##### Bash
|
|
127
118
|
```bash
|
|
@@ -510,4 +501,4 @@ MIT License - see LICENSE file for details.
|
|
|
510
501
|
|
|
511
502
|
## Contributing
|
|
512
503
|
|
|
513
|
-
|
|
504
|
+
Contributions are welcome! Please ensure tests pass and follow the existing code style.
|
|
@@ -49,16 +49,7 @@ pip install -e ".[dev]"
|
|
|
49
49
|
|
|
50
50
|
awsquery supports tab completion for AWS services and actions through argcomplete.
|
|
51
51
|
|
|
52
|
-
####
|
|
53
|
-
```bash
|
|
54
|
-
# Run the setup script
|
|
55
|
-
./setup-autocomplete.sh
|
|
56
|
-
|
|
57
|
-
# Then reload your shell configuration
|
|
58
|
-
source ~/.bashrc # or ~/.zshrc, etc.
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
#### Manual Setup
|
|
52
|
+
#### Setup
|
|
62
53
|
|
|
63
54
|
##### Bash
|
|
64
55
|
```bash
|
|
@@ -447,4 +438,4 @@ MIT License - see LICENSE file for details.
|
|
|
447
438
|
|
|
448
439
|
## Contributing
|
|
449
440
|
|
|
450
|
-
|
|
441
|
+
Contributions are welcome! Please ensure tests pass and follow the existing code style.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "awsquery"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Advanced CLI tool for querying AWS APIs with flexible filtering and automatic parameter resolution"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Florian Motlik", email = "flo@flomotlik.me" }]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[pytest]
|
|
2
|
+
minversion = 7.0
|
|
3
|
+
testpaths = tests
|
|
4
|
+
python_files = test_*.py
|
|
5
|
+
python_classes = Test*
|
|
6
|
+
python_functions = test_*
|
|
7
|
+
# Coverage disabled by default for Python 3.8-3.10 performance
|
|
8
|
+
# Use: pytest --cov=src/awsquery --cov-report=term-missing
|
|
9
|
+
# Or: make coverage
|
|
10
|
+
addopts = -ra --durations=0 --tb=short
|
|
@@ -24,9 +24,18 @@ from .formatters import (
|
|
|
24
24
|
format_table_output,
|
|
25
25
|
show_keys,
|
|
26
26
|
)
|
|
27
|
-
from .security import
|
|
27
|
+
from .security import (
|
|
28
|
+
action_to_policy_format,
|
|
29
|
+
get_service_valid_operations,
|
|
30
|
+
is_readonly_operation,
|
|
31
|
+
validate_readonly,
|
|
32
|
+
)
|
|
28
33
|
from .utils import create_session, debug_print, get_aws_services, sanitize_input
|
|
29
34
|
|
|
35
|
+
# CLI flag constants
|
|
36
|
+
SIMPLE_FLAGS = ["-d", "--debug", "-j", "--json", "-k", "--keys", "--allow-unsafe"]
|
|
37
|
+
VALUE_FLAGS = ["--region", "--profile"]
|
|
38
|
+
|
|
30
39
|
|
|
31
40
|
def _extract_flags_from_args(remaining_args):
|
|
32
41
|
"""Extract CLI flags from remaining arguments."""
|
|
@@ -35,9 +44,9 @@ def _extract_flags_from_args(remaining_args):
|
|
|
35
44
|
i = 0
|
|
36
45
|
while i < len(remaining_args):
|
|
37
46
|
arg = remaining_args[i]
|
|
38
|
-
if arg in
|
|
47
|
+
if arg in SIMPLE_FLAGS:
|
|
39
48
|
flags.append(arg)
|
|
40
|
-
elif arg in
|
|
49
|
+
elif arg in VALUE_FLAGS:
|
|
41
50
|
flags.append(arg)
|
|
42
51
|
# Check for value after the flag
|
|
43
52
|
if i + 1 < len(remaining_args):
|
|
@@ -60,6 +69,8 @@ def _preserve_parsed_flags(args):
|
|
|
60
69
|
flags.append("-j")
|
|
61
70
|
if getattr(args, "keys", False):
|
|
62
71
|
flags.append("-k")
|
|
72
|
+
if getattr(args, "allow_unsafe", False):
|
|
73
|
+
flags.append("--allow-unsafe")
|
|
63
74
|
if getattr(args, "region", None):
|
|
64
75
|
flags.extend(["--region", args.region])
|
|
65
76
|
if getattr(args, "profile", None):
|
|
@@ -96,7 +107,7 @@ def _extract_flag_and_value(args, i):
|
|
|
96
107
|
"""Extract a flag and optionally its value from args list."""
|
|
97
108
|
flags = []
|
|
98
109
|
flags.append(args[i])
|
|
99
|
-
if args[i] in
|
|
110
|
+
if args[i] in VALUE_FLAGS:
|
|
100
111
|
if i + 1 < len(args) and not args[i + 1].startswith("-") and args[i + 1] != "--":
|
|
101
112
|
flags.append(args[i + 1])
|
|
102
113
|
return flags, 2 # consumed 2 args
|
|
@@ -110,10 +121,10 @@ def _process_remaining_args_after_separator(remaining):
|
|
|
110
121
|
i = 0
|
|
111
122
|
while i < len(remaining):
|
|
112
123
|
arg = remaining[i]
|
|
113
|
-
if arg in
|
|
124
|
+
if arg in SIMPLE_FLAGS:
|
|
114
125
|
flags.append(arg)
|
|
115
126
|
i += 1
|
|
116
|
-
elif arg in
|
|
127
|
+
elif arg in VALUE_FLAGS:
|
|
117
128
|
extracted, consumed = _extract_flag_and_value(remaining, i)
|
|
118
129
|
flags.extend(extracted)
|
|
119
130
|
i += consumed
|
|
@@ -130,10 +141,10 @@ def _process_remaining_args(remaining):
|
|
|
130
141
|
i = 0
|
|
131
142
|
while i < len(remaining):
|
|
132
143
|
arg = remaining[i]
|
|
133
|
-
if arg in
|
|
144
|
+
if arg in SIMPLE_FLAGS:
|
|
134
145
|
flags.append(arg)
|
|
135
146
|
i += 1
|
|
136
|
-
elif arg in
|
|
147
|
+
elif arg in VALUE_FLAGS:
|
|
137
148
|
extracted, consumed = _extract_flag_and_value(remaining, i)
|
|
138
149
|
flags.extend(extracted)
|
|
139
150
|
i += consumed
|
|
@@ -154,10 +165,10 @@ def _build_filter_argv(args, remaining):
|
|
|
154
165
|
i = 0
|
|
155
166
|
while i < len(remaining):
|
|
156
167
|
arg = remaining[i]
|
|
157
|
-
if arg in
|
|
168
|
+
if arg in SIMPLE_FLAGS:
|
|
158
169
|
i += 1
|
|
159
170
|
continue
|
|
160
|
-
if arg in
|
|
171
|
+
if arg in VALUE_FLAGS:
|
|
161
172
|
i += 1
|
|
162
173
|
if i < len(remaining) and not remaining[i].startswith("-"):
|
|
163
174
|
i += 1
|
|
@@ -195,6 +206,8 @@ def action_completer(prefix, parsed_args, **kwargs):
|
|
|
195
206
|
if not parsed_args.service:
|
|
196
207
|
return []
|
|
197
208
|
|
|
209
|
+
service = parsed_args.service
|
|
210
|
+
|
|
198
211
|
try:
|
|
199
212
|
# Create a client with minimal configuration to get operation names
|
|
200
213
|
# This doesn't require valid AWS credentials, just the service model
|
|
@@ -206,36 +219,29 @@ def action_completer(prefix, parsed_args, **kwargs):
|
|
|
206
219
|
session = botocore.session.Session()
|
|
207
220
|
|
|
208
221
|
# Check if service exists in botocore's data
|
|
209
|
-
if
|
|
222
|
+
if service not in session.get_available_services():
|
|
210
223
|
return []
|
|
211
224
|
|
|
212
225
|
# Load the service model to get operations
|
|
213
|
-
service_model = session.get_service_model(
|
|
226
|
+
service_model = session.get_service_model(service)
|
|
214
227
|
operations = list(service_model.operation_names)
|
|
215
228
|
finally:
|
|
216
229
|
# Restore AWS_PROFILE if it existed
|
|
217
230
|
if old_profile is not None:
|
|
218
231
|
os.environ["AWS_PROFILE"] = old_profile
|
|
219
232
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
except:
|
|
223
|
-
# If policy loading fails, allow common read operations
|
|
224
|
-
allowed_actions = set()
|
|
225
|
-
for op in operations:
|
|
226
|
-
if any(op.startswith(prefix) for prefix in ["Describe", "List", "Get"]):
|
|
227
|
-
allowed_actions.add(f"{parsed_args.service}:{op}")
|
|
233
|
+
# Filter operations to only show read-only ones in autocomplete
|
|
234
|
+
valid_operations = get_service_valid_operations(service, operations)
|
|
228
235
|
|
|
236
|
+
# Convert to CLI format
|
|
229
237
|
cli_operations = []
|
|
230
238
|
for op in operations:
|
|
231
|
-
if
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
kebab_case = re.sub("([a-z0-9])([A-Z])", r"\1-\2", op).lower()
|
|
235
|
-
cli_operations.append(kebab_case)
|
|
239
|
+
if op in valid_operations:
|
|
240
|
+
kebab_case = re.sub("([a-z0-9])([A-Z])", r"\1-\2", op).lower()
|
|
241
|
+
cli_operations.append(kebab_case)
|
|
236
242
|
|
|
237
243
|
matched_ops = [op for op in cli_operations if op.startswith(prefix)]
|
|
238
|
-
return sorted(
|
|
244
|
+
return sorted(matched_ops)
|
|
239
245
|
except Exception:
|
|
240
246
|
# If we can't get operations, return empty
|
|
241
247
|
return []
|
|
@@ -277,6 +283,11 @@ Examples:
|
|
|
277
283
|
) # pragma: no mutate
|
|
278
284
|
parser.add_argument("--region", help="AWS region to use for requests") # pragma: no mutate
|
|
279
285
|
parser.add_argument("--profile", help="AWS profile to use for requests") # pragma: no mutate
|
|
286
|
+
parser.add_argument(
|
|
287
|
+
"--allow-unsafe",
|
|
288
|
+
action="store_true",
|
|
289
|
+
help="Allow potentially unsafe (non-readonly) operations without prompting",
|
|
290
|
+
) # pragma: no mutate
|
|
280
291
|
|
|
281
292
|
service_arg = parser.add_argument(
|
|
282
293
|
"service", nargs="?", help="AWS service name"
|
|
@@ -315,6 +326,8 @@ Examples:
|
|
|
315
326
|
flags.append("-j")
|
|
316
327
|
if getattr(args, "keys", False):
|
|
317
328
|
flags.append("-k")
|
|
329
|
+
if getattr(args, "allow_unsafe", False):
|
|
330
|
+
flags.append("--allow-unsafe")
|
|
318
331
|
if getattr(args, "region", None):
|
|
319
332
|
flags.extend(["--region", args.region])
|
|
320
333
|
if getattr(args, "profile", None):
|
|
@@ -373,23 +386,17 @@ Examples:
|
|
|
373
386
|
value_filters = [sanitize_input(f) for f in value_filters] if value_filters else []
|
|
374
387
|
column_filters = [sanitize_input(f) for f in column_filters] if column_filters else []
|
|
375
388
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
debug_print(f"DEBUG: Policy has {len(allowed_actions)} allowed actions") # pragma: no mutate
|
|
385
|
-
|
|
386
|
-
if not validate_security(service, policy_action, allowed_actions):
|
|
387
|
-
print(f"ERROR: Action {service}:{action} not permitted by security policy", file=sys.stderr)
|
|
389
|
+
# Validate operation safety (only if we have a non-empty action)
|
|
390
|
+
if (
|
|
391
|
+
action is not None
|
|
392
|
+
and action
|
|
393
|
+
and str(action).strip() != "None"
|
|
394
|
+
and not validate_readonly(service, action, allow_unsafe=args.allow_unsafe)
|
|
395
|
+
):
|
|
396
|
+
print(f"ERROR: Operation {service}:{action} was not allowed", file=sys.stderr)
|
|
388
397
|
sys.exit(1)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
f"DEBUG: Action {service}:{policy_action} IS ALLOWED by security policy"
|
|
392
|
-
) # pragma: no mutate
|
|
398
|
+
|
|
399
|
+
debug_print(f"DEBUG: Operation {service}:{action} validated successfully") # pragma: no mutate
|
|
393
400
|
|
|
394
401
|
# Create session with region/profile if specified
|
|
395
402
|
session = create_session(region=args.region, profile=args.profile)
|