Skip to content

Commit c203821

Browse files
committed
updating docs"
1 parent 5048ede commit c203821

10 files changed

Lines changed: 86 additions & 60 deletions

File tree

devolv/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.1.9"

devolv/cli.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import typer
2-
from devolv.iam.validator.cli import app as validate_app
2+
from devolv import __version__
3+
from devolv.iam.validator.cli import validate
34

45
app = typer.Typer(help="Devolv CLI - Modular DevOps Toolkit")
5-
app.add_typer(validate_app, name="validate")
66

7-
if __name__ == "__main__": # pragma: no cover
8-
app()
7+
app.command("validate")(validate)
98

10-
@app.command("version")
11-
def version():
12-
"""Print CLI version."""
13-
typer.echo("Devolv v0.1.0")
9+
@app.callback()
10+
def main(
11+
version: bool = typer.Option(
12+
None,
13+
"--version",
14+
"-v",
15+
help="Show Devolv version and exit.",
16+
callback=lambda value: _print_version(value),
17+
is_eager=True,
18+
)
19+
):
20+
pass
21+
22+
def _print_version(value: bool):
23+
if value:
24+
typer.echo(f"Devolv version: {__version__}")
25+
raise typer.Exit()

devolv/iam/validator/cli.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
11
import typer
22
import os
3+
from typer import Exit
34
from devolv.iam.validator.core import validate_policy_file
45
from devolv.iam.validator.folder import validate_policy_folder
5-
app = typer.Typer(help="IAM Policy Validator CLI")
66

7-
@app.command("file")
8-
def validate_file(path: str):
9-
"""
10-
Validate an AWS IAM policy file (JSON or YAML).
11-
"""
7+
def validate(
8+
path: str,
9+
json_output: bool = typer.Option(False, "--json", help="Output findings in JSON format"),
10+
quiet: bool = typer.Option(False, "--quiet", help="Suppress debug logs"),
11+
):
1212
if not os.path.exists(path):
13-
typer.secho(f"❌ File not found: {path}", fg=typer.colors.RED)
14-
raise typer.Exit(code=1)
13+
typer.secho(f"❌ Path not found: {path}", fg=typer.colors.RED)
14+
raise Exit(code=1)
1515

16-
try:
16+
if os.path.isfile(path):
1717
findings = validate_policy_file(path)
1818
if not findings:
1919
typer.secho("✅ Policy is valid and passed all checks.", fg=typer.colors.GREEN)
20-
else:
21-
for finding in findings:
22-
typer.secho(f"❌ {finding['level'].upper()}: {finding['message']}", fg=typer.colors.RED)
23-
raise typer.Exit(code=1)
24-
except Exception as e:
25-
typer.secho(f"❌ Error: {str(e)}", fg=typer.colors.RED)
26-
raise typer.Exit(code=1)
20+
raise Exit(code=0)
21+
for finding in findings:
22+
typer.secho(f"❌ {finding['level'].upper()}: {finding['message']}", fg=typer.colors.RED)
23+
raise Exit(code=1)
2724

25+
elif os.path.isdir(path):
26+
findings = validate_policy_folder(path)
27+
if not findings:
28+
typer.secho("✅ All policies passed validation.", fg=typer.colors.GREEN)
29+
raise Exit(code=0)
30+
for finding in findings:
31+
typer.secho(f"❌ {finding['level'].upper()}: {finding['message']}", fg=typer.colors.RED)
32+
if any(f["level"] == "error" for f in findings):
33+
raise Exit(code=1)
34+
raise Exit(code=0)
2835

29-
@app.command("folder")
30-
def validate_folder(path: str):
31-
"""
32-
Recursively validate all IAM policy files in a folder.
33-
"""
34-
exit_code = validate_policy_folder(path)
35-
raise typer.Exit(code=exit_code)
36-
36+
else:
37+
typer.secho(f"❌ Unsupported path type: {path}", fg=typer.colors.RED)
38+
raise Exit(code=1)

devolv/iam/validator/core.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ def validate_policy_file(path: str):
2020
for rule in RULES:
2121
result = rule["check"](data)
2222
if result:
23-
findings.append({
23+
finding = {
2424
"id": rule["id"],
2525
"level": rule["level"],
2626
"message": result
27-
})
27+
}
28+
findings.append(finding)
2829
return findings
30+
31+

devolv/iam/validator/folder.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
from pathlib import Path
2-
from .core import validate_policy_file # ✅ reusing logic from core.py
2+
from .core import validate_policy_file
33

4-
def validate_policy_folder(folder_path: str) -> int:
4+
def validate_policy_folder(folder_path: str) -> list:
55
folder = Path(folder_path)
66
if not folder.exists() or not folder.is_dir():
77
print(f"❌ Folder '{folder_path}' not found or is not a directory.")
8-
return 1
8+
return [{"level": "error", "message": f"Folder '{folder_path}' not found or invalid."}]
99

1010
policy_files = list(folder.rglob("*.json")) + list(folder.rglob("*.yaml")) + list(folder.rglob("*.yml"))
1111
if not policy_files:
1212
print(f"⚠️ No policy files found in '{folder_path}'.")
13-
return 1
13+
return [{"level": "warning", "message": f"No policy files found in '{folder_path}'."}]
1414

