machineconfig 2.8__py3-none-any.whl → 2.9__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 machineconfig might be problematic. Click here for more details.
- machineconfig/jobs/python/python_ve_symlink.py +1 -1
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
- machineconfig/scripts/python/devops.py +18 -64
- machineconfig/scripts/python/devops_add_identity.py +6 -2
- machineconfig/scripts/python/devops_add_ssh_key.py +5 -2
- machineconfig/scripts/python/devops_backup_retrieve.py +3 -15
- machineconfig/scripts/python/devops_devapps_install.py +8 -6
- machineconfig/scripts/python/devops_update_repos.py +125 -226
- machineconfig/scripts/python/fire_agents.py +108 -151
- machineconfig/scripts/python/fire_agents_help_launch.py +97 -0
- machineconfig/scripts/python/fire_agents_help_search.py +83 -0
- machineconfig/scripts/python/helpers/cloud_helpers.py +2 -5
- machineconfig/scripts/python/repos.py +1 -1
- machineconfig/scripts/python/repos_helper_update.py +288 -0
- machineconfig/utils/installer_utils/installer_class.py +3 -3
- machineconfig/utils/notifications.py +24 -4
- machineconfig/utils/path.py +2 -1
- machineconfig/utils/procs.py +7 -7
- machineconfig/utils/source_of_truth.py +2 -0
- {machineconfig-2.8.dist-info → machineconfig-2.9.dist-info}/METADATA +7 -10
- {machineconfig-2.8.dist-info → machineconfig-2.9.dist-info}/RECORD +26 -22
- {machineconfig-2.8.dist-info → machineconfig-2.9.dist-info}/WHEEL +0 -0
- {machineconfig-2.8.dist-info → machineconfig-2.9.dist-info}/entry_points.txt +0 -0
- {machineconfig-2.8.dist-info → machineconfig-2.9.dist-info}/top_level.txt +0 -0
|
@@ -1,236 +1,107 @@
|
|
|
1
1
|
"""Update repositories with fancy output"""
|
|
2
2
|
|
|
3
3
|
import git
|
|
4
|
-
import subprocess
|
|
5
|
-
import hashlib
|
|
6
4
|
from pathlib import Path
|
|
5
|
+
from machineconfig.scripts.python.repos_helper_update import RepositoryUpdateResult, run_uv_sync, update_repository
|
|
7
6
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
8
7
|
from machineconfig.utils.source_of_truth import DEFAULTS_PATH
|
|
9
8
|
from machineconfig.utils.utils2 import read_ini
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
def
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if
|
|
22
|
-
|
|
23
|
-
if
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
changed_files_raw.extend([item.a_path for item in repo.index.diff("HEAD")])
|
|
61
|
-
# Filter out None values and remove duplicates
|
|
62
|
-
changed_files = list(set(file for file in changed_files_raw if file is not None))
|
|
11
|
+
def _display_summary(results: list[RepositoryUpdateResult]) -> None:
|
|
12
|
+
"""Display a comprehensive summary of all repository update operations."""
|
|
13
|
+
print("\n" + "=" * 80)
|
|
14
|
+
print("📊 REPOSITORY UPDATE SUMMARY")
|
|
15
|
+
print("=" * 80)
|
|
16
|
+
|
|
17
|
+
# Calculate statistics
|
|
18
|
+
total_repos = len(results)
|
|
19
|
+
successful_repos = sum(1 for r in results if r["status"] == "success")
|
|
20
|
+
error_repos = sum(1 for r in results if r["status"] == "error")
|
|
21
|
+
skipped_repos = sum(1 for r in results if r["status"] == "skipped")
|
|
22
|
+
auth_failed_repos = sum(1 for r in results if r["status"] == "auth_failed")
|
|
23
|
+
|
|
24
|
+
repos_with_changes = sum(1 for r in results if r["commits_changed"])
|
|
25
|
+
repos_with_uncommitted = sum(1 for r in results if r["had_uncommitted_changes"])
|
|
26
|
+
repos_with_dep_changes = sum(1 for r in results if r["dependencies_changed"])
|
|
27
|
+
uv_sync_runs = sum(1 for r in results if r["uv_sync_ran"])
|
|
28
|
+
uv_sync_successes = sum(1 for r in results if r["uv_sync_ran"] and r["uv_sync_success"])
|
|
29
|
+
|
|
30
|
+
# Overview statistics
|
|
31
|
+
print("📈 OVERVIEW:")
|
|
32
|
+
print(f" Total repositories processed: {total_repos}")
|
|
33
|
+
print(f" ✅ Successful updates: {successful_repos}")
|
|
34
|
+
print(f" ❌ Failed updates: {error_repos}")
|
|
35
|
+
print(f" ⏭️ Skipped: {skipped_repos}")
|
|
36
|
+
if auth_failed_repos > 0:
|
|
37
|
+
print(f" 🔐 Authentication failed: {auth_failed_repos}")
|
|
38
|
+
print()
|
|
39
|
+
|
|
40
|
+
print("🔄 CHANGES:")
|
|
41
|
+
print(f" Repositories with new commits: {repos_with_changes}")
|
|
42
|
+
print(f" Repositories with dependency changes: {repos_with_dep_changes}")
|
|
43
|
+
print(f" Repositories with uncommitted changes: {repos_with_uncommitted}")
|
|
44
|
+
print()
|
|
45
|
+
|
|
46
|
+
print("📦 UV SYNC:")
|
|
47
|
+
print(f" uv sync operations attempted: {uv_sync_runs}")
|
|
48
|
+
print(f" uv sync operations successful: {uv_sync_successes}")
|
|
49
|
+
if uv_sync_runs > uv_sync_successes:
|
|
50
|
+
print(f" uv sync operations failed: {uv_sync_runs - uv_sync_successes}")
|
|
51
|
+
print()
|
|
52
|
+
|
|
53
|
+
# Detailed results per repository
|
|
54
|
+
print("📋 DETAILED RESULTS:")
|
|
55
|
+
for result in results:
|
|
56
|
+
repo_name = Path(result["repo_path"]).name
|
|
57
|
+
status_icon = {"success": "✅", "error": "❌", "skipped": "⏭️", "auth_failed": "🔐"}.get(result["status"], "❓")
|
|
58
|
+
print(f" {status_icon} {repo_name}")
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
if result["status"] == "error" and result["error_message"]:
|
|
61
|
+
print(f" 💥 Error: {result['error_message']}")
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
if result["commits_changed"]:
|
|
64
|
+
print(f" 🔄 Updated: {result['commit_before'][:8]} → {result['commit_after'][:8]}")
|
|
65
|
+
elif result["status"] == "success":
|
|
66
|
+
print(" 📍 Already up to date")
|
|
67
|
+
|
|
68
|
+
if result["had_uncommitted_changes"]:
|
|
69
|
+
files_str = ", ".join(result["uncommitted_files"])
|
|
70
|
+
print(f" ⚠️ Had uncommitted changes: {files_str}")
|
|
71
|
+
|
|
72
|
+
if result["dependencies_changed"]:
|
|
73
|
+
changes = []
|
|
74
|
+
if result["pyproject_changed"]:
|
|
75
|
+
changes.append("pyproject.toml")
|
|
76
|
+
if result["uv_lock_changed"]:
|
|
77
|
+
changes.append("uv.lock")
|
|
78
|
+
print(f" 📋 Dependencies changed: {', '.join(changes)}")
|
|
79
|
+
|
|
80
|
+
if result["uv_sync_ran"]:
|
|
81
|
+
sync_status = "✅" if result["uv_sync_success"] else "❌"
|
|
82
|
+
print(f" 📦 uv sync: {sync_status}")
|
|
83
|
+
|
|
84
|
+
if result["is_machineconfig_repo"] and result["permissions_updated"]:
|
|
85
|
+
print(" 🛠 Updated permissions for machineconfig files")
|
|
86
|
+
|
|
87
|
+
if result["remotes_processed"]:
|
|
88
|
+
print(f" 📡 Processed remotes: {', '.join(result['remotes_processed'])}")
|
|
89
|
+
if result["remotes_skipped"]:
|
|
90
|
+
print(f" ⏭️ Skipped remotes: {', '.join(result['remotes_skipped'])}")
|
|
91
|
+
|
|
92
|
+
print("\n" + "=" * 80)
|
|
93
|
+
|
|
94
|
+
# Final status
|
|
95
|
+
if error_repos == 0 and auth_failed_repos == 0:
|
|
96
|
+
print("🎉 All repositories processed successfully!")
|
|
97
|
+
elif successful_repos > 0:
|
|
98
|
+
print(f"⚠️ {successful_repos}/{total_repos} repositories processed successfully")
|
|
79
99
|
else:
|
|
80
|
-
print("
|
|
81
|
-
|
|
82
|
-
# Check if this repo has pyproject.toml or uv.lock
|
|
83
|
-
pyproject_path = repo_path / "pyproject.toml"
|
|
84
|
-
uv_lock_path = repo_path / "uv.lock"
|
|
85
|
-
|
|
86
|
-
# Get hashes before pull
|
|
87
|
-
pyproject_hash_before = get_file_hash(pyproject_path)
|
|
88
|
-
uv_lock_hash_before = get_file_hash(uv_lock_path)
|
|
89
|
-
|
|
90
|
-
# Get current commit hash before pull
|
|
91
|
-
commit_before = repo.head.commit.hexsha
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
# Use subprocess for git pull to get better output control
|
|
95
|
-
dependencies_changed = False
|
|
96
|
-
|
|
97
|
-
# Get list of remotes
|
|
98
|
-
remotes = list(repo.remotes)
|
|
99
|
-
if not remotes:
|
|
100
|
-
print("⚠️ No remotes configured for this repository")
|
|
101
|
-
return False
|
|
102
|
-
|
|
103
|
-
for remote in remotes:
|
|
104
|
-
try:
|
|
105
|
-
print(f"📥 Fetching from {remote.name}...")
|
|
106
|
-
|
|
107
|
-
# Set up environment for git commands
|
|
108
|
-
env = None
|
|
109
|
-
if not allow_password_prompt:
|
|
110
|
-
# Disable interactive prompts
|
|
111
|
-
import os
|
|
112
|
-
|
|
113
|
-
env = os.environ.copy()
|
|
114
|
-
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
115
|
-
env["GIT_ASKPASS"] = "echo" # Returns empty string for any credential request
|
|
116
|
-
|
|
117
|
-
# First fetch to see what's available
|
|
118
|
-
fetch_result = subprocess.run(
|
|
119
|
-
["git", "fetch", remote.name, "--verbose"],
|
|
120
|
-
cwd=repo_path,
|
|
121
|
-
capture_output=True,
|
|
122
|
-
text=True,
|
|
123
|
-
env=env,
|
|
124
|
-
timeout=30, # Add timeout to prevent hanging
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
# Check if fetch failed due to authentication
|
|
128
|
-
if fetch_result.returncode != 0 and not allow_password_prompt:
|
|
129
|
-
auth_error_indicators = [
|
|
130
|
-
"Authentication failed",
|
|
131
|
-
"Password for",
|
|
132
|
-
"Username for",
|
|
133
|
-
"could not read Username",
|
|
134
|
-
"could not read Password",
|
|
135
|
-
"fatal: Authentication failed",
|
|
136
|
-
"fatal: could not read Username",
|
|
137
|
-
"fatal: could not read Password",
|
|
138
|
-
]
|
|
100
|
+
print("❌ No repositories were successfully processed")
|
|
101
|
+
print("=" * 80)
|
|
139
102
|
|
|
140
|
-
error_output = (fetch_result.stderr or "") + (fetch_result.stdout or "")
|
|
141
|
-
if any(indicator in error_output for indicator in auth_error_indicators):
|
|
142
|
-
print(f"⚠️ Skipping {remote.name} - authentication required but password prompts are disabled")
|
|
143
|
-
continue
|
|
144
103
|
|
|
145
|
-
|
|
146
|
-
print(f"📡 Fetch output: {fetch_result.stdout.strip()}")
|
|
147
|
-
if fetch_result.stderr:
|
|
148
|
-
print(f"📡 Fetch info: {fetch_result.stderr.strip()}")
|
|
149
|
-
|
|
150
|
-
# Now pull with verbose output
|
|
151
|
-
print(f"📥 Pulling from {remote.name}/{repo.active_branch.name}...")
|
|
152
|
-
pull_result = subprocess.run(["git", "pull", remote.name, repo.active_branch.name, "--verbose"], cwd=repo_path, capture_output=True, text=True, env=env, timeout=30)
|
|
153
|
-
|
|
154
|
-
# Check if pull failed due to authentication
|
|
155
|
-
if pull_result.returncode != 0 and not allow_password_prompt:
|
|
156
|
-
auth_error_indicators = [
|
|
157
|
-
"Authentication failed",
|
|
158
|
-
"Password for",
|
|
159
|
-
"Username for",
|
|
160
|
-
"could not read Username",
|
|
161
|
-
"could not read Password",
|
|
162
|
-
"fatal: Authentication failed",
|
|
163
|
-
"fatal: could not read Username",
|
|
164
|
-
"fatal: could not read Password",
|
|
165
|
-
]
|
|
166
|
-
|
|
167
|
-
error_output = (pull_result.stderr or "") + (pull_result.stdout or "")
|
|
168
|
-
if any(indicator in error_output for indicator in auth_error_indicators):
|
|
169
|
-
print(f"⚠️ Skipping pull from {remote.name} - authentication required but password prompts are disabled")
|
|
170
|
-
continue
|
|
171
|
-
|
|
172
|
-
if pull_result.stdout:
|
|
173
|
-
print(f"📦 Pull output: {pull_result.stdout.strip()}")
|
|
174
|
-
if pull_result.stderr:
|
|
175
|
-
print(f"📦 Pull info: {pull_result.stderr.strip()}")
|
|
176
|
-
|
|
177
|
-
# Check if pull was successful
|
|
178
|
-
if pull_result.returncode == 0:
|
|
179
|
-
# Check if commits changed
|
|
180
|
-
commit_after = repo.head.commit.hexsha
|
|
181
|
-
if commit_before != commit_after:
|
|
182
|
-
print(f"✅ Repository updated: {commit_before[:8]} → {commit_after[:8]}")
|
|
183
|
-
else:
|
|
184
|
-
print("✅ Already up to date")
|
|
185
|
-
else:
|
|
186
|
-
print(f"❌ Pull failed with return code {pull_result.returncode}")
|
|
187
|
-
|
|
188
|
-
except Exception as e:
|
|
189
|
-
print(f"⚠️ Failed to pull from {remote.name}: {e}")
|
|
190
|
-
continue
|
|
191
|
-
|
|
192
|
-
# Check if pyproject.toml or uv.lock changed after pull
|
|
193
|
-
pyproject_hash_after = get_file_hash(pyproject_path)
|
|
194
|
-
uv_lock_hash_after = get_file_hash(uv_lock_path)
|
|
195
|
-
|
|
196
|
-
if pyproject_hash_before != pyproject_hash_after:
|
|
197
|
-
print("📋 pyproject.toml has changed")
|
|
198
|
-
dependencies_changed = True
|
|
199
|
-
|
|
200
|
-
if uv_lock_hash_before != uv_lock_hash_after:
|
|
201
|
-
print("🔒 uv.lock has changed")
|
|
202
|
-
dependencies_changed = True
|
|
203
|
-
|
|
204
|
-
# Special handling for machineconfig repository
|
|
205
|
-
if "machineconfig" in str(repo_path):
|
|
206
|
-
print("🛠 Special handling for machineconfig repository...")
|
|
207
|
-
scripts_path = Path.home() / "scripts"
|
|
208
|
-
if scripts_path.exists():
|
|
209
|
-
set_permissions_recursive(scripts_path)
|
|
210
|
-
print(f"✅ Set permissions for {scripts_path}")
|
|
211
|
-
|
|
212
|
-
linux_jobs_path = repo_path / "src" / "machineconfig" / "jobs" / "linux"
|
|
213
|
-
if linux_jobs_path.exists():
|
|
214
|
-
set_permissions_recursive(linux_jobs_path)
|
|
215
|
-
print(f"✅ Set permissions for {linux_jobs_path}")
|
|
216
|
-
|
|
217
|
-
lf_exe_path = repo_path / "src" / "machineconfig" / "settings" / "lf" / "linux" / "exe"
|
|
218
|
-
if lf_exe_path.exists():
|
|
219
|
-
set_permissions_recursive(lf_exe_path)
|
|
220
|
-
print(f"✅ Set permissions for {lf_exe_path}")
|
|
221
|
-
|
|
222
|
-
# Run uv sync if dependencies changed and auto_sync is enabled
|
|
223
|
-
if dependencies_changed and auto_sync:
|
|
224
|
-
run_uv_sync(repo_path)
|
|
225
|
-
|
|
226
|
-
return dependencies_changed
|
|
227
|
-
|
|
228
|
-
except Exception as e:
|
|
229
|
-
print(f"❌ Error updating repository {repo_path}: {e}")
|
|
230
|
-
return False
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def main(verbose: bool = True, allow_password_prompt: bool = False) -> str:
|
|
104
|
+
def main(verbose: bool = True, allow_password_prompt: bool = False) -> None:
|
|
234
105
|
"""Main function to update all configured repositories."""
|
|
235
106
|
_ = verbose
|
|
236
107
|
repos: list[PathExtended] = [PathExtended.home() / "code/machineconfig", PathExtended.home() / "code/crocodile"]
|
|
@@ -260,24 +131,52 @@ def main(verbose: bool = True, allow_password_prompt: bool = False) -> str:
|
|
|
260
131
|
└────────────────────────────────────────────────────────────────""")
|
|
261
132
|
|
|
262
133
|
# Process repositories
|
|
134
|
+
results: list[RepositoryUpdateResult] = []
|
|
263
135
|
repos_with_changes = []
|
|
136
|
+
|
|
264
137
|
for expanded_path in repos:
|
|
265
138
|
try:
|
|
266
139
|
repo = git.Repo(str(expanded_path), search_parent_directories=True)
|
|
267
|
-
# Update repository and
|
|
268
|
-
|
|
269
|
-
|
|
140
|
+
# Update repository and get detailed results
|
|
141
|
+
result = update_repository(repo, allow_password_prompt=allow_password_prompt, auto_sync=True)
|
|
142
|
+
results.append(result)
|
|
143
|
+
|
|
144
|
+
# Keep track of repos with dependency changes for additional uv sync
|
|
145
|
+
if result["dependencies_changed"] and not result["uv_sync_ran"]:
|
|
270
146
|
repos_with_changes.append(Path(repo.working_dir))
|
|
147
|
+
|
|
271
148
|
except Exception as ex:
|
|
149
|
+
# Create a result for failed repos
|
|
150
|
+
error_result: RepositoryUpdateResult = {
|
|
151
|
+
"repo_path": str(expanded_path),
|
|
152
|
+
"status": "error",
|
|
153
|
+
"had_uncommitted_changes": False,
|
|
154
|
+
"uncommitted_files": [],
|
|
155
|
+
"commit_before": "",
|
|
156
|
+
"commit_after": "",
|
|
157
|
+
"commits_changed": False,
|
|
158
|
+
"pyproject_changed": False,
|
|
159
|
+
"uv_lock_changed": False,
|
|
160
|
+
"dependencies_changed": False,
|
|
161
|
+
"uv_sync_ran": False,
|
|
162
|
+
"uv_sync_success": False,
|
|
163
|
+
"remotes_processed": [],
|
|
164
|
+
"remotes_skipped": [],
|
|
165
|
+
"error_message": str(ex),
|
|
166
|
+
"is_machineconfig_repo": False,
|
|
167
|
+
"permissions_updated": False,
|
|
168
|
+
}
|
|
169
|
+
results.append(error_result)
|
|
272
170
|
print(f"""❌ Repository Error: Path: {expanded_path}
|
|
273
171
|
Exception: {ex}
|
|
274
172
|
{"-" * 50}""")
|
|
275
|
-
|
|
173
|
+
|
|
174
|
+
# Run uv sync for repositories where pyproject.toml or uv.lock changed but sync wasn't run yet
|
|
276
175
|
for repo_path in repos_with_changes:
|
|
277
176
|
run_uv_sync(repo_path)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
177
|
+
|
|
178
|
+
# Generate and display summary
|
|
179
|
+
_display_summary(results)
|
|
281
180
|
|
|
282
181
|
if __name__ == "__main__":
|
|
283
182
|
main()
|