vm-tool 1.0.32__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.
Files changed (73) hide show
  1. examples/README.md +5 -0
  2. examples/__init__.py +1 -0
  3. examples/cloud/README.md +3 -0
  4. examples/cloud/__init__.py +1 -0
  5. examples/cloud/ssh_identity_file.py +27 -0
  6. examples/cloud/ssh_password.py +27 -0
  7. examples/cloud/template_cloud_setup.py +36 -0
  8. examples/deploy_full_setup.py +44 -0
  9. examples/docker-compose.example.yml +47 -0
  10. examples/ec2-setup.sh +95 -0
  11. examples/github-actions-ec2.yml +245 -0
  12. examples/github-actions-full-setup.yml +58 -0
  13. examples/local/.keep +1 -0
  14. examples/local/README.md +3 -0
  15. examples/local/__init__.py +1 -0
  16. examples/local/template_local_setup.py +27 -0
  17. examples/production-deploy.sh +70 -0
  18. examples/rollback.sh +52 -0
  19. examples/setup.sh +52 -0
  20. examples/ssh_key_management.py +22 -0
  21. examples/version_check.sh +3 -0
  22. vm_tool/__init__.py +0 -0
  23. vm_tool/alerting.py +274 -0
  24. vm_tool/audit.py +118 -0
  25. vm_tool/backup.py +125 -0
  26. vm_tool/benchmarking.py +200 -0
  27. vm_tool/cli.py +761 -0
  28. vm_tool/cloud.py +125 -0
  29. vm_tool/completion.py +200 -0
  30. vm_tool/compliance.py +104 -0
  31. vm_tool/config.py +92 -0
  32. vm_tool/drift.py +98 -0
  33. vm_tool/generator.py +462 -0
  34. vm_tool/health.py +197 -0
  35. vm_tool/history.py +131 -0
  36. vm_tool/kubernetes.py +89 -0
  37. vm_tool/metrics.py +183 -0
  38. vm_tool/notifications.py +152 -0
  39. vm_tool/plugins.py +119 -0
  40. vm_tool/policy.py +197 -0
  41. vm_tool/rbac.py +140 -0
  42. vm_tool/recovery.py +169 -0
  43. vm_tool/reporting.py +218 -0
  44. vm_tool/runner.py +445 -0
  45. vm_tool/secrets.py +285 -0
  46. vm_tool/ssh.py +150 -0
  47. vm_tool/state.py +122 -0
  48. vm_tool/strategies/__init__.py +16 -0
  49. vm_tool/strategies/ab_testing.py +258 -0
  50. vm_tool/strategies/blue_green.py +227 -0
  51. vm_tool/strategies/canary.py +277 -0
  52. vm_tool/validation.py +267 -0
  53. vm_tool/vm_setup/cleanup.yml +27 -0
  54. vm_tool/vm_setup/docker/create_docker_service.yml +63 -0
  55. vm_tool/vm_setup/docker/docker_setup.yml +7 -0
  56. vm_tool/vm_setup/docker/install_docker_and_compose.yml +92 -0
  57. vm_tool/vm_setup/docker/login_to_docker_hub.yml +6 -0
  58. vm_tool/vm_setup/github/git_configuration.yml +68 -0
  59. vm_tool/vm_setup/inventory.yml +1 -0
  60. vm_tool/vm_setup/k8s.yml +15 -0
  61. vm_tool/vm_setup/main.yml +27 -0
  62. vm_tool/vm_setup/monitoring.yml +42 -0
  63. vm_tool/vm_setup/project_service.yml +17 -0
  64. vm_tool/vm_setup/push_code.yml +40 -0
  65. vm_tool/vm_setup/setup.yml +17 -0
  66. vm_tool/vm_setup/setup_project_env.yml +7 -0
  67. vm_tool/webhooks.py +83 -0
  68. vm_tool-1.0.32.dist-info/METADATA +213 -0
  69. vm_tool-1.0.32.dist-info/RECORD +73 -0
  70. vm_tool-1.0.32.dist-info/WHEEL +5 -0
  71. vm_tool-1.0.32.dist-info/entry_points.txt +2 -0
  72. vm_tool-1.0.32.dist-info/licenses/LICENSE +21 -0
  73. vm_tool-1.0.32.dist-info/top_level.txt +2 -0
