vm-tool 1.0.44__tar.gz → 1.0.45__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.
Files changed (124) hide show
  1. {vm_tool-1.0.44 → vm_tool-1.0.45}/PKG-INFO +1 -1
  2. {vm_tool-1.0.44 → vm_tool-1.0.45}/pyproject.toml +2 -2
  3. {vm_tool-1.0.44 → vm_tool-1.0.45}/setup.py +1 -1
  4. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/cli.py +81 -6
  5. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/generator.py +3 -5
  6. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/health.py +2 -2
  7. vm_tool-1.0.45/vm_tool/release.py +123 -0
  8. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/runner.py +82 -15
  9. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/push_code_tasks.yml +12 -0
  10. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool.egg-info/PKG-INFO +1 -1
  11. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool.egg-info/SOURCES.txt +1 -0
  12. {vm_tool-1.0.44 → vm_tool-1.0.45}/.agent/workflows/push.md +0 -0
  13. {vm_tool-1.0.44 → vm_tool-1.0.45}/.agent/workflows/test_and_lint.md +0 -0
  14. {vm_tool-1.0.44 → vm_tool-1.0.45}/.devcontainer/devcontainer.json +0 -0
  15. {vm_tool-1.0.44 → vm_tool-1.0.45}/.github/dependabot.yml +0 -0
  16. {vm_tool-1.0.44 → vm_tool-1.0.45}/.github/workflows/ci.yml +0 -0
  17. {vm_tool-1.0.44 → vm_tool-1.0.45}/.github/workflows/deploy.yml +0 -0
  18. {vm_tool-1.0.44 → vm_tool-1.0.45}/.github/workflows/publish.yml +0 -0
  19. {vm_tool-1.0.44 → vm_tool-1.0.45}/.gitignore +0 -0
  20. {vm_tool-1.0.44 → vm_tool-1.0.45}/.pre-commit-config.yaml +0 -0
  21. {vm_tool-1.0.44 → vm_tool-1.0.45}/CONTRIBUTING +0 -0
  22. {vm_tool-1.0.44 → vm_tool-1.0.45}/CONTRIBUTING.md +0 -0
  23. {vm_tool-1.0.44 → vm_tool-1.0.45}/LICENSE +0 -0
  24. {vm_tool-1.0.44 → vm_tool-1.0.45}/MANIFEST.in +0 -0
  25. {vm_tool-1.0.44 → vm_tool-1.0.45}/Makefile +0 -0
  26. {vm_tool-1.0.44 → vm_tool-1.0.45}/README.md +0 -0
  27. {vm_tool-1.0.44 → vm_tool-1.0.45}/codePushToGithub.py +0 -0
  28. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/MODULE_GUIDE.md +0 -0
  29. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/deployment-approaches.md +0 -0
  30. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/ec2-github-actions-guide.md +0 -0
  31. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/features.md +0 -0
  32. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/generator.md +0 -0
  33. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/index.md +0 -0
  34. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/pipeline-generator.md +0 -0
  35. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/reference/runner.md +0 -0
  36. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/reference/ssh.md +0 -0
  37. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/ssh-key-setup.md +0 -0
  38. {vm_tool-1.0.44 → vm_tool-1.0.45}/docs/usage.md +0 -0
  39. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/README.md +0 -0
  40. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/__init__.py +0 -0
  41. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/cloud/README.md +0 -0
  42. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/cloud/__init__.py +0 -0
  43. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/cloud/ssh_identity_file.py +0 -0
  44. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/cloud/ssh_password.py +0 -0
  45. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/cloud/template_cloud_setup.py +0 -0
  46. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/deploy_full_setup.py +0 -0
  47. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/docker-compose.example.yml +0 -0
  48. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/ec2-setup.sh +0 -0
  49. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/github-actions-ec2.yml +0 -0
  50. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/github-actions-full-setup.yml +0 -0
  51. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/local/.keep +0 -0
  52. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/local/README.md +0 -0
  53. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/local/__init__.py +0 -0
  54. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/local/template_local_setup.py +0 -0
  55. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/production-deploy.sh +0 -0
  56. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/rollback.sh +0 -0
  57. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/setup.sh +0 -0
  58. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/ssh_key_management.py +0 -0
  59. {vm_tool-1.0.44 → vm_tool-1.0.45}/examples/version_check.sh +0 -0
  60. {vm_tool-1.0.44 → vm_tool-1.0.45}/jingo.code-workspace +0 -0
  61. {vm_tool-1.0.44 → vm_tool-1.0.45}/mkdocs.yml +0 -0
  62. {vm_tool-1.0.44 → vm_tool-1.0.45}/molecule/default/converge.yml +0 -0
  63. {vm_tool-1.0.44 → vm_tool-1.0.45}/molecule/default/molecule.yml +0 -0
  64. {vm_tool-1.0.44 → vm_tool-1.0.45}/molecule/default/verify.yml +0 -0
  65. {vm_tool-1.0.44 → vm_tool-1.0.45}/requirements-docs.txt +0 -0
  66. {vm_tool-1.0.44 → vm_tool-1.0.45}/requirements.txt +0 -0
  67. {vm_tool-1.0.44 → vm_tool-1.0.45}/runtime.txt +0 -0
  68. {vm_tool-1.0.44 → vm_tool-1.0.45}/setup.cfg +0 -0
  69. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/conftest.py +0 -0
  70. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/integration/test_deployment.py +0 -0
  71. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_config.py +0 -0
  72. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_generator.py +0 -0
  73. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_health.py +0 -0
  74. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_history.py +0 -0
  75. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_logging.py +0 -0
  76. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_runner.py +0 -0
  77. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_ssh.py +0 -0
  78. {vm_tool-1.0.44 → vm_tool-1.0.45}/tests/test_state.py +0 -0
  79. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/__init__.py +0 -0
  80. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/alerting.py +0 -0
  81. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/audit.py +0 -0
  82. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/backup.py +0 -0
  83. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/benchmarking.py +0 -0
  84. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/cloud.py +0 -0
  85. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/completion.py +0 -0
  86. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/compliance.py +0 -0
  87. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/config.py +0 -0
  88. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/drift.py +0 -0
  89. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/history.py +0 -0
  90. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/kubernetes.py +0 -0
  91. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/metrics.py +0 -0
  92. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/notifications.py +0 -0
  93. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/plugins.py +0 -0
  94. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/policy.py +0 -0
  95. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/rbac.py +0 -0
  96. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/recovery.py +0 -0
  97. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/reporting.py +0 -0
  98. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/secrets.py +0 -0
  99. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/ssh.py +0 -0
  100. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/state.py +0 -0
  101. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/strategies/__init__.py +0 -0
  102. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/strategies/ab_testing.py +0 -0
  103. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/strategies/blue_green.py +0 -0
  104. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/strategies/canary.py +0 -0
  105. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/validation.py +0 -0
  106. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/cleanup.yml +0 -0
  107. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/docker/create_docker_service.yml +0 -0
  108. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/docker/docker_setup.yml +0 -0
  109. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/docker/install_docker_and_compose.yml +0 -0
  110. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/docker/login_to_docker_hub.yml +0 -0
  111. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/github/git_configuration.yml +0 -0
  112. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/inventory.yml +0 -0
  113. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/k8s.yml +0 -0
  114. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/main.yml +0 -0
  115. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/monitoring.yml +0 -0
  116. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/project_service.yml +0 -0
  117. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/push_code.yml +0 -0
  118. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/setup.yml +0 -0
  119. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/vm_setup/setup_project_env.yml +0 -0
  120. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool/webhooks.py +0 -0
  121. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool.egg-info/dependency_links.txt +0 -0
  122. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool.egg-info/entry_points.txt +0 -0
  123. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool.egg-info/requires.txt +0 -0
  124. {vm_tool-1.0.44 → vm_tool-1.0.45}/vm_tool.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vm_tool
