datasecops-cli 0.2.8__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.8 → datasecops_cli-0.3.0}/CHANGELOG.md +15 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/PKG-INFO +15 -19
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/README.md +14 -18
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/pyproject.toml +1 -1
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/main.py +114 -5
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/test_main.py +103 -8
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/.gitignore +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/LICENSE +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/docs/getting-started.md +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/docs/legacy.md +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/mcp-servers.json +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/setup.ps1 +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/setup.sh +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/__init__.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/test_config.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/test_models.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/test_version.py +0 -0
- {datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
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
|
+
|
|
14
|
+
## [0.2.9] - 2026-05-14
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`install-sqlfluff` and `install-dbt` download items** — `datasecops download install-sqlfluff` and `datasecops download install-dbt` fetch framework-pinned package versions from the native app and install them via `uv pip install`. `datasecops download all` now includes both installs.
|
|
19
|
+
|
|
5
20
|
## [0.2.8] - 2026-05-14
|
|
6
21
|
|
|
7
22
|
### 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
|
-
|
|
75
|
-
|
|
76
|
-
```powershell
|
|
77
|
-
.\setup.ps1
|
|
78
|
-
```
|
|
70
|
+
This prompts for your Snowflake connection name and native app database, then writes `.datasecops.yml`.
|
|
79
71
|
|
|
80
|
-
|
|
72
|
+
If you skip this step, running `datasecops` will offer to run setup automatically.
|
|
81
73
|
|
|
82
|
-
### 2.
|
|
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
|
|
|
@@ -106,11 +95,18 @@ datasecops download sqlfluff
|
|
|
106
95
|
datasecops download sqlfluff packages
|
|
107
96
|
datasecops download pipelines macros
|
|
108
97
|
|
|
109
|
-
#
|
|
98
|
+
# Install framework-pinned package versions
|
|
99
|
+
datasecops download install-sqlfluff
|
|
100
|
+
datasecops download install-dbt
|
|
101
|
+
|
|
102
|
+
# Download config and install packages together
|
|
103
|
+
datasecops download sqlfluff install-sqlfluff
|
|
104
|
+
|
|
105
|
+
# Download and install everything
|
|
110
106
|
datasecops download all
|
|
111
107
|
```
|
|
112
108
|
|
|
113
|
-
Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `all`
|
|
109
|
+
Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `install-sqlfluff`, `install-dbt`, `all`
|
|
114
110
|
|
|
115
111
|
The pipeline platform (GitHub / Azure DevOps) is auto-detected from the native app's source control configuration.
|
|
116
112
|
|
|
@@ -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
|
-
|
|
55
|
-
|
|
56
|
-
```powershell
|
|
57
|
-
.\setup.ps1
|
|
58
|
-
```
|
|
50
|
+
This prompts for your Snowflake connection name and native app database, then writes `.datasecops.yml`.
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
If you skip this step, running `datasecops` will offer to run setup automatically.
|
|
61
53
|
|
|
62
|
-
### 2.
|
|
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
|
|
|
@@ -86,11 +75,18 @@ datasecops download sqlfluff
|
|
|
86
75
|
datasecops download sqlfluff packages
|
|
87
76
|
datasecops download pipelines macros
|
|
88
77
|
|
|
89
|
-
#
|
|
78
|
+
# Install framework-pinned package versions
|
|
79
|
+
datasecops download install-sqlfluff
|
|
80
|
+
datasecops download install-dbt
|
|
81
|
+
|
|
82
|
+
# Download config and install packages together
|
|
83
|
+
datasecops download sqlfluff install-sqlfluff
|
|
84
|
+
|
|
85
|
+
# Download and install everything
|
|
90
86
|
datasecops download all
|
|
91
87
|
```
|
|
92
88
|
|
|
93
|
-
Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `all`
|
|
89
|
+
Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `install-sqlfluff`, `install-dbt`, `all`
|
|
94
90
|
|
|
95
91
|
The pipeline platform (GitHub / Azure DevOps) is auto-detected from the native app's source control configuration.
|
|
96
92
|
|
|
@@ -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,9 +20,13 @@ 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
|
-
DOWNLOAD_ITEMS = [
|
|
26
|
+
DOWNLOAD_ITEMS = [
|
|
27
|
+
"sqlfluff", "pipelines", "packages", "macros",
|
|
28
|
+
"install-sqlfluff", "install-dbt", "all",
|
|
29
|
+
]
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -30,6 +36,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
30
36
|
)
|
|
31
37
|
sub = parser.add_subparsers(dest="command")
|
|
32
38
|
|
|
39
|
+
sub.add_parser("setup", help="Configure .datasecops.yml for this project")
|
|
33
40
|
sub.add_parser("bootstrap", help="Set up a new dbt project with all framework config")
|
|
34
41
|
|
|
35
42
|
dl = sub.add_parser("download", help="Download framework config non-interactively (for CI/CD)")
|
|
@@ -37,7 +44,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
37
44
|
"items",
|
|
38
45
|
nargs="+",
|
|
39
46
|
choices=DOWNLOAD_ITEMS,
|
|
40
|
-
help="Item(s) to download: sqlfluff, pipelines, packages, macros, or all",
|
|
47
|
+
help="Item(s) to download/install: sqlfluff, pipelines, packages, macros, install-sqlfluff, install-dbt, or all",
|
|
41
48
|
)
|
|
42
49
|
|
|
43
50
|
return parser
|
|
@@ -50,7 +57,9 @@ def main():
|
|
|
50
57
|
|
|
51
58
|
config = Config()
|
|
52
59
|
|
|
53
|
-
if args.command == "
|
|
60
|
+
if args.command == "setup":
|
|
61
|
+
_run_setup(config.project_dir)
|
|
62
|
+
elif args.command == "bootstrap":
|
|
54
63
|
_run_bootstrap(config)
|
|
55
64
|
elif args.command == "download":
|
|
56
65
|
_run_download(config, args.items)
|
|
@@ -58,16 +67,82 @@ def main():
|
|
|
58
67
|
_run_interactive(config)
|
|
59
68
|
|
|
60
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
|
+
|
|
61
127
|
def _connect_and_load(config: Config) -> SnowflakeService:
|
|
62
128
|
"""Load config, connect to Snowflake, and load native app settings.
|
|
63
129
|
|
|
64
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.
|
|
65
132
|
"""
|
|
66
133
|
if not config.load():
|
|
67
|
-
|
|
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)
|
|
68
142
|
|
|
69
143
|
sf_config = config.datasecops
|
|
70
144
|
sf_service = SnowflakeService(sf_config)
|
|
145
|
+
profile_was_set = bool(config.profile_name)
|
|
71
146
|
|
|
72
147
|
try:
|
|
73
148
|
info_line(f"Connecting to Snowflake ({sf_config.connection_name})...")
|
|
@@ -80,6 +155,24 @@ def _connect_and_load(config: Config) -> SnowflakeService:
|
|
|
80
155
|
info_line("Loading framework configuration...")
|
|
81
156
|
config.load_from_native_app(sf_service)
|
|
82
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
|
+
|
|
83
176
|
if not config.profile:
|
|
84
177
|
error_line(f"Profile '{config.profile_name}' not found in native app")
|
|
85
178
|
sf_service.close()
|
|
@@ -129,10 +222,12 @@ def _run_download(config: Config, items: list[str]):
|
|
|
129
222
|
|
|
130
223
|
try:
|
|
131
224
|
download_service = DownloadService(sf_service, config.project_dir)
|
|
225
|
+
linting_service = LintingService(config.dbt_project_dir)
|
|
132
226
|
profiles_dir = str(config.get_dbt_profiles_dir())
|
|
133
227
|
|
|
134
228
|
if "all" in items:
|
|
135
|
-
items = ["sqlfluff", "pipelines", "packages", "macros"
|
|
229
|
+
items = ["sqlfluff", "pipelines", "packages", "macros",
|
|
230
|
+
"install-sqlfluff", "install-dbt"]
|
|
136
231
|
|
|
137
232
|
failed = False
|
|
138
233
|
for item in items:
|
|
@@ -153,6 +248,20 @@ def _run_download(config: Config, items: list[str]):
|
|
|
153
248
|
elif item == "macros":
|
|
154
249
|
if not download_service.download_macros(config.profile_name, config.dbt_project_dir):
|
|
155
250
|
failed = True
|
|
251
|
+
elif item == "install-sqlfluff":
|
|
252
|
+
packages = download_service.get_sqlfluff_requirements()
|
|
253
|
+
if packages:
|
|
254
|
+
if not linting_service.install_requirements(packages):
|
|
255
|
+
failed = True
|
|
256
|
+
else:
|
|
257
|
+
failed = True
|
|
258
|
+
elif item == "install-dbt":
|
|
259
|
+
packages = download_service.get_dbt_requirements()
|
|
260
|
+
if packages:
|
|
261
|
+
if not linting_service.install_requirements(packages):
|
|
262
|
+
failed = True
|
|
263
|
+
else:
|
|
264
|
+
failed = True
|
|
156
265
|
|
|
157
266
|
sys.exit(1 if failed else 0)
|
|
158
267
|
finally:
|
|
@@ -73,7 +73,8 @@ class TestRunDownload:
|
|
|
73
73
|
mock_sf = MagicMock()
|
|
74
74
|
mock_connect.return_value = mock_sf
|
|
75
75
|
|
|
76
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
76
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
77
|
+
patch("datasecops_cli.main.LintingService"):
|
|
77
78
|
mock_ds = MockDS.return_value
|
|
78
79
|
mock_ds.download_sqlfluff_config.return_value = True
|
|
79
80
|
|
|
@@ -92,7 +93,8 @@ class TestRunDownload:
|
|
|
92
93
|
mock_sf = MagicMock()
|
|
93
94
|
mock_connect.return_value = mock_sf
|
|
94
95
|
|
|
95
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
96
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
97
|
+
patch("datasecops_cli.main.LintingService"):
|
|
96
98
|
mock_ds = MockDS.return_value
|
|
97
99
|
mock_ds.download_pipelines.return_value = True
|
|
98
100
|
|
|
@@ -108,7 +110,8 @@ class TestRunDownload:
|
|
|
108
110
|
mock_sf = MagicMock()
|
|
109
111
|
mock_connect.return_value = mock_sf
|
|
110
112
|
|
|
111
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
113
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
114
|
+
patch("datasecops_cli.main.LintingService"):
|
|
112
115
|
mock_ds = MockDS.return_value
|
|
113
116
|
mock_ds.download_dbt_packages.return_value = True
|
|
114
117
|
|
|
@@ -124,7 +127,8 @@ class TestRunDownload:
|
|
|
124
127
|
mock_sf = MagicMock()
|
|
125
128
|
mock_connect.return_value = mock_sf
|
|
126
129
|
|
|
127
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
130
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
131
|
+
patch("datasecops_cli.main.LintingService"):
|
|
128
132
|
mock_ds = MockDS.return_value
|
|
129
133
|
mock_ds.download_macros.return_value = True
|
|
130
134
|
|
|
@@ -140,12 +144,17 @@ class TestRunDownload:
|
|
|
140
144
|
mock_sf = MagicMock()
|
|
141
145
|
mock_connect.return_value = mock_sf
|
|
142
146
|
|
|
143
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
147
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
148
|
+
patch("datasecops_cli.main.LintingService") as MockLS:
|
|
144
149
|
mock_ds = MockDS.return_value
|
|
150
|
+
mock_ls = MockLS.return_value
|
|
145
151
|
mock_ds.download_sqlfluff_config.return_value = True
|
|
146
152
|
mock_ds.download_pipelines.return_value = True
|
|
147
153
|
mock_ds.download_dbt_packages.return_value = True
|
|
148
154
|
mock_ds.download_macros.return_value = True
|
|
155
|
+
mock_ds.get_sqlfluff_requirements.return_value = ["sqlfluff==3.4.0"]
|
|
156
|
+
mock_ds.get_dbt_requirements.return_value = ["dbt-core==1.9.0"]
|
|
157
|
+
mock_ls.install_requirements.return_value = True
|
|
149
158
|
|
|
150
159
|
with pytest.raises(SystemExit) as exc:
|
|
151
160
|
_run_download(config, ["all"])
|
|
@@ -155,6 +164,9 @@ class TestRunDownload:
|
|
|
155
164
|
mock_ds.download_pipelines.assert_called_once()
|
|
156
165
|
mock_ds.download_dbt_packages.assert_called_once()
|
|
157
166
|
mock_ds.download_macros.assert_called_once()
|
|
167
|
+
mock_ds.get_sqlfluff_requirements.assert_called_once()
|
|
168
|
+
mock_ds.get_dbt_requirements.assert_called_once()
|
|
169
|
+
assert mock_ls.install_requirements.call_count == 2
|
|
158
170
|
|
|
159
171
|
@patch("datasecops_cli.main._connect_and_load")
|
|
160
172
|
def test_download_failure_exits_1(self, mock_connect, tmp_path):
|
|
@@ -162,7 +174,8 @@ class TestRunDownload:
|
|
|
162
174
|
mock_sf = MagicMock()
|
|
163
175
|
mock_connect.return_value = mock_sf
|
|
164
176
|
|
|
165
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
177
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
178
|
+
patch("datasecops_cli.main.LintingService"):
|
|
166
179
|
mock_ds = MockDS.return_value
|
|
167
180
|
mock_ds.download_sqlfluff_config.return_value = False # failure
|
|
168
181
|
|
|
@@ -178,7 +191,8 @@ class TestRunDownload:
|
|
|
178
191
|
mock_sf = MagicMock()
|
|
179
192
|
mock_connect.return_value = mock_sf
|
|
180
193
|
|
|
181
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
194
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
195
|
+
patch("datasecops_cli.main.LintingService"):
|
|
182
196
|
mock_ds = MockDS.return_value
|
|
183
197
|
mock_ds.download_sqlfluff_config.return_value = True
|
|
184
198
|
mock_ds.download_dbt_packages.return_value = False
|
|
@@ -196,7 +210,8 @@ class TestRunDownload:
|
|
|
196
210
|
mock_sf = MagicMock()
|
|
197
211
|
mock_connect.return_value = mock_sf
|
|
198
212
|
|
|
199
|
-
with patch("datasecops_cli.main.DownloadService") as MockDS
|
|
213
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
214
|
+
patch("datasecops_cli.main.LintingService"):
|
|
200
215
|
mock_ds = MockDS.return_value
|
|
201
216
|
mock_ds.download_pipelines.return_value = True
|
|
202
217
|
|
|
@@ -205,3 +220,83 @@ class TestRunDownload:
|
|
|
205
220
|
|
|
206
221
|
assert exc.value.code == 0
|
|
207
222
|
mock_ds.download_pipelines.assert_called_once_with(platform="azuredevops")
|
|
223
|
+
|
|
224
|
+
@patch("datasecops_cli.main._connect_and_load")
|
|
225
|
+
def test_install_sqlfluff(self, mock_connect, tmp_path):
|
|
226
|
+
"""install-sqlfluff fetches pinned versions and installs them."""
|
|
227
|
+
config = self._make_config(tmp_path)
|
|
228
|
+
mock_connect.return_value = MagicMock()
|
|
229
|
+
|
|
230
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
231
|
+
patch("datasecops_cli.main.LintingService") as MockLS:
|
|
232
|
+
mock_ds = MockDS.return_value
|
|
233
|
+
mock_ls = MockLS.return_value
|
|
234
|
+
mock_ds.get_sqlfluff_requirements.return_value = ["sqlfluff==3.4.0", "sqlfluff-templater-dbt==3.4.0"]
|
|
235
|
+
mock_ls.install_requirements.return_value = True
|
|
236
|
+
|
|
237
|
+
with pytest.raises(SystemExit) as exc:
|
|
238
|
+
_run_download(config, ["install-sqlfluff"])
|
|
239
|
+
|
|
240
|
+
assert exc.value.code == 0
|
|
241
|
+
mock_ds.get_sqlfluff_requirements.assert_called_once()
|
|
242
|
+
mock_ls.install_requirements.assert_called_once_with(
|
|
243
|
+
["sqlfluff==3.4.0", "sqlfluff-templater-dbt==3.4.0"]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
@patch("datasecops_cli.main._connect_and_load")
|
|
247
|
+
def test_install_dbt(self, mock_connect, tmp_path):
|
|
248
|
+
"""install-dbt fetches pinned versions and installs them."""
|
|
249
|
+
config = self._make_config(tmp_path)
|
|
250
|
+
mock_connect.return_value = MagicMock()
|
|
251
|
+
|
|
252
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
253
|
+
patch("datasecops_cli.main.LintingService") as MockLS:
|
|
254
|
+
mock_ds = MockDS.return_value
|
|
255
|
+
mock_ls = MockLS.return_value
|
|
256
|
+
mock_ds.get_dbt_requirements.return_value = ["dbt-core==1.9.0", "dbt-snowflake==1.9.0"]
|
|
257
|
+
mock_ls.install_requirements.return_value = True
|
|
258
|
+
|
|
259
|
+
with pytest.raises(SystemExit) as exc:
|
|
260
|
+
_run_download(config, ["install-dbt"])
|
|
261
|
+
|
|
262
|
+
assert exc.value.code == 0
|
|
263
|
+
mock_ds.get_dbt_requirements.assert_called_once()
|
|
264
|
+
mock_ls.install_requirements.assert_called_once_with(
|
|
265
|
+
["dbt-core==1.9.0", "dbt-snowflake==1.9.0"]
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
@patch("datasecops_cli.main._connect_and_load")
|
|
269
|
+
def test_install_sqlfluff_no_versions_exits_1(self, mock_connect, tmp_path):
|
|
270
|
+
"""install-sqlfluff fails if no versions returned from framework."""
|
|
271
|
+
config = self._make_config(tmp_path)
|
|
272
|
+
mock_connect.return_value = MagicMock()
|
|
273
|
+
|
|
274
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
275
|
+
patch("datasecops_cli.main.LintingService") as MockLS:
|
|
276
|
+
mock_ds = MockDS.return_value
|
|
277
|
+
mock_ls = MockLS.return_value
|
|
278
|
+
mock_ds.get_sqlfluff_requirements.return_value = []
|
|
279
|
+
|
|
280
|
+
with pytest.raises(SystemExit) as exc:
|
|
281
|
+
_run_download(config, ["install-sqlfluff"])
|
|
282
|
+
|
|
283
|
+
assert exc.value.code == 1
|
|
284
|
+
mock_ls.install_requirements.assert_not_called()
|
|
285
|
+
|
|
286
|
+
@patch("datasecops_cli.main._connect_and_load")
|
|
287
|
+
def test_install_dbt_pip_failure_exits_1(self, mock_connect, tmp_path):
|
|
288
|
+
"""install-dbt exits 1 if uv pip install fails."""
|
|
289
|
+
config = self._make_config(tmp_path)
|
|
290
|
+
mock_connect.return_value = MagicMock()
|
|
291
|
+
|
|
292
|
+
with patch("datasecops_cli.main.DownloadService") as MockDS, \
|
|
293
|
+
patch("datasecops_cli.main.LintingService") as MockLS:
|
|
294
|
+
mock_ds = MockDS.return_value
|
|
295
|
+
mock_ls = MockLS.return_value
|
|
296
|
+
mock_ds.get_dbt_requirements.return_value = ["dbt-core==1.9.0"]
|
|
297
|
+
mock_ls.install_requirements.return_value = False # pip failure
|
|
298
|
+
|
|
299
|
+
with pytest.raises(SystemExit) as exc:
|
|
300
|
+
_run_download(config, ["install-dbt"])
|
|
301
|
+
|
|
302
|
+
assert exc.value.code == 1
|
|
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.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.8 → datasecops_cli-0.3.0}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.8 → 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
|