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.
Files changed (48) hide show
  1. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/CHANGELOG.md +9 -0
  2. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/PKG-INFO +6 -17
  3. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/README.md +5 -16
  4. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/pyproject.toml +1 -1
  5. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/main.py +92 -2
  6. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/.github/workflows/publish-cli.yml +0 -0
  7. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/.gitignore +0 -0
  8. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/DEVELOPMENT.md +0 -0
  9. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/LICENSE +0 -0
  10. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/getting-started.md +0 -0
  11. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/legacy.md +0 -0
  12. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/legacy_plan_of_action.md +0 -0
  13. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/docs/mcp-server.md +0 -0
  14. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/mcp-servers.json +0 -0
  15. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/setup.ps1 +0 -0
  16. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/setup.sh +0 -0
  17. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/__init__.py +0 -0
  18. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/config.py +0 -0
  19. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/__init__.py +0 -0
  20. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/development.py +0 -0
  21. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/downloads.py +0 -0
  22. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/menus/git_operations.py +0 -0
  23. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/models/__init__.py +0 -0
  24. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/models/git_helpers.py +0 -0
  25. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/models/project_config.py +0 -0
  26. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/__init__.py +0 -0
  27. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  28. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/dbt_runner.py +0 -0
  29. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/download_service.py +0 -0
  30. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/git_service.py +0 -0
  31. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/linting_service.py +0 -0
  32. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/skill_service.py +0 -0
  33. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/services/snowflake_service.py +0 -0
  34. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/__init__.py +0 -0
  35. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/display.py +0 -0
  36. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/file_utils.py +0 -0
  37. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  38. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/__init__.py +0 -0
  39. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/__main__.py +0 -0
  40. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/connection.py +0 -0
  41. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/src/datasecops_mcp/server.py +0 -0
  42. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/__init__.py +0 -0
  43. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_config.py +0 -0
  44. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_file_utils.py +0 -0
  45. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_main.py +0 -0
  46. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_models.py +0 -0
  47. {datasecops_cli-0.2.9 → datasecops_cli-0.3.0}/tests/test_version.py +0 -0
  48. {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.2.9
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. Run the setup script
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
- chmod +x setup.sh && ./setup.sh
67
+ datasecops setup
72
68
  ```
73
69
 
74
- **Windows (PowerShell):**
70
+ This prompts for your Snowflake connection name and native app database, then writes `.datasecops.yml`.
75
71
 
76
- ```powershell
77
- .\setup.ps1
78
- ```
72
+ If you skip this step, running `datasecops` will offer to run setup automatically.
79
73
 
80
- You'll be prompted for your Snowflake connection name and the native app database name.
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. Run the setup script
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
- chmod +x setup.sh && ./setup.sh
47
+ datasecops setup
52
48
  ```
53
49
 
54
- **Windows (PowerShell):**
50
+ This prompts for your Snowflake connection name and native app database, then writes `.datasecops.yml`.
55
51
 
56
- ```powershell
57
- .\setup.ps1
58
- ```
52
+ If you skip this step, running `datasecops` will offer to run setup automatically.
59
53
 
60
- You'll be prompted for your Snowflake connection name and the native app database name.
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.2.9"
7
+ version = "0.3.0"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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 == "bootstrap":
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
- sys.exit(1)
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