15-
has_errors = False
15+
all_findings = []
1616
for file in policy_files:
1717
print(f"\n🔍 Validating: {file}")
1818
try:
19-
findings = validate_policy_file(str(file)) # ✅ core logic used here
19+
findings = validate_policy_file(str(file))
2020
if not findings:
2121
print("✅ Policy is valid.")
2222
else:
23-
has_errors = True
23+
all_findings.extend(findings)
2424
for f in findings:
2525
print(f"❌ {f['level'].upper()}: {f['message']}")
2626
except Exception as e:
27-
has_errors = True
27+
all_findings.append({"level": "error", "message": f"{file.name} failed: {str(e)}"})
2828
print(f"❌ ERROR parsing {file.name}: {str(e)}")
2929

30-
return 1 if has_errors else 0
30+
return all_findings

docs/index.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ pip install devolv
2525

2626
| Command | Status | Description |
2727
|--------------------------|----------|----------------------------------------------------|
28-
| `devolv validate file` | ✅ Ready | Validate a single AWS IAM JSON/YAML policy file |
29-
| `devolv validate folder` | ✅ Ready | Validate all policies inside a folder |
28+
| `devolv validate file` | ✅ Ready | Validate a AWS IAM JSON/YAML policy file/fodler |
3029
| `devolv scan` | 🔜 WIP | Scan AWS accounts for live misconfigurations |
3130
| `devolv generate` | 🔜 WIP | AI/Rule-based IAM policy generation |
3231
| `devolv etl` | 🔜 WIP | Transform/clean policies for IAM pipelines |

docs/validator.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ Statically validate AWS IAM policy files to detect:
2626
### 🔹 Validate a Single File
2727

2828
```bash
29-
devolv validate file path/to/policy.json
29+
devolv validate path/to/policy.json
3030
```
3131

3232
### 🔹 Validate a Folder
3333

3434
```bash
35-
devolv validate folder path/to/folder/
35+
devolv validate path/to/folder/
3636
```
3737

3838
> Scans all `.json`, `.yaml`, and `.yml` files in the folder recursively.

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "devolv"
7-
version = "0.1.6"
87
description = "Modular DevOps Toolkit with IAM policy validation"
98
readme = "README.md"
109
requires-python = ">=3.8"
11-
license = { text = "MIT" }
10+
license = "MIT"
1211
authors = [{ name = "Devolv Dev", email = "devolv.dev@gmail.com" }]
1312
dependencies = ["typer>=0.9", "PyYAML"]
13+
dynamic = ["version"] # ✔️ Only declare version here
1414

1515
[project.optional-dependencies]
1616
dev = ["pytest"]
1717

1818
[project.scripts]
1919
devolv = "devolv.cli:app"
20+
21+
[tool.setuptools.dynamic]
22+
version = { attr = "devolv.__version__" }

tests/__init__.py

Whitespace-only changes.

tests/test_cli.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,33 @@ def make_policy_file(policy_dict):
1717
def test_validate_file_success():
1818
path = make_policy_file({
1919
"Version": "2012-10-17",
20-
"Statement": [{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*"}]
20+
"Statement": [{
21+
"Effect": "Allow",
22+
"Action": "s3:GetObject",
23+
"Resource": "arn:aws:s3:::my-bucket/my-object.txt"
24+
25+
}]
2126
})
22-
result = runner.invoke(app, ["validate", "file", path])
27+
result = runner.invoke(app, ["validate", path])
2328
assert result.exit_code == 0
24-
assert "✅" in result.output
2529
os.remove(path)
2630

31+
32+
2733
def test_validate_file_error():
2834
path = make_policy_file({
2935
"Version": "2012-10-17",
3036
"Statement": [{"Effect": "Allow", "Action": "*", "Resource": "*"}]
3137
})
32-
result = runner.invoke(app, ["validate", "file", path])
38+
result = runner.invoke(app, ["validate", path])
3339
assert result.exit_code == 1
3440
assert "❌" in result.output
3541
os.remove(path)
3642

3743
def test_validate_file_missing():
38-
result = runner.invoke(app, ["validate", "file", "no_such_file.json"])
44+
result = runner.invoke(app, ["validate", "no_such_file.json"])
3945
assert result.exit_code == 1
40-
assert "File not found" in result.output
46+
assert "not found" in result.output
4147

4248
def test_validate_folder_all_valid(tmp_path):
4349
valid = {
@@ -48,7 +54,7 @@ def test_validate_folder_all_valid(tmp_path):
4854
path = tmp_path / f"v{i}.json"
4955
path.write_text(json.dumps(valid))
5056

51-
result = runner.invoke(app, ["validate", "folder", str(tmp_path)])
57+
result = runner.invoke(app, ["validate", str(tmp_path)])
5258
assert result.exit_code == 0
5359
assert "✅" in result.output
5460

@@ -64,7 +70,7 @@ def test_validate_folder_with_errors(tmp_path):
6470
(tmp_path / "good.json").write_text(json.dumps(good))
6571
(tmp_path / "bad.json").write_text(json.dumps(bad))
6672

67-
result = runner.invoke(app, ["validate", "folder", str(tmp_path)])
73+
result = runner.invoke(app, ["validate", str(tmp_path)])
6874
assert result.exit_code == 1
6975
assert "❌" in result.output
7076

@@ -76,9 +82,9 @@ def test_cli_help():
7682
def test_cli_root_help():
7783
result = runner.invoke(app, ["--help"])
7884
assert result.exit_code == 0
79-
assert "Devolv CLI - Modular DevOps Toolkit" in result.output
85+
assert "Modular DevOps Toolkit" in result.output
8086

8187
def test_cli_version():
82-
result = runner.invoke(app, ["version"])
88+
result = runner.invoke(app, ["--version"])
8389
assert result.exit_code == 0
84-
assert "v0.1.0" in result.output
90+
assert "0.1." in result.output # Adjust if dynamic version

0 commit comments

Comments
 (0)