forked from jorgesolebur/CumulusCI_AzureDevOps
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupgrade.py
More file actions
executable file
·336 lines (285 loc) · 13 KB
/
Copy pathupgrade.py
File metadata and controls
executable file
·336 lines (285 loc) · 13 KB
1
2
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
59
60
61
62
63
64
65
66
67
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/usr/bin/env python3
"""
Upgrade script for cumulusci-plus-azure-devops and its dependencies
This script helps users properly upgrade the package and its dependencies
when using pipx, which has limitations with dependency upgrades.
"""
import argparse
import json
import subprocess
import sys
def run_command(cmd, capture_output=True, check=True):
"""Run a command and return the result."""
try:
result = subprocess.run(
cmd, shell=True, capture_output=capture_output, text=True, check=check
)
return result
except subprocess.CalledProcessError as e:
return e
def get_pipx_info():
"""Get information about pipx installation."""
result = run_command("pipx list --json", check=False)
if result.returncode != 0:
print("❌ pipx not found or not working properly")
return None
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
print("❌ Could not parse pipx list output")
return None
def detect_installation_method():
"""Detect how the package was installed."""
pipx_info = get_pipx_info()
if not pipx_info:
return None
venvs = pipx_info.get("venvs", {})
# Check if installed as main package
if "cumulusci-plus-azure-devops" in venvs:
return "main_package"
# Check if injected into cumulusci-plus
if "cumulusci-plus" in venvs:
# Get the venv info for cumulusci-plus
venv_info = venvs["cumulusci-plus"]
venv_dir = venv_info.get("pyvenv_cfg", {}).get("home", "").replace("/bin", "")
if not venv_dir:
# Fallback: try to find venv directory from metadata
metadata = venv_info.get("metadata", {})
if metadata:
# pipx stores venv path info, let's try to extract it
try:
# Check using pipx run which should use the right environment
result = run_command(
"pipx run --spec cumulusci-plus python -c \"import pkg_resources; pkgs = [d.project_name for d in pkg_resources.working_set if 'cumulusci-plus-azure-devops' in d.project_name]; print('found' if pkgs else 'not_found')\"",
check=False,
)
if result.returncode == 0 and "found" in result.stdout:
return "injected"
except Exception:
pass # noqa: E722
else:
# Use the venv path directly
try:
python_exe = f"{venv_dir}/bin/python"
result = run_command(
f"{python_exe} -c \"import pkg_resources; pkgs = [d.project_name for d in pkg_resources.working_set if 'cumulusci-plus-azure-devops' in d.project_name]; print('found' if pkgs else 'not_found')\"",
check=False,
)
if result.returncode == 0 and "found" in result.stdout:
return "injected"
except Exception:
pass # noqa: E722
return "unknown"
def check_package_installed(package_name):
"""Check if a package is installed via pipx."""
pipx_info = get_pipx_info()
if not pipx_info:
return False
venvs = pipx_info.get("venvs", {})
return package_name in venvs
def get_dependency_versions(package_name, installation_method=None):
"""Get versions of dependencies in the pipx environment."""
print(f"📦 Checking dependency versions for {package_name}...")
if installation_method == "injected":
# For injected plugins, check the main cumulusci-plus environment directly
# Try to get venv path from pipx info
pipx_info = get_pipx_info()
if pipx_info and "cumulusci-plus" in pipx_info.get("venvs", {}):
try:
# Use a simple approach - find the pipx venv directory
result = run_command(
"pipx list | grep -A 1 'package cumulusci-plus'", check=False
)
if result.returncode == 0:
# Try common pipx venv locations
import os
home_dir = os.path.expanduser("~")
possible_paths = [
f"{home_dir}/.local/pipx/venvs/cumulusci-plus/bin/python",
f"{home_dir}/.local/share/pipx/venvs/cumulusci-plus/bin/python", # Some pipx versions
]
for python_path in possible_paths:
if os.path.exists(python_path):
cmd = f"{python_path} -c \"import pkg_resources; deps = [str(d) for d in pkg_resources.working_set if d.project_name in ['cumulusci-plus', 'cumulusci-plus-azure-devops', 'azure-devops', 'requests', 'humanfriendly', 'distro', 'packaging']]; print('\\n'.join(sorted(deps)))\""
result = run_command(cmd, check=False)
if result.returncode == 0:
return result.stdout.strip().split("\n")
except Exception:
pass # noqa: E722 # pragma: no cover
# Fallback to pipx run if direct access fails
cmd = "pipx run --spec cumulusci-plus python -c \"import pkg_resources; deps = [str(d) for d in pkg_resources.working_set if d.project_name in ['cumulusci-plus', 'cumulusci-plus-azure-devops', 'azure-devops', 'requests', 'humanfriendly', 'distro', 'packaging']]; print('\\n'.join(sorted(deps)))\""
else:
# For main package installations
cmd = f"pipx run --spec {package_name} python -c \"import pkg_resources; deps = [str(d) for d in pkg_resources.working_set if d.project_name in ['cumulusci-plus', 'azure-devops', 'requests', 'humanfriendly', 'distro', 'packaging']]; print('\\n'.join(sorted(deps)))\""
result = run_command(cmd, check=False)
if result.returncode == 0:
return result.stdout.strip().split("\n")
else:
print("❌ Could not check dependency versions")
return []
def upgrade_package(package_name, force_reinstall=False, installation_method=None):
"""Upgrade the package based on installation method."""
print(f"🔄 Upgrading {package_name}...")
if installation_method == "injected":
print("📦 Detected: Plugin injected into cumulusci-plus environment")
if force_reinstall:
print("🔄 Force reinstalling to ensure dependencies are updated...")
# Uninstall main package
print("🗑️ Uninstalling cumulusci-plus...")
result = run_command("pipx uninstall cumulusci-plus", check=False)
# Reinstall main package
print("📦 Reinstalling cumulusci-plus...")
result = run_command("pipx install cumulusci-plus", capture_output=False)
if result.returncode != 0:
print("❌ Failed to reinstall cumulusci-plus!")
return False
# Re-inject plugin
print("💉 Re-injecting plugin...")
result = run_command(
f"pipx inject cumulusci-plus {package_name} --include-apps --force",
capture_output=False,
)
if result.returncode == 0:
print("✅ Force reinstallation completed successfully!")
return True
else:
# Normal upgrade for injected plugin
print("📦 Upgrading main package...")
result = run_command("pipx upgrade cumulusci-plus", capture_output=False)
if result.returncode != 0:
print("❌ Failed to upgrade main package!")
return False
print("💉 Updating injected plugin...")
result = run_command(
f"pipx inject cumulusci-plus {package_name} --include-apps --force",
capture_output=False,
)
if result.returncode == 0:
print("✅ Upgrade completed successfully!")
return True
else:
print("❌ Plugin injection failed!")
return False
elif installation_method == "main_package":
print("📦 Detected: Plugin installed as main package")
if force_reinstall:
print("🔄 Force reinstalling to ensure dependencies are updated...")
# Uninstall first
print("🗑️ Uninstalling current version...")
result = run_command(f"pipx uninstall {package_name}", check=False)
if result.returncode != 0:
print(f"⚠️ Could not uninstall {package_name}: {result.stderr}")
# Reinstall
print("📦 Reinstalling latest version...")
result = run_command(
f"pipx install {package_name} --include-deps", capture_output=False
)
if result.returncode == 0:
print("✅ Reinstallation completed successfully!")
return True
else:
print("❌ Reinstallation failed!")
return False
else:
# Try normal upgrade
result = run_command(f"pipx upgrade {package_name}", capture_output=False)
if result.returncode == 0:
print("✅ Upgrade completed successfully!")
return True
else:
print("❌ Upgrade failed!")
return False
else:
print("⚠️ Unknown installation method, using standard upgrade...")
# Fallback to standard upgrade
result = run_command(f"pipx upgrade {package_name}", capture_output=False)
if result.returncode == 0:
print("✅ Upgrade completed successfully!")
return True
else:
print("❌ Upgrade failed!")
return False
def main():
"""Main function."""
parser = argparse.ArgumentParser(
description="Upgrade cumulusci-plus-azure-devops and its dependencies"
)
parser.add_argument(
"--force-reinstall",
action="store_true",
help="Force reinstall to ensure dependencies are updated",
)
parser.add_argument(
"--check-only",
action="store_true",
help="Only check current versions, don't upgrade",
)
args = parser.parse_args()
package_name = "cumulusci-plus-azure-devops"
print("🚀 CumulusCI Plus Azure DevOps Upgrade Tool")
print("=" * 50)
# Detect installation method
print("🔍 Detecting installation method...")
installation_method = detect_installation_method()
if installation_method == "injected":
print("✅ Detected: Plugin injected into cumulusci-plus environment")
print(" This is the recommended installation method!")
elif installation_method == "main_package":
print("✅ Detected: Plugin installed as main package with --include-deps")
elif installation_method == "unknown":
print("⚠️ Could not determine installation method")
print(" Will attempt standard upgrade...")
else:
print(f"❌ {package_name} does not appear to be installed via pipx")
print(" Available installation methods:")
print(
" 1. pipx install cumulusci-plus && pipx inject cumulusci-plus cumulusci-plus-azure-devops --include-apps"
)
print(" 2. pipx install cumulusci-plus-azure-devops --include-deps")
sys.exit(1)
# Show current dependency versions
print("\n📊 Current dependency versions:")
deps = get_dependency_versions(package_name, installation_method)
for dep in deps:
if dep.strip():
print(f" {dep}")
if args.check_only:
print("\n✅ Version check completed!")
return
print("\n🔄 Upgrade Options:")
if installation_method == "injected":
print("1. Normal upgrade (upgrade main app + update plugin)")
print("2. Force reinstall (reinstall main app + re-inject plugin)")
else:
print("1. Normal upgrade (recommended)")
print("2. Force reinstall (if dependencies seem outdated)")
if not args.force_reinstall:
try:
choice = input("\nChoose option (1 or 2): ").strip()
if choice == "2":
args.force_reinstall = True
except KeyboardInterrupt:
print("\n❌ Upgrade cancelled by user")
sys.exit(1)
# Perform upgrade
success = upgrade_package(package_name, args.force_reinstall, installation_method)
if success:
print("\n🎉 Upgrade completed!")
print("\n📊 Updated dependency versions:")
deps = get_dependency_versions(package_name, installation_method)
for dep in deps:
if dep.strip():
print(f" {dep}")
print("\n✅ Test your installation:")
if installation_method == "injected":
print(" cci --version")
print(" cumulusci-ado status")
else:
print(" cumulusci-ado status")
print(" cci-ado version")
else:
print("\n❌ Upgrade failed!")
print("💡 Try running with --force-reinstall option")
sys.exit(1)
if __name__ == "__main__":
main()