salla-gitpuller 1.0.0__tar.gz → 1.0.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.
- salla_gitpuller-1.0.2/PKG-INFO +35 -0
- salla_gitpuller-1.0.2/README.md +24 -0
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/gitpuller/__init__.py +18 -18
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/gitpuller/alert_manager.py +63 -63
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/gitpuller/gitpull.py +194 -202
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/gitpuller/slack_notifier.py +57 -59
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/gitpuller/state_manager.py +112 -112
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/gitpuller/utils.py +35 -35
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/pyproject.toml +19 -19
- salla_gitpuller-1.0.2/salla_gitpuller.egg-info/PKG-INFO +35 -0
- salla_gitpuller-1.0.0/PKG-INFO +0 -33
- salla_gitpuller-1.0.0/README.md +0 -22
- salla_gitpuller-1.0.0/salla_gitpuller.egg-info/PKG-INFO +0 -33
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/LICENSE +0 -0
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/salla_gitpuller.egg-info/SOURCES.txt +0 -0
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/salla_gitpuller.egg-info/dependency_links.txt +0 -0
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/salla_gitpuller.egg-info/top_level.txt +0 -0
- {salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/setup.cfg +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: salla_gitpuller
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A lightweight utility to git pull a repository using SSH deploy keys stored in environment variables
|
|
5
|
+
Author-email: Muhammad Zahid <zahidmuhammad127@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Zahid07/gitpuller
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# gitpuller
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from gitpuller import GitPullExecutor
|
|
18
|
+
|
|
19
|
+
executor = GitPullExecutor(slack_webhook_url="your_webhook_url", use_mage_ai=True)
|
|
20
|
+
result = executor.execute_with_alerting(
|
|
21
|
+
repo_path="/path/to/repo",
|
|
22
|
+
git_url="git@github.com:user/repo.git",
|
|
23
|
+
branch="master",
|
|
24
|
+
workspace_name="workspace1"
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Build
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
rm -rf build dist *.egg-info
|
|
32
|
+
python -m build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Upload to PyPI and update version in `pyproject.toml`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# gitpuller
|
|
2
|
+
|
|
3
|
+
## Usage
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from gitpuller import GitPullExecutor
|
|
7
|
+
|
|
8
|
+
executor = GitPullExecutor(slack_webhook_url="your_webhook_url", use_mage_ai=True)
|
|
9
|
+
result = executor.execute_with_alerting(
|
|
10
|
+
repo_path="/path/to/repo",
|
|
11
|
+
git_url="git@github.com:user/repo.git",
|
|
12
|
+
branch="master",
|
|
13
|
+
workspace_name="workspace1"
|
|
14
|
+
)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Build
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rm -rf build dist *.egg-info
|
|
21
|
+
python -m build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Upload to PyPI and update version in `pyproject.toml`.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
from .gitpull import GitPullExecutor
|
|
2
|
-
from .alert_manager import AlertManager
|
|
3
|
-
from .state_manager import StateManager, InMemoryStateManager, MageAIStateManager
|
|
4
|
-
from .slack_notifier import SlackNotifier
|
|
5
|
-
from .utils import transform_custom, get_repo_path, get_env_base_path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
__version__ = "1.0.0"
|
|
9
|
-
__all__ = [
|
|
10
|
-
"GitPullExecutor",
|
|
11
|
-
"AlertManager",
|
|
12
|
-
"StateManager",
|
|
13
|
-
"InMemoryStateManager",
|
|
14
|
-
"MageAIStateManager",
|
|
15
|
-
"SlackNotifier",
|
|
16
|
-
"transform_custom",
|
|
17
|
-
"get_repo_path",
|
|
18
|
-
"get_env_base_path"
|
|
1
|
+
from .gitpull import GitPullExecutor
|
|
2
|
+
from .alert_manager import AlertManager
|
|
3
|
+
from .state_manager import StateManager, InMemoryStateManager, MageAIStateManager
|
|
4
|
+
from .slack_notifier import SlackNotifier
|
|
5
|
+
from .utils import transform_custom, get_repo_path, get_env_base_path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"GitPullExecutor",
|
|
11
|
+
"AlertManager",
|
|
12
|
+
"StateManager",
|
|
13
|
+
"InMemoryStateManager",
|
|
14
|
+
"MageAIStateManager",
|
|
15
|
+
"SlackNotifier",
|
|
16
|
+
"transform_custom",
|
|
17
|
+
"get_repo_path",
|
|
18
|
+
"get_env_base_path"
|
|
19
19
|
]
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
2
|
-
from typing import Any, Dict, Optional, Tuple
|
|
3
|
-
from .state_manager import StateManager, InMemoryStateManager, MageAIStateManager
|
|
4
|
-
|
|
5
|
-
class AlertManager:
|
|
6
|
-
def __init__(self, state_manager: Optional[StateManager] = None, use_mage_ai: bool = False):
|
|
7
|
-
if state_manager is not None:
|
|
8
|
-
self.state_manager = state_manager
|
|
9
|
-
elif use_mage_ai:
|
|
10
|
-
try:
|
|
11
|
-
self.state_manager = MageAIStateManager()
|
|
12
|
-
except ImportError:
|
|
13
|
-
print("⚠️ Mage AI not available, falling back to in-memory state manager")
|
|
14
|
-
self.state_manager = InMemoryStateManager()
|
|
15
|
-
else:
|
|
16
|
-
self.state_manager = InMemoryStateManager()
|
|
17
|
-
|
|
18
|
-
def should_send_alert(
|
|
19
|
-
self,
|
|
20
|
-
pipeline_uuid: str,
|
|
21
|
-
current_error: str,
|
|
22
|
-
suppression_hours: int = 1
|
|
23
|
-
) -> Tuple[bool, Dict[str, Any]]:
|
|
24
|
-
|
|
25
|
-
state = self.state_manager.load_alert_state(pipeline_uuid)
|
|
26
|
-
last_error = state.get("last_error_message")
|
|
27
|
-
last_alert_time_str = state.get("last_alert_time")
|
|
28
|
-
|
|
29
|
-
# If no previous alert, always send
|
|
30
|
-
if not last_error or not last_alert_time_str:
|
|
31
|
-
return True, {"last_error_message": None, "last_alert_time": None}
|
|
32
|
-
|
|
33
|
-
# If error is different, always send
|
|
34
|
-
if last_error != current_error:
|
|
35
|
-
return True, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
36
|
-
|
|
37
|
-
# Same error - check if enough time has passed
|
|
38
|
-
try:
|
|
39
|
-
last_alert_time = datetime.fromisoformat(last_alert_time_str)
|
|
40
|
-
time_since_last_alert = datetime.now() - last_alert_time
|
|
41
|
-
|
|
42
|
-
if time_since_last_alert >= timedelta(hours=suppression_hours):
|
|
43
|
-
return True, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
44
|
-
else:
|
|
45
|
-
return False, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
46
|
-
except (ValueError, TypeError):
|
|
47
|
-
# If we can't parse the time, send the alert to be safe
|
|
48
|
-
return True, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
49
|
-
|
|
50
|
-
def save_alert_state(
|
|
51
|
-
self,
|
|
52
|
-
pipeline_uuid: str,
|
|
53
|
-
error_message: str,
|
|
54
|
-
alert_time: datetime,
|
|
55
|
-
pipeline_status: str
|
|
56
|
-
) -> None:
|
|
57
|
-
"""Save alert state."""
|
|
58
|
-
self.state_manager.save_alert_state(pipeline_uuid, error_message, alert_time, pipeline_status)
|
|
59
|
-
|
|
60
|
-
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
61
|
-
"""Clear alert state on success."""
|
|
62
|
-
self.state_manager.clear_alert_state(pipeline_uuid)
|
|
63
|
-
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple
|
|
3
|
+
from .state_manager import StateManager, InMemoryStateManager, MageAIStateManager
|
|
4
|
+
|
|
5
|
+
class AlertManager:
|
|
6
|
+
def __init__(self, state_manager: Optional[StateManager] = None, use_mage_ai: bool = False):
|
|
7
|
+
if state_manager is not None:
|
|
8
|
+
self.state_manager = state_manager
|
|
9
|
+
elif use_mage_ai:
|
|
10
|
+
try:
|
|
11
|
+
self.state_manager = MageAIStateManager()
|
|
12
|
+
except ImportError:
|
|
13
|
+
print("⚠️ Mage AI not available, falling back to in-memory state manager")
|
|
14
|
+
self.state_manager = InMemoryStateManager()
|
|
15
|
+
else:
|
|
16
|
+
self.state_manager = InMemoryStateManager()
|
|
17
|
+
|
|
18
|
+
def should_send_alert(
|
|
19
|
+
self,
|
|
20
|
+
pipeline_uuid: str,
|
|
21
|
+
current_error: str,
|
|
22
|
+
suppression_hours: int = 1
|
|
23
|
+
) -> Tuple[bool, Dict[str, Any]]:
|
|
24
|
+
|
|
25
|
+
state = self.state_manager.load_alert_state(pipeline_uuid)
|
|
26
|
+
last_error = state.get("last_error_message")
|
|
27
|
+
last_alert_time_str = state.get("last_alert_time")
|
|
28
|
+
|
|
29
|
+
# If no previous alert, always send
|
|
30
|
+
if not last_error or not last_alert_time_str:
|
|
31
|
+
return True, {"last_error_message": None, "last_alert_time": None}
|
|
32
|
+
|
|
33
|
+
# If error is different, always send
|
|
34
|
+
if last_error != current_error:
|
|
35
|
+
return True, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
36
|
+
|
|
37
|
+
# Same error - check if enough time has passed
|
|
38
|
+
try:
|
|
39
|
+
last_alert_time = datetime.fromisoformat(last_alert_time_str)
|
|
40
|
+
time_since_last_alert = datetime.now() - last_alert_time
|
|
41
|
+
|
|
42
|
+
if time_since_last_alert >= timedelta(hours=suppression_hours):
|
|
43
|
+
return True, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
44
|
+
else:
|
|
45
|
+
return False, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
46
|
+
except (ValueError, TypeError):
|
|
47
|
+
# If we can't parse the time, send the alert to be safe
|
|
48
|
+
return True, {"last_error_message": last_error, "last_alert_time": last_alert_time_str}
|
|
49
|
+
|
|
50
|
+
def save_alert_state(
|
|
51
|
+
self,
|
|
52
|
+
pipeline_uuid: str,
|
|
53
|
+
error_message: str,
|
|
54
|
+
alert_time: datetime,
|
|
55
|
+
pipeline_status: str
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Save alert state."""
|
|
58
|
+
self.state_manager.save_alert_state(pipeline_uuid, error_message, alert_time, pipeline_status)
|
|
59
|
+
|
|
60
|
+
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
61
|
+
"""Clear alert state on success."""
|
|
62
|
+
self.state_manager.clear_alert_state(pipeline_uuid)
|
|
63
|
+
|
|
@@ -1,202 +1,194 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
from typing import Dict, Any, Optional
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from .alert_manager import AlertManager
|
|
6
|
-
from .slack_notifier import SlackNotifier
|
|
7
|
-
|
|
8
|
-
class GitPullExecutor:
|
|
9
|
-
def __init__(
|
|
10
|
-
self,
|
|
11
|
-
slack_webhook_url: Optional[str] = None,
|
|
12
|
-
use_mage_ai: bool = False,
|
|
13
|
-
state_manager: Optional[Any] = None
|
|
14
|
-
):
|
|
15
|
-
self.slack_notifier = SlackNotifier(webhook_url=slack_webhook_url)
|
|
16
|
-
self.alert_manager = AlertManager(state_manager=state_manager, use_mage_ai=use_mage_ai)
|
|
17
|
-
|
|
18
|
-
def normalize_ssh_key(self, key_material: str) -> str:
|
|
19
|
-
if not key_material:
|
|
20
|
-
return key_material
|
|
21
|
-
|
|
22
|
-
# Remove any quotes that might be wrapping the key
|
|
23
|
-
key_material = key_material.strip().strip('"').strip("'")
|
|
24
|
-
|
|
25
|
-
# Handle literal \n sequences
|
|
26
|
-
if "\\n" in key_material:
|
|
27
|
-
key_material = key_material.replace("\\n", "\n")
|
|
28
|
-
|
|
29
|
-
# Ensure proper line endings
|
|
30
|
-
if not key_material.endswith("\n"):
|
|
31
|
-
key_material += "\n"
|
|
32
|
-
|
|
33
|
-
return key_material
|
|
34
|
-
|
|
35
|
-
def prepare_ssh_key(
|
|
36
|
-
self,
|
|
37
|
-
key_material: str,
|
|
38
|
-
key_filename: str = "deploy_key",
|
|
39
|
-
ssh_dir: str = "/home/src/.ssh"
|
|
40
|
-
) -> str:
|
|
41
|
-
os.makedirs(ssh_dir, exist_ok=True)
|
|
42
|
-
os.chmod(ssh_dir, 0o700)
|
|
43
|
-
|
|
44
|
-
key_path = os.path.join(ssh_dir, key_filename)
|
|
45
|
-
|
|
46
|
-
# Write key with restricted permissions
|
|
47
|
-
with open(key_path, "w") as f:
|
|
48
|
-
f.write(key_material)
|
|
49
|
-
os.chmod(key_path, 0o600)
|
|
50
|
-
|
|
51
|
-
return key_path
|
|
52
|
-
|
|
53
|
-
def execute_git_pull(
|
|
54
|
-
self,
|
|
55
|
-
repo_path: str,
|
|
56
|
-
git_url: str,
|
|
57
|
-
branch: str = "master",
|
|
58
|
-
ssh_key: Optional[str] = None,
|
|
59
|
-
workspace_name: Optional[str] = None,
|
|
60
|
-
key_filename: Optional[str] = None,
|
|
61
|
-
ssh_dir: str = "/home/src/.ssh"
|
|
62
|
-
) -> Dict[str, Any]:
|
|
63
|
-
if not os.path.exists(repo_path):
|
|
64
|
-
raise ValueError(f"Repo path does not exist: {repo_path}")
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
if not ssh_key
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
time_since = datetime.now() - last_alert_dt
|
|
196
|
-
print(f"🔇 Alert suppressed - same error occurred {time_since} ago (suppression window: {suppression_hours} hour(s))")
|
|
197
|
-
except:
|
|
198
|
-
print(f"🔇 Alert suppressed - same error within suppression window ({suppression_hours} hour(s))")
|
|
199
|
-
|
|
200
|
-
# Re-raise the exception
|
|
201
|
-
raise
|
|
202
|
-
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from .alert_manager import AlertManager
|
|
6
|
+
from .slack_notifier import SlackNotifier
|
|
7
|
+
|
|
8
|
+
class GitPullExecutor:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
slack_webhook_url: Optional[str] = None,
|
|
12
|
+
use_mage_ai: bool = False,
|
|
13
|
+
state_manager: Optional[Any] = None
|
|
14
|
+
):
|
|
15
|
+
self.slack_notifier = SlackNotifier(webhook_url=slack_webhook_url)
|
|
16
|
+
self.alert_manager = AlertManager(state_manager=state_manager, use_mage_ai=use_mage_ai)
|
|
17
|
+
|
|
18
|
+
def normalize_ssh_key(self, key_material: str) -> str:
|
|
19
|
+
if not key_material:
|
|
20
|
+
return key_material
|
|
21
|
+
|
|
22
|
+
# Remove any quotes that might be wrapping the key
|
|
23
|
+
key_material = key_material.strip().strip('"').strip("'")
|
|
24
|
+
|
|
25
|
+
# Handle literal \n sequences
|
|
26
|
+
if "\\n" in key_material:
|
|
27
|
+
key_material = key_material.replace("\\n", "\n")
|
|
28
|
+
|
|
29
|
+
# Ensure proper line endings
|
|
30
|
+
if not key_material.endswith("\n"):
|
|
31
|
+
key_material += "\n"
|
|
32
|
+
|
|
33
|
+
return key_material
|
|
34
|
+
|
|
35
|
+
def prepare_ssh_key(
|
|
36
|
+
self,
|
|
37
|
+
key_material: str,
|
|
38
|
+
key_filename: str = "deploy_key",
|
|
39
|
+
ssh_dir: str = "/home/src/.ssh"
|
|
40
|
+
) -> str:
|
|
41
|
+
os.makedirs(ssh_dir, exist_ok=True)
|
|
42
|
+
os.chmod(ssh_dir, 0o700)
|
|
43
|
+
|
|
44
|
+
key_path = os.path.join(ssh_dir, key_filename)
|
|
45
|
+
|
|
46
|
+
# Write key with restricted permissions
|
|
47
|
+
with open(key_path, "w") as f:
|
|
48
|
+
f.write(key_material)
|
|
49
|
+
os.chmod(key_path, 0o600)
|
|
50
|
+
|
|
51
|
+
return key_path
|
|
52
|
+
|
|
53
|
+
def execute_git_pull(
|
|
54
|
+
self,
|
|
55
|
+
repo_path: str,
|
|
56
|
+
git_url: str,
|
|
57
|
+
branch: str = "master",
|
|
58
|
+
ssh_key: Optional[str] = None,
|
|
59
|
+
workspace_name: Optional[str] = None,
|
|
60
|
+
key_filename: Optional[str] = None,
|
|
61
|
+
ssh_dir: str = "/home/src/.ssh"
|
|
62
|
+
) -> Dict[str, Any]:
|
|
63
|
+
if not os.path.exists(repo_path):
|
|
64
|
+
raise ValueError(f"Repo path does not exist: {repo_path}")
|
|
65
|
+
|
|
66
|
+
# Validate SSH key
|
|
67
|
+
if not ssh_key:
|
|
68
|
+
raise ValueError("SSH key is required. Provide it directly as a parameter.")
|
|
69
|
+
|
|
70
|
+
# Normalize key
|
|
71
|
+
ssh_key = self.normalize_ssh_key(ssh_key)
|
|
72
|
+
|
|
73
|
+
# Prepare key file
|
|
74
|
+
if not key_filename:
|
|
75
|
+
key_filename = f"{workspace_name}_deploy" if workspace_name else "deploy_key"
|
|
76
|
+
|
|
77
|
+
key_path = self.prepare_ssh_key(ssh_key, key_filename, ssh_dir)
|
|
78
|
+
|
|
79
|
+
# Execute git pull
|
|
80
|
+
cmd = [
|
|
81
|
+
"git",
|
|
82
|
+
"-c",
|
|
83
|
+
f"core.sshCommand=ssh -i {key_path} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new",
|
|
84
|
+
"pull",
|
|
85
|
+
git_url,
|
|
86
|
+
branch,
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
git_output = ""
|
|
90
|
+
git_status = "success"
|
|
91
|
+
|
|
92
|
+
original_dir = None
|
|
93
|
+
try:
|
|
94
|
+
original_dir = os.getcwd()
|
|
95
|
+
os.chdir(repo_path)
|
|
96
|
+
|
|
97
|
+
result = subprocess.run(
|
|
98
|
+
cmd,
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
check=True,
|
|
102
|
+
)
|
|
103
|
+
git_output = result.stdout
|
|
104
|
+
git_status = "success"
|
|
105
|
+
except subprocess.CalledProcessError as e:
|
|
106
|
+
git_output = (e.stdout or "") + (e.stderr or "")
|
|
107
|
+
git_status = "error"
|
|
108
|
+
raise
|
|
109
|
+
finally:
|
|
110
|
+
if original_dir:
|
|
111
|
+
try:
|
|
112
|
+
os.chdir(original_dir)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
# Clean up the key file
|
|
116
|
+
try:
|
|
117
|
+
os.remove(key_path)
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
"workspace": workspace_name or "N/A",
|
|
123
|
+
"repo_path": repo_path,
|
|
124
|
+
"git_pull_status": git_status,
|
|
125
|
+
"git_pull_output": git_output.strip(),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def execute_with_alerting(
|
|
129
|
+
self,
|
|
130
|
+
repo_path: str,
|
|
131
|
+
git_url: str,
|
|
132
|
+
branch: str = "master",
|
|
133
|
+
ssh_key: Optional[str] = None,
|
|
134
|
+
workspace_name: Optional[str] = None,
|
|
135
|
+
pipeline_uuid: str = "auto_git_pull",
|
|
136
|
+
suppression_hours: int = 1,
|
|
137
|
+
key_filename: Optional[str] = None,
|
|
138
|
+
ssh_dir: str = "/home/src/.ssh"
|
|
139
|
+
) -> Dict[str, Any]:
|
|
140
|
+
repo_name = git_url.split('/')[-1].replace('.git', '')
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
result = self.execute_git_pull(
|
|
144
|
+
repo_path=repo_path,
|
|
145
|
+
git_url=git_url,
|
|
146
|
+
branch=branch,
|
|
147
|
+
ssh_key=ssh_key,
|
|
148
|
+
workspace_name=workspace_name,
|
|
149
|
+
key_filename=key_filename,
|
|
150
|
+
ssh_dir=ssh_dir
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Clear alert state on success
|
|
154
|
+
self.alert_manager.clear_alert_state(pipeline_uuid)
|
|
155
|
+
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
git_output = str(e)
|
|
160
|
+
error_signature = git_output.strip()[:500]
|
|
161
|
+
|
|
162
|
+
# Check if we should send alert
|
|
163
|
+
should_alert, _ = self.alert_manager.should_send_alert(
|
|
164
|
+
pipeline_uuid,
|
|
165
|
+
error_signature,
|
|
166
|
+
suppression_hours=suppression_hours
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if should_alert:
|
|
170
|
+
# Send alert
|
|
171
|
+
self.slack_notifier.send_alert(repo_name, git_output[:1500])
|
|
172
|
+
|
|
173
|
+
# Save alert state
|
|
174
|
+
self.alert_manager.save_alert_state(
|
|
175
|
+
pipeline_uuid,
|
|
176
|
+
error_signature,
|
|
177
|
+
datetime.now(),
|
|
178
|
+
pipeline_status="failed"
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
# Alert suppressed
|
|
182
|
+
state = self.alert_manager.state_manager.load_alert_state(pipeline_uuid)
|
|
183
|
+
last_alert_time = state.get("last_alert_time")
|
|
184
|
+
if last_alert_time:
|
|
185
|
+
try:
|
|
186
|
+
last_alert_dt = datetime.fromisoformat(last_alert_time)
|
|
187
|
+
time_since = datetime.now() - last_alert_dt
|
|
188
|
+
print(f"🔇 Alert suppressed - same error occurred {time_since} ago (suppression window: {suppression_hours} hour(s))")
|
|
189
|
+
except:
|
|
190
|
+
print(f"🔇 Alert suppressed - same error within suppression window ({suppression_hours} hour(s))")
|
|
191
|
+
|
|
192
|
+
# Re-raise the exception
|
|
193
|
+
raise
|
|
194
|
+
|
|
@@ -1,60 +1,58 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
except Exception as e:
|
|
59
|
-
print(f"⚠️ Failed to send Slack alert: {e}")
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SlackNotifier:
|
|
6
|
+
|
|
7
|
+
def __init__(self, webhook_url: Optional[str] = None):
|
|
8
|
+
if not webhook_url:
|
|
9
|
+
raise ValueError(
|
|
10
|
+
"Slack webhook URL is required. Provide it as parameter."
|
|
11
|
+
)
|
|
12
|
+
self.webhook_url = webhook_url
|
|
13
|
+
|
|
14
|
+
def create_failure_payload(self, repo_name: str, error_output: str) -> Dict[str, Any]:
|
|
15
|
+
payload = {
|
|
16
|
+
"text": f"Automate Git Pull Pipeline Failed",
|
|
17
|
+
"blocks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "header",
|
|
20
|
+
"text": {
|
|
21
|
+
"type": "plain_text",
|
|
22
|
+
"text": f":alert: Automate Git Pull Pipeline Failed :alert:"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "divider"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "section",
|
|
30
|
+
"text": {
|
|
31
|
+
"type": "mrkdwn",
|
|
32
|
+
"text": f"*Repository:* `{repo_name}`"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if error_output:
|
|
39
|
+
payload["blocks"].append({
|
|
40
|
+
"type": "section",
|
|
41
|
+
"text": {
|
|
42
|
+
"type": "mrkdwn",
|
|
43
|
+
"text": f"*Error Output:*\n```{error_output[:1500]}```"
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return payload
|
|
48
|
+
|
|
49
|
+
def send_alert(self, repo_name: str, error_output: str = "") -> bool:
|
|
50
|
+
try:
|
|
51
|
+
payload = self.create_failure_payload(repo_name, error_output)
|
|
52
|
+
response = requests.post(self.webhook_url, json=payload, timeout=10)
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
print("✅ Alert sent to Slack")
|
|
55
|
+
return True
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"⚠️ Failed to send Slack alert: {e}")
|
|
60
58
|
return False
|
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
from typing import Any, Dict, Optional
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
|
|
4
|
-
class StateManager:
|
|
5
|
-
def load_alert_state(self, pipeline_uuid: str) -> Dict[str, Any]:
|
|
6
|
-
"""Load alert state for a pipeline."""
|
|
7
|
-
raise NotImplementedError
|
|
8
|
-
|
|
9
|
-
def save_alert_state(
|
|
10
|
-
self,
|
|
11
|
-
pipeline_uuid: str,
|
|
12
|
-
error_message: str,
|
|
13
|
-
alert_time: datetime,
|
|
14
|
-
pipeline_status: str
|
|
15
|
-
) -> None:
|
|
16
|
-
"""Save alert state for a pipeline."""
|
|
17
|
-
raise NotImplementedError
|
|
18
|
-
|
|
19
|
-
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
20
|
-
"""Clear alert state for a pipeline."""
|
|
21
|
-
raise NotImplementedError
|
|
22
|
-
|
|
23
|
-
class InMemoryStateManager(StateManager):
|
|
24
|
-
def __init__(self):
|
|
25
|
-
self._state: Dict[str, Dict[str, Any]] = {}
|
|
26
|
-
|
|
27
|
-
def load_alert_state(self, pipeline_uuid: str) -> Dict[str, Any]:
|
|
28
|
-
"""Load alert state from memory."""
|
|
29
|
-
return self._state.get(pipeline_uuid, {
|
|
30
|
-
"last_alert_time": None,
|
|
31
|
-
"last_error_message": None
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
def save_alert_state(
|
|
35
|
-
self,
|
|
36
|
-
pipeline_uuid: str,
|
|
37
|
-
error_message: str,
|
|
38
|
-
alert_time: datetime,
|
|
39
|
-
pipeline_status: str
|
|
40
|
-
) -> None:
|
|
41
|
-
"""Save alert state to memory."""
|
|
42
|
-
self._state[pipeline_uuid] = {
|
|
43
|
-
"last_alert_time": alert_time.isoformat(),
|
|
44
|
-
"last_error_message": error_message,
|
|
45
|
-
"pipeline_status": pipeline_status,
|
|
46
|
-
"last_alert_timestamp": alert_time.isoformat()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
50
|
-
"""Clear alert state from memory."""
|
|
51
|
-
if pipeline_uuid in self._state:
|
|
52
|
-
self._state[pipeline_uuid] = {
|
|
53
|
-
"last_alert_time": None,
|
|
54
|
-
"last_error_message": None,
|
|
55
|
-
"pipeline_status": "success"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
class MageAIStateManager(StateManager):
|
|
59
|
-
def __init__(self):
|
|
60
|
-
try:
|
|
61
|
-
from mage_ai.data_preparation.variable_manager import (
|
|
62
|
-
set_global_variable,
|
|
63
|
-
get_global_variable
|
|
64
|
-
)
|
|
65
|
-
self._set_global_variable = set_global_variable
|
|
66
|
-
self._get_global_variable = get_global_variable
|
|
67
|
-
except ImportError:
|
|
68
|
-
raise ImportError(
|
|
69
|
-
"Mage AI is not installed. Install it with: pip install mage-ai"
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
def load_alert_state(self, pipeline_uuid: str) -> Dict[str, Any]:
|
|
73
|
-
try:
|
|
74
|
-
last_error_message = self._get_global_variable(pipeline_uuid, 'last_error_message')
|
|
75
|
-
last_alert_time = self._get_global_variable(pipeline_uuid, 'last_alert_time')
|
|
76
|
-
|
|
77
|
-
if last_error_message and last_alert_time:
|
|
78
|
-
return {
|
|
79
|
-
"last_alert_time": last_alert_time,
|
|
80
|
-
"last_error_message": last_error_message
|
|
81
|
-
}
|
|
82
|
-
except Exception as e:
|
|
83
|
-
print(f"⚠️ Warning: Could not load alert state from global variables: {e}")
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
"last_alert_time": None,
|
|
87
|
-
"last_error_message": None
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
def save_alert_state(
|
|
91
|
-
self,
|
|
92
|
-
pipeline_uuid: str,
|
|
93
|
-
error_message: str,
|
|
94
|
-
alert_time: datetime,
|
|
95
|
-
pipeline_status: str
|
|
96
|
-
) -> None:
|
|
97
|
-
try:
|
|
98
|
-
self._set_global_variable(pipeline_uuid, 'last_error_message', error_message)
|
|
99
|
-
self._set_global_variable(pipeline_uuid, 'last_alert_time', alert_time.isoformat())
|
|
100
|
-
self._set_global_variable(pipeline_uuid, 'pipeline_status', pipeline_status)
|
|
101
|
-
self._set_global_variable(pipeline_uuid, 'last_alert_timestamp', alert_time.isoformat())
|
|
102
|
-
print(f"✅ Saved alert state to global variables: error_message, pipeline_status={pipeline_status}")
|
|
103
|
-
except Exception as e:
|
|
104
|
-
print(f"⚠️ Warning: Could not save alert state to global variables: {e}")
|
|
105
|
-
|
|
106
|
-
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
107
|
-
try:
|
|
108
|
-
self._set_global_variable(pipeline_uuid, 'last_error_message', None)
|
|
109
|
-
self._set_global_variable(pipeline_uuid, 'last_alert_time', None)
|
|
110
|
-
self._set_global_variable(pipeline_uuid, 'pipeline_status', 'success')
|
|
111
|
-
print("✅ Cleared alert state from global variables")
|
|
112
|
-
except Exception as e:
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
class StateManager:
|
|
5
|
+
def load_alert_state(self, pipeline_uuid: str) -> Dict[str, Any]:
|
|
6
|
+
"""Load alert state for a pipeline."""
|
|
7
|
+
raise NotImplementedError
|
|
8
|
+
|
|
9
|
+
def save_alert_state(
|
|
10
|
+
self,
|
|
11
|
+
pipeline_uuid: str,
|
|
12
|
+
error_message: str,
|
|
13
|
+
alert_time: datetime,
|
|
14
|
+
pipeline_status: str
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Save alert state for a pipeline."""
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
20
|
+
"""Clear alert state for a pipeline."""
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
class InMemoryStateManager(StateManager):
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self._state: Dict[str, Dict[str, Any]] = {}
|
|
26
|
+
|
|
27
|
+
def load_alert_state(self, pipeline_uuid: str) -> Dict[str, Any]:
|
|
28
|
+
"""Load alert state from memory."""
|
|
29
|
+
return self._state.get(pipeline_uuid, {
|
|
30
|
+
"last_alert_time": None,
|
|
31
|
+
"last_error_message": None
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
def save_alert_state(
|
|
35
|
+
self,
|
|
36
|
+
pipeline_uuid: str,
|
|
37
|
+
error_message: str,
|
|
38
|
+
alert_time: datetime,
|
|
39
|
+
pipeline_status: str
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Save alert state to memory."""
|
|
42
|
+
self._state[pipeline_uuid] = {
|
|
43
|
+
"last_alert_time": alert_time.isoformat(),
|
|
44
|
+
"last_error_message": error_message,
|
|
45
|
+
"pipeline_status": pipeline_status,
|
|
46
|
+
"last_alert_timestamp": alert_time.isoformat()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
50
|
+
"""Clear alert state from memory."""
|
|
51
|
+
if pipeline_uuid in self._state:
|
|
52
|
+
self._state[pipeline_uuid] = {
|
|
53
|
+
"last_alert_time": None,
|
|
54
|
+
"last_error_message": None,
|
|
55
|
+
"pipeline_status": "success"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class MageAIStateManager(StateManager):
|
|
59
|
+
def __init__(self):
|
|
60
|
+
try:
|
|
61
|
+
from mage_ai.data_preparation.variable_manager import (
|
|
62
|
+
set_global_variable,
|
|
63
|
+
get_global_variable
|
|
64
|
+
)
|
|
65
|
+
self._set_global_variable = set_global_variable
|
|
66
|
+
self._get_global_variable = get_global_variable
|
|
67
|
+
except ImportError:
|
|
68
|
+
raise ImportError(
|
|
69
|
+
"Mage AI is not installed. Install it with: pip install mage-ai"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def load_alert_state(self, pipeline_uuid: str) -> Dict[str, Any]:
|
|
73
|
+
try:
|
|
74
|
+
last_error_message = self._get_global_variable(pipeline_uuid, 'last_error_message')
|
|
75
|
+
last_alert_time = self._get_global_variable(pipeline_uuid, 'last_alert_time')
|
|
76
|
+
|
|
77
|
+
if last_error_message and last_alert_time:
|
|
78
|
+
return {
|
|
79
|
+
"last_alert_time": last_alert_time,
|
|
80
|
+
"last_error_message": last_error_message
|
|
81
|
+
}
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"⚠️ Warning: Could not load alert state from global variables: {e}")
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
"last_alert_time": None,
|
|
87
|
+
"last_error_message": None
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def save_alert_state(
|
|
91
|
+
self,
|
|
92
|
+
pipeline_uuid: str,
|
|
93
|
+
error_message: str,
|
|
94
|
+
alert_time: datetime,
|
|
95
|
+
pipeline_status: str
|
|
96
|
+
) -> None:
|
|
97
|
+
try:
|
|
98
|
+
self._set_global_variable(pipeline_uuid, 'last_error_message', error_message)
|
|
99
|
+
self._set_global_variable(pipeline_uuid, 'last_alert_time', alert_time.isoformat())
|
|
100
|
+
self._set_global_variable(pipeline_uuid, 'pipeline_status', pipeline_status)
|
|
101
|
+
self._set_global_variable(pipeline_uuid, 'last_alert_timestamp', alert_time.isoformat())
|
|
102
|
+
print(f"✅ Saved alert state to global variables: error_message, pipeline_status={pipeline_status}")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(f"⚠️ Warning: Could not save alert state to global variables: {e}")
|
|
105
|
+
|
|
106
|
+
def clear_alert_state(self, pipeline_uuid: str) -> None:
|
|
107
|
+
try:
|
|
108
|
+
self._set_global_variable(pipeline_uuid, 'last_error_message', None)
|
|
109
|
+
self._set_global_variable(pipeline_uuid, 'last_alert_time', None)
|
|
110
|
+
self._set_global_variable(pipeline_uuid, 'pipeline_status', 'success')
|
|
111
|
+
print("✅ Cleared alert state from global variables")
|
|
112
|
+
except Exception as e:
|
|
113
113
|
print(f"⚠️ Warning: Could not clear alert state: {e}")
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def get_repo_path() -> str:
|
|
5
|
-
"""
|
|
6
|
-
Returns the current working repository path (directory of this script).
|
|
7
|
-
"""
|
|
8
|
-
return os.getcwd()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_env_base_path() -> str:
|
|
12
|
-
"""
|
|
13
|
-
Returns the value of the ENV environment variable.
|
|
14
|
-
"""
|
|
15
|
-
return os.environ.get("ENV", "")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def transform_custom() -> tuple[str, str]:
|
|
19
|
-
"""
|
|
20
|
-
Returns a tuple of (repo_path, base_path)
|
|
21
|
-
"""
|
|
22
|
-
repo_path = get_repo_path()
|
|
23
|
-
base_path = get_env_base_path()
|
|
24
|
-
|
|
25
|
-
if not repo_path:
|
|
26
|
-
raise Exception("Unable to determine repository path.")
|
|
27
|
-
|
|
28
|
-
return repo_path, base_path
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_output(output) -> None:
|
|
32
|
-
"""
|
|
33
|
-
Simple test function to validate non-empty output.
|
|
34
|
-
"""
|
|
35
|
-
assert output is not None, "Output is undefined"
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_repo_path() -> str:
|
|
5
|
+
"""
|
|
6
|
+
Returns the current working repository path (directory of this script).
|
|
7
|
+
"""
|
|
8
|
+
return os.getcwd()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_env_base_path() -> str:
|
|
12
|
+
"""
|
|
13
|
+
Returns the value of the ENV environment variable.
|
|
14
|
+
"""
|
|
15
|
+
return os.environ.get("ENV", "")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def transform_custom() -> tuple[str, str]:
|
|
19
|
+
"""
|
|
20
|
+
Returns a tuple of (repo_path, base_path)
|
|
21
|
+
"""
|
|
22
|
+
repo_path = get_repo_path()
|
|
23
|
+
base_path = get_env_base_path()
|
|
24
|
+
|
|
25
|
+
if not repo_path:
|
|
26
|
+
raise Exception("Unable to determine repository path.")
|
|
27
|
+
|
|
28
|
+
return repo_path, base_path
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_output(output) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Simple test function to validate non-empty output.
|
|
34
|
+
"""
|
|
35
|
+
assert output is not None, "Output is undefined"
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "salla_gitpuller"
|
|
7
|
-
version = "1.0.
|
|
8
|
-
description = "A lightweight utility to git pull a repository using SSH deploy keys stored in environment variables"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
authors = [
|
|
11
|
-
{ name="Muhammad Zahid", email="zahidmuhammad127@gmail.com" }
|
|
12
|
-
]
|
|
13
|
-
license = { text = "MIT" }
|
|
14
|
-
dependencies = []
|
|
15
|
-
|
|
16
|
-
[project.urls]
|
|
17
|
-
Homepage = "https://github.com/Zahid07/gitpuller"
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "salla_gitpuller"
|
|
7
|
+
version = "1.0.2"
|
|
8
|
+
description = "A lightweight utility to git pull a repository using SSH deploy keys stored in environment variables"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name="Muhammad Zahid", email="zahidmuhammad127@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
dependencies = []
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/Zahid07/gitpuller"
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: salla_gitpuller
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A lightweight utility to git pull a repository using SSH deploy keys stored in environment variables
|
|
5
|
+
Author-email: Muhammad Zahid <zahidmuhammad127@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Zahid07/gitpuller
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# gitpuller
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from gitpuller import GitPullExecutor
|
|
18
|
+
|
|
19
|
+
executor = GitPullExecutor(slack_webhook_url="your_webhook_url", use_mage_ai=True)
|
|
20
|
+
result = executor.execute_with_alerting(
|
|
21
|
+
repo_path="/path/to/repo",
|
|
22
|
+
git_url="git@github.com:user/repo.git",
|
|
23
|
+
branch="master",
|
|
24
|
+
workspace_name="workspace1"
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Build
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
rm -rf build dist *.egg-info
|
|
32
|
+
python -m build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Upload to PyPI and update version in `pyproject.toml`.
|
salla_gitpuller-1.0.0/PKG-INFO
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: salla_gitpuller
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: A lightweight utility to git pull a repository using SSH deploy keys stored in environment variables
|
|
5
|
-
Author-email: Muhammad Zahid <zahidmuhammad127@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/Zahid07/gitpuller
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Dynamic: license-file
|
|
11
|
-
|
|
12
|
-
# gitpuller
|
|
13
|
-
|
|
14
|
-
This is how to use this
|
|
15
|
-
|
|
16
|
-
from gitpuller import git_pull, transform_custom
|
|
17
|
-
|
|
18
|
-
repo_path, base_path = transform_custom()
|
|
19
|
-
result = git_pull(repo_path, "workspace1", "git@github.com:youruser/yourrepo.git", "1234")
|
|
20
|
-
print(result)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## New Build
|
|
24
|
-
|
|
25
|
-
Clean up the orignal build
|
|
26
|
-
|
|
27
|
-
rm -rf build dist *.egg-info
|
|
28
|
-
|
|
29
|
-
Make a new build
|
|
30
|
-
|
|
31
|
-
python -m build
|
|
32
|
-
|
|
33
|
-
Then Upload to PyPi and dont forget to change the version in pyproject.toml
|
salla_gitpuller-1.0.0/README.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# gitpuller
|
|
2
|
-
|
|
3
|
-
This is how to use this
|
|
4
|
-
|
|
5
|
-
from gitpuller import git_pull, transform_custom
|
|
6
|
-
|
|
7
|
-
repo_path, base_path = transform_custom()
|
|
8
|
-
result = git_pull(repo_path, "workspace1", "git@github.com:youruser/yourrepo.git", "1234")
|
|
9
|
-
print(result)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## New Build
|
|
13
|
-
|
|
14
|
-
Clean up the orignal build
|
|
15
|
-
|
|
16
|
-
rm -rf build dist *.egg-info
|
|
17
|
-
|
|
18
|
-
Make a new build
|
|
19
|
-
|
|
20
|
-
python -m build
|
|
21
|
-
|
|
22
|
-
Then Upload to PyPi and dont forget to change the version in pyproject.toml
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: salla_gitpuller
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: A lightweight utility to git pull a repository using SSH deploy keys stored in environment variables
|
|
5
|
-
Author-email: Muhammad Zahid <zahidmuhammad127@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/Zahid07/gitpuller
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Dynamic: license-file
|
|
11
|
-
|
|
12
|
-
# gitpuller
|
|
13
|
-
|
|
14
|
-
This is how to use this
|
|
15
|
-
|
|
16
|
-
from gitpuller import git_pull, transform_custom
|
|
17
|
-
|
|
18
|
-
repo_path, base_path = transform_custom()
|
|
19
|
-
result = git_pull(repo_path, "workspace1", "git@github.com:youruser/yourrepo.git", "1234")
|
|
20
|
-
print(result)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## New Build
|
|
24
|
-
|
|
25
|
-
Clean up the orignal build
|
|
26
|
-
|
|
27
|
-
rm -rf build dist *.egg-info
|
|
28
|
-
|
|
29
|
-
Make a new build
|
|
30
|
-
|
|
31
|
-
python -m build
|
|
32
|
-
|
|
33
|
-
Then Upload to PyPi and dont forget to change the version in pyproject.toml
|
|
File without changes
|
|
File without changes
|
{salla_gitpuller-1.0.0 → salla_gitpuller-1.0.2}/salla_gitpuller.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|