datasecops-cli 0.3.1__tar.gz → 0.3.2__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.3.1 → datasecops_cli-0.3.2}/CHANGELOG.md +8 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/PKG-INFO +1 -2
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/pyproject.toml +1 -2
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/main.py +60 -6
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/models/project_config.py +1 -0
- datasecops_cli-0.3.2/src/datasecops_cli/services/upstream_service.py +158 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/utilities/display.py +14 -2
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/.gitignore +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/LICENSE +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/README.md +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/docs/getting-started.md +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/docs/legacy.md +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/mcp-servers.json +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/setup.ps1 +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/setup.sh +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/__init__.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/test_config.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/test_main.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/test_models.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/test_version.py +0 -0
- {datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.2] - 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Upstream project version tracking** — if a profile has `upstream_projects` configured in the native app, the main menu header shows each upstream project's current pinned version vs the latest available tag (fetched via `git ls-remote`). Outdated dependencies are highlighted in yellow.
|
|
10
|
+
- **Auto-update upstream packages** — when outdated upstream packages are detected, a new menu option `[5] update pkgs` appears, which updates `packages.yml` revisions to the latest tags and optionally runs `dbtf deps`
|
|
11
|
+
- **`upstream_projects` field on `ProjectProfile`** — new model field to support upstream dependency tracking from the native app
|
|
12
|
+
|
|
5
13
|
## [0.3.0] - 2026-05-14
|
|
6
14
|
|
|
7
15
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasecops-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: DataSecOps Framework CLI for Snowflake Native App
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -10,7 +10,6 @@ Requires-Dist: gitpython>=3.1
|
|
|
10
10
|
Requires-Dist: pydantic>=2.0
|
|
11
11
|
Requires-Dist: pyyaml>=6.0
|
|
12
12
|
Requires-Dist: snowflake-connector-python>=3.0
|
|
13
|
-
Requires-Dist: sqlfluff>=3.0
|
|
14
13
|
Provides-Extra: mcp
|
|
15
14
|
Requires-Dist: mcp>=1.0; extra == 'mcp'
|
|
16
15
|
Provides-Extra: test
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datasecops-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "DataSecOps Framework CLI for Snowflake Native App"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -14,7 +14,6 @@ dependencies = [
|
|
|
14
14
|
"snowflake-connector-python>=3.0",
|
|
15
15
|
"colorama>=0.4",
|
|
16
16
|
"pyyaml>=6.0",
|
|
17
|
-
"sqlfluff>=3.0",
|
|
18
17
|
"pydantic>=2.0",
|
|
19
18
|
]
|
|
20
19
|
|
|
@@ -247,6 +247,15 @@ def _run_interactive(config: Config):
|
|
|
247
247
|
warning_line("dbt Fusion (dbtf) not found on PATH — dbt commands will not work.")
|
|
248
248
|
warning_line("Install dbt Fusion: https://docs.getdbt.com/docs/core/installation")
|
|
249
249
|
|
|
250
|
+
# Check upstream project versions
|
|
251
|
+
from datasecops_cli.services.upstream_service import check_upstream_versions
|
|
252
|
+
upstream_statuses = []
|
|
253
|
+
if config.profile and config.profile.upstream_projects:
|
|
254
|
+
info_line("Checking upstream project versions...")
|
|
255
|
+
upstream_statuses = check_upstream_versions(
|
|
256
|
+
config.profile, config.all_profiles, config.dbt_project_dir
|
|
257
|
+
)
|
|
258
|
+
|
|
250
259
|
# Initialize services
|
|
251
260
|
dbt_runner = DbtRunner(
|
|
252
261
|
project_dir=config.dbt_project_dir,
|
|
@@ -265,7 +274,8 @@ def _run_interactive(config: Config):
|
|
|
265
274
|
|
|
266
275
|
# Main menu loop
|
|
267
276
|
_main_menu(config, dbt_runner, git_service, linting_service,
|
|
268
|
-
download_service, skill_service, sf_service
|
|
277
|
+
download_service, skill_service, sf_service,
|
|
278
|
+
upstream_statuses=upstream_statuses)
|
|
269
279
|
finally:
|
|
270
280
|
sf_service.close()
|
|
271
281
|
|
|
@@ -341,13 +351,44 @@ def _run_download(config: Config, items: list[str],
|
|
|
341
351
|
sf_service.close()
|
|
342
352
|
|
|
343
353
|
|
|
354
|
+
def _update_upstream_packages(config: Config, dbt_runner: DbtRunner,
|
|
355
|
+
upstream_statuses: list):
|
|
356
|
+
"""Update packages.yml with latest upstream versions and optionally run deps."""
|
|
357
|
+
from datasecops_cli.services.upstream_service import update_packages_yml
|
|
358
|
+
from datasecops_cli.utilities.display import display_action_header, get_input_true_false, complete_action
|
|
359
|
+
|
|
360
|
+
display_action_header("Update Upstream Packages")
|
|
361
|
+
|
|
362
|
+
outdated = [s for s in upstream_statuses if s.needs_update and s.latest_tag != "unknown"]
|
|
363
|
+
if not outdated:
|
|
364
|
+
info_line("No packages to update")
|
|
365
|
+
complete_action()
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
for s in outdated:
|
|
369
|
+
info_line(f" {s.profile_name}: {s.current_version} → {s.latest_tag}")
|
|
370
|
+
|
|
371
|
+
info_line("")
|
|
372
|
+
count = update_packages_yml(config.dbt_project_dir, upstream_statuses)
|
|
373
|
+
if count:
|
|
374
|
+
success_line(f"Updated {count} package(s) in packages.yml")
|
|
375
|
+
if get_input_true_false("Run dbtf deps now?"):
|
|
376
|
+
dbt_runner.deps()
|
|
377
|
+
else:
|
|
378
|
+
info_line("No changes made to packages.yml")
|
|
379
|
+
|
|
380
|
+
complete_action()
|
|
381
|
+
|
|
382
|
+
|
|
344
383
|
def _main_menu(config: Config, dbt_runner: DbtRunner, git_service: GitService,
|
|
345
384
|
linting_service: LintingService, download_service: DownloadService,
|
|
346
|
-
skill_service: SkillService, sf_service: SnowflakeService
|
|
385
|
+
skill_service: SkillService, sf_service: SnowflakeService,
|
|
386
|
+
upstream_statuses: list = None):
|
|
347
387
|
"""Main menu loop."""
|
|
348
388
|
profile_name = config.profile_name
|
|
389
|
+
has_outdated = upstream_statuses and any(s.needs_update for s in upstream_statuses)
|
|
349
390
|
|
|
350
|
-
_show_main_menu(profile_name, git_service)
|
|
391
|
+
_show_main_menu(profile_name, git_service, upstream_statuses)
|
|
351
392
|
option = get_input_number("Choose an option: ")
|
|
352
393
|
|
|
353
394
|
while option != 0:
|
|
@@ -391,21 +432,34 @@ def _main_menu(config: Config, dbt_runner: DbtRunner, git_service: GitService,
|
|
|
391
432
|
profile=config.profile,
|
|
392
433
|
)
|
|
393
434
|
bootstrap.run(platform=platform, install_skills=install_skills, run_deps=run_deps)
|
|
435
|
+
|
|
436
|
+
elif option == 5 and has_outdated:
|
|
437
|
+
_update_upstream_packages(config, dbt_runner, upstream_statuses)
|
|
438
|
+
# Refresh upstream statuses after update
|
|
439
|
+
from datasecops_cli.services.upstream_service import check_upstream_versions
|
|
440
|
+
upstream_statuses = check_upstream_versions(
|
|
441
|
+
config.profile, config.all_profiles, config.dbt_project_dir
|
|
442
|
+
)
|
|
443
|
+
has_outdated = any(s.needs_update for s in upstream_statuses)
|
|
394
444
|
|
|
395
|
-
_show_main_menu(profile_name, git_service)
|
|
445
|
+
_show_main_menu(profile_name, git_service, upstream_statuses)
|
|
396
446
|
option = get_input_number("Choose an option: ")
|
|
397
447
|
|
|
398
448
|
info_line("Exiting DataSecOps Framework CLI")
|
|
399
449
|
|
|
400
450
|
|
|
401
|
-
def _show_main_menu(profile_name: str, git_service: GitService = None
|
|
451
|
+
def _show_main_menu(profile_name: str, git_service: GitService = None,
|
|
452
|
+
upstream_statuses: list = None):
|
|
402
453
|
clear()
|
|
403
454
|
branch = git_service.get_current_branch() if git_service else None
|
|
404
|
-
section_header("DataSecOps Framework CLI", profile_name, branch
|
|
455
|
+
section_header("DataSecOps Framework CLI", profile_name, branch,
|
|
456
|
+
upstream_info=upstream_statuses)
|
|
405
457
|
menu_option(1, "development - dbt Development Commands")
|
|
406
458
|
menu_option(2, "git - Source Control Operations")
|
|
407
459
|
menu_option(3, "downloads - Download Configs & Skills")
|
|
408
460
|
menu_option(4, "bootstrap - Set up a new dbt project with all framework config")
|
|
461
|
+
if upstream_statuses and any(s.needs_update for s in upstream_statuses):
|
|
462
|
+
menu_option(5, "update pkgs - Update upstream packages to latest versions")
|
|
409
463
|
menu_option(0, "exit - Exit")
|
|
410
464
|
|
|
411
465
|
|
|
@@ -61,6 +61,7 @@ class ProjectProfile(BaseModel):
|
|
|
61
61
|
project_description: str = ""
|
|
62
62
|
model_types: list[str] = Field(default_factory=list)
|
|
63
63
|
downstream_projects: list[str] = Field(default_factory=list)
|
|
64
|
+
upstream_projects: list[str] = Field(default_factory=list)
|
|
64
65
|
git_url: str = ""
|
|
65
66
|
target_database: str = ""
|
|
66
67
|
dbt_version: str = "1.9"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Service for checking upstream project versions against packages.yml."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from datasecops_cli.models.project_config import ProjectProfile
|
|
10
|
+
from datasecops_cli.utilities.display import info_line, warning_line
|
|
11
|
+
from datasecops_cli.utilities.yaml_utils import read_yaml, write_yaml
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class UpstreamStatus:
|
|
16
|
+
"""Version status of an upstream project dependency."""
|
|
17
|
+
profile_name: str
|
|
18
|
+
git_url: str
|
|
19
|
+
latest_tag: str = ""
|
|
20
|
+
current_version: str = ""
|
|
21
|
+
needs_update: bool = False
|
|
22
|
+
error: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_latest_tag(git_url: str, timeout: int = 10) -> Optional[str]:
|
|
26
|
+
"""Fetch the latest tag from a remote git repo without cloning."""
|
|
27
|
+
try:
|
|
28
|
+
result = subprocess.run(
|
|
29
|
+
["git", "ls-remote", "--tags", "--sort=-v:refname", git_url],
|
|
30
|
+
capture_output=True, text=True, timeout=timeout,
|
|
31
|
+
)
|
|
32
|
+
if result.returncode != 0:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
for line in result.stdout.strip().splitlines():
|
|
36
|
+
ref = line.split("\t")[-1]
|
|
37
|
+
# Skip ^{} dereferenced tags
|
|
38
|
+
if ref.endswith("^{}"):
|
|
39
|
+
continue
|
|
40
|
+
tag = ref.replace("refs/tags/", "")
|
|
41
|
+
# Skip pre-release tags
|
|
42
|
+
if any(pre in tag.lower() for pre in ("alpha", "beta", "rc", "dev")):
|
|
43
|
+
continue
|
|
44
|
+
return tag
|
|
45
|
+
return None
|
|
46
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_packages_yml(dbt_project_dir: Path) -> Optional[dict]:
|
|
51
|
+
"""Read packages.yml from the dbt project directory."""
|
|
52
|
+
return read_yaml(dbt_project_dir / "packages.yml")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def find_pinned_version(packages_data: dict, git_url: str) -> str:
|
|
56
|
+
"""Find the pinned revision/version for a git URL in packages.yml."""
|
|
57
|
+
if not packages_data:
|
|
58
|
+
return ""
|
|
59
|
+
for pkg in packages_data.get("packages", []):
|
|
60
|
+
pkg_url = pkg.get("git", "")
|
|
61
|
+
if pkg_url and _urls_match(pkg_url, git_url):
|
|
62
|
+
return pkg.get("revision", "")
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _urls_match(url1: str, url2: str) -> bool:
|
|
67
|
+
"""Compare two git URLs, ignoring trailing .git and protocol differences."""
|
|
68
|
+
def normalize(url: str) -> str:
|
|
69
|
+
url = url.rstrip("/").removesuffix(".git").lower()
|
|
70
|
+
url = re.sub(r"^https?://", "", url)
|
|
71
|
+
url = re.sub(r"^git@([^:]+):", r"\1/", url)
|
|
72
|
+
return url
|
|
73
|
+
return normalize(url1) == normalize(url2)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check_upstream_versions(
|
|
77
|
+
profile: ProjectProfile,
|
|
78
|
+
all_profiles: list[ProjectProfile],
|
|
79
|
+
dbt_project_dir: Path,
|
|
80
|
+
) -> list[UpstreamStatus]:
|
|
81
|
+
"""Check version status of all upstream project dependencies."""
|
|
82
|
+
if not profile.upstream_projects:
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
# Build lookup of profiles by name
|
|
86
|
+
profile_map = {p.profile_name: p for p in all_profiles}
|
|
87
|
+
|
|
88
|
+
# Read current packages.yml
|
|
89
|
+
packages_data = read_packages_yml(dbt_project_dir)
|
|
90
|
+
|
|
91
|
+
results = []
|
|
92
|
+
for upstream_name in profile.upstream_projects:
|
|
93
|
+
upstream_profile = profile_map.get(upstream_name)
|
|
94
|
+
if not upstream_profile:
|
|
95
|
+
results.append(UpstreamStatus(
|
|
96
|
+
profile_name=upstream_name,
|
|
97
|
+
git_url="",
|
|
98
|
+
error=f"Profile '{upstream_name}' not found",
|
|
99
|
+
))
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
git_url = upstream_profile.git_url
|
|
103
|
+
if not git_url:
|
|
104
|
+
results.append(UpstreamStatus(
|
|
105
|
+
profile_name=upstream_name,
|
|
106
|
+
git_url="",
|
|
107
|
+
error="No git_url configured",
|
|
108
|
+
))
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
latest_tag = get_latest_tag(git_url)
|
|
112
|
+
current_version = find_pinned_version(packages_data, git_url)
|
|
113
|
+
|
|
114
|
+
needs_update = False
|
|
115
|
+
if latest_tag and current_version:
|
|
116
|
+
needs_update = latest_tag.lstrip("v") != current_version.lstrip("v")
|
|
117
|
+
elif latest_tag and not current_version:
|
|
118
|
+
needs_update = True
|
|
119
|
+
|
|
120
|
+
results.append(UpstreamStatus(
|
|
121
|
+
profile_name=upstream_name,
|
|
122
|
+
git_url=git_url,
|
|
123
|
+
latest_tag=latest_tag or "unknown",
|
|
124
|
+
current_version=current_version or "not pinned",
|
|
125
|
+
needs_update=needs_update,
|
|
126
|
+
))
|
|
127
|
+
|
|
128
|
+
return results
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def update_packages_yml(
|
|
132
|
+
dbt_project_dir: Path,
|
|
133
|
+
upstream_statuses: list[UpstreamStatus],
|
|
134
|
+
) -> int:
|
|
135
|
+
"""Update packages.yml with latest versions for outdated upstream packages.
|
|
136
|
+
|
|
137
|
+
Returns the number of packages updated.
|
|
138
|
+
"""
|
|
139
|
+
packages_data = read_packages_yml(dbt_project_dir)
|
|
140
|
+
if not packages_data:
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
updated = 0
|
|
144
|
+
for status in upstream_statuses:
|
|
145
|
+
if not status.needs_update or status.latest_tag == "unknown":
|
|
146
|
+
continue
|
|
147
|
+
for pkg in packages_data.get("packages", []):
|
|
148
|
+
pkg_url = pkg.get("git", "")
|
|
149
|
+
if pkg_url and _urls_match(pkg_url, status.git_url):
|
|
150
|
+
pkg["revision"] = status.latest_tag
|
|
151
|
+
updated += 1
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if updated:
|
|
155
|
+
dest = dbt_project_dir / "packages.yml"
|
|
156
|
+
write_yaml(dest, packages_data)
|
|
157
|
+
|
|
158
|
+
return updated
|
|
@@ -16,7 +16,8 @@ def print_long_line(color: str = Fore.GREEN) -> None:
|
|
|
16
16
|
print_detail("=" * 76, color)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def section_header(message: str, active_profile: str = None, current_branch: str = None
|
|
19
|
+
def section_header(message: str, active_profile: str = None, current_branch: str = None,
|
|
20
|
+
upstream_info: list = None) -> None:
|
|
20
21
|
print_long_line(Fore.GREEN)
|
|
21
22
|
print_detail(message, Fore.GREEN)
|
|
22
23
|
print_long_line(Fore.GREEN)
|
|
@@ -24,7 +25,18 @@ def section_header(message: str, active_profile: str = None, current_branch: str
|
|
|
24
25
|
print_detail(f" Profile: {active_profile}", Fore.GREEN)
|
|
25
26
|
if current_branch:
|
|
26
27
|
print_detail(f" Branch: {current_branch}", Fore.GREEN)
|
|
27
|
-
if
|
|
28
|
+
if upstream_info:
|
|
29
|
+
for status in upstream_info:
|
|
30
|
+
if status.error:
|
|
31
|
+
print_detail(f" Upstream: {status.profile_name} — {status.error}", Fore.YELLOW)
|
|
32
|
+
elif status.needs_update:
|
|
33
|
+
print_detail(
|
|
34
|
+
f" Upstream: {status.profile_name} ({status.current_version} → {status.latest_tag} available)",
|
|
35
|
+
Fore.YELLOW,
|
|
36
|
+
)
|
|
37
|
+
else:
|
|
38
|
+
print_detail(f" Upstream: {status.profile_name} ({status.current_version} ✓)", Fore.GREEN)
|
|
39
|
+
if active_profile or current_branch or upstream_info:
|
|
28
40
|
print_long_line(Fore.GREEN)
|
|
29
41
|
|
|
30
42
|
|
|
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.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.3.1 → datasecops_cli-0.3.2}/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
|