3
- Version: 1.0.44
3
+ Version: 1.0.45
4
4
  Summary: A Comprehensive Tool for Setting Up Virtual Machines.
5
5
  Home-page: https://github.com/thesunnysinha/vm_tool
6
6
  Author: Sunny Sinha
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vm_tool"
7
- version = "1.0.44"
7
+ version = "1.0.45"
8
8
  description = "A Comprehensive Tool for Setting Up Virtual Machines."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -76,7 +76,7 @@ include = '\.pyi?$'
76
76
  profile = "black"
77
77
  multi_line_output = 3
78
78
  [tool.bumpversion]
79
- current_version = "1.0.44"
79
+ current_version = "1.0.45"
80
80
  commit = true
81
81
  tag = true
82
82
 
@@ -12,7 +12,7 @@ else:
12
12
 
13
13
  setup(
14
14
  name="vm_tool",
15
- version="1.0.44", # This will be updated by bump2version
15
+ version="1.0.45", # This will be updated by bump2version
16
16
  packages=find_packages(),
17
17
  description="A Comprehensive Tool for Setting Up Virtual Machines.",
18
18
  long_description=long_description,
@@ -264,11 +264,21 @@ def main():
264
264
  action="store_true",
265
265
  help="Preview deployment without executing (shows what would be deployed)",
266
266
  )
