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,70 @@
1
+ #!/bin/bash
2
+
3
+ # Production Deployment Script
4
+ # This script demonstrates a complete production deployment workflow using vm_tool
5
+
6
+ set -e # Exit on error
7
+
8
+ echo "🚀 Starting Production Deployment"
9
+ echo "=================================="
10
+
11
+ # Configuration
12
+ PROFILE="production"
13
+ HOST="10.0.2.10"
14
+ BACKUP_PATHS="/app /etc/nginx /var/www"
15
+
16
+ # Step 1: Create backup before deployment
17
+ echo ""
18
+ echo "📦 Step 1: Creating backup..."
19
+ vm_tool backup create \
20
+ --host $HOST \
21
+ --paths $BACKUP_PATHS
22
+
23
+ # Step 2: Check for configuration drift
24
+ echo ""
25
+ echo "🔍 Step 2: Checking for configuration drift..."
26
+ if ! vm_tool drift-check --host $HOST; then
27
+ echo "⚠️ Warning: Drift detected. Review changes before proceeding."
28
+ read -p "Continue anyway? (yes/no): " continue
29
+ if [ "$continue" != "yes" ]; then
30
+ echo "❌ Deployment cancelled"
31
+ exit 1
32
+ fi
33
+ fi
34
+
35
+ # Step 3: Dry-run deployment
36
+ echo ""
37
+ echo "🔍 Step 3: Running dry-run..."
38
+ vm_tool deploy-docker --profile $PROFILE --dry-run
39
+
40
+ # Step 4: Confirm deployment
41
+ echo ""
42
+ read -p "Proceed with deployment? (yes/no): " confirm
43
+ if [ "$confirm" != "yes" ]; then
44
+ echo "❌ Deployment cancelled"
45
+ exit 1
46
+ fi
47
+
48
+ # Step 5: Deploy with health checks
49
+ echo ""
50
+ echo "🚀 Step 5: Deploying..."
51
+ vm_tool deploy-docker \
52
+ --profile $PROFILE \
53
+ --health-port 8000 \
54
+ --health-url "http://$HOST:8000/health" \
55
+ --health-check "docker ps | grep web"
56
+
57
+ # Step 6: Verify deployment
58
+ echo ""
59
+ echo "✅ Step 6: Verifying deployment..."
60
+ vm_tool history --host $HOST --limit 1
61
+
62
+ # Step 7: Final drift check
63
+ echo ""
64
+ echo "🔍 Step 7: Final drift check..."
65
+ vm_tool drift-check --host $HOST
66
+
67
+ echo ""
68
+ echo "=================================="
69
+ echo "✅ Production deployment complete!"
70
+ echo "=================================="
examples/rollback.sh ADDED
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+
3
+ # Emergency Rollback Script
4
+
5
+ set -e
6
+
7
+ echo "🔄 Emergency Rollback"
8
+ echo "===================="
9
+
10
+ # Get host from argument or prompt
11
+ HOST=${1:-}
12
+ if [ -z "$HOST" ]; then
13
+ read -p "Enter host IP: " HOST
14
+ fi
15
+
16
+ # Show recent deployments
17
+ echo ""
18
+ echo "📜 Recent deployments for $HOST:"
19
+ vm_tool history --host $HOST --limit 5
20
+
21
+ # Get deployment ID
22
+ echo ""
23
+ read -p "Enter deployment ID to rollback to (or press Enter for previous): " DEPLOYMENT_ID
24
+
25
+ # Confirm rollback
26
+ echo ""
27
+ echo "⚠️ WARNING: This will rollback the deployment on $HOST"
28
+ read -p "Are you sure? (yes/no): " confirm
29
+
30
+ if [ "$confirm" != "yes" ]; then
31
+ echo "❌ Rollback cancelled"
32
+ exit 1
33
+ fi
34
+
35
+ # Execute rollback
36
+ echo ""
37
+ echo "🔄 Rolling back..."
38
+ if [ -z "$DEPLOYMENT_ID" ]; then
39
+ vm_tool rollback --host $HOST
40
+ else
41
+ vm_tool rollback --host $HOST --to $DEPLOYMENT_ID
42
+ fi
43
+
44
+ # Verify
45
+ echo ""
46
+ echo "✅ Rollback complete. Current deployment:"
47
+ vm_tool history --host $HOST --limit 1
48
+
49
+ echo ""
50
+ echo "🔍 Checking system health..."
51
+ # Add your health check commands here
52
+ # vm_tool drift-check --host $HOST
examples/setup.sh ADDED
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+
3
+ # Setup Script - Configure vm_tool for your environment
4
+
5
+ echo "🔧 VM Tool Setup"
6
+ echo "================"
7
+
8
+ # Create development profile
9
+ echo ""
10
+ echo "Creating development profile..."
11
+ vm_tool config create-profile dev \
12
+ --environment development \
13
+ --host 192.168.1.100 \
14
+ --user ubuntu \
15
+ --compose-file docker-compose.dev.yml
16
+
17
+ # Create staging profile
18
+ echo ""
19
+ echo "Creating staging profile..."
20
+ vm_tool config create-profile staging \
21
+ --environment staging \
22
+ --host 10.0.1.5 \
23
+ --user deploy \
24
+ --compose-file docker-compose.yml
25
+
26
+ # Create production profile
27
+ echo ""
28
+ echo "Creating production profile..."
29
+ vm_tool config create-profile production \
30
+ --environment production \
31
+ --host 10.0.2.10 \
32
+ --user prod \
33
+ --compose-file docker-compose.yml
34
+
35
+ # Set defaults
36
+ echo ""
37
+ echo "Setting default configuration..."
38
+ vm_tool config set default-user ubuntu
39
+ vm_tool config set default-compose-file docker-compose.yml
40
+
41
+ # List all profiles
42
+ echo ""
43
+ echo "📋 Configured profiles:"
44
+ vm_tool config list-profiles
45
+
46
+ echo ""
47
+ echo "✅ Setup complete!"
48
+ echo ""
49
+ echo "Usage examples:"
50
+ echo " vm_tool deploy-docker --profile dev"
51
+ echo " vm_tool deploy-docker --profile staging --dry-run"
52
+ echo " vm_tool deploy-docker --profile production --health-port 8000"
@@ -0,0 +1,22 @@
1
+ """
2
+ Example: SSH Key Management for a VM.
3
+ - Generates an SSH key pair (if not present), configures the VM for SSH access, and updates local SSH config.
4
+ - Useful for preparing a VM for secure, passwordless SSH access.
5
+ """
6
+
7
+ from vm_tool.ssh import SSHSetup
8
+
9
+
10
+ def main():
11
+ ssh_setup = SSHSetup(
12
+ hostname="your_vm_hostname",
13
+ username="your_vm_username",
14
+ password="your_vm_password",
15
+ email="your_email_for_ssh_key",
16
+ )
17
+
18
+ ssh_setup.setup()
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ # Check installed version of vm_tool
3
+ vm_tool --version
vm_tool/__init__.py ADDED
File without changes
vm_tool/alerting.py ADDED
@@ -0,0 +1,274 @@
1
+ """Alerting system with multiple notification channels."""
2
+
3
+ import logging
4
+ from typing import Dict, Any, List, Optional
5
+ from enum import Enum
6
+ from dataclasses import dataclass
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class AlertSeverity(Enum):
12
+ """Alert severity levels."""
13
+
14
+ INFO = "info"
15
+ WARNING = "warning"
16
+ ERROR = "error"
17
+ CRITICAL = "critical"
18
+
19
+
20
+ @dataclass
21
+ class Alert:
22
+ """Alert message."""
23
+
24
+ title: str
25
+ message: str
26
+ severity: AlertSeverity
27
+ metadata: Optional[Dict[str, Any]] = None
28
+
29
+
30
+ class AlertChannel:
31
+ """Base class for alert channels."""
32
+
33
+ def send(self, alert: Alert) -> bool:
34
+ """Send alert through this channel."""
35
+ raise NotImplementedError
36
+
37
+
38
+ class SlackAlertChannel(AlertChannel):
39
+ """Send alerts to Slack."""
40
+
41
+ def __init__(self, webhook_url: str):
42
+ self.webhook_url = webhook_url
43
+
44
+ def send(self, alert: Alert) -> bool:
45
+ """Send alert to Slack."""
46
+ import requests
47
+
48
+ # Color based on severity
49
+ colors = {
50
+ AlertSeverity.INFO: "#36a64f",
51
+ AlertSeverity.WARNING: "#ff9900",
52
+ AlertSeverity.ERROR: "#ff0000",
53
+ AlertSeverity.CRITICAL: "#8b0000",
54
+ }
55
+
56
+ payload = {
57
+ "attachments": [
58
+ {
59
+ "color": colors.get(alert.severity, "#808080"),
60
+ "title": alert.title,
61
+ "text": alert.message,
62
+ "fields": [
63
+ {
64
+ "title": "Severity",
65
+ "value": alert.severity.value.upper(),
66
+ "short": True,
67
+ }
68
+ ],
69
+ }
70
+ ]
71
+ }
72
+
73
+ if alert.metadata:
74
+ for key, value in alert.metadata.items():
75
+ payload["attachments"][0]["fields"].append(
76
+ {
77
+ "title": key.replace("_", " ").title(),
78
+ "value": str(value),
79
+ "short": True,
80
+ }
81
+ )
82
+
83
+ try:
84
+ response = requests.post(self.webhook_url, json=payload)
85
+ response.raise_for_status()
86
+ logger.info("Alert sent to Slack")
87
+ return True
88
+ except Exception as e:
89
+ logger.error(f"Failed to send Slack alert: {e}")
90
+ return False
91
+
92
+
93
+ class PagerDutyAlertChannel(AlertChannel):
94
+ """Send alerts to PagerDuty."""
95
+
96
+ def __init__(self, integration_key: str):
97
+ self.integration_key = integration_key
98
+
99
+ def send(self, alert: Alert) -> bool:
100
+ """Send alert to PagerDuty."""
101
+ import requests
102
+
103
+ # Map severity to PagerDuty severity
104
+ severity_map = {
105
+ AlertSeverity.INFO: "info",
106
+ AlertSeverity.WARNING: "warning",
107
+ AlertSeverity.ERROR: "error",
108
+ AlertSeverity.CRITICAL: "critical",
109
+ }
110
+
111
+ payload = {
112
+ "routing_key": self.integration_key,
113
+ "event_action": "trigger",
114
+ "payload": {
115
+ "summary": alert.title,
116
+ "severity": severity_map.get(alert.severity, "error"),
117
+ "source": "vm-tool",
118
+ "custom_details": alert.metadata or {},
119
+ },
120
+ }
121
+
122
+ try:
123
+ response = requests.post(
124
+ "https://events.pagerduty.com/v2/enqueue", json=payload
125
+ )
126
+ response.raise_for_status()
127
+ logger.info("Alert sent to PagerDuty")
128
+ return True
129
+ except Exception as e:
130
+ logger.error(f"Failed to send PagerDuty alert: {e}")
131
+ return False
132
+
133
+
134
+ class SMSAlertChannel(AlertChannel):
135
+ """Send alerts via SMS using Twilio."""
136
+
137
+ def __init__(
138
+ self, account_sid: str, auth_token: str, from_number: str, to_numbers: List[str]
139
+ ):
140
+ self.account_sid = account_sid
141
+ self.auth_token = auth_token
142
+ self.from_number = from_number
143
+ self.to_numbers = to_numbers
144
+
145
+ def send(self, alert: Alert) -> bool:
146
+ """Send alert via SMS."""
147
+ try:
148
+ from twilio.rest import Client
149
+
150
+ client = Client(self.account_sid, self.auth_token)
151
+
152
+ # Format message
153
+ message = f"[{alert.severity.value.upper()}] {alert.title}\n{alert.message}"
154
+
155
+ # Send to all numbers
156
+ for number in self.to_numbers:
157
+ client.messages.create(body=message, from_=self.from_number, to=number)
158
+
159
+ logger.info(f"Alert sent via SMS to {len(self.to_numbers)} recipients")
160
+ return True
161
+ except Exception as e:
162
+ logger.error(f"Failed to send SMS alert: {e}")
163
+ return False
164
+
165
+
166
+ class EmailAlertChannel(AlertChannel):
167
+ """Send alerts via email."""
168
+
169
+ def __init__(
170
+ self,
171
+ smtp_host: str,
172
+ smtp_port: int,
173
+ from_email: str,
174
+ to_emails: List[str],
175
+ smtp_user: Optional[str] = None,
176
+ smtp_password: Optional[str] = None,
177
+ ):
178
+ self.smtp_host = smtp_host
179
+ self.smtp_port = smtp_port
180
+ self.from_email = from_email
181
+ self.to_emails = to_emails
182
+ self.smtp_user = smtp_user
183
+ self.smtp_password = smtp_password
184
+
185
+ def send(self, alert: Alert) -> bool:
186
+ """Send alert via email."""
187
+ from vm_tool.notifications import EmailNotifier
188
+
189
+ notifier = EmailNotifier(
190
+ smtp_host=self.smtp_host,
191
+ smtp_port=self.smtp_port,
192
+ smtp_user=self.smtp_user,
193
+ smtp_password=self.smtp_password,
194
+ from_email=self.from_email,
195
+ )
196
+
197
+ subject = f"[{alert.severity.value.upper()}] {alert.title}"
198
+
199
+ return notifier.send_email(
200
+ to_emails=self.to_emails, subject=subject, body=alert.message
201
+ )
202
+
203
+
204
+ class AlertingSystem:
205
+ """Centralized alerting system."""
206
+
207
+ def __init__(self):
208
+ self.channels: List[AlertChannel] = []
209
+
210
+ def add_channel(self, channel: AlertChannel):
211
+ """Add an alert channel."""
212
+ self.channels.append(channel)
213
+
214
+ def send_alert(self, alert: Alert):
215
+ """Send alert through all channels."""
216
+ logger.info(f"Sending alert: {alert.title} ({alert.severity.value})")
217
+
218
+ for channel in self.channels:
219
+ try:
220
+ channel.send(alert)
221
+ except Exception as e:
222
+ logger.error(f"Failed to send alert through channel: {e}")
223
+
224
+ def deployment_failed(self, host: str, error: str, **metadata):
225
+ """Send deployment failure alert."""
226
+ alert = Alert(
227
+ title="Deployment Failed",
228
+ message=f"Deployment to {host} failed: {error}",
229
+ severity=AlertSeverity.ERROR,
230
+ metadata={"host": host, "error": error, **metadata},
231
+ )
232
+ self.send_alert(alert)
233
+
234
+ def deployment_succeeded(self, host: str, duration: float, **metadata):
235
+ """Send deployment success notification."""
236
+ alert = Alert(
237
+ title="Deployment Successful",
238
+ message=f"Deployment to {host} completed in {duration:.2f}s",
239
+ severity=AlertSeverity.INFO,
240
+ metadata={"host": host, "duration": duration, **metadata},
241
+ )
242
+ self.send_alert(alert)
243
+
244
+ def health_check_failed(self, host: str, check_type: str, **metadata):
245
+ """Send health check failure alert."""
246
+ alert = Alert(
247
+ title="Health Check Failed",
248
+ message=f"Health check '{check_type}' failed on {host}",
249
+ severity=AlertSeverity.WARNING,
250
+ metadata={"host": host, "check_type": check_type, **metadata},
251
+ )
252
+ self.send_alert(alert)
253
+
254
+ def critical_error(self, title: str, message: str, **metadata):
255
+ """Send critical error alert."""
256
+ alert = Alert(
257
+ title=title,
258
+ message=message,
259
+ severity=AlertSeverity.CRITICAL,
260
+ metadata=metadata,
261
+ )
262
+ self.send_alert(alert)
263
+
264
+
265
+ # Global alerting system instance
266
+ _alerting_system = None
267
+
268
+
269
+ def get_alerting_system() -> AlertingSystem:
270
+ """Get global alerting system instance."""
271
+ global _alerting_system
272
+ if _alerting_system is None:
273
+ _alerting_system = AlertingSystem()
274
+ return _alerting_system
vm_tool/audit.py ADDED
@@ -0,0 +1,118 @@
1
+ """Audit logging for all deployment operations."""
2
+
3
+ import logging
4
+ import json
5
+ from typing import Dict, Any, Optional
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from enum import Enum
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class AuditEventType(Enum):
14
+ """Types of audit events."""
15
+
16
+ DEPLOYMENT_STARTED = "deployment.started"
17
+ DEPLOYMENT_SUCCESS = "deployment.success"
18
+ DEPLOYMENT_FAILED = "deployment.failed"
19
+ ROLLBACK_STARTED = "rollback.started"
20
+ ROLLBACK_SUCCESS = "rollback.success"
21
+ CONFIG_CHANGED = "config.changed"
22
+ SECRET_ACCESSED = "secret.accessed"
23
+ USER_LOGIN = "user.login"
24
+ PERMISSION_DENIED = "permission.denied"
25
+
26
+
27
+ class AuditLogger:
28
+ """Centralized audit logging system."""
29
+
30
+ def __init__(self, audit_dir: str = ".vm_tool/audit"):
31
+ self.audit_dir = Path(audit_dir)
32
+ self.audit_dir.mkdir(parents=True, exist_ok=True)
33
+ self.audit_file = self.audit_dir / "audit.jsonl"
34
+
35
+ def log_event(
36
+ self,
37
+ event_type: AuditEventType,
38
+ user: str,
39
+ action: str,
40
+ resource: str,
41
+ success: bool = True,
42
+ metadata: Optional[Dict[str, Any]] = None,
43
+ ):
44
+ """Log an audit event."""
45
+ event = {
46
+ "timestamp": datetime.utcnow().isoformat() + "Z",
47
+ "event_type": event_type.value,
48
+ "user": user,
49
+ "action": action,
50
+ "resource": resource,
51
+ "success": success,
52
+ "metadata": metadata or {},
53
+ }
54
+
55
+ # Append to audit log
56
+ with open(self.audit_file, "a") as f:
57
+ f.write(json.dumps(event) + "\n")
58
+
59
+ logger.info(
60
+ f"Audit: {user} {action} {resource} - {'success' if success else 'failed'}"
61
+ )
62
+
63
+ def get_audit_trail(self, limit: int = 100) -> list:
64
+ """Get recent audit events."""
65
+ if not self.audit_file.exists():
66
+ return []
67
+
68
+ events = []
69
+ with open(self.audit_file) as f:
70
+ for line in f:
71
+ events.append(json.loads(line))
72
+
73
+ return events[-limit:]
74
+
75
+ def search_events(
76
+ self,
77
+ user: Optional[str] = None,
78
+ event_type: Optional[str] = None,
79
+ start_time: Optional[datetime] = None,
80
+ end_time: Optional[datetime] = None,
81
+ ) -> list:
82
+ """Search audit events with filters."""
83
+ events = self.get_audit_trail(limit=10000)
84
+
85
+ filtered = []
86
+ for event in events:
87
+ if user and event.get("user") != user:
88
+ continue
89
+ if event_type and event.get("event_type") != event_type:
90
+ continue
91
+ if start_time:
92
+ event_time = datetime.fromisoformat(
93
+ event["timestamp"].replace("Z", "+00:00")
94
+ )
95
+ if event_time < start_time:
96
+ continue
97
+ if end_time:
98
+ event_time = datetime.fromisoformat(
99
+ event["timestamp"].replace("Z", "+00:00")
100
+ )
101
+ if event_time > end_time:
102
+ continue
103
+
104
+ filtered.append(event)
105
+
106
+ return filtered
107
+
108
+
109
+ # Global audit logger
110
+ _audit_logger = None
111
+
112
+
113
+ def get_audit_logger() -> AuditLogger:
114
+ """Get global audit logger instance."""
115
+ global _audit_logger
116
+ if _audit_logger is None:
117
+ _audit_logger = AuditLogger()
118
+ return _audit_logger