datasecops-cli 0.2.9__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.
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/CHANGELOG.md +16 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/PKG-INFO +6 -17
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/README.md +5 -16
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/pyproject.toml +1 -1
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/main.py +170 -7
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/menus/downloads.py +1 -1
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/models/project_config.py +1 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/download_service.py +6 -2
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/snowflake_service.py +4 -1
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/test_main.py +2 -2
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/.gitignore +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/LICENSE +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/docs/getting-started.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/docs/legacy.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/mcp-servers.json +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/setup.ps1 +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/setup.sh +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/__init__.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/test_config.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/test_models.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/test_version.py +0 -0
- {datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
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
|
+
- **Prerequisite checks during setup** — checks for `dbtf`, `gh`, and `az` on PATH and verifies authentication status (`gh auth status`, `az account show`)
|
|
11
|
+
- **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. Selected profile is saved back to `.datasecops.yml`.
|
|
12
|
+
- **Auto-setup on first run** — running `datasecops` without a `.datasecops.yml` now offers to run setup interactively instead of just exiting with an error
|
|
13
|
+
- **`--connection`, `--app-database`, `--profile` flags on `download`** — allows CI/CD pipelines to run without a committed `.datasecops.yml` by passing connection details as CLI flags (e.g. `datasecops download sqlfluff -c ci -d MY_APP_DB -p my_project`)
|
|
14
|
+
- **Default app database value** — setup now defaults to `DATA_ENGINEERS_DATASECOPS_FRAMEWORK` (press Enter to accept)
|
|
15
|
+
- **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
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Non-interactive mode safety** — `_connect_and_load` accepts an `interactive` flag; the `download` subcommand passes `interactive=False` so it never prompts (missing config exits 1, multiple profiles use the first one silently)
|
|
20
|
+
|
|
5
21
|
## [0.2.9] - 2026-05-14
|
|
6
22
|
|
|
7
23
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasecops-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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)")
|
|
@@ -42,6 +46,18 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
42
46
|
choices=DOWNLOAD_ITEMS,
|
|
43
47
|
help="Item(s) to download/install: sqlfluff, pipelines, packages, macros, install-sqlfluff, install-dbt, or all",
|
|
44
48
|
)
|
|
49
|
+
dl.add_argument(
|
|
50
|
+
"--connection", "-c",
|
|
51
|
+
help="Snowflake connection name (overrides .datasecops.yml)",
|
|
52
|
+
)
|
|
53
|
+
dl.add_argument(
|
|
54
|
+
"--app-database", "-d",
|
|
55
|
+
help="Native app database name (overrides .datasecops.yml)",
|
|
56
|
+
)
|
|
57
|
+
dl.add_argument(
|
|
58
|
+
"--profile", "-p",
|
|
59
|
+
help="Project profile name (overrides .datasecops.yml)",
|
|
60
|
+
)
|
|
45
61
|
|
|
46
62
|
return parser
|
|
47
63
|
|
|
@@ -53,24 +69,121 @@ def main():
|
|
|
53
69
|
|
|
54
70
|
config = Config()
|
|
55
71
|
|
|
56
|
-
if args.command == "
|
|
72
|
+
if args.command == "setup":
|
|
73
|
+
_run_setup(config.project_dir)
|
|
74
|
+
elif args.command == "bootstrap":
|
|
57
75
|
_run_bootstrap(config)
|
|
58
76
|
elif args.command == "download":
|
|
59
|
-
_run_download(config, args.items
|
|
77
|
+
_run_download(config, args.items,
|
|
78
|
+
connection=args.connection, app_database=args.app_database,
|
|
79
|
+
profile=args.profile)
|
|
60
80
|
else:
|
|
61
81
|
_run_interactive(config)
|
|
62
82
|
|
|
63
83
|
|
|
64
|
-
def
|
|
84
|
+
def _run_setup(project_dir: Path):
|
|
85
|
+
"""Interactive setup: create .datasecops.yml by prompting for connection and app database."""
|
|
86
|
+
from datasecops_cli.utilities.display import (
|
|
87
|
+
get_input_string, get_input_string_or_default, select_from_list, warning_line
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
section_header("DataSecOps Setup")
|
|
91
|
+
|
|
92
|
+
# --- Check prerequisites ---
|
|
93
|
+
info_line("Checking prerequisites...")
|
|
94
|
+
|
|
95
|
+
if shutil.which("dbtf"):
|
|
96
|
+
success_line("dbt Fusion (dbtf) found")
|
|
97
|
+
else:
|
|
98
|
+
warning_line("dbt Fusion (dbtf) not found — dbt commands will not work")
|
|
99
|
+
warning_line(" Install: https://docs.getdbt.com/docs/core/installation")
|
|
100
|
+
|
|
101
|
+
if shutil.which("gh"):
|
|
102
|
+
result = subprocess.run(["gh", "auth", "status"], capture_output=True, text=True)
|
|
103
|
+
if result.returncode == 0:
|
|
104
|
+
success_line("GitHub CLI (gh) found and authenticated")
|
|
105
|
+
else:
|
|
106
|
+
warning_line("GitHub CLI (gh) found but not logged in — run: gh auth login")
|
|
107
|
+
else:
|
|
108
|
+
warning_line("GitHub CLI (gh) not found — install from https://cli.github.com if using GitHub")
|
|
109
|
+
|
|
110
|
+
if shutil.which("az"):
|
|
111
|
+
result = subprocess.run(["az", "account", "show"], capture_output=True, text=True)
|
|
112
|
+
if result.returncode == 0:
|
|
113
|
+
success_line("Azure CLI (az) found and authenticated")
|
|
114
|
+
else:
|
|
115
|
+
warning_line("Azure CLI (az) found but not logged in — run: az login")
|
|
116
|
+
else:
|
|
117
|
+
warning_line("Azure CLI (az) not found — install from https://aka.ms/installazurecli if using Azure DevOps")
|
|
118
|
+
|
|
119
|
+
info_line("")
|
|
120
|
+
|
|
121
|
+
# --- Discover Snowflake connections ---
|
|
122
|
+
connections_file = Path.home() / ".snowflake" / "connections.toml"
|
|
123
|
+
connection_names = []
|
|
124
|
+
if connections_file.exists():
|
|
125
|
+
import re
|
|
126
|
+
for line in connections_file.read_text(encoding="utf-8").splitlines():
|
|
127
|
+
m = re.match(r"^\[([^\]]+)\]", line)
|
|
128
|
+
if m:
|
|
129
|
+
connection_names.append(m.group(1))
|
|
130
|
+
|
|
131
|
+
if connection_names:
|
|
132
|
+
connection_name = select_from_list(connection_names, "Snowflake connection", add_back=False)
|
|
133
|
+
else:
|
|
134
|
+
info_line("No connections found in ~/.snowflake/connections.toml")
|
|
135
|
+
connection_name = get_input_string("Enter Snowflake connection name: ")
|
|
136
|
+
|
|
137
|
+
if not connection_name:
|
|
138
|
+
error_line("Connection name is required")
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
# --- App database ---
|
|
142
|
+
app_database = get_input_string_or_default(
|
|
143
|
+
"Enter native app database name",
|
|
144
|
+
"DATA_ENGINEERS_DATASECOPS_FRAMEWORK"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# --- Write config ---
|
|
148
|
+
config_data = {
|
|
149
|
+
"connection_name": connection_name,
|
|
150
|
+
"app_database": app_database,
|
|
151
|
+
"profile_name": "",
|
|
152
|
+
}
|
|
153
|
+
write_datasecops_config(project_dir, config_data)
|
|
154
|
+
success_line(f".datasecops.yml written to {project_dir / '.datasecops.yml'}")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _offer_setup(project_dir: Path):
|
|
158
|
+
"""Offer to run setup when .datasecops.yml is missing."""
|
|
159
|
+
from datasecops_cli.utilities.display import get_input_true_false
|
|
160
|
+
|
|
161
|
+
info_line("No .datasecops.yml found in this project.")
|
|
162
|
+
if not get_input_true_false("Run setup now?"):
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
|
|
165
|
+
_run_setup(project_dir)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _connect_and_load(config: Config, interactive: bool = True) -> SnowflakeService:
|
|
65
169
|
"""Load config, connect to Snowflake, and load native app settings.
|
|
66
170
|
|
|
67
171
|
Returns the connected SnowflakeService, or calls sys.exit on failure.
|
|
172
|
+
If .datasecops.yml is missing and interactive is True, offers to run setup.
|
|
68
173
|
"""
|
|
69
174
|
if not config.load():
|
|
70
|
-
|
|
175
|
+
# Check if the failure is due to missing .datasecops.yml
|
|
176
|
+
if not (config.project_dir / ".datasecops.yml").exists() and interactive:
|
|
177
|
+
_offer_setup(config.project_dir)
|
|
178
|
+
# Retry after setup
|
|
179
|
+
if not config.load():
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
else:
|
|
182
|
+
sys.exit(1)
|
|
71
183
|
|
|
72
184
|
sf_config = config.datasecops
|
|
73
185
|
sf_service = SnowflakeService(sf_config)
|
|
186
|
+
profile_was_set = bool(config.profile_name)
|
|
74
187
|
|
|
75
188
|
try:
|
|
76
189
|
info_line(f"Connecting to Snowflake ({sf_config.connection_name})...")
|
|
@@ -83,6 +196,37 @@ def _connect_and_load(config: Config) -> SnowflakeService:
|
|
|
83
196
|
info_line("Loading framework configuration...")
|
|
84
197
|
config.load_from_native_app(sf_service)
|
|
85
198
|
|
|
199
|
+
# If profile_name was not explicitly set and there are multiple profiles, ask the user
|
|
200
|
+
if not profile_was_set and len(config.all_profiles) > 1:
|
|
201
|
+
if interactive:
|
|
202
|
+
from datasecops_cli.utilities.display import select_from_list
|
|
203
|
+
names = [p.profile_name for p in config.all_profiles]
|
|
204
|
+
info_line(f"Found {len(names)} project profiles:")
|
|
205
|
+
selected = select_from_list(names, "profile", add_back=False)
|
|
206
|
+
config.profile = None
|
|
207
|
+
for p in config.all_profiles:
|
|
208
|
+
if p.profile_name == selected:
|
|
209
|
+
config.profile = p
|
|
210
|
+
config.profile_name = selected
|
|
211
|
+
break
|
|
212
|
+
# Save the selected profile back to .datasecops.yml
|
|
213
|
+
if config.profile:
|
|
214
|
+
config_data = {
|
|
215
|
+
"connection_name": config.datasecops.connection_name,
|
|
216
|
+
"app_database": config.datasecops.app_database,
|
|
217
|
+
"profile_name": config.profile_name,
|
|
218
|
+
}
|
|
219
|
+
write_datasecops_config(config.project_dir, config_data)
|
|
220
|
+
success_line(f"Saved profile_name '{config.profile_name}' to .datasecops.yml")
|
|
221
|
+
else:
|
|
222
|
+
# Non-interactive: use the auto-selected first profile
|
|
223
|
+
info_line(f"Using default profile: {config.profile_name}")
|
|
224
|
+
|
|
225
|
+
# Re-resolve dbt_project_dir using framework project_dir setting
|
|
226
|
+
framework_dbt_dir = config.project_dir / config.project_settings.project_dir
|
|
227
|
+
if (framework_dbt_dir / "dbt_project.yml").exists():
|
|
228
|
+
config.dbt_project_dir = framework_dbt_dir
|
|
229
|
+
|
|
86
230
|
if not config.profile:
|
|
87
231
|
error_line(f"Profile '{config.profile_name}' not found in native app")
|
|
88
232
|
sf_service.close()
|
|
@@ -126,9 +270,28 @@ def _run_interactive(config: Config):
|
|
|
126
270
|
sf_service.close()
|
|
127
271
|
|
|
128
272
|
|
|
129
|
-
def _run_download(config: Config, items: list[str]
|
|
273
|
+
def _run_download(config: Config, items: list[str],
|
|
274
|
+
connection: str = None, app_database: str = None,
|
|
275
|
+
profile: str = None):
|
|
130
276
|
"""Run non-interactive downloads for CI/CD pipelines."""
|
|
131
|
-
|
|
277
|
+
# If CLI flags provided, write/override .datasecops.yml on the fly
|
|
278
|
+
if connection or app_database:
|
|
279
|
+
from datasecops_cli.utilities.yaml_utils import read_datasecops_config
|
|
280
|
+
existing = read_datasecops_config(config.project_dir) or {}
|
|
281
|
+
config_data = {
|
|
282
|
+
"connection_name": connection or existing.get("connection_name", ""),
|
|
283
|
+
"app_database": app_database or existing.get("app_database", ""),
|
|
284
|
+
"profile_name": profile or existing.get("profile_name", ""),
|
|
285
|
+
}
|
|
286
|
+
write_datasecops_config(config.project_dir, config_data)
|
|
287
|
+
info_line(f"Wrote .datasecops.yml (connection={config_data['connection_name']}, app_database={config_data['app_database']})")
|
|
288
|
+
elif profile:
|
|
289
|
+
from datasecops_cli.utilities.yaml_utils import read_datasecops_config
|
|
290
|
+
existing = read_datasecops_config(config.project_dir) or {}
|
|
291
|
+
existing["profile_name"] = profile
|
|
292
|
+
write_datasecops_config(config.project_dir, existing)
|
|
293
|
+
|
|
294
|
+
sf_service = _connect_and_load(config, interactive=False)
|
|
132
295
|
|
|
133
296
|
try:
|
|
134
297
|
download_service = DownloadService(sf_service, config.project_dir)
|
|
@@ -150,7 +313,7 @@ def _run_download(config: Config, items: list[str]):
|
|
|
150
313
|
elif item == "pipelines":
|
|
151
314
|
platform = config.source_control.source_control_platform.lower()
|
|
152
315
|
info_line(f"Platform: {platform}")
|
|
153
|
-
if not download_service.download_pipelines(platform=platform):
|
|
316
|
+
if not download_service.download_pipelines(platform=platform, profile_name=config.profile_name):
|
|
154
317
|
failed = True
|
|
155
318
|
elif item == "packages":
|
|
156
319
|
if not download_service.download_dbt_packages(config.dbt_project_dir):
|
|
@@ -38,7 +38,7 @@ class DownloadsMenu:
|
|
|
38
38
|
display_action_header("Download Pipeline Files")
|
|
39
39
|
platform = self.source_control.source_control_platform.lower() if self.source_control else "github"
|
|
40
40
|
info_line(f"Platform: {platform}")
|
|
41
|
-
self.downloads.download_pipelines(platform=platform)
|
|
41
|
+
self.downloads.download_pipelines(platform=platform, profile_name=self.profile_name)
|
|
42
42
|
complete_action()
|
|
43
43
|
elif option == 3:
|
|
44
44
|
display_action_header("Download dbt Packages")
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/download_service.py
RENAMED
|
@@ -220,9 +220,13 @@ class DownloadService:
|
|
|
220
220
|
error_line("No active dbt versions found in framework configuration")
|
|
221
221
|
return dbt_packages
|
|
222
222
|
|
|
223
|
-
def download_pipelines(self, platform: str = "github") -> bool:
|
|
223
|
+
def download_pipelines(self, platform: str = "github", profile_name: str = "") -> bool:
|
|
224
224
|
info_line(f"Downloading {platform} pipeline configurations...")
|
|
225
|
-
|
|
225
|
+
if profile_name:
|
|
226
|
+
info_line(f"Filtering pipelines for profile: {profile_name}")
|
|
227
|
+
raw = self.sf.get_pipelines_for_profile(profile_name)
|
|
228
|
+
else:
|
|
229
|
+
raw = self.sf.get_framework_config("PIPELINES")
|
|
226
230
|
if not raw:
|
|
227
231
|
error_line("No pipeline configuration found in native app")
|
|
228
232
|
return False
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
@@ -59,7 +59,10 @@ class SnowflakeService:
|
|
|
59
59
|
|
|
60
60
|
def get_project_profiles(self) -> Optional[list[dict]]:
|
|
61
61
|
return self.call_procedure("get_project_profiles")
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
def get_pipelines_for_profile(self, profile_name: str) -> Optional[dict]:
|
|
64
|
+
return self.call_procedure("get_pipelines_for_profile", profile_name)
|
|
65
|
+
|
|
63
66
|
def get_account_info(self) -> dict:
|
|
64
67
|
rows = self.execute_query("SELECT CURRENT_ACCOUNT_NAME() as account, CURRENT_USER() as user_name")
|
|
65
68
|
return rows[0] if rows else {}
|
|
@@ -102,7 +102,7 @@ class TestRunDownload:
|
|
|
102
102
|
_run_download(config, ["pipelines"])
|
|
103
103
|
|
|
104
104
|
assert exc.value.code == 0
|
|
105
|
-
mock_ds.download_pipelines.assert_called_once_with(platform="github")
|
|
105
|
+
mock_ds.download_pipelines.assert_called_once_with(platform="github", profile_name="test_profile")
|
|
106
106
|
|
|
107
107
|
@patch("datasecops_cli.main._connect_and_load")
|
|
108
108
|
def test_download_packages(self, mock_connect, tmp_path):
|
|
@@ -219,7 +219,7 @@ class TestRunDownload:
|
|
|
219
219
|
_run_download(config, ["pipelines"])
|
|
220
220
|
|
|
221
221
|
assert exc.value.code == 0
|
|
222
|
-
mock_ds.download_pipelines.assert_called_once_with(platform="azuredevops")
|
|
222
|
+
mock_ds.download_pipelines.assert_called_once_with(platform="azuredevops", profile_name="test_profile")
|
|
223
223
|
|
|
224
224
|
@patch("datasecops_cli.main._connect_and_load")
|
|
225
225
|
def test_install_sqlfluff(self, mock_connect, tmp_path):
|
|
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.1}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.9 → datasecops_cli-0.3.1}/src/datasecops_cli/services/linting_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
|