recce-nightly 1.2.0.20250506__py3-none-any.whl → 1.26.0.20251124__py3-none-any.whl
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.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +810 -480
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +39 -28
- recce/apis/check_func.py +33 -27
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +29 -23
- recce/artifact.py +119 -51
- recce/cli.py +1299 -323
- recce/config.py +42 -33
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404.html +1 -1
- recce/data/__next.__PAGE__.txt +10 -0
- recce/data/__next._full.txt +23 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +8 -0
- recce/data/__next._tree.txt +5 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_buildManifest.js +11 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_clientMiddlewareManifest.json +1 -0
- recce/data/_next/static/chunks/02b996c7f6a29a06.js +4 -0
- recce/data/_next/static/chunks/19c10d219a6a21ff.js +1 -0
- recce/data/_next/static/chunks/2df9ec28a061971d.js +11 -0
- recce/data/_next/static/chunks/3098c987393bda15.js +1 -0
- recce/data/_next/static/chunks/393dc43e483f717a.css +2 -0
- recce/data/_next/static/chunks/399e8d91a7e45073.js +2 -0
- recce/data/_next/static/chunks/4d0186f631230245.js +1 -0
- recce/data/_next/static/chunks/5794ba9e10a9c060.js +11 -0
- recce/data/_next/static/chunks/715761c929a3f28b.js +110 -0
- recce/data/_next/static/chunks/71f88fcc615bf282.js +1 -0
- recce/data/_next/static/chunks/80d2a95eaf1201ea.js +1 -0
- recce/data/_next/static/chunks/9979c6109bbbee35.js +1 -0
- recce/data/_next/static/chunks/99d638224186c118.js +1 -0
- recce/data/_next/static/chunks/d003eb36240e92f3.js +1 -0
- recce/data/_next/static/chunks/d3167cdfec4fc351.js +1 -0
- recce/data/_next/static/chunks/e124bccf574a3361.css +1 -0
- recce/data/_next/static/chunks/f40141db1bdb46f0.css +6 -0
- recce/data/_next/static/chunks/fcc53a88741a52f9.js +1 -0
- recce/data/_next/static/chunks/turbopack-b1920d28cfb1f28d.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/media/reload-image.7aa931c7.svg +4 -0
- recce/data/_not-found/__next._full.txt +17 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +8 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +3 -0
- recce/data/_not-found.html +1 -0
- recce/data/_not-found.txt +17 -0
- recce/data/auth_callback.html +68 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +1 -27
- recce/data/index.txt +23 -7
- recce/diff.py +6 -12
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +716 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +6 -7
- recce/models/run.py +1 -0
- recce/models/types.py +131 -28
- recce/pull_request.py +27 -25
- recce/run.py +165 -121
- recce/server.py +303 -111
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +632 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +188 -143
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +139 -87
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/valuediff.py +216 -152
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +87 -85
- recce/util/cll.py +274 -219
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +322 -72
- recce/util/singleton.py +4 -4
- recce/yaml/__init__.py +7 -10
- recce_cloud/__init__.py +24 -0
- recce_cloud/api/__init__.py +17 -0
- recce_cloud/api/base.py +111 -0
- recce_cloud/api/client.py +150 -0
- recce_cloud/api/exceptions.py +26 -0
- recce_cloud/api/factory.py +63 -0
- recce_cloud/api/github.py +76 -0
- recce_cloud/api/gitlab.py +82 -0
- recce_cloud/artifact.py +57 -0
- recce_cloud/ci_providers/__init__.py +9 -0
- recce_cloud/ci_providers/base.py +82 -0
- recce_cloud/ci_providers/detector.py +147 -0
- recce_cloud/ci_providers/github_actions.py +136 -0
- recce_cloud/ci_providers/gitlab_ci.py +130 -0
- recce_cloud/cli.py +245 -0
- recce_cloud/upload.py +214 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/METADATA +68 -37
- recce_nightly-1.26.0.20251124.dist-info/RECORD +180 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/WHEEL +1 -1
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/top_level.txt +1 -0
- tests/adapter/dbt_adapter/conftest.py +9 -5
- tests/adapter/dbt_adapter/dbt_test_helper.py +37 -22
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
- tests/adapter/dbt_adapter/test_dbt_cll.py +656 -41
- tests/adapter/dbt_adapter/test_selector.py +22 -21
- tests/recce_cloud/__init__.py +0 -0
- tests/recce_cloud/test_ci_providers.py +351 -0
- tests/recce_cloud/test_cli.py +372 -0
- tests/recce_cloud/test_client.py +273 -0
- tests/recce_cloud/test_platform_clients.py +333 -0
- tests/tasks/conftest.py +1 -1
- tests/tasks/test_histogram.py +58 -66
- tests/tasks/test_lineage.py +36 -23
- tests/tasks/test_preset_checks.py +45 -31
- tests/tasks/test_profile.py +339 -15
- tests/tasks/test_query.py +46 -46
- tests/tasks/test_row_count.py +65 -46
- tests/tasks/test_schema.py +65 -42
- tests/tasks/test_top_k.py +22 -18
- tests/tasks/test_valuediff.py +43 -32
- tests/test_cli.py +174 -60
- tests/test_cli_mcp_optional.py +45 -0
- tests/test_cloud_listing_cli.py +324 -0
- tests/test_config.py +7 -9
- tests/test_connect_to_cloud.py +82 -0
- tests/test_core.py +151 -4
- tests/test_dbt.py +7 -7
- tests/test_mcp_server.py +332 -0
- tests/test_pull_request.py +1 -1
- tests/test_server.py +25 -19
- tests/test_summary.py +29 -17
- recce/data/_next/static/Kcbs3GEIyH2LxgLYat0es/_buildManifest.js +0 -1
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/368-7587b306577df275.js +0 -65
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/3a92ee20-3b5d922d4157af5e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/6ef81909-694dc38134099299.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/8d700b6a-f0b1f6b9e0d97ce2.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-cee661090afbd6aa.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/state.py +0 -753
- recce_nightly-1.2.0.20250506.dist-info/RECORD +0 -142
- tests/test_state.py +0 -123
- /recce/data/_next/static/{Kcbs3GEIyH2LxgLYat0es → 52aV_JrNUZU6dMFgvTQEO}/_ssgManifest.js +0 -0
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CI provider detection and orchestration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from recce_cloud.ci_providers.base import BaseCIProvider, CIInfo
|
|
10
|
+
from recce_cloud.ci_providers.github_actions import GitHubActionsProvider
|
|
11
|
+
from recce_cloud.ci_providers.gitlab_ci import GitLabCIProvider
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CIDetector:
|
|
17
|
+
"""
|
|
18
|
+
Detects CI platform and extracts information.
|
|
19
|
+
|
|
20
|
+
Supports:
|
|
21
|
+
- GitHub Actions
|
|
22
|
+
- GitLab CI/CD
|
|
23
|
+
- Generic fallback (git commands)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Order matters: check in priority order
|
|
27
|
+
PROVIDERS = [
|
|
28
|
+
GitHubActionsProvider,
|
|
29
|
+
GitLabCIProvider,
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def detect(cls) -> CIInfo:
|
|
34
|
+
"""
|
|
35
|
+
Detect CI platform and extract information.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
CIInfo object with detected information
|
|
39
|
+
"""
|
|
40
|
+
# Try each provider in order
|
|
41
|
+
for provider_class in cls.PROVIDERS:
|
|
42
|
+
provider = provider_class()
|
|
43
|
+
if provider.can_handle():
|
|
44
|
+
logger.info(f"CI Platform: {provider_class.__name__.replace('Provider', '')}")
|
|
45
|
+
ci_info = provider.extract_ci_info()
|
|
46
|
+
cls._log_detected_values(ci_info)
|
|
47
|
+
return ci_info
|
|
48
|
+
|
|
49
|
+
# No CI platform detected, use generic fallback
|
|
50
|
+
logger.info("No CI platform detected, using git fallback")
|
|
51
|
+
ci_info = cls._fallback_detection()
|
|
52
|
+
cls._log_detected_values(ci_info)
|
|
53
|
+
return ci_info
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def apply_overrides(
|
|
57
|
+
cls,
|
|
58
|
+
ci_info: CIInfo,
|
|
59
|
+
cr: Optional[int] = None,
|
|
60
|
+
session_type: Optional[str] = None,
|
|
61
|
+
) -> CIInfo:
|
|
62
|
+
"""
|
|
63
|
+
Apply manual overrides to detected CI information.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
ci_info: Detected CI information
|
|
67
|
+
cr: Manual change request number override
|
|
68
|
+
session_type: Manual session type override
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
CIInfo with overrides applied
|
|
72
|
+
"""
|
|
73
|
+
# Log overrides
|
|
74
|
+
if cr is not None and cr != ci_info.cr_number:
|
|
75
|
+
logger.info(f"Using manual override: --cr {cr} (detected: {ci_info.cr_number})")
|
|
76
|
+
ci_info.cr_number = cr
|
|
77
|
+
# Rebuild CR URL if we have repository info
|
|
78
|
+
if ci_info.repository:
|
|
79
|
+
if ci_info.platform == "github-actions":
|
|
80
|
+
ci_info.cr_url = f"https://github.com/{ci_info.repository}/pull/{cr}"
|
|
81
|
+
elif ci_info.platform == "gitlab-ci":
|
|
82
|
+
server_url = os.getenv("CI_SERVER_URL", "https://gitlab.com")
|
|
83
|
+
ci_info.cr_url = f"{server_url}/{ci_info.repository}/-/merge_requests/{cr}"
|
|
84
|
+
|
|
85
|
+
if session_type is not None and session_type != ci_info.session_type:
|
|
86
|
+
logger.info(f"Using manual override: --type {session_type} (detected: {ci_info.session_type})")
|
|
87
|
+
ci_info.session_type = session_type
|
|
88
|
+
|
|
89
|
+
# Re-determine session type if CR was overridden
|
|
90
|
+
if cr is not None:
|
|
91
|
+
if session_type is None: # Only if not manually overridden
|
|
92
|
+
ci_info.session_type = BaseCIProvider.determine_session_type(ci_info.cr_number, ci_info.source_branch)
|
|
93
|
+
|
|
94
|
+
return ci_info
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def _fallback_detection(cls) -> CIInfo:
|
|
98
|
+
"""
|
|
99
|
+
Fallback detection using git commands when no CI platform is detected.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
CIInfo with basic git information
|
|
103
|
+
"""
|
|
104
|
+
commit_sha = BaseCIProvider.run_git_command(["git", "rev-parse", "HEAD"])
|
|
105
|
+
source_branch = BaseCIProvider.run_git_command(["git", "branch", "--show-current"])
|
|
106
|
+
|
|
107
|
+
session_type = BaseCIProvider.determine_session_type(None, source_branch)
|
|
108
|
+
|
|
109
|
+
return CIInfo(
|
|
110
|
+
platform=None,
|
|
111
|
+
cr_number=None,
|
|
112
|
+
session_type=session_type,
|
|
113
|
+
commit_sha=commit_sha,
|
|
114
|
+
base_branch="main", # Default
|
|
115
|
+
source_branch=source_branch,
|
|
116
|
+
repository=None,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def _log_detected_values(cls, ci_info: CIInfo) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Log detected values for transparency.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
ci_info: Detected CI information
|
|
126
|
+
"""
|
|
127
|
+
if ci_info.cr_number is not None:
|
|
128
|
+
if ci_info.platform == "github-actions":
|
|
129
|
+
logger.info(f"Detected PR number: {ci_info.cr_number}")
|
|
130
|
+
elif ci_info.platform == "gitlab-ci":
|
|
131
|
+
logger.info(f"Detected MR number: {ci_info.cr_number}")
|
|
132
|
+
else:
|
|
133
|
+
logger.info(f"Detected CR number: {ci_info.cr_number}")
|
|
134
|
+
else:
|
|
135
|
+
logger.info("No CR number detected")
|
|
136
|
+
|
|
137
|
+
if ci_info.commit_sha:
|
|
138
|
+
logger.info(f"Detected commit SHA: {ci_info.commit_sha[:8]}...")
|
|
139
|
+
else:
|
|
140
|
+
logger.warning("Could not detect commit SHA")
|
|
141
|
+
|
|
142
|
+
logger.info(f"Detected base branch: {ci_info.base_branch}")
|
|
143
|
+
logger.info(f"Detected source branch: {ci_info.source_branch}")
|
|
144
|
+
logger.info(f"Session type: {ci_info.session_type}")
|
|
145
|
+
|
|
146
|
+
if ci_info.repository:
|
|
147
|
+
logger.info(f"Repository: {ci_info.repository}")
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitHub Actions CI provider.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from recce_cloud.ci_providers.base import BaseCIProvider, CIInfo
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitHubActionsProvider(BaseCIProvider):
|
|
13
|
+
"""GitHub Actions CI provider implementation."""
|
|
14
|
+
|
|
15
|
+
def can_handle(self) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Check if running in GitHub Actions.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
True if GITHUB_ACTIONS environment variable is 'true'
|
|
21
|
+
"""
|
|
22
|
+
return os.getenv("GITHUB_ACTIONS") == "true"
|
|
23
|
+
|
|
24
|
+
def extract_ci_info(self) -> CIInfo:
|
|
25
|
+
"""
|
|
26
|
+
Extract CI information from GitHub Actions environment.
|
|
27
|
+
|
|
28
|
+
Environment variables used:
|
|
29
|
+
- GITHUB_EVENT_PATH: Path to event payload JSON (for PR number)
|
|
30
|
+
- GITHUB_SHA: Commit SHA
|
|
31
|
+
- GITHUB_BASE_REF: Base/target branch (PR only)
|
|
32
|
+
- GITHUB_HEAD_REF: Source/head branch (PR only)
|
|
33
|
+
- GITHUB_REF_NAME: Branch name (fallback)
|
|
34
|
+
- GITHUB_REPOSITORY: Repository (owner/repo)
|
|
35
|
+
- GITHUB_TOKEN: Default access token (automatically provided by GitHub Actions)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
CIInfo object with extracted information
|
|
39
|
+
"""
|
|
40
|
+
cr_number = self._extract_pr_number()
|
|
41
|
+
commit_sha = self._extract_commit_sha()
|
|
42
|
+
base_branch = self._extract_base_branch()
|
|
43
|
+
source_branch = self._extract_source_branch()
|
|
44
|
+
repository = os.getenv("GITHUB_REPOSITORY")
|
|
45
|
+
access_token = os.getenv("GITHUB_TOKEN")
|
|
46
|
+
|
|
47
|
+
# Build CR URL (PR URL) if we have the necessary information
|
|
48
|
+
cr_url = None
|
|
49
|
+
if cr_number is not None and repository:
|
|
50
|
+
cr_url = f"https://github.com/{repository}/pull/{cr_number}"
|
|
51
|
+
|
|
52
|
+
session_type = self.determine_session_type(cr_number, source_branch)
|
|
53
|
+
|
|
54
|
+
return CIInfo(
|
|
55
|
+
platform="github-actions",
|
|
56
|
+
cr_number=cr_number,
|
|
57
|
+
cr_url=cr_url,
|
|
58
|
+
session_type=session_type,
|
|
59
|
+
commit_sha=commit_sha,
|
|
60
|
+
base_branch=base_branch,
|
|
61
|
+
source_branch=source_branch,
|
|
62
|
+
repository=repository,
|
|
63
|
+
access_token=access_token,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _extract_pr_number(self) -> Optional[int]:
|
|
67
|
+
"""
|
|
68
|
+
Extract PR number from GitHub event payload.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
PR number if detected, None otherwise
|
|
72
|
+
"""
|
|
73
|
+
event_path = os.getenv("GITHUB_EVENT_PATH")
|
|
74
|
+
if not event_path or not os.path.exists(event_path):
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with open(event_path, "r") as f:
|
|
79
|
+
event_data = json.load(f)
|
|
80
|
+
pr_data = event_data.get("pull_request", {})
|
|
81
|
+
pr_number = pr_data.get("number")
|
|
82
|
+
if pr_number is not None:
|
|
83
|
+
return int(pr_number)
|
|
84
|
+
except (json.JSONDecodeError, ValueError, OSError):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def _extract_commit_sha(self) -> Optional[str]:
|
|
90
|
+
"""
|
|
91
|
+
Extract commit SHA from GitHub Actions environment.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Commit SHA if detected, falls back to git command
|
|
95
|
+
"""
|
|
96
|
+
commit_sha = os.getenv("GITHUB_SHA")
|
|
97
|
+
if commit_sha:
|
|
98
|
+
return commit_sha
|
|
99
|
+
|
|
100
|
+
# Fallback to git command
|
|
101
|
+
return self.run_git_command(["git", "rev-parse", "HEAD"])
|
|
102
|
+
|
|
103
|
+
def _extract_base_branch(self) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Extract base/target branch from GitHub Actions environment.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Base branch name, defaults to 'main' if not detected
|
|
109
|
+
"""
|
|
110
|
+
# GITHUB_BASE_REF is only set for pull_request events
|
|
111
|
+
base_branch = os.getenv("GITHUB_BASE_REF")
|
|
112
|
+
if base_branch:
|
|
113
|
+
return base_branch
|
|
114
|
+
|
|
115
|
+
# Default to main
|
|
116
|
+
return "main"
|
|
117
|
+
|
|
118
|
+
def _extract_source_branch(self) -> Optional[str]:
|
|
119
|
+
"""
|
|
120
|
+
Extract source/head branch from GitHub Actions environment.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Source branch name if detected
|
|
124
|
+
"""
|
|
125
|
+
# GITHUB_HEAD_REF is only set for pull_request events
|
|
126
|
+
source_branch = os.getenv("GITHUB_HEAD_REF")
|
|
127
|
+
if source_branch:
|
|
128
|
+
return source_branch
|
|
129
|
+
|
|
130
|
+
# Fallback to GITHUB_REF_NAME
|
|
131
|
+
source_branch = os.getenv("GITHUB_REF_NAME")
|
|
132
|
+
if source_branch:
|
|
133
|
+
return source_branch
|
|
134
|
+
|
|
135
|
+
# Fallback to git command
|
|
136
|
+
return self.run_git_command(["git", "branch", "--show-current"])
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitLab CI/CD provider.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from recce_cloud.ci_providers.base import BaseCIProvider, CIInfo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitLabCIProvider(BaseCIProvider):
|
|
12
|
+
"""GitLab CI/CD provider implementation."""
|
|
13
|
+
|
|
14
|
+
def can_handle(self) -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Check if running in GitLab CI.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
True if GITLAB_CI environment variable is 'true'
|
|
20
|
+
"""
|
|
21
|
+
return os.getenv("GITLAB_CI") == "true"
|
|
22
|
+
|
|
23
|
+
def extract_ci_info(self) -> CIInfo:
|
|
24
|
+
"""
|
|
25
|
+
Extract CI information from GitLab CI environment.
|
|
26
|
+
|
|
27
|
+
Environment variables used:
|
|
28
|
+
- CI_MERGE_REQUEST_IID: Merge request number
|
|
29
|
+
- CI_COMMIT_SHA: Commit SHA
|
|
30
|
+
- CI_MERGE_REQUEST_TARGET_BRANCH_NAME: Target branch (MR only)
|
|
31
|
+
- CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: Source branch (MR only)
|
|
32
|
+
- CI_COMMIT_REF_NAME: Branch name (fallback)
|
|
33
|
+
- CI_PROJECT_PATH: Repository path (group/project)
|
|
34
|
+
- CI_SERVER_URL: GitLab instance URL (defaults to https://gitlab.com)
|
|
35
|
+
- CI_JOB_TOKEN: Default access token (automatically provided by GitLab CI)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
CIInfo object with extracted information
|
|
39
|
+
"""
|
|
40
|
+
cr_number = self._extract_mr_number()
|
|
41
|
+
commit_sha = self._extract_commit_sha()
|
|
42
|
+
base_branch = self._extract_base_branch()
|
|
43
|
+
source_branch = self._extract_source_branch()
|
|
44
|
+
repository = os.getenv("CI_PROJECT_PATH")
|
|
45
|
+
access_token = os.getenv("CI_JOB_TOKEN")
|
|
46
|
+
|
|
47
|
+
# Build CR URL (MR URL) if we have the necessary information
|
|
48
|
+
cr_url = None
|
|
49
|
+
if cr_number is not None and repository:
|
|
50
|
+
server_url = os.getenv("CI_SERVER_URL", "https://gitlab.com")
|
|
51
|
+
cr_url = f"{server_url}/{repository}/-/merge_requests/{cr_number}"
|
|
52
|
+
|
|
53
|
+
session_type = self.determine_session_type(cr_number, source_branch)
|
|
54
|
+
|
|
55
|
+
return CIInfo(
|
|
56
|
+
platform="gitlab-ci",
|
|
57
|
+
cr_number=cr_number,
|
|
58
|
+
cr_url=cr_url,
|
|
59
|
+
session_type=session_type,
|
|
60
|
+
commit_sha=commit_sha,
|
|
61
|
+
base_branch=base_branch,
|
|
62
|
+
source_branch=source_branch,
|
|
63
|
+
repository=repository,
|
|
64
|
+
access_token=access_token,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _extract_mr_number(self) -> Optional[int]:
|
|
68
|
+
"""
|
|
69
|
+
Extract MR number from GitLab CI environment.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
MR number if detected, None otherwise
|
|
73
|
+
"""
|
|
74
|
+
mr_iid = os.getenv("CI_MERGE_REQUEST_IID")
|
|
75
|
+
if mr_iid:
|
|
76
|
+
try:
|
|
77
|
+
return int(mr_iid)
|
|
78
|
+
except ValueError:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def _extract_commit_sha(self) -> Optional[str]:
|
|
84
|
+
"""
|
|
85
|
+
Extract commit SHA from GitLab CI environment.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Commit SHA if detected, falls back to git command
|
|
89
|
+
"""
|
|
90
|
+
commit_sha = os.getenv("CI_COMMIT_SHA")
|
|
91
|
+
if commit_sha:
|
|
92
|
+
return commit_sha
|
|
93
|
+
|
|
94
|
+
# Fallback to git command
|
|
95
|
+
return self.run_git_command(["git", "rev-parse", "HEAD"])
|
|
96
|
+
|
|
97
|
+
def _extract_base_branch(self) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Extract base/target branch from GitLab CI environment.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Base branch name, defaults to 'main' if not detected
|
|
103
|
+
"""
|
|
104
|
+
# CI_MERGE_REQUEST_TARGET_BRANCH_NAME is only set for merge request pipelines
|
|
105
|
+
base_branch = os.getenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME")
|
|
106
|
+
if base_branch:
|
|
107
|
+
return base_branch
|
|
108
|
+
|
|
109
|
+
# Default to main
|
|
110
|
+
return "main"
|
|
111
|
+
|
|
112
|
+
def _extract_source_branch(self) -> Optional[str]:
|
|
113
|
+
"""
|
|
114
|
+
Extract source branch from GitLab CI environment.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Source branch name if detected
|
|
118
|
+
"""
|
|
119
|
+
# CI_MERGE_REQUEST_SOURCE_BRANCH_NAME is only set for merge request pipelines
|
|
120
|
+
source_branch = os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME")
|
|
121
|
+
if source_branch:
|
|
122
|
+
return source_branch
|
|
123
|
+
|
|
124
|
+
# Fallback to CI_COMMIT_REF_NAME
|
|
125
|
+
source_branch = os.getenv("CI_COMMIT_REF_NAME")
|
|
126
|
+
if source_branch:
|
|
127
|
+
return source_branch
|
|
128
|
+
|
|
129
|
+
# Fallback to git command
|
|
130
|
+
return self.run_git_command(["git", "branch", "--show-current"])
|
recce_cloud/cli.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Recce Cloud CLI - Lightweight command for managing Recce Cloud operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.logging import RichHandler
|
|
13
|
+
|
|
14
|
+
from recce_cloud import __version__
|
|
15
|
+
from recce_cloud.artifact import get_adapter_type, verify_artifacts_path
|
|
16
|
+
from recce_cloud.ci_providers import CIDetector
|
|
17
|
+
from recce_cloud.upload import upload_to_existing_session, upload_with_platform_apis
|
|
18
|
+
|
|
19
|
+
# Configure logging
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=logging.INFO,
|
|
22
|
+
format="%(message)s",
|
|
23
|
+
handlers=[RichHandler(console=Console(stderr=True), show_time=False, show_path=False)],
|
|
24
|
+
)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# Suppress CI detector logs since we display formatted output in the CLI
|
|
28
|
+
logging.getLogger("recce_cloud.ci_providers.detector").setLevel(logging.WARNING)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
def cloud_cli():
|
|
33
|
+
"""
|
|
34
|
+
Recce Cloud CLI - Manage Recce Cloud sessions and state files.
|
|
35
|
+
|
|
36
|
+
A lightweight tool for CI/CD environments to interact with Recce Cloud
|
|
37
|
+
without the heavy dependencies of the full recce package.
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@cloud_cli.command()
|
|
43
|
+
def version():
|
|
44
|
+
"""Show the version of recce-cloud."""
|
|
45
|
+
click.echo(__version__)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@cloud_cli.command()
|
|
49
|
+
@click.option(
|
|
50
|
+
"--target-path",
|
|
51
|
+
type=click.Path(exists=True),
|
|
52
|
+
default="target",
|
|
53
|
+
help="Path to dbt target directory containing manifest.json and catalog.json",
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--session-id",
|
|
57
|
+
envvar="RECCE_SESSION_ID",
|
|
58
|
+
help="Recce Cloud session ID to upload artifacts to (or use RECCE_SESSION_ID env var). "
|
|
59
|
+
"If not provided, session will be created automatically using platform-specific APIs (GitHub/GitLab).",
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--cr",
|
|
63
|
+
type=int,
|
|
64
|
+
help="Change request number (PR/MR) (overrides auto-detection)",
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"--type",
|
|
68
|
+
"session_type",
|
|
69
|
+
type=click.Choice(["cr", "prod", "dev"]),
|
|
70
|
+
help="Session type (overrides auto-detection)",
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"--dry-run",
|
|
74
|
+
is_flag=True,
|
|
75
|
+
help="Show what would be uploaded without actually uploading",
|
|
76
|
+
)
|
|
77
|
+
def upload(target_path, session_id, cr, session_type, dry_run):
|
|
78
|
+
"""
|
|
79
|
+
Upload dbt artifacts (manifest.json, catalog.json) to Recce Cloud.
|
|
80
|
+
|
|
81
|
+
\b
|
|
82
|
+
Authentication (auto-detected):
|
|
83
|
+
- RECCE_API_TOKEN (for --session-id workflow)
|
|
84
|
+
- GITHUB_TOKEN (GitHub Actions)
|
|
85
|
+
- CI_JOB_TOKEN (GitLab CI)
|
|
86
|
+
|
|
87
|
+
\b
|
|
88
|
+
Common Examples:
|
|
89
|
+
# Auto-create session in PR/MR
|
|
90
|
+
recce-cloud upload
|
|
91
|
+
|
|
92
|
+
# Upload production metadata from main branch
|
|
93
|
+
recce-cloud upload --type prod
|
|
94
|
+
|
|
95
|
+
# Upload to specific session
|
|
96
|
+
recce-cloud upload --session-id abc123
|
|
97
|
+
|
|
98
|
+
# Custom target path
|
|
99
|
+
recce-cloud upload --target-path custom-target
|
|
100
|
+
"""
|
|
101
|
+
console = Console()
|
|
102
|
+
|
|
103
|
+
# 1. Auto-detect CI environment information
|
|
104
|
+
console.rule("CI Environment Detection", style="blue")
|
|
105
|
+
try:
|
|
106
|
+
ci_info = CIDetector.detect()
|
|
107
|
+
ci_info = CIDetector.apply_overrides(ci_info, cr=cr, session_type=session_type)
|
|
108
|
+
|
|
109
|
+
# Display detected CI information immediately
|
|
110
|
+
if ci_info:
|
|
111
|
+
info_table = []
|
|
112
|
+
if ci_info.platform:
|
|
113
|
+
info_table.append(f"[cyan]Platform:[/cyan] {ci_info.platform}")
|
|
114
|
+
|
|
115
|
+
# Display CR number as PR or MR based on platform
|
|
116
|
+
if ci_info.cr_number is not None:
|
|
117
|
+
if ci_info.platform == "github-actions":
|
|
118
|
+
info_table.append(f"[cyan]PR Number:[/cyan] {ci_info.cr_number}")
|
|
119
|
+
elif ci_info.platform == "gitlab-ci":
|
|
120
|
+
info_table.append(f"[cyan]MR Number:[/cyan] {ci_info.cr_number}")
|
|
121
|
+
else:
|
|
122
|
+
info_table.append(f"[cyan]CR Number:[/cyan] {ci_info.cr_number}")
|
|
123
|
+
|
|
124
|
+
# Display CR URL as PR URL or MR URL based on platform
|
|
125
|
+
if ci_info.cr_url:
|
|
126
|
+
if ci_info.platform == "github-actions":
|
|
127
|
+
info_table.append(f"[cyan]PR URL:[/cyan] {ci_info.cr_url}")
|
|
128
|
+
elif ci_info.platform == "gitlab-ci":
|
|
129
|
+
info_table.append(f"[cyan]MR URL:[/cyan] {ci_info.cr_url}")
|
|
130
|
+
else:
|
|
131
|
+
info_table.append(f"[cyan]CR URL:[/cyan] {ci_info.cr_url}")
|
|
132
|
+
|
|
133
|
+
if ci_info.session_type:
|
|
134
|
+
info_table.append(f"[cyan]Session Type:[/cyan] {ci_info.session_type}")
|
|
135
|
+
if ci_info.commit_sha:
|
|
136
|
+
info_table.append(f"[cyan]Commit SHA:[/cyan] {ci_info.commit_sha[:8]}...")
|
|
137
|
+
if ci_info.base_branch:
|
|
138
|
+
info_table.append(f"[cyan]Base Branch:[/cyan] {ci_info.base_branch}")
|
|
139
|
+
if ci_info.source_branch:
|
|
140
|
+
info_table.append(f"[cyan]Source Branch:[/cyan] {ci_info.source_branch}")
|
|
141
|
+
if ci_info.repository:
|
|
142
|
+
info_table.append(f"[cyan]Repository:[/cyan] {ci_info.repository}")
|
|
143
|
+
|
|
144
|
+
for line in info_table:
|
|
145
|
+
console.print(line)
|
|
146
|
+
else:
|
|
147
|
+
console.print("[yellow]No CI environment detected[/yellow]")
|
|
148
|
+
except Exception as e:
|
|
149
|
+
console.print(f"[yellow]Warning:[/yellow] Failed to detect CI environment: {e}")
|
|
150
|
+
console.print("Continuing without CI metadata...")
|
|
151
|
+
ci_info = None
|
|
152
|
+
|
|
153
|
+
# 2. Validate artifacts exist
|
|
154
|
+
if not verify_artifacts_path(target_path):
|
|
155
|
+
console.print(f"[red]Error:[/red] Invalid target path: {target_path}")
|
|
156
|
+
console.print("Please provide a valid target path containing manifest.json and catalog.json.")
|
|
157
|
+
sys.exit(3)
|
|
158
|
+
|
|
159
|
+
manifest_path = os.path.join(target_path, "manifest.json")
|
|
160
|
+
catalog_path = os.path.join(target_path, "catalog.json")
|
|
161
|
+
|
|
162
|
+
# 3. Extract adapter type from manifest
|
|
163
|
+
try:
|
|
164
|
+
adapter_type = get_adapter_type(manifest_path)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
console.print("[red]Error:[/red] Failed to parse adapter type from manifest.json")
|
|
167
|
+
console.print(f"Reason: {e}")
|
|
168
|
+
sys.exit(3)
|
|
169
|
+
|
|
170
|
+
# 4. Handle dry-run mode (before authentication or API calls)
|
|
171
|
+
if dry_run:
|
|
172
|
+
console.rule("Dry Run Summary", style="yellow")
|
|
173
|
+
console.print("[yellow]Dry run mode enabled - no actual upload will be performed[/yellow]")
|
|
174
|
+
console.print()
|
|
175
|
+
|
|
176
|
+
# Display platform information if detected
|
|
177
|
+
if ci_info and ci_info.platform:
|
|
178
|
+
console.print("[cyan]Platform Information:[/cyan]")
|
|
179
|
+
console.print(f" • Platform: {ci_info.platform}")
|
|
180
|
+
if ci_info.repository:
|
|
181
|
+
console.print(f" • Repository: {ci_info.repository}")
|
|
182
|
+
if ci_info.cr_number is not None:
|
|
183
|
+
console.print(f" • CR Number: {ci_info.cr_number}")
|
|
184
|
+
if ci_info.commit_sha:
|
|
185
|
+
console.print(f" • Commit SHA: {ci_info.commit_sha[:8]}")
|
|
186
|
+
if ci_info.source_branch:
|
|
187
|
+
console.print(f" • Source Branch: {ci_info.source_branch}")
|
|
188
|
+
if ci_info.base_branch:
|
|
189
|
+
console.print(f" • Base Branch: {ci_info.base_branch}")
|
|
190
|
+
if ci_info.session_type:
|
|
191
|
+
console.print(f" • Session Type: {ci_info.session_type}")
|
|
192
|
+
console.print()
|
|
193
|
+
|
|
194
|
+
# Display upload summary
|
|
195
|
+
console.print("[cyan]Upload Workflow:[/cyan]")
|
|
196
|
+
if session_id:
|
|
197
|
+
console.print(" • Upload to existing session")
|
|
198
|
+
console.print(f" • Session ID: {session_id}")
|
|
199
|
+
else:
|
|
200
|
+
console.print(" • Auto-create session and upload")
|
|
201
|
+
if ci_info and ci_info.platform in ["github-actions", "gitlab-ci"]:
|
|
202
|
+
console.print(" • Platform-specific APIs will be used")
|
|
203
|
+
else:
|
|
204
|
+
console.print(" • [yellow]Warning: Platform not supported for auto-session creation[/yellow]")
|
|
205
|
+
|
|
206
|
+
console.print()
|
|
207
|
+
console.print("[cyan]Files to upload:[/cyan]")
|
|
208
|
+
console.print(f" • manifest.json: {os.path.abspath(manifest_path)}")
|
|
209
|
+
console.print(f" • catalog.json: {os.path.abspath(catalog_path)}")
|
|
210
|
+
console.print(f" • Adapter type: {adapter_type}")
|
|
211
|
+
|
|
212
|
+
console.print()
|
|
213
|
+
console.print("[green]✓[/green] Dry run completed successfully")
|
|
214
|
+
sys.exit(0)
|
|
215
|
+
|
|
216
|
+
# 5. Choose upload workflow based on whether session_id is provided
|
|
217
|
+
if session_id:
|
|
218
|
+
# Generic workflow: Upload to existing session using session ID
|
|
219
|
+
# This workflow requires RECCE_API_TOKEN
|
|
220
|
+
token = os.getenv("RECCE_API_TOKEN")
|
|
221
|
+
if not token:
|
|
222
|
+
console.print("[red]Error:[/red] No RECCE_API_TOKEN provided")
|
|
223
|
+
console.print("Set RECCE_API_TOKEN environment variable for session-based upload")
|
|
224
|
+
sys.exit(2)
|
|
225
|
+
|
|
226
|
+
upload_to_existing_session(console, token, session_id, manifest_path, catalog_path, adapter_type, target_path)
|
|
227
|
+
else:
|
|
228
|
+
# Platform-specific workflow: Use platform APIs to create session and upload
|
|
229
|
+
# This workflow MUST use CI job tokens (CI_JOB_TOKEN or GITHUB_TOKEN)
|
|
230
|
+
if not ci_info or not ci_info.access_token:
|
|
231
|
+
console.print("[red]Error:[/red] Platform-specific upload requires CI environment")
|
|
232
|
+
console.print("Either run in GitHub Actions/GitLab CI or provide --session-id for generic upload")
|
|
233
|
+
sys.exit(2)
|
|
234
|
+
|
|
235
|
+
token = ci_info.access_token
|
|
236
|
+
if ci_info.platform == "github-actions":
|
|
237
|
+
console.print("[cyan]Info:[/cyan] Using GITHUB_TOKEN for platform-specific authentication")
|
|
238
|
+
elif ci_info.platform == "gitlab-ci":
|
|
239
|
+
console.print("[cyan]Info:[/cyan] Using CI_JOB_TOKEN for platform-specific authentication")
|
|
240
|
+
|
|
241
|
+
upload_with_platform_apis(console, token, ci_info, manifest_path, catalog_path, adapter_type, target_path)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
if __name__ == "__main__":
|
|
245
|
+
cloud_cli()
|