datasecops-cli 0.2.9__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.
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/CHANGELOG.md +9 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/PKG-INFO +6 -17
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/README.md +5 -16
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/pyproject.toml +1 -1
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/main.py +92 -2
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/.gitignore +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/LICENSE +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/getting-started.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/legacy.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/mcp-servers.json +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/setup.ps1 +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/setup.sh +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_config.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_main.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_models.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_version.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-05-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`datasecops setup` subcommand** — pure-Python replacement for `setup.ps1` / `setup.sh`. Discovers Snowflake connections from `~/.snowflake/connections.toml`, prompts for connection and app database, and writes `.datasecops.yml`. No platform-specific scripts needed.
|
|
10
|
+
- **Interactive profile selection** — when `profile_name` is not set (no `dbt_project.yml` and no profile in `.datasecops.yml`), the CLI prompts the user to choose from available profiles in the native app instead of silently picking the first one
|
|
11
|
+
- **Auto-setup on first run** — running `datasecops` without a `.datasecops.yml` now offers to run setup interactively instead of just exiting with an error
|
|
12
|
+
- **dbt project directory resolution from framework** — `dbt_project_dir` is now re-resolved using the framework's `project_dir` setting after native app config is loaded
|
|
13
|
+
|
|
5
14
|
## [0.2.9] - 2026-05-14
|
|
6
15
|
|
|
7
16
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasecops-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: DataSecOps Framework CLI for Snowflake Native App
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -61,30 +61,19 @@ Optional:
|
|
|
61
61
|
|
|
62
62
|
## Quick Start
|
|
63
63
|
|
|
64
|
-
### 1.
|
|
65
|
-
|
|
66
|
-
The setup script creates a virtual environment, installs the CLI, and writes your local configuration.
|
|
67
|
-
|
|
68
|
-
**Linux / macOS:**
|
|
64
|
+
### 1. Configure your project
|
|
69
65
|
|
|
70
66
|
```bash
|
|
71
|
-
|
|
67
|
+
datasecops setup
|
|
72
68
|
```
|
|
73
69
|
|
|
74
|
-
|
|
70
|
+
This prompts for your Snowflake connection name and native app database, then writes `.datasecops.yml`.
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
.\setup.ps1
|
|
78
|
-
```
|
|
72
|
+
If you skip this step, running `datasecops` will offer to run setup automatically.
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
### 2. Activate the virtual environment and run
|
|
74
|
+
### 2. Run the CLI
|
|
83
75
|
|
|
84
76
|
```bash
|
|
85
|
-
source .venv/bin/activate # Linux/macOS
|
|
86
|
-
.\.venv\Scripts\Activate.ps1 # Windows
|
|
87
|
-
|
|
88
77
|
datasecops
|
|
89
78
|
```
|
|
90
79
|
|
|
@@ -41,30 +41,19 @@ Optional:
|
|
|
41
41
|
|
|
42
42
|
## Quick Start
|
|
43
43
|
|
|
44
|
-
### 1.
|
|
45
|
-
|
|
46
|
-
The setup script creates a virtual environment, installs the CLI, and writes your local configuration.
|
|
47
|
-
|
|
48
|
-
**Linux / macOS:**
|
|
44
|
+
### 1. Configure your project
|
|
49
45
|
|
|
50
46
|
```bash
|
|
51
|
-
|
|
47
|
+
datasecops setup
|
|
52
48
|
```
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
This prompts for your Snowflake connection name and native app database, then writes `.datasecops.yml`.
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
.\setup.ps1
|
|
58
|
-
```
|
|
52
|
+
If you skip this step, running `datasecops` will offer to run setup automatically.
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
### 2. Activate the virtual environment and run
|
|
54
|
+
### 2. Run the CLI
|
|
63
55
|
|
|
64
56
|
```bash
|
|
65
|
-
source .venv/bin/activate # Linux/macOS
|
|
66
|
-
.\.venv\Scripts\Activate.ps1 # Windows
|
|
67
|
-
|
|
68
57
|
datasecops
|
|
69
58
|
```
|
|
70
59
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import os
|
|
2
3
|
import shutil
|
|
4
|
+
import subprocess
|
|
3
5
|
import sys
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
|
|
@@ -18,6 +20,7 @@ from datasecops_cli.utilities.display import (
|
|
|
18
20
|
clear, section_header, menu_option, get_input_number,
|
|
19
21
|
info_line, error_line, success_line
|
|
20
22
|
)
|
|
23
|
+
from datasecops_cli.utilities.yaml_utils import write_datasecops_config
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
DOWNLOAD_ITEMS = [
|
|
@@ -33,6 +36,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
33
36
|
)
|
|
34
37
|
sub = parser.add_subparsers(dest="command")
|
|
35
38
|
|
|
39
|
+
sub.add_parser("setup", help="Configure .datasecops.yml for this project")
|
|
36
40
|
sub.add_parser("bootstrap", help="Set up a new dbt project with all framework config")
|
|
37
41
|
|
|
38
42
|
dl = sub.add_parser("download", help="Download framework config non-interactively (for CI/CD)")
|
|
@@ -53,7 +57,9 @@ def main():
|
|
|
53
57
|
|
|
54
58
|
config = Config()
|
|
55
59
|
|
|
56
|
-
if args.command == "
|
|
60
|
+
if args.command == "setup":
|
|
61
|
+
_run_setup(config.project_dir)
|
|
62
|
+
elif args.command == "bootstrap":
|
|
57
63
|
_run_bootstrap(config)
|
|
58
64
|
elif args.command == "download":
|
|
59
65
|
_run_download(config, args.items)
|
|
@@ -61,16 +67,82 @@ def main():
|
|
|
61
67
|
_run_interactive(config)
|
|
62
68
|
|
|
63
69
|
|
|
70
|
+
def _run_setup(project_dir: Path):
|
|
71
|
+
"""Interactive setup: create .datasecops.yml by prompting for connection and app database."""
|
|
72
|
+
from datasecops_cli.utilities.display import (
|
|
73
|
+
get_input_string, select_from_list
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
section_header("DataSecOps Setup")
|
|
77
|
+
|
|
78
|
+
# --- Discover Snowflake connections ---
|
|
79
|
+
connections_file = Path.home() / ".snowflake" / "connections.toml"
|
|
80
|
+
connection_names = []
|
|
81
|
+
if connections_file.exists():
|
|
82
|
+
import re
|
|
83
|
+
for line in connections_file.read_text(encoding="utf-8").splitlines():
|
|
84
|
+
m = re.match(r"^\[([^\]]+)\]", line)
|
|
85
|
+
if m:
|
|
86
|
+
connection_names.append(m.group(1))
|
|
87
|
+
|
|
88
|
+
if connection_names:
|
|
89
|
+
connection_name = select_from_list(connection_names, "Snowflake connection", add_back=False)
|
|
90
|
+
else:
|
|
91
|
+
info_line("No connections found in ~/.snowflake/connections.toml")
|
|
92
|
+
connection_name = get_input_string("Enter Snowflake connection name: ")
|
|
93
|
+
|
|
94
|
+
if not connection_name:
|
|
95
|
+
error_line("Connection name is required")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
|
|
98
|
+
# --- App database ---
|
|
99
|
+
app_database = get_input_string(
|
|
100
|
+
"Enter native app database name (e.g. DATA_ENGINEERS_DATASECOPS_FRAMEWORK): "
|
|
101
|
+
)
|
|
102
|
+
if not app_database:
|
|
103
|
+
error_line("App database name is required")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
106
|
+
# --- Write config ---
|
|
107
|
+
config_data = {
|
|
108
|
+
"connection_name": connection_name,
|
|
109
|
+
"app_database": app_database,
|
|
110
|
+
"profile_name": "",
|
|
111
|
+
}
|
|
112
|
+
write_datasecops_config(project_dir, config_data)
|
|
113
|
+
success_line(f".datasecops.yml written to {project_dir / '.datasecops.yml'}")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _offer_setup(project_dir: Path):
|
|
117
|
+
"""Offer to run setup when .datasecops.yml is missing."""
|
|
118
|
+
from datasecops_cli.utilities.display import get_input_true_false
|
|
119
|
+
|
|
120
|
+
info_line("No .datasecops.yml found in this project.")
|
|
121
|
+
if not get_input_true_false("Run setup now?"):
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
_run_setup(project_dir)
|
|
125
|
+
|
|
126
|
+
|
|
64
127
|
def _connect_and_load(config: Config) -> SnowflakeService:
|
|
65
128
|
"""Load config, connect to Snowflake, and load native app settings.
|
|
66
129
|
|
|
67
130
|
Returns the connected SnowflakeService, or calls sys.exit on failure.
|
|
131
|
+
If .datasecops.yml is missing and a setup script exists, offers to run it.
|
|
68
132
|
"""
|
|
69
133
|
if not config.load():
|
|
70
|
-
|
|
134
|
+
# Check if the failure is due to missing .datasecops.yml
|
|
135
|
+
if not (config.project_dir / ".datasecops.yml").exists():
|
|
136
|
+
_offer_setup(config.project_dir)
|
|
137
|
+
# Retry after setup
|
|
138
|
+
if not config.load():
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
else:
|
|
141
|
+
sys.exit(1)
|
|
71
142
|
|
|
72
143
|
sf_config = config.datasecops
|
|
73
144
|
sf_service = SnowflakeService(sf_config)
|
|
145
|
+
profile_was_set = bool(config.profile_name)
|
|
74
146
|
|
|
75
147
|
try:
|
|
76
148
|
info_line(f"Connecting to Snowflake ({sf_config.connection_name})...")
|
|
@@ -83,6 +155,24 @@ def _connect_and_load(config: Config) -> SnowflakeService:
|
|
|
83
155
|
info_line("Loading framework configuration...")
|
|
84
156
|
config.load_from_native_app(sf_service)
|
|
85
157
|
|
|
158
|
+
# If profile_name was not explicitly set and there are multiple profiles, ask the user
|
|
159
|
+
if not profile_was_set and len(config.all_profiles) > 1:
|
|
160
|
+
from datasecops_cli.utilities.display import select_from_list
|
|
161
|
+
names = [p.profile_name for p in config.all_profiles]
|
|
162
|
+
info_line(f"Found {len(names)} project profiles:")
|
|
163
|
+
selected = select_from_list(names, "profile", add_back=False)
|
|
164
|
+
config.profile = None
|
|
165
|
+
for p in config.all_profiles:
|
|
166
|
+
if p.profile_name == selected:
|
|
167
|
+
config.profile = p
|
|
168
|
+
config.profile_name = selected
|
|
169
|
+
break
|
|
170
|
+
|
|
171
|
+
# Re-resolve dbt_project_dir using framework project_dir setting
|
|
172
|
+
framework_dbt_dir = config.project_dir / config.project_settings.project_dir
|
|
173
|
+
if (framework_dbt_dir / "dbt_project.yml").exists():
|
|
174
|
+
config.dbt_project_dir = framework_dbt_dir
|
|
175
|
+
|
|
86
176
|
if not config.profile:
|
|
87
177
|
error_line(f"Profile '{config.profile_name}' not found in native app")
|
|
88
178
|
sf_service.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|