machineconfig 2.7__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.

Files changed (30) hide show
  1. machineconfig/jobs/python/check_installations.py +0 -2
  2. machineconfig/jobs/python/python_ve_symlink.py +1 -1
  3. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  4. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  5. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  6. machineconfig/scripts/python/__pycache__/repos_helper_record.cpython-313.pyc +0 -0
  7. machineconfig/scripts/python/devops.py +18 -64
  8. machineconfig/scripts/python/devops_add_identity.py +6 -2
  9. machineconfig/scripts/python/devops_add_ssh_key.py +5 -2
  10. machineconfig/scripts/python/devops_backup_retrieve.py +3 -15
  11. machineconfig/scripts/python/devops_devapps_install.py +22 -8
  12. machineconfig/scripts/python/devops_update_repos.py +125 -226
  13. machineconfig/scripts/python/fire_agents.py +108 -151
  14. machineconfig/scripts/python/fire_agents_help_launch.py +97 -0
  15. machineconfig/scripts/python/fire_agents_help_search.py +83 -0
  16. machineconfig/scripts/python/helpers/cloud_helpers.py +2 -5
  17. machineconfig/scripts/python/repos.py +1 -1
  18. machineconfig/scripts/python/repos_helper_record.py +82 -5
  19. machineconfig/scripts/python/repos_helper_update.py +288 -0
  20. machineconfig/utils/installer_utils/installer_class.py +3 -3
  21. machineconfig/utils/notifications.py +24 -4
  22. machineconfig/utils/path.py +2 -1
  23. machineconfig/utils/procs.py +50 -35
  24. machineconfig/utils/source_of_truth.py +2 -0
  25. machineconfig/utils/ssh.py +29 -7
  26. {machineconfig-2.7.dist-info → machineconfig-2.9.dist-info}/METADATA +7 -11
  27. {machineconfig-2.7.dist-info → machineconfig-2.9.dist-info}/RECORD +30 -26
  28. {machineconfig-2.7.dist-info → machineconfig-2.9.dist-info}/WHEEL +0 -0
  29. {machineconfig-2.7.dist-info → machineconfig-2.9.dist-info}/entry_points.txt +0 -0
  30. {machineconfig-2.7.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 get_file_hash(file_path: Path) -> str | None:
13
- """Get SHA256 hash of a file, return None if file doesn't exist."""
14
- if not file_path.exists():
15
- return None
16
- return hashlib.sha256(file_path.read_bytes()).hexdigest()
17
-
18
-
19
- def set_permissions_recursive(path: Path, executable: bool = True) -> None:
20
- """Set permissions recursively for a directory."""
21
- if not path.exists():
22
- return
23
- if path.is_file():
24
- if executable:
25
- path.chmod(0o755)
26
- else:
27
- path.chmod(0o644)
28
- elif path.is_dir():
29
- path.chmod(0o755)
30
- for item in path.rglob("*"):
31
- set_permissions_recursive(item, executable)
32
-
33
-
34
- def run_uv_sync(repo_path: Path) -> bool:
35
- """Run uv sync in the given repository path. Returns True if successful."""
36
- try:
37
- print(f"🔄 Running uv sync in {repo_path}")
38
- # Run uv sync with output directly to terminal (no capture)
39
- subprocess.run(["uv", "sync"], cwd=repo_path, check=True)
40
- print("✅ uv sync completed successfully")
41
- return True
42
- except subprocess.CalledProcessError as e:
43
- print(f" uv sync failed with return code {e.returncode}")
44
- return False
45
- except FileNotFoundError:
46
- print("⚠️ uv command not found. Please install uv first.")
47
- return False
48
-
49
-
50
- def update_repository(repo: git.Repo, auto_sync: bool, allow_password_prompt: bool) -> bool:
51
- """Update a single repository and return True if pyproject.toml or uv.lock changed."""
52
- repo_path = Path(repo.working_dir)
53
- print(f"🔄 {'Updating ' + str(repo_path):.^80}")
54
-
55
- # Check git status first
56
- print("📊 Checking git status...")
57
- if repo.is_dirty():
58
- # Get the list of modified files
59
- changed_files_raw = [item.a_path for item in repo.index.diff(None)]
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
- print(f"⚠️ Repository has uncommitted changes: {', '.join(changed_files)}")
60
+ if result["status"] == "error" and result["error_message"]:
61
+ print(f" 💥 Error: {result['error_message']}")
65
62
 
66
- # Check if the only change is uv.lock
67
- if len(changed_files) == 1 and changed_files[0] == "uv.lock":
68
- print("🔒 Only uv.lock has changes, resetting it...")
69
- try:
70
- # Reset uv.lock file
71
- subprocess.run(["git", "checkout", "HEAD", "--", "uv.lock"], cwd=repo_path, check=True)
72
- print("✅ uv.lock has been reset")
73
- except subprocess.CalledProcessError as e:
74
- print(f"❌ Failed to reset uv.lock: {e}")
75
- return False
76
- else:
77
- # Multiple files or files other than uv.lock have changes
78
- raise RuntimeError(f"❌ Cannot update repository - there are pending changes in: {', '.join(changed_files)}. Please commit or stash your changes first.")
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(" Repository is clean")
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
- if fetch_result.stdout:
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 check if dependencies changed
268
- dependencies_changed = update_repository(repo, allow_password_prompt=allow_password_prompt, auto_sync=True)
269
- if dependencies_changed:
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
- # Run uv sync for repositories where pyproject.toml or uv.lock changed
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
- # print("\n🎉 All repositories updated successfully!")
279
- return """echo "🎉 All repositories updated successfully!" """
280
-
177
+
178
+ # Generate and display summary
179
+ _display_summary(results)
281
180
 
282
181
  if __name__ == "__main__":
283
182
  main()