267
+ docker_parser.add_argument(
268
+ "--repo-name",
269
+ type=str,
270
+ help="Repository name (used to default project-dir to ~/apps/NAME)",
271
+ )
267
272
  docker_parser.add_argument(
268
273
  "--project-dir",
269
274
  type=str,
270
- default="~/app",
271
- help="Target directory on the server for deployment (default: ~/app)",
275
+ help="Target directory on the server (default: ~/apps/<repo-name>)",
276
+ )
277
+ docker_parser.add_argument(
278
+ "--health-timeout",
279
+ type=int,
280
+ default=300,
281
+ help="Timeout in seconds for health checks (default: 300)",
272
282
  )
273
283
 
274
284
  # Completion command
@@ -299,6 +309,32 @@ def main():
299
309
  help="CI/CD Platform",
300
310
  )
301
311
 
312
+ # Release Prep command
313
+ release_parser = subparsers.add_parser(
314
+ "prepare-release", help="Prepare Docker Compose file for release"
315
+ )
316
+ release_parser.add_argument(
317
+ "--base-file", required=True, help="Base docker-compose file"
318
+ )
319
+ release_parser.add_argument(
320
+ "--prod-file", required=True, help="Production overlay file"
321
+ )
322
+ release_parser.add_argument("--output", required=True, help="Output file path")
323
+ release_parser.add_argument(
324
+ "--strip-volumes", help="Comma-separated list of services to strip volumes from"
325
+ )
326
+ release_parser.add_argument(
327
+ "--fix-paths", action="store_true", help="Fix CI absolute paths to relative"
328
+ )
329
+
330
+ # Setup CI command
331
+ setup_ci_parser = subparsers.add_parser(
332
+ "setup-ci", help="Configure CI environment (SSH, Inventory)"
333
+ )
334
+ setup_ci_parser.add_argument(
335
+ "--provider", default="github", choices=["github"], help="CI Provider"
336
+ )
337
+
302
338
  # Secrets command
303
339
  secrets_parser = subparsers.add_parser("secrets", help="Manage secrets")
304
340
  secrets_subparsers = secrets_parser.add_subparsers(
@@ -612,6 +648,14 @@ def main():
612
648
  "compose_file", "docker-compose.yml"
613
649
  )
614
650
 
651
+ # Project Directory Logic
652
+ project_dir = args.project_dir
653
+ if not project_dir:
654
+ if args.repo_name:
655
+ project_dir = f"~/apps/{args.repo_name}"
656
+ else:
657
+ project_dir = "~/app"
658
+
615
659
  # Dry-run mode: show what would be deployed
616
660
  if args.dry_run:
617
661
  print("\n🔍 DRY-RUN MODE - No changes will be made\n")
@@ -619,6 +663,7 @@ def main():
619
663
  print(f" Target Host: {host or 'from inventory'}")
620
664
  print(f" SSH User: {user or 'from inventory'}")
621
665
  print(f" Compose File: {compose_file}")
666
+ print(f" Project Dir: {project_dir}")
622
667
  print(f" Inventory: {args.inventory}")
623
668
  if args.env_file:
624
669
  print(f" Env File: {args.env_file}")
@@ -651,15 +696,15 @@ def main():
651
696
  env_file=args.env_file,
652
697
  deploy_command=args.deploy_command,
653
698
  force=args.force,
654
- project_dir=args.project_dir,
699
+ project_dir=project_dir,
655
700
  )
656
701
 
657
702
  # Run health checks if specified
658
703
  if host and (args.health_check or args.health_port or args.health_url):
659
704
  from vm_tool.health import SmokeTestSuite
660
705
 
661
- print("\n🏥 Running Health Checks...")
662
- suite = SmokeTestSuite(host)
706
+ print(f"\n🏥 Running Health Checks (Timeout: {args.health_timeout}s)...")
707
+ suite = SmokeTestSuite(host, timeout=args.health_timeout)
663
708
 
664
709
  if args.health_port:
665
710
  suite.add_port_check(args.health_port)
@@ -779,7 +824,37 @@ def main():
779
824
  if deployment_type == "custom":
780
825
  deploy_command = input("Enter custom deployment command: ").strip()
781
826
 
782
- elif deployment_type == "docker":
827
+ elif args.command == "prepare-release":
828
+ from vm_tool.release import ReleaseManager
829
+ manager = ReleaseManager(verbose=args.verbose)
830
+ try:
831
+ manager.prepare_release(
832
+ base_file=args.base_file,
833
+ prod_file=args.prod_file,
834
+ output_file=args.output,
835
+ strip_volumes=args.strip_volumes,
836
+ fix_paths=args.fix_paths
837
+ )
838
+ except Exception as e:
839
+ print(f"❌ Release preparation failed: {e}")
840
+ sys.exit(1)
841
+
842
+ except Exception as e:
843
+ print(f"❌ Release preparation failed: {e}")
844
+ sys.exit(1)
845
+
846
+ elif args.command == "setup-ci":
847
+ from vm_tool.runner import SetupRunner
848
+ # No config needed for this utility
849
+ try:
850
+ # We can add a static method or simple function for this
851
+ SetupRunner.setup_ci_environment(provider=args.provider)
852
+ print("✅ CI Environment configured successfully")
853
+ except Exception as e:
854
+ print(f"❌ CI Setup failed: {e}")
855
+ sys.exit(1)
856
+
857
+ elif deployment_type == "docker":
783
858
  docker_compose_file = (
784
859
  input(
785
860
  "Enter Docker Compose file name [docker-compose.yml]: "
@@ -345,7 +345,7 @@ jobs:
345
345
 
346
346
  # Replace absolute paths (from CI runner) with relative paths
347
347
  # This assumes the structure: /home/runner/work/repo/repo/ -> ./
348
- sed -i 's|/home/runner/work/[^/]*/[^/]*||g' docker-compose.released.yml
348
+ sed -i 's|/home/runner/work/[^/]*/[^/]*|.|g' docker-compose.released.yml
349
349
 
350
350
  echo "✅ Generated combined Docker Compose file"
351
351
  cat docker-compose.released.yml
@@ -447,11 +447,9 @@ jobs:
447
447
  "--user" "${{ secrets.SSH_USERNAME }}"
448
448
  )
449
449
 
450
- # Move .env if it exists and add to args
450
+ # Add .env if it exists
451
451
  if [ -f .env ]; then
452
- mkdir -p env
453
- mv .env env/.env
454
- ARGS+=("--env-file" "env/.env")
452
+ ARGS+=("--env-file" ".env")
455
453
  fi
456
454
 
457
455
  vm_tool deploy-docker "${ARGS[@]}"
@@ -120,9 +120,9 @@ class HealthCheck:
120
120
  class SmokeTestSuite:
121
121
  """Manages a suite of smoke tests."""
122
122
 
123
- def __init__(self, host: str):
123
+ def __init__(self, host: str, timeout: int = 300):
124
124
  self.host = host
125
- self.health_check = HealthCheck(host)
125
+ self.health_check = HealthCheck(host, timeout=timeout)
126
126
  self.tests: List[Dict] = []
127
127
 
128
128
  def add_port_check(self, port: int, name: str = None):
@@ -0,0 +1,123 @@
1
+ import subprocess
2
+ import yaml
3
+ import os
4
+ import shutil
5
+
6
+
7
+ class ReleaseManager:
8
+ def __init__(self, verbose=False):
9
+ self.verbose = verbose
10
+
11
+ def _log(self, message):
12
+ if self.verbose:
13
+ print(f"DEBUG: {message}")
14
+
15
+ def merge_compose_files(self, files, output_file):
16
+ """
17
+ Merge multiple docker-compose files using `docker compose config`.
18
+ """
19
+ if not files:
20
+ raise ValueError("No input files provided")
21
+
22
+ cmd = ["docker", "compose"]
23
+ for f in files:
24
+ if not os.path.exists(f):
25
+ # Fallback: check if it exists relative to current dir?
26
+ # Or just skip/warn? For now, fail if explicit file missing.
27
+ # But our logic handles missing optional files upstream.
28
+ if self.verbose:
29
+ print(f"Warning: File {f} does not exist, skipping.")
30
+ continue
31
+ cmd.extend(["-f", f])
32
+
33
+ cmd.append("config")
34
+
35
+ self._log(f"Running: {' '.join(cmd)}")
36
+
37
+ try:
38
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
39
+ with open(output_file, "w") as f:
40
+ f.write(result.stdout)
41
+ print(f"✅ Merged configuration written to {output_file}")
42
+ return True
43
+ except subprocess.CalledProcessError as e:
44
+ print(f"❌ Error merging compose files: {e.stderr}")
45
+ raise
46
+
47
+ def fix_paths(
48
+ self,
49
+ file_path,
50
+ target_pattern=r"/home/runner/work/[^/]*/[^/]*",
51
+ replacement=".",
52
+ ):
53
+ """
54
+ Replace absolute CI paths with relative paths.
55
+ Uses sed-like logic but in Python.
56
+ """
57
+ import re
58
+
59
+ with open(file_path, "r") as f:
60
+ content = f.read()
61
+
62
+ # Regex replacement
63
+ new_content = re.sub(target_pattern, replacement, content)
64
+
65
+ if content != new_content:
66
+ with open(file_path, "w") as f:
67
+ f.write(new_content)
68
+ print(f"✅ Fixed paths in {file_path}")
69
+ else:
70
+ self._log("No path replacements needed.")
71
+
72
+ def strip_service_volumes(self, file_path, service_name):
73
+ """
74
+ Load YAML, remove 'volumes' from the specified service, and write back.
75
+ Also remove nginx-specific hardcoded volume keys like 'nginx.conf' if present
76
+ in a way that pyyaml doesn't mess up.
77
+ Actually, let's use a pure YAML approach.
78
+ """
79
+ with open(file_path, "r") as f:
80
+ data = yaml.safe_load(f)
81
+
82
+ services = data.get("services", {})
83
+ if service_name in services:
84
+ service = services[service_name]
85
+ if "volumes" in service:
86
+ del service["volumes"]
87
+ print(f"✅ Stripped volumes from service '{service_name}'")
88
+
89
+ # Write back
90
+ with open(file_path, "w") as f:
91
+ yaml.dump(data, f, default_flow_style=False, sort_keys=False)
92
+ else:
93
+ self._log(f"Service '{service_name}' has no volumes to strip.")
94
+ else:
95
+ self._log(f"Service '{service_name}' not found in compose file.")
96
+
97
+ def prepare_release(
98
+ self, base_file, prod_file, output_file, strip_volumes=None, fix_paths=True
99
+ ):
100
+ """
101
+ Orchestrate the release preparation.
102
+ """
103
+ files_to_merge = []
104
+ if os.path.exists(base_file):
105
+ files_to_merge.append(base_file)
106
+
107
+ # Check prod file variants
108
+ if os.path.exists(prod_file):
109
+ files_to_merge.append(prod_file)
110
+ elif os.path.exists(os.path.basename(prod_file)):
111
+ files_to_merge.append(os.path.basename(prod_file))
112
+
113
+ if not files_to_merge:
114
+ raise FileNotFoundError("No valid docker-compose files found to merge.")
115
+
116
+ self.merge_compose_files(files_to_merge, output_file)
117
+
118
+ if fix_paths:
119
+ self.fix_paths(output_file)
120
+
121
+ if strip_volumes:
122
+ for svc in strip_volumes.split(","):
123
+ self.strip_service_volumes(output_file, svc.strip())
@@ -215,25 +215,36 @@ class SetupRunner:
215
215
  # Check volumes (bind mounts)
216
216
  volumes = service.get("volumes", [])
217
217
  for vol in volumes:
218
+ host_path = None
218
219
  if isinstance(vol, str):
219
220
  parts = vol.split(":")
220
221
  if len(parts) >= 2:
221
222
  host_path = parts[0]
222
- # Check if it's a relative path bind mount
223
- if (
224
- host_path.startswith("./")
225
- or host_path.startswith("../")
226
- ) and os.path.exists(host_path):
227
- if host_path.startswith("./"):
228
- clean_path = host_path[2:]
229
- else:
230
- clean_path = host_path
231
-
232
- if clean_path not in found_paths:
233
- found_paths.add(clean_path)
234
- dependencies.append(
235
- {"src": clean_path, "dest": clean_path}
236
- )
223
+ elif isinstance(vol, dict):
224
+ # Handle object-style volume definitions
225
+ if vol.get("type") == "bind":
226
+ host_path = vol.get("source")
227
+ elif "source" in vol: # Sometimes type is omitted
228
+ host_path = vol.get("source")
229
+
230
+ if host_path:
231
+ # Check if it's a relative path bind mount
232
+ # Normalize path for existence check
233
+ if host_path.startswith("./"):
234
+ check_path = host_path[2:]
235
+ else:
236
+ check_path = host_path
237
+
238
+ if (
239
+ host_path.startswith("./")
240
+ or host_path.startswith("../")
241
+ or not host_path.startswith("/")
242
+ ) and os.path.exists(check_path):
243
+ if check_path not in found_paths:
244
+ found_paths.add(check_path)
245
+ dependencies.append(
246
+ {"src": check_path, "dest": check_path}
247
+ )
237
248
 
238
249
  except Exception as e:
239
250
  logger.warning(f"Failed to parse compose file for dependencies: {e}")
@@ -594,3 +605,59 @@ class SetupRunner:
594
605
  if host:
595
606
  state.mark_failed(host, service_name, str(e))
596
607
  raise
608
+
609
+ @staticmethod
610
+ def setup_ci_environment(provider="github"):
611
+ """
612
+ Configures CI environment (SSH, Inventory) based on standard env vars.
613
+ Inputs (Env Vars): SSH_ID_RSA, SSH_HOSTNAME, SSH_USERNAME
614
+ Outputs: ~/.ssh/deploy_key, inventory.yml
615
+ """
616
+ ssh_key = os.environ.get("SSH_ID_RSA")
617
+ ssh_host = os.environ.get("SSH_HOSTNAME")
618
+ ssh_user = os.environ.get("SSH_USERNAME")
619
+
620
+ missing = []
621
+ if not ssh_key:
622
+ missing.append("SSH_ID_RSA")
623
+ if not ssh_host:
624
+ missing.append("SSH_HOSTNAME")
625
+ if not ssh_user:
626
+ missing.append("SSH_USERNAME")
627
+
628
+ if missing:
629
+ raise ValueError(
630
+ f"Missing required environment variables: {', '.join(missing)}"
631
+ )
632
+
633
+ # 1. Setup SSH Key
634
+ ssh_dir = os.path.expanduser("~/.ssh")
635
+ os.makedirs(ssh_dir, exist_ok=True)
636
+ key_path = os.path.join(ssh_dir, "deploy_key")
637
+
638
+ # Clean newlines if potentially mangled
639
+ ssh_key_clean = ssh_key.replace(r"\n", "\n")
640
+
641
+ with open(key_path, "w") as f:
642
+ f.write(ssh_key_clean)
643
+ os.chmod(key_path, 0o600)
644
+ logger.info(f"✅ SSH key written to {key_path}")
645
+
646
+ # 2. Create Inventory
647
+ inventory_content = {
648
+ "all": {
649
+ "hosts": {
650
+ "production": {
651
+ "ansible_host": ssh_host,
652
+ "ansible_user": ssh_user,
653
+ "ansible_ssh_private_key_file": key_path,
654
+ # Using standard CI strict checking disabled for automation
655
+ "ansible_ssh_common_args": "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=60 -o ServerAliveCountMax=10",
656
+ }
657
+ }
658
+ }
659
+ }
660
+
661
+ with open("inventory.yml", "w") as f:
662
+ yaml.dump(inventory_content, f)
663
+ logger.info("✅ inventory.yml created")
@@ -40,6 +40,18 @@
40
40
  loop: "{{ FILES_TO_COPY | default([]) }}"
41
41
  when: FILES_TO_COPY is defined and (item.dest | dirname) != ''
42
42
 
43
+ - name: Clean up conflicting directories
44
+ shell: |
45
+ target="{{ project_dest_dir }}/{{ item.dest }}"
46
+ if [ -d "$target" ]; then
47
+ rm -rf "$target"
48
+ echo "removed_directory"
49
+ fi
50
+ loop: "{{ FILES_TO_COPY | default([]) }}"
51
+ register: dir_cleanup
52
+ changed_when: "'removed_directory' in dir_cleanup.stdout"
53
+ when: FILES_TO_COPY is defined
54
+
43
55
  - name: Copy dependency files
44
56
  copy:
45
57
  src: "{{ SOURCE_PATH }}/{{ item.src }}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vm_tool
3
- Version: 1.0.44
3
+ Version: 1.0.45
4
4
  Summary: A Comprehensive Tool for Setting Up Virtual Machines.
5
5
  Home-page: https://github.com/thesunnysinha/vm_tool
6
6
  Author: Sunny Sinha
@@ -87,6 +87,7 @@ vm_tool/plugins.py
87
87
  vm_tool/policy.py
88
88
  vm_tool/rbac.py
89
89
  vm_tool/recovery.py
90
+ vm_tool/release.py
90
91
  vm_tool/reporting.py
91
92
  vm_tool/runner.py
92
93
  vm_tool/secrets.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes