vm-tool 1.0.42__tar.gz → 1.0.44__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 (123) hide show
  1. vm_tool-1.0.44/.agent/workflows/push.md +11 -0
  2. vm_tool-1.0.44/.agent/workflows/test_and_lint.md +18 -0
  3. {vm_tool-1.0.42 → vm_tool-1.0.44}/PKG-INFO +1 -1
  4. {vm_tool-1.0.42 → vm_tool-1.0.44}/codePushToGithub.py +32 -6
  5. vm_tool-1.0.44/jingo.code-workspace +11 -0
  6. {vm_tool-1.0.42 → vm_tool-1.0.44}/pyproject.toml +3 -6
  7. {vm_tool-1.0.42 → vm_tool-1.0.44}/setup.py +1 -1
  8. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/cli.py +80 -1
  9. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/generator.py +121 -37
  10. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/secrets.py +75 -0
  11. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool.egg-info/PKG-INFO +1 -1
  12. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool.egg-info/SOURCES.txt +3 -0
  13. {vm_tool-1.0.42 → vm_tool-1.0.44}/.devcontainer/devcontainer.json +0 -0
  14. {vm_tool-1.0.42 → vm_tool-1.0.44}/.github/dependabot.yml +0 -0
  15. {vm_tool-1.0.42 → vm_tool-1.0.44}/.github/workflows/ci.yml +0 -0
  16. {vm_tool-1.0.42 → vm_tool-1.0.44}/.github/workflows/deploy.yml +0 -0
  17. {vm_tool-1.0.42 → vm_tool-1.0.44}/.github/workflows/publish.yml +0 -0
  18. {vm_tool-1.0.42 → vm_tool-1.0.44}/.gitignore +0 -0
  19. {vm_tool-1.0.42 → vm_tool-1.0.44}/.pre-commit-config.yaml +0 -0
  20. {vm_tool-1.0.42 → vm_tool-1.0.44}/CONTRIBUTING +0 -0
  21. {vm_tool-1.0.42 → vm_tool-1.0.44}/CONTRIBUTING.md +0 -0
  22. {vm_tool-1.0.42 → vm_tool-1.0.44}/LICENSE +0 -0
  23. {vm_tool-1.0.42 → vm_tool-1.0.44}/MANIFEST.in +0 -0
  24. {vm_tool-1.0.42 → vm_tool-1.0.44}/Makefile +0 -0
  25. {vm_tool-1.0.42 → vm_tool-1.0.44}/README.md +0 -0
  26. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/MODULE_GUIDE.md +0 -0
  27. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/deployment-approaches.md +0 -0
  28. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/ec2-github-actions-guide.md +0 -0
  29. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/features.md +0 -0
  30. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/generator.md +0 -0
  31. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/index.md +0 -0
  32. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/pipeline-generator.md +0 -0
  33. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/reference/runner.md +0 -0
  34. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/reference/ssh.md +0 -0
  35. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/ssh-key-setup.md +0 -0
  36. {vm_tool-1.0.42 → vm_tool-1.0.44}/docs/usage.md +0 -0
  37. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/README.md +0 -0
  38. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/__init__.py +0 -0
  39. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/cloud/README.md +0 -0
  40. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/cloud/__init__.py +0 -0
  41. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/cloud/ssh_identity_file.py +0 -0
  42. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/cloud/ssh_password.py +0 -0
  43. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/cloud/template_cloud_setup.py +0 -0
  44. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/deploy_full_setup.py +0 -0
  45. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/docker-compose.example.yml +0 -0
  46. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/ec2-setup.sh +0 -0
  47. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/github-actions-ec2.yml +0 -0
  48. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/github-actions-full-setup.yml +0 -0
  49. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/local/.keep +0 -0
  50. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/local/README.md +0 -0
  51. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/local/__init__.py +0 -0
  52. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/local/template_local_setup.py +0 -0
  53. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/production-deploy.sh +0 -0
  54. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/rollback.sh +0 -0
  55. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/setup.sh +0 -0
  56. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/ssh_key_management.py +0 -0
  57. {vm_tool-1.0.42 → vm_tool-1.0.44}/examples/version_check.sh +0 -0
  58. {vm_tool-1.0.42 → vm_tool-1.0.44}/mkdocs.yml +0 -0
  59. {vm_tool-1.0.42 → vm_tool-1.0.44}/molecule/default/converge.yml +0 -0
  60. {vm_tool-1.0.42 → vm_tool-1.0.44}/molecule/default/molecule.yml +0 -0
  61. {vm_tool-1.0.42 → vm_tool-1.0.44}/molecule/default/verify.yml +0 -0
  62. {vm_tool-1.0.42 → vm_tool-1.0.44}/requirements-docs.txt +0 -0
  63. {vm_tool-1.0.42 → vm_tool-1.0.44}/requirements.txt +0 -0
  64. {vm_tool-1.0.42 → vm_tool-1.0.44}/runtime.txt +0 -0
  65. {vm_tool-1.0.42 → vm_tool-1.0.44}/setup.cfg +0 -0
  66. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/conftest.py +0 -0
  67. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/integration/test_deployment.py +0 -0
  68. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_config.py +0 -0
  69. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_generator.py +0 -0
  70. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_health.py +0 -0
  71. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_history.py +0 -0
  72. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_logging.py +0 -0
  73. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_runner.py +0 -0
  74. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_ssh.py +0 -0
  75. {vm_tool-1.0.42 → vm_tool-1.0.44}/tests/test_state.py +0 -0
  76. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/__init__.py +0 -0
  77. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/alerting.py +0 -0
  78. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/audit.py +0 -0
  79. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/backup.py +0 -0
  80. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/benchmarking.py +0 -0
  81. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/cloud.py +0 -0
  82. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/completion.py +0 -0
  83. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/compliance.py +0 -0
  84. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/config.py +0 -0
  85. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/drift.py +0 -0
  86. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/health.py +0 -0
  87. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/history.py +0 -0
  88. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/kubernetes.py +0 -0
  89. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/metrics.py +0 -0
  90. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/notifications.py +0 -0
  91. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/plugins.py +0 -0
  92. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/policy.py +0 -0
  93. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/rbac.py +0 -0
  94. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/recovery.py +0 -0
  95. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/reporting.py +0 -0
  96. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/runner.py +0 -0
  97. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/ssh.py +0 -0
  98. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/state.py +0 -0
  99. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/strategies/__init__.py +0 -0
  100. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/strategies/ab_testing.py +0 -0
  101. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/strategies/blue_green.py +0 -0
  102. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/strategies/canary.py +0 -0
  103. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/validation.py +0 -0
  104. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/cleanup.yml +0 -0
  105. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/docker/create_docker_service.yml +0 -0
  106. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/docker/docker_setup.yml +0 -0
  107. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/docker/install_docker_and_compose.yml +0 -0
  108. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/docker/login_to_docker_hub.yml +0 -0
  109. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/github/git_configuration.yml +0 -0
  110. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/inventory.yml +0 -0
  111. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/k8s.yml +0 -0
  112. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/main.yml +0 -0
  113. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/monitoring.yml +0 -0
  114. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/project_service.yml +0 -0
  115. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/push_code.yml +0 -0
  116. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/push_code_tasks.yml +0 -0
  117. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/setup.yml +0 -0
  118. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/vm_setup/setup_project_env.yml +0 -0
  119. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool/webhooks.py +0 -0
  120. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool.egg-info/dependency_links.txt +0 -0
  121. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool.egg-info/entry_points.txt +0 -0
  122. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool.egg-info/requires.txt +0 -0
  123. {vm_tool-1.0.42 → vm_tool-1.0.44}/vm_tool.egg-info/top_level.txt +0 -0
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: Push code using the custom automation script
3
+ ---
4
+
5
+ This workflow uses the project's custom script to commit, bump version, and push changes. It ensures consistent versioning and environment management.
6
+
7
+ 1. Run the custom push script
8
+ ```bash
9
+ # Replace arguments as needed
10
+ python3 codePushToGithub.py --branch main --message "Update via Agent"
11
+ ```
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: Run tests and linting for vm_tool
3
+ ---
4
+
5
+ This workflow runs the project's tests and linting checks using the configured Makefile and Python tools.
6
+
7
+ 1. Install dependencies (if needed) and run tests
8
+
9
+ ```bash
10
+ pip install -e ".[dev]"
11
+ pytest
12
+ ```
13
+
14
+ 2. Run linting checks
15
+ ```bash
16
+ flake8 vm_tool
17
+ black --check vm_tool
18
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vm_tool
3
- Version: 1.0.42
3
+ Version: 1.0.44
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
@@ -128,20 +128,38 @@ def get_current_version():
128
128
 
129
129
 
130
130
  def main():
131
+ import argparse
132
+
133
+ parser = argparse.ArgumentParser(
134
+ description="Automate git commits and pushes with version bumping."
135
+ )
136
+ parser.add_argument("--branch", "-b", help="Branch name to push to", default=None)
137
+ parser.add_argument("--message", "-m", help="Commit message", default=None)
138
+ args = parser.parse_args()
139
+
131
140
  # Setup virtual environment
132
141
  print("🔧 Setting up environment...")
133
142
  setup_environment()
134
143
  print()
135
144
 
136
- # Ask for the branch name
137
- branch_name = input("Enter the branch name: ").strip()
145
+ # Ask for the branch name if not provided
146
+ if args.branch:
147
+ branch_name = args.branch
148
+ else:
149
+ branch_name = input("Enter the branch name: ").strip()
138
150
 
139
- if branch_name == "main":
151
+ if branch_name.lower() in ["main", "master"]:
140
152
  # 1. Commit User Changes FIRST
141
153
  print("Adding changes...")
142
154
  run_command("git add .")
143
155
 
144
- commit_message = input("Enter the commit message for your changes: ").strip()
156
+ if args.message:
157
+ commit_message = args.message
158
+ else:
159
+ commit_message = input(
160
+ "Enter the commit message for your changes: "
161
+ ).strip()
162
+
145
163
  print("Committing changes...")
146
164
  # Try to commit changes
147
165
  result = run_command(f'git commit -m "{commit_message}"', check=False)
@@ -181,14 +199,22 @@ def main():
181
199
  else:
182
200
  # Create and switch to the new branch
183
201
  print(f"Creating and switching to branch: {branch_name}")
184
- run_command(f"git checkout -b {branch_name}")
202
+ # Check if branch exists
203
+ result = run_command(f"git rev-parse --verify {branch_name}", check=False)
204
+ if result.returncode == 0:
205
+ run_command(f"git checkout {branch_name}")
206
+ else:
207
+ run_command(f"git checkout -b {branch_name}")
185
208
 
186
209
  # Add all changes
187
210
  print("Adding changes...")
188
211
  run_command("git add .")
189
212
 
190
213
  # Ask for commit message
191
- commit_message = input("Enter the commit message: ").strip()
214
+ if args.message:
215
+ commit_message = args.message
216
+ else:
217
+ commit_message = input("Enter the commit message: ").strip()
192
218
 
193
219
  # Commit with the entered message
194
220
  print("Committing changes...")
@@ -0,0 +1,11 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": "../jingo"
5
+ },
6
+ {
7
+ "path": "."
8
+ }
9
+ ],
10
+ "settings": {}
11
+ }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vm_tool"
7
- version = "1.0.42"
7
+ version = "1.0.44"
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.42"
79
+ current_version = "1.0.44"
80
80
  commit = true
81
81
  tag = true
82
82
 
@@ -85,10 +85,7 @@ filename = "setup.py"
85
85
  search = 'version="{current_version}"'
86
86
  replace = 'version="{new_version}"'
87
87
 
88
- [[tool.bumpversion.files]]
89
- filename = "vm_tool/cli.py"
90
- search = 'version="{current_version}"'
91
- replace = 'version="{new_version}"'
88
+
92
89
 
93
90
  [[tool.bumpversion.files]]
94
91
  filename = "pyproject.toml"
@@ -12,7 +12,7 @@ else:
12
12
 
13
13
  setup(
14
14
  name="vm_tool",
15
- version="1.0.42", # This will be updated by bump2version
15
+ version="1.0.44", # 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,
@@ -1,12 +1,17 @@
1
1
  import argparse
2
2
  import sys
3
+ import importlib.metadata
3
4
 
4
5
 
5
6
  def main():
6
7
  parser = argparse.ArgumentParser(
7
8
  description="VM Tool: Setup, Provision, and Manage VMs"
8
9
  )
9
- parser.add_argument("--version", action="version", version="1.0.42")
10
+ try:
11
+ version = importlib.metadata.version("vm_tool")
12
+ except importlib.metadata.PackageNotFoundError:
13
+ version = "unknown"
14
+ parser.add_argument("--version", action="version", version=version)
10
15
  parser.add_argument(
11
16
  "--verbose", "-v", action="store_true", help="Enable verbose output"
12
17
  )
@@ -294,6 +299,23 @@ def main():
294
299
  help="CI/CD Platform",
295
300
  )
296
301
 
302
+ # Secrets command
303
+ secrets_parser = subparsers.add_parser("secrets", help="Manage secrets")
304
+ secrets_subparsers = secrets_parser.add_subparsers(
305
+ dest="secrets_command", help="Secrets operations"
306
+ )
307
+
308
+ # secrets sync
309
+ sync_parser = secrets_subparsers.add_parser(
310
+ "sync", help="Sync local .env to GitHub Secrets"
311
+ )
312
+ sync_parser.add_argument(
313
+ "--env-file", type=str, default=".env", help="Path to .env file"
314
+ )
315
+ sync_parser.add_argument(
316
+ "--repo", type=str, help="Target GitHub repository (owner/repo)"
317
+ )
318
+
297
319
  args = parser.parse_args()
298
320
 
299
321
  # Configure logging based on flags
@@ -802,6 +824,63 @@ def main():
802
824
  except Exception as e:
803
825
  print(f"Error: {e}")
804
826
  sys.exit(1)
827
+ elif args.command == "secrets":
828
+ if args.secrets_command == "sync":
829
+ from vm_tool.secrets import SecretsManager
830
+ import os
831
+
832
+ if not os.path.exists(args.env_file):
833
+ print(f"❌ Env file not found: {args.env_file}")
834
+ sys.exit(1)
835
+
836
+ try:
837
+ manager = SecretsManager.from_github(repo=args.repo)
838
+
839
+ print(f"📖 Reading secrets from {args.env_file}...")
840
+ secrets_to_sync = {}
841
+ with open(args.env_file, "r") as f:
842
+ for line in f:
843
+ line = line.strip()
844
+ if not line or line.startswith("#"):
845
+ continue
846
+ # Basic env parsing
847
+ if "=" in line:
848
+ key, value = line.split("=", 1)
849
+ key = key.strip()
850
+ value = value.strip().strip("'").strip('"')
851
+ secrets_to_sync[key] = value
852
+
853
+ if not secrets_to_sync:
854
+ print("⚠️ No secrets found to sync.")
855
+ sys.exit(0)
856
+
857
+ print(f"🚀 Syncing {len(secrets_to_sync)} secrets to GitHub...")
858
+ # Confirm with user
859
+ confirm = (
860
+ input(
861
+ f"Proceed to upload {len(secrets_to_sync)} secrets? (yes/no): "
862
+ )
863
+ .strip()
864
+ .lower()
865
+ )
866
+ if confirm != "yes":
867
+ print("❌ Operation cancelled.")
868
+ sys.exit(0)
869
+
870
+ success_count = 0
871
+ for key, value in secrets_to_sync.items():
872
+ print(f" Uploading {key}...")
873
+ if manager.set(key, value):
874
+ success_count += 1
875
+
876
+ print(
877
+ f"\n✨ Successfully synced {success_count}/{len(secrets_to_sync)} secrets!"
878
+ )
879
+
880
+ except Exception as e:
881
+ print(f"❌ Error syncing secrets: {e}")
882
+ sys.exit(1)
883
+
805
884
  else:
806
885
  parser.print_help()
807
886
 
@@ -22,6 +22,8 @@ class PipelineGenerator:
22
22
  health_url: Optional[str] = None,
23
23
  backup_paths: Optional[list] = None,
24
24
  app_port: int = 8000,
25
+ hydrate_env: bool = True,
26
+ combine_compose: bool = True,
25
27
  ):
26
28
  self.platform = platform
27
29
  self.strategy = strategy
@@ -37,6 +39,8 @@ class PipelineGenerator:
37
39
  )
38
40
  self.backup_paths = backup_paths or ["/app", "/etc/nginx"]
39
41
  self.app_port = app_port
42
+ self.hydrate_env = hydrate_env
43
+ self.combine_compose = combine_compose
40
44
 
41
45
  # New options
42
46
  self.run_linting = False
@@ -50,8 +54,7 @@ class PipelineGenerator:
50
54
  run_tests: bool = False,
51
55
  python_version: str = "3.11",
52
56
  branch: str = "main",
53
- ):
54
- """Set additional options for the pipeline."""
57
+ ): """Set additional options for the pipeline."""
55
58
  self.run_linting = run_linting
56
59
  self.run_tests = run_tests
57
60
  self.python_version = python_version
@@ -120,6 +123,12 @@ class PipelineGenerator:
120
123
  if self.enable_dry_run:
121
124
  steps.append(self._step_dry_run())
122
125
 
126
+ if self.hydrate_env:
127
+ steps.append(self._step_hydrate_env())
128
+
129
+ if self.combine_compose:
130
+ steps.append(self._step_combine_compose())
131
+
123
132
  # Main deployment
124
133
  steps.append(self._step_deploy())
125
134
 
@@ -143,7 +152,7 @@ class PipelineGenerator:
143
152
  # Combine all steps
144
153
  steps_yaml = "\n".join(steps)
145
154
 
146
- return f"""name: Deploy to EC2 with vm_tool
155
+ return f"""name: Deploy with vm_tool
147
156
 
148
157
  on:
149
158
  push:
@@ -153,8 +162,8 @@ on:
153
162
  workflow_dispatch:
154
163
 
155
164
  env:
156
- EC2_HOST: ${{{{ secrets.EC2_HOST }}}}
157
- EC2_USER: ${{{{ secrets.EC2_USER }}}}
165
+ SSH_HOSTNAME: ${{{{ secrets.SSH_HOSTNAME }}}}
166
+ SSH_USERNAME: ${{{{ secrets.SSH_USERNAME }}}}
158
167
  APP_PORT: {self.app_port}
159
168
 
160
169
  jobs:
@@ -176,16 +185,16 @@ jobs:
176
185
  echo "🔐 Validating GitHub Secrets..."
177
186
  MISSING_SECRETS=()
178
187
 
179
- if [ -z "${{ secrets.EC2_HOST }}" ]; then
180
- MISSING_SECRETS+=("EC2_HOST")
188
+ if [ -z "${{ secrets.SSH_HOSTNAME }}" ]; then
189
+ MISSING_SECRETS+=("SSH_HOSTNAME")
181
190
  fi
182
191
 
183
- if [ -z "${{ secrets.EC2_USER }}" ]; then
184
- MISSING_SECRETS+=("EC2_USER")
192
+ if [ -z "${{ secrets.SSH_USERNAME }}" ]; then
193
+ MISSING_SECRETS+=("SSH_USERNAME")
185
194
  fi
186
195
 
187
- if [ -z "${{ secrets.EC2_SSH_KEY }}" ]; then
188
- MISSING_SECRETS+=("EC2_SSH_KEY")
196
+ if [ -z "${{ secrets.SSH_ID_RSA }}" ]; then
197
+ MISSING_SECRETS+=("SSH_ID_RSA")
189
198
  fi
190
199
 
191
200
  if [ ${#MISSING_SECRETS[@]} -ne 0 ]; then
@@ -199,16 +208,16 @@ jobs:
199
208
  echo "2. Add each secret:"
200
209
  echo ""
201
210
 
202
- if [[ " ${MISSING_SECRETS[*]} " =~ " EC2_HOST " ]]; then
203
- echo " EC2_HOST: Your EC2 IP (e.g., 54.123.45.67)"
211
+ if [[ " ${MISSING_SECRETS[*]} " =~ " SSH_HOSTNAME " ]]; then
212
+ echo " SSH_HOSTNAME: Your Server IP (e.g., 54.123.45.67)"
204
213
  fi
205
214
 
206
- if [[ " ${MISSING_SECRETS[*]} " =~ " EC2_USER " ]]; then
207
- echo " EC2_USER: SSH username (e.g., ubuntu)"
215
+ if [[ " ${MISSING_SECRETS[*]} " =~ " SSH_USERNAME " ]]; then
216
+ echo " SSH_USERNAME: SSH username (e.g., ubuntu)"
208
217
  fi
209
218
 
210
- if [[ " ${MISSING_SECRETS[*]} " =~ " EC2_SSH_KEY " ]]; then
211
- echo " EC2_SSH_KEY: Run 'cat ~/.ssh/id_rsa' and copy output"
219
+ if [[ " ${MISSING_SECRETS[*]} " =~ " SSH_ID_RSA " ]]; then
220
+ echo " SSH_ID_RSA: Run 'cat ~/.ssh/id_rsa' and copy output"
212
221
  fi
213
222
 
214
223
  echo ""
@@ -293,9 +302,54 @@ jobs:
293
302
  - name: Set up SSH
294
303
  run: |
295
304
  mkdir -p ~/.ssh
296
- echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/deploy_key
305
+ echo "${{ secrets.SSH_ID_RSA }}" > ~/.ssh/deploy_key
297
306
  chmod 600 ~/.ssh/deploy_key
298
- ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts"""
307
+ ssh-keyscan -H ${{ secrets.SSH_HOSTNAME }} >> ~/.ssh/known_hosts
308
+
309
+ # Create SSH config to use the key for the specific host
310
+ cat > ~/.ssh/config <<EOF
311
+ Host ${{ secrets.SSH_HOSTNAME }}
312
+ User ${{ secrets.SSH_USERNAME }}
313
+ IdentityFile ~/.ssh/deploy_key
314
+ StrictHostKeyChecking no
315
+ EOF"""
316
+
317
+ def _step_hydrate_env(self) -> str:
318
+ return """
319
+ - name: Hydrate Environment Files from Secrets
320
+ env:
321
+ SECRETS_CONTEXT: ${{ toJSON(secrets) }}
322
+ run: |
323
+ # Use vm_tool to create local env files from secrets based on compose file
324
+ vm_tool hydrate-env \\
325
+ --compose-file docker-compose.yml \\
326
+ --secrets "$SECRETS_CONTEXT"
327
+ echo "✅ Hydrated environment files from secrets"
328
+ """
329
+
330
+ def _step_combine_compose(self) -> str:
331
+ return """
332
+ - name: Combine Docker Compose files
333
+ run: |
334
+ # Combine base and prod compose files if prod exists
335
+ if [ -f docker/docker-compose.prod.yml ]; then
336
+ echo "Merging docker-compose.yml and docker/docker-compose.prod.yml..."
337
+ docker compose -f docker-compose.yml -f docker/docker-compose.prod.yml config > docker-compose.released.yml
338
+ elif [ -f docker-compose.prod.yml ]; then
339
+ echo "Merging docker-compose.yml and docker-compose.prod.yml..."
340
+ docker compose -f docker-compose.yml -f docker-compose.prod.yml config > docker-compose.released.yml
341
+ else
342
+ echo "No prod overriding file found, using base file..."
343
+ docker compose -f docker-compose.yml config > docker-compose.released.yml
344
+ fi
345
+
346
+ # Replace absolute paths (from CI runner) with relative paths
347
+ # This assumes the structure: /home/runner/work/repo/repo/ -> ./
348
+ sed -i 's|/home/runner/work/[^/]*/[^/]*||g' docker-compose.released.yml
349
+
350
+ echo "✅ Generated combined Docker Compose file"
351
+ cat docker-compose.released.yml
352
+ """
299
353
 
300
354
  def _step_validate_ssh(self) -> str:
301
355
  return """
@@ -356,25 +410,52 @@ jobs:
356
410
  def _step_deploy(self) -> str:
357
411
  return """
358
412
  - name: Deploy with vm_tool (Ansible-based)
413
+ env:
414
+ # Define deployment command for the released file
415
+ DEPLOY_COMMAND: "docker compose -f docker-compose.released.yml up -d --remove-orphans"
359
416
  run: |
360
417
  # Create inventory file for Ansible
361
418
  cat > inventory.yml << EOF
362
419
  all:
363
420
  hosts:
364
421
  production:
365
- ansible_host: ${{ secrets.EC2_HOST }}
366
- ansible_user: ${{ secrets.EC2_USER }}
422
+ ansible_host: ${{ secrets.SSH_HOSTNAME }}
423
+ ansible_user: ${{ secrets.SSH_USERNAME }}
367
424
  ansible_ssh_private_key_file: ~/.ssh/deploy_key
368
425
  EOF
369
426
 
370
427
  # Deploy using vm_tool (uses Ansible under the hood)
371
428
  export GITHUB_REPOSITORY_OWNER=${{ github.repository_owner }}
372
- vm_tool deploy-docker \\
373
- --host ${{ secrets.EC2_HOST }} \\
374
- --user ${{ secrets.EC2_USER }} \\
375
- --compose-file ~/app/docker-compose.yml \\
376
- --inventory inventory.yml \\
377
- --force"""
429
+
430
+ # Determine Project Directory based on user (handle root case)
431
+ if [ "${{ secrets.SSH_USERNAME }}" = "root" ]; then
432
+ HOME_DIR="/root"
433
+ else
434
+ HOME_DIR="/home/${{ secrets.SSH_USERNAME }}"
435
+ fi
436
+ PROJECT_DIR="${HOME_DIR}/apps/${{ github.event.repository.name }}"
437
+
438
+ # Prepare arguments
439
+ ARGS=(
440
+ "--inventory" "inventory.yml"
441
+ "--compose-file" "docker-compose.released.yml"
442
+ "--project-dir" "$PROJECT_DIR"
443
+ "--deploy-command" "${{ env.DEPLOY_COMMAND }}"
444
+ "--force"
445
+ "--health-url" "http://${{ secrets.SSH_HOSTNAME }}:${{ env.APP_PORT }}/health"
446
+ "--host" "${{ secrets.SSH_HOSTNAME }}"
447
+ "--user" "${{ secrets.SSH_USERNAME }}"
448
+ )
449
+
450
+ # Move .env if it exists and add to args
451
+ if [ -f .env ]; then
452
+ mkdir -p env
453
+ mv .env env/.env
454
+ ARGS+=("--env-file" "env/.env")
455
+ fi
456
+
457
+ vm_tool deploy-docker "${ARGS[@]}"
458
+ """
378
459
 
379
460
  def _step_health_check(self) -> str:
380
461
  return f"""
@@ -394,10 +475,13 @@ jobs:
394
475
  return """
395
476
  - name: Verify
396
477
  run: |
397
- ssh -i ~/.ssh/deploy_key ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF'
398
- cd ~/app
399
- docker-compose ps
400
- docker-compose logs --tail=20
478
+ # Use absolute path for verify step too
479
+ PROJECT_DIR="/home/${{ secrets.SSH_USERNAME }}/apps/${{ github.event.repository.name }}"
480
+
481
+ ssh -i ~/.ssh/deploy_key ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOSTNAME }} << EOF
482
+ cd $PROJECT_DIR
483
+ docker compose ps
484
+ docker compose logs --tail=20
401
485
  EOF"""
402
486
 
403
487
  def _step_rollback(self) -> str:
@@ -406,12 +490,12 @@ jobs:
406
490
  if: failure()
407
491
  run: |
408
492
  echo "⚠️ Rolling back..."
409
- ssh -i ~/.ssh/deploy_key ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF'
493
+ # Note: Rollback logic might need adjustment for absolute paths, keeping simple for now
494
+ ssh -i ~/.ssh/deploy_key ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOSTNAME }} << 'EOF'
410
495
  BACKUP=$(ls -t ~/backups/*.tar.gz 2>/dev/null | head -1)
411
496
  if [ -n "$BACKUP" ]; then
412
- cd ~/app && tar -xzf $BACKUP
413
- docker-compose up -d
414
- echo "✅ Rolled back"
497
+ # TODO: Improve rollback to handle dynamic dirs
498
+ echo "Rollback not fully implemented for dynamic paths yet"
415
499
  fi
416
500
  EOF"""
417
501
 
@@ -420,7 +504,7 @@ jobs:
420
504
  - name: Cleanup
421
505
  if: success()
422
506
  run: |
423
- ssh -i ~/.ssh/deploy_key ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << 'EOF'
507
+ ssh -i ~/.ssh/deploy_key ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOSTNAME }} << 'EOF'
424
508
  cd ~/backups 2>/dev/null || exit 0
425
509
  ls -t *.tar.gz 2>/dev/null | tail -n +6 | xargs rm -f || true
426
510
  EOF"""
@@ -431,7 +515,7 @@ jobs:
431
515
  if: always()
432
516
  run: |
433
517
  if [ "${{ job.status }}" == "success" ]; then
434
- echo "✅ Deployed to ${{ secrets.EC2_HOST }}:${{ env.APP_PORT }}"
518
+ echo "✅ Deployed to ${{ secrets.SSH_HOSTNAME }}:${{ env.APP_PORT }}"
435
519
  else
436
520
  echo "❌ Deployment failed"
437
521
  fi"""
@@ -246,6 +246,76 @@ class EncryptedFileBackend(SecretsBackend):
246
246
  return False
247
247
 
248
248
 
249
+ class GitHubSecretsBackend(SecretsBackend):
250
+ """GitHub Actions Secrets backend using 'gh' CLI."""
251
+
252
+ def __init__(self, repo: Optional[str] = None):
253
+ import shutil
254
+
255
+ self.gh_cli = shutil.which("gh")
256
+ if not self.gh_cli:
257
+ raise RuntimeError("GitHub CLI ('gh') is not installed.")
258
+ self.repo = repo
259
+
260
+ def get_secret(self, key: str) -> Optional[str]:
261
+ """
262
+ Get secret from GitHub.
263
+ Note: GitHub Secrets are write-only. We can't retrieve the value.
264
+ """
265
+ logger.warning("Cannot retrieve secret value from GitHub (write-only).")
266
+ return None
267
+
268
+ def set_secret(self, key: str, value: str) -> bool:
269
+ """Set secret in GitHub Actions."""
270
+ import subprocess
271
+
272
+ cmd = ["gh", "secret", "set", key]
273
+ if self.repo:
274
+ cmd.extend(["--repo", self.repo])
275
+
276
+ try:
277
+ # Pass value via stdin
278
+ subprocess.run(cmd, input=value, check=True, text=True, capture_output=True)
279
+ logger.info(f"Secret '{key}' stored in GitHub Secrets")
280
+ return True
281
+ except subprocess.CalledProcessError as e:
282
+ logger.error(f"Failed to set secret in GitHub: {e.stderr.strip()}")
283
+ return False
284
+
285
+ def delete_secret(self, key: str) -> bool:
286
+ """Delete secret from GitHub Actions."""
287
+ import subprocess
288
+
289
+ cmd = ["gh", "secret", "delete", key]
290
+ if self.repo:
291
+ cmd.extend(["--repo", self.repo])
292
+
293
+ try:
294
+ subprocess.run(cmd, check=True, text=True, capture_output=True)
295
+ logger.info(f"Secret '{key}' deleted from GitHub Secrets")
296
+ return True
297
+ except subprocess.CalledProcessError as e:
298
+ logger.error(f"Failed to delete secret from GitHub: {e.stderr.strip()}")
299
+ return False
300
+
301
+ def list_secrets(self) -> list:
302
+ """List all secret keys in GitHub Actions."""
303
+ import subprocess
304
+ import json
305
+
306
+ cmd = ["gh", "secret", "list", "--json", "name"]
307
+ if self.repo:
308
+ cmd.extend(["--repo", self.repo])
309
+
310
+ try:
311
+ result = subprocess.run(cmd, check=True, text=True, capture_output=True)
312
+ secrets = json.loads(result.stdout)
313
+ return [s["name"] for s in secrets]
314
+ except Exception as e:
315
+ logger.error(f"Failed to list secrets from GitHub: {e}")
316
+ return []
317
+
318
+
249
319
  class SecretsManager:
250
320
  """Unified secrets manager supporting multiple backends."""
251
321
 
@@ -283,3 +353,8 @@ class SecretsManager:
283
353
  def from_file(cls, secrets_file: str = ".secrets.enc", **kwargs):
284
354
  """Create secrets manager with encrypted file backend."""
285
355
  return cls(EncryptedFileBackend(secrets_file, **kwargs))
356
+
357
+ @classmethod
358
+ def from_github(cls, repo: Optional[str] = None):
359
+ """Create secrets manager with GitHub backend."""
360
+ return cls(GitHubSecretsBackend(repo))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vm_tool
3
- Version: 1.0.42
3
+ Version: 1.0.44
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
@@ -7,12 +7,15 @@ MANIFEST.in
7
7
  Makefile
8
8
  README.md
9
9
  codePushToGithub.py
10
+ jingo.code-workspace
10
11
  mkdocs.yml
11
12
  pyproject.toml
12
13
  requirements-docs.txt
13
14
  requirements.txt
14
15
  runtime.txt
15
16
  setup.py
17
+ .agent/workflows/push.md
18
+ .agent/workflows/test_and_lint.md
16
19
  .devcontainer/devcontainer.json
17
20
  .github/dependabot.yml
18
21
  .github/workflows/ci.yml
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