@@ -0,0 +1,277 @@
1
+ """Canary deployment strategy with gradual rollout and automatic rollback."""
2
+
3
+ import logging
4
+ import time
5
+ from typing import Optional, Dict, Any, List
6
+ from dataclasses import dataclass
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class CanaryConfig:
13
+ """Configuration for canary deployment."""
14
+
15
+ initial_percentage: int = 10 # Start with 10% traffic
16
+ increment_percentage: int = 10 # Increase by 10% each step
17
+ increment_interval: int = 300 # Wait 5 minutes between increments
18
+ success_threshold: float = 99.0 # 99% success rate required
19
+ error_threshold: float = 1.0 # Max 1% error rate
20
+ rollback_on_failure: bool = True
21
+
22
+
23
+ class CanaryDeployment:
24
+ """Manage canary deployments with gradual traffic shifting."""
25
+
26
+ def __init__(
27
+ self,
28
+ production_host: str,
29
+ canary_host: str,
30
+ config: Optional[CanaryConfig] = None,
31
+ ):
32
+ self.production_host = production_host
33
+ self.canary_host = canary_host
34
+ self.config = config or CanaryConfig()
35
+ self.current_percentage = 0
36
+
37
+ def deploy(
38
+ self,
39
+ compose_file: str,
40
+ monitor_metrics: bool = True,
41
+ ) -> Dict[str, Any]:
42
+ """Execute canary deployment with gradual rollout.
43
+
44
+ Args:
45
+ compose_file: Docker compose file to deploy
46
+ monitor_metrics: Whether to monitor metrics during rollout
47
+
48
+ Returns:
49
+ Deployment result
50
+ """
51
+ logger.info("🐤 Starting canary deployment")
52
+ logger.info(f" Production: {self.production_host}")
53
+ logger.info(f" Canary: {self.canary_host}")
54
+ logger.info(f" Initial traffic: {self.config.initial_percentage}%")
55
+
56
+ result = {
57
+ "success": False,
58
+ "canary_host": self.canary_host,
59
+ "final_percentage": 0,
60
+ "steps": [],
61
+ }
62
+
63
+ try:
64
+ # Step 1: Deploy to canary environment
65
+ logger.info("📦 Deploying to canary environment...")
66
+ self._deploy_to_canary(compose_file)
67
+
68
+ # Step 2: Health check canary
69
+ logger.info("🏥 Running health checks on canary...")
70
+ if not self._health_check(self.canary_host):
71
+ raise Exception("Canary health check failed")
72
+
73
+ # Step 3: Gradual traffic shift
74
+ logger.info("🔄 Starting gradual traffic shift...")
75
+ self.current_percentage = self.config.initial_percentage
76
+
77
+ while self.current_percentage < 100:
78
+ logger.info(
79
+ f" Shifting {self.current_percentage}% traffic to canary..."
80
+ )
81
+ self._shift_traffic(self.current_percentage)
82
+
83
+ result["steps"].append(
84
+ {
85
+ "percentage": self.current_percentage,
86
+ "timestamp": time.time(),
87
+ }
88
+ )
89
+
90
+ # Monitor metrics if enabled
91
+ if monitor_metrics and self.current_percentage < 100:
92
+ logger.info(
93
+ f" Monitoring metrics for {self.config.increment_interval}s..."
94
+ )
95
+ time.sleep(self.config.increment_interval)
96
+
97
+ metrics = self._get_canary_metrics()
98
+ logger.info(f" Canary metrics: {metrics}")
99
+
100
+ # Check if metrics are acceptable
101
+ if not self._metrics_acceptable(metrics):
102
+ logger.error("❌ Canary metrics failed threshold")
103
+ if self.config.rollback_on_failure:
104
+ self._rollback()
105
+ raise Exception("Canary metrics below threshold")
106
+
107
+ # Increment traffic percentage
108
+ if self.current_percentage < 100:
109
+ self.current_percentage = min(
110
+ 100, self.current_percentage + self.config.increment_percentage
111
+ )
112
+
113
+ # Step 4: Full cutover
114
+ logger.info("✅ Canary deployment successful - full cutover complete")
115
+ result["success"] = True
116
+ result["final_percentage"] = 100
117
+
118
+ return result
119
+
120
+ except Exception as e:
121
+ logger.error(f"❌ Canary deployment failed: {e}")
122
+ result["error"] = str(e)
123
+ return result
124
+
125
+ def _deploy_to_canary(self, compose_file: str):
126
+ """Deploy to canary environment."""
127
+ logger.info(f" Deploying {compose_file} to {self.canary_host}")
128
+ # TODO: Integrate with deploy-docker
129
+ pass
130
+
131
+ def _health_check(self, host: str) -> bool:
132
+ """Check if host is healthy."""
133
+ from vm_tool.health import check_http
134
+
135
+ try:
136
+ return check_http(f"http://{host}/health")
137
+ except:
138
+ return False
139
+
140
+ def _shift_traffic(self, percentage: int):
141
+ """Shift traffic to canary.
142
+
143
+ Args:
144
+ percentage: Percentage of traffic to send to canary (0-100)
145
+ """
146
+ # This would update load balancer configuration
147
+ # Implementation depends on LB type (nginx, AWS ALB, etc.)
148
+ logger.info(f" Traffic shift: {percentage}% to canary")
149
+ # TODO: Implement actual traffic shifting
150
+ pass
151
+
152
+ def _get_canary_metrics(self) -> Dict[str, float]:
153
+ """Get metrics from canary environment."""
154
+ # This would fetch actual metrics from monitoring system
155
+ # For now, return dummy metrics
156
+ return {
157
+ "success_rate": 99.5,
158
+ "error_rate": 0.5,
159
+ "avg_response_time": 120,
160
+ "requests_per_second": 100,
161
+ }
162
+
163
+ def _metrics_acceptable(self, metrics: Dict[str, float]) -> bool:
164
+ """Check if metrics meet thresholds."""
165
+ success_rate = metrics.get("success_rate", 0)
166
+ error_rate = metrics.get("error_rate", 100)
167
+
168
+ if success_rate < self.config.success_threshold:
169
+ logger.warning(
170
+ f" Success rate {success_rate}% below threshold {self.config.success_threshold}%"
171
+ )
172
+ return False
173
+
174
+ if error_rate > self.config.error_threshold:
175
+ logger.warning(
176
+ f" Error rate {error_rate}% above threshold {self.config.error_threshold}%"
177
+ )
178
+ return False
179
+
180
+ return True
181
+
182
+ def _rollback(self):
183
+ """Rollback canary deployment."""
184
+ logger.info("🔄 Rolling back canary deployment...")
185
+ self._shift_traffic(0) # Send all traffic back to production
186
+ logger.info("✅ Rollback complete")
187
+
188
+
189
+ class ProgressiveRollout:
190
+ """Progressive rollout across multiple hosts."""
191
+
192
+ def __init__(self, hosts: List[str], batch_size: int = 1):
193
+ self.hosts = hosts
194
+ self.batch_size = batch_size
195
+ self.deployed_hosts: List[str] = []
196
+
197
+ def deploy(
198
+ self,
199
+ compose_file: str,
200
+ wait_between_batches: int = 60,
201
+ ) -> Dict[str, Any]:
202
+ """Deploy progressively across hosts.
203
+
204
+ Args:
205
+ compose_file: Docker compose file
206
+ wait_between_batches: Seconds to wait between batches
207
+
208
+ Returns:
209
+ Deployment result
210
+ """
211
+ logger.info(f"🚀 Starting progressive rollout to {len(self.hosts)} hosts")
212
+ logger.info(f" Batch size: {self.batch_size}")
213
+
214
+ result = {
215
+ "success": False,
216
+ "deployed_hosts": [],
217
+ "failed_hosts": [],
218
+ }
219
+
220
+ # Deploy in batches
221
+ for i in range(0, len(self.hosts), self.batch_size):
222
+ batch = self.hosts[i : i + self.batch_size]
223
+ batch_num = (i // self.batch_size) + 1
224
+ total_batches = (len(self.hosts) + self.batch_size - 1) // self.batch_size
225
+
226
+ logger.info(f"📦 Deploying batch {batch_num}/{total_batches}: {batch}")
227
+
228
+ for host in batch:
229
+ try:
230
+ self._deploy_to_host(host, compose_file)
231
+
232
+ # Health check
233
+ if self._health_check(host):
234
+ self.deployed_hosts.append(host)
235
+ result["deployed_hosts"].append(host)
236
+ logger.info(f" ✅ {host} deployed successfully")
237
+ else:
238
+ raise Exception("Health check failed")
239
+
240
+ except Exception as e:
241
+ logger.error(f" ❌ {host} deployment failed: {e}")
242
+ result["failed_hosts"].append(host)
243
+ # Optionally stop on first failure
244
+ # raise
245
+
246
+ # Wait before next batch (unless this is the last batch)
247
+ if i + self.batch_size < len(self.hosts):
248
+ logger.info(f"⏳ Waiting {wait_between_batches}s before next batch...")
249
+ time.sleep(wait_between_batches)
250
+
251
+ result["success"] = len(result["failed_hosts"]) == 0
252
+
253
+ if result["success"]:
254
+ logger.info(
255
+ f"✅ Progressive rollout complete: {len(self.deployed_hosts)} hosts"
256
+ )
257
+ else:
258
+ logger.warning(
259
+ f"⚠️ Rollout completed with failures: {len(result['failed_hosts'])} failed"
260
+ )
261
+
262
+ return result
263
+
264
+ def _deploy_to_host(self, host: str, compose_file: str):
265
+ """Deploy to a single host."""
266
+ logger.info(f" Deploying to {host}...")
267
+ # TODO: Integrate with deploy-docker
268
+ pass
269
+
270
+ def _health_check(self, host: str) -> bool:
271
+ """Check if host is healthy."""
272
+ from vm_tool.health import check_http
273
+
274
+ try:
275
+ return check_http(f"http://{host}/health")
276
+ except:
277
+ return False
vm_tool/validation.py ADDED
@@ -0,0 +1,267 @@
1
+ """Deployment validation framework."""
2
+
3
+ import logging
4
+ from typing import List, Dict, Any, Callable, Optional
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ValidationStatus(Enum):
12
+ """Validation check status."""
13
+
14
+ PASSED = "passed"
15
+ FAILED = "failed"
16
+ SKIPPED = "skipped"
17
+ WARNING = "warning"
18
+
19
+
20
+ @dataclass
21
+ class ValidationResult:
22
+ """Result of a validation check."""
23
+
24
+ name: str
25
+ status: ValidationStatus
26
+ message: str
27
+ details: Optional[Dict[str, Any]] = None
28
+
29
+
30
+ class DeploymentValidator:
31
+ """Validate deployment readiness and health."""
32
+
33
+ def __init__(self, host: str, port: int = 8000):
34
+ self.host = host
35
+ self.port = port
36
+ self.checks: List[Callable] = []
37
+ self.results: List[ValidationResult] = []
38
+
39
+ def add_check(self, check_func: Callable, name: str):
40
+ """Add a validation check."""
41
+ self.checks.append((name, check_func))
42
+
43
+ def validate_all(self) -> bool:
44
+ """Run all validation checks.
45
+
46
+ Returns:
47
+ True if all checks passed, False otherwise
48
+ """
49
+ self.results = []
50
+ all_passed = True
51
+
52
+ logger.info(f"🔍 Running {len(self.checks)} validation checks...")
53
+
54
+ for name, check_func in self.checks:
55
+ try:
56
+ result = check_func()
57
+ self.results.append(result)
58
+
59
+ if result.status == ValidationStatus.PASSED:
60
+ logger.info(f" ✅ {name}: {result.message}")
61
+ elif result.status == ValidationStatus.WARNING:
62
+ logger.warning(f" ⚠️ {name}: {result.message}")
63
+ elif result.status == ValidationStatus.FAILED:
64
+ logger.error(f" ❌ {name}: {result.message}")
65
+ all_passed = False
66
+ else: # SKIPPED
67
+ logger.info(f" ⏭️ {name}: {result.message}")
68
+
69
+ except Exception as e:
70
+ logger.error(f" ❌ {name}: Check failed with error: {e}")
71
+ self.results.append(
72
+ ValidationResult(
73
+ name=name,
74
+ status=ValidationStatus.FAILED,
75
+ message=f"Check failed: {e}",
76
+ )
77
+ )
78
+ all_passed = False
79
+
80
+ return all_passed
81
+
82
+ def check_port_open(self) -> ValidationResult:
83
+ """Check if application port is open."""
84
+ import socket
85
+
86
+ try:
87
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
88
+ sock.settimeout(5)
89
+ result = sock.connect_ex((self.host, self.port))
90
+ sock.close()
91
+
92
+ if result == 0:
93
+ return ValidationResult(
94
+ name="Port Check",
95
+ status=ValidationStatus.PASSED,
96
+ message=f"Port {self.port} is open",
97
+ )
98
+ else:
99
+ return ValidationResult(
100
+ name="Port Check",
101
+ status=ValidationStatus.FAILED,
102
+ message=f"Port {self.port} is not accessible",
103
+ )
104
+ except Exception as e:
105
+ return ValidationResult(
106
+ name="Port Check",
107
+ status=ValidationStatus.FAILED,
108
+ message=f"Port check failed: {e}",
109
+ )
110
+
111
+ def check_http_response(self, endpoint: str = "/") -> ValidationResult:
112
+ """Check if HTTP endpoint responds."""
113
+ import requests
114
+
115
+ url = f"http://{self.host}:{self.port}{endpoint}"
116
+
117
+ try:
118
+ response = requests.get(url, timeout=10)
119
+
120
+ if response.status_code == 200:
121
+ return ValidationResult(
122
+ name="HTTP Response",
123
+ status=ValidationStatus.PASSED,
124
+ message=f"Endpoint {endpoint} returned 200",
125
+ details={"status_code": response.status_code},
126
+ )
127
+ else:
128
+ return ValidationResult(
129
+ name="HTTP Response",
130
+ status=ValidationStatus.WARNING,
131
+ message=f"Endpoint {endpoint} returned {response.status_code}",
132
+ details={"status_code": response.status_code},
133
+ )
134
+ except Exception as e:
135
+ return ValidationResult(
136
+ name="HTTP Response",
137
+ status=ValidationStatus.FAILED,
138
+ message=f"HTTP check failed: {e}",
139
+ )
140
+
141
+ def check_health_endpoint(self, endpoint: str = "/health") -> ValidationResult:
142
+ """Check health endpoint."""
143
+ import requests
144
+
145
+ url = f"http://{self.host}:{self.port}{endpoint}"
146
+
147
+ try:
148
+ response = requests.get(url, timeout=10)
149
+
150
+ if response.status_code == 200:
151
+ try:
152
+ data = response.json()
153
+ status = data.get("status", "unknown")
154
+
155
+ if status == "healthy" or status == "ok":
156
+ return ValidationResult(
157
+ name="Health Check",
158
+ status=ValidationStatus.PASSED,
159
+ message="Application is healthy",
160
+ details=data,
161
+ )
162
+ else:
163
+ return ValidationResult(
164
+ name="Health Check",
165
+ status=ValidationStatus.WARNING,
166
+ message=f"Health status: {status}",
167
+ details=data,
168
+ )
169
+ except:
170
+ return ValidationResult(
171
+ name="Health Check",
172
+ status=ValidationStatus.PASSED,
173
+ message="Health endpoint accessible",
174
+ )
175
+ else:
176
+ return ValidationResult(
177
+ name="Health Check",
178
+ status=ValidationStatus.FAILED,
179
+ message=f"Health endpoint returned {response.status_code}",
180
+ )
181
+ except Exception as e:
182
+ return ValidationResult(
183
+ name="Health Check",
184
+ status=ValidationStatus.FAILED,
185
+ message=f"Health check failed: {e}",
186
+ )
187
+
188
+ def check_database_connection(self, db_url: str) -> ValidationResult:
189
+ """Check database connectivity."""
190
+ # Placeholder - would implement actual DB connection check
191
+ return ValidationResult(
192
+ name="Database Connection",
193
+ status=ValidationStatus.SKIPPED,
194
+ message="Database check not configured",
195
+ )
196
+
197
+ def check_dependencies(self, dependencies: List[str]) -> ValidationResult:
198
+ """Check if required dependencies are available."""
199
+ # Placeholder - would check service dependencies
200
+ return ValidationResult(
201
+ name="Dependencies Check",
202
+ status=ValidationStatus.SKIPPED,
203
+ message="Dependency check not configured",
204
+ )
205
+
206
+ def generate_report(self) -> str:
207
+ """Generate validation report."""
208
+ passed = sum(1 for r in self.results if r.status == ValidationStatus.PASSED)
209
+ failed = sum(1 for r in self.results if r.status == ValidationStatus.FAILED)
210
+ warnings = sum(1 for r in self.results if r.status == ValidationStatus.WARNING)
211
+ skipped = sum(1 for r in self.results if r.status == ValidationStatus.SKIPPED)
212
+
213
+ report = f"""
214
+ Deployment Validation Report
215
+ ============================
216
+ Host: {self.host}:{self.port}
217
+ Total Checks: {len(self.results)}
218
+
219
+ Results:
220
+ ✅ Passed: {passed}
221
+ ❌ Failed: {failed}
222
+ ⚠️ Warnings: {warnings}
223
+ ⏭️ Skipped: {skipped}
224
+
225
+ Details:
226
+ """
227
+ for result in self.results:
228
+ icon = {
229
+ ValidationStatus.PASSED: "✅",
230
+ ValidationStatus.FAILED: "❌",
231
+ ValidationStatus.WARNING: "⚠️",
232
+ ValidationStatus.SKIPPED: "⏭️",
233
+ }[result.status]
234
+
235
+ report += f"\n{icon} {result.name}: {result.message}"
236
+ if result.details:
237
+ report += f"\n Details: {result.details}"
238
+
239
+ return report
240
+
241
+
242
+ def validate_deployment(host: str, port: int = 8000) -> bool:
243
+ """Quick deployment validation.
244
+
245
+ Args:
246
+ host: Host to validate
247
+ port: Port to check
248
+
249
+ Returns:
250
+ True if validation passed, False otherwise
251
+ """
252
+ validator = DeploymentValidator(host, port)
253
+
254
+ # Add standard checks
255
+ validator.add_check(validator.check_port_open, "Port Accessibility")
256
+ validator.add_check(validator.check_http_response, "HTTP Response")
257
+ validator.add_check(
258
+ lambda: validator.check_health_endpoint("/health"), "Health Endpoint"
259
+ )
260
+
261
+ # Run validation
262
+ result = validator.validate_all()
263
+
264
+ # Print report
265
+ print(validator.generate_report())
266
+
267
+ return result
@@ -0,0 +1,27 @@
1
+ - name: Cleanup VM
2
+ block:
3
+ - name: Remove GitHub credentials from git-credentials
4
+ file:
5
+ path: ~/.git-credentials
6
+ state: absent
7
+ become: yes
8
+ when: GITHUB_TOKEN is defined
9
+
10
+ - name: Clear Git credential helper configuration
11
+ shell: git config --global --unset credential.helper
12
+ become: yes
13
+ when: GITHUB_TOKEN is defined
14
+
15
+ - name: Unset environment variables
16
+ shell: |
17
+ unset GITHUB_PROJECT_URL
18
+ unset GITHUB_USERNAME
19
+ unset GITHUB_TOKEN
20
+ unset DOCKERHUB_USERNAME
21
+ unset DOCKERHUB_PASSWORD
22
+ environment:
23
+ GITHUB_PROJECT_URL: "{{ GITHUB_PROJECT_URL }}"
24
+ GITHUB_USERNAME: "{{ GITHUB_USERNAME }}"
25
+ GITHUB_TOKEN: "{{ GITHUB_TOKEN }}"
26
+ DOCKERHUB_USERNAME: "{{ DOCKERHUB_USERNAME }}"
27
+ DOCKERHUB_PASSWORD: "{{ DOCKERHUB_PASSWORD }}"
@@ -0,0 +1,63 @@
1
+ - name: Clean up Docker Environment and Set up Docker Service
2
+ block:
3
+ - name: Check if Docker Service File Exists
4
+ ansible.builtin.stat:
5
+ path: "{{ service_file_path }}"
6
+ register: service_file_stat
7
+
8
+ - name: Stop project service and ignore errors
9
+ ansible.builtin.systemd:
10
+ name: project
11
+ state: stopped
12
+ ignore_errors: yes
13
+
14
+ # - name: Run Docker System Prune
15
+ # ansible.builtin.command:
16
+ # cmd: docker system prune -f
17
+ # ignore_errors: yes
18
+
19
+ - name: Remove Existing Docker Service File
20
+ ansible.builtin.file:
21
+ path: "{{ service_file_path }}"
22
+ state: absent
23
+ when: service_file_stat.stat.exists
24
+
25
+ - name: Create Docker Service File
26
+ ansible.builtin.file:
27
+ path: "{{ service_file_path }}"
28
+ state: touch
29
+ mode: '0644'
30
+
31
+ - name: Check if Docker Compose File Path is Valid
32
+ ansible.builtin.stat:
33
+ path: "{{ project_dest_dir }}/{{ DOCKER_COMPOSE_FILE_PATH | default('docker-compose.yml') }}"
34
+ register: compose_file_stat
35
+
36
+ - name: Fail if Docker Compose File Path is Invalid
37
+ ansible.builtin.fail:
38
+ msg: "The specified Docker Compose file path '{{ project_dest_dir }}/{{ DOCKER_COMPOSE_FILE_PATH | default('docker-compose.yml') }}' does not exist."
39
+ when: not compose_file_stat.stat.exists
40
+
41
+ - name: Insert Docker Service Configuration
42
+ ansible.builtin.blockinfile:
43
+ path: "{{ service_file_path }}"
44
+ block: |
45
+ [Unit]
46
+ Description=Project Docker Container
47
+ After=docker.service
48
+
49
+ [Service]
50
+ Type=simple
51
+ WorkingDirectory={{ project_dest_dir }}
52
+ ExecStart={{ DEPLOY_COMMAND if DEPLOY_COMMAND is defined else ('docker compose -f ' + (DOCKER_COMPOSE_FILE_PATH | default('docker-compose.yml')) + (' --env-file ' + ENV_FILE_PATH if ENV_FILE_PATH is defined else '') + ' up -d') }}
53
+ Restart=always
54
+ RestartSec=10s
55
+
56
+ [Install]
57
+ WantedBy=multi-user.target
58
+
59
+ - name: Reload Systemd Daemon
60
+ ansible.builtin.systemd:
61
+ daemon_reload: yes
62
+
63
+ become: yes
@@ -0,0 +1,7 @@
1
+ - name: Docker Setup Playbook
2
+ block:
3
+ - name: Adding Docker and Compose packages
4
+ include_tasks: docker/install_docker_and_compose.yml
5
+
6
+ - name: Log in to Docker Hub
7
+ include_tasks: docker/login_to_docker_hub.yml