@@ -5,7 +5,6 @@ use v5.38;
5
5
use TorrustDeploy::App -command;
6
6
use TorrustDeploy::Provision::OpenTofu;
7
7
use TorrustDeploy::Provision::Ansible;
8
- use TorrustDeploy::Infrastructure::SSH::Connection;
9
8
use Path::Tiny qw( path) ;
10
9
use File::Spec;
11
10
use Time::HiRes qw( sleep) ;
@@ -21,10 +20,12 @@ This command will:
21
20
1. Initialize OpenTofu if needed
22
21
2. Copy configuration templates to the working directory
23
22
3. Create a VM with hardcoded minimal configuration
24
- 4. Wait for IP assignment and cloud-init completion
25
- 5. Monitor cloud-init progress via SSH
23
+ 4. Wait for cloud-init completion using Ansible
24
+ 5. Run post-provision verification via Ansible
25
+ 6. Restart the VM after successful verification
26
26
27
27
The VM will be created locally using libvirt/KVM.
28
+ Cloud-init handles initial setup, Ansible manages the orchestration.
28
29
END_DESCRIPTION
29
30
}
30
31
@@ -56,21 +57,27 @@ sub execute {
56
57
# Get VM IP address
57
58
my $vm_ip = $tofu -> get_vm_ip($tofu_dir );
58
59
STDOUT -> flush();
59
-
60
- # Create SSH connection
61
- my $ssh_connection = TorrustDeploy::Infrastructure::SSH::Connection-> new(host => $vm_ip );
62
-
63
- # Wait for cloud-init completion
64
- $self -> _wait_for_cloud_init($ssh_connection );
65
-
60
+
61
+ # Set up Ansible working directory and copy templates
62
+ my $ansible_dir = $work_dir -> child(' ansible' );
63
+ my $ansible = TorrustDeploy::Provision::Ansible-> new();
64
+ $ansible -> copy_templates_and_generate_inventory($vm_ip , $ansible_dir );
65
+
66
+ # Wait for cloud-init completion using Ansible
67
+ $ansible -> wait_for_cloud_init($ansible_dir );
68
+
66
69
# Run Ansible post-provision verification
67
- $self -> _run_ansible_verification($vm_ip , $work_dir );
70
+ $ansible -> run_verification($ansible_dir );
71
+
72
+ # Restart VM after verification
73
+ $ansible -> restart_vm($ansible_dir );
68
74
69
75
# Final completion message
70
76
say " " ;
71
77
say " ✅ Provisioning completed successfully!" ;
72
78
say " VM is ready at IP: " . $vm_ip ;
73
79
say " You can connect using: ssh -i ~/.ssh/testing_rsa torrust@" . $vm_ip ;
80
+ say " VM has been restarted and is ready for production use!" ;
74
81
STDOUT -> flush();
75
82
}
76
83
@@ -109,185 +116,6 @@ sub _copy_templates {
109
116
say " Templates copied successfully." ;
110
117
}
111
118
112
- sub _wait_for_cloud_init {
113
- my ($self , $ssh_connection ) = @_ ;
114
-
115
- say " Waiting for cloud-init to complete..." ;
116
- say " This may take several minutes while packages are installed and configured." ;
117
- STDOUT -> flush();
118
-
119
- my $completion_file = " /var/lib/cloud/torrust-setup-complete" ;
120
- my $max_attempts = 360; # 30 minutes with 5-second intervals
121
- my $attempt = 0;
122
- my $ssh_connected = 0;
123
- my $cloud_init_success = 0;
124
-
125
- # Step 1: Wait until SSH connection is available (for password auth to check cloud-init)
126
- say " ⏳ Waiting for SSH service to become available..." ;
127
- STDOUT -> flush();
128
-
129
- while ($attempt < $max_attempts && !$ssh_connected ) {
130
- $attempt ++;
131
-
132
- if ($ssh_connection -> test_password_connection()) {
133
- $ssh_connected = 1;
134
- say " ✅ SSH password connection established to " . $ssh_connection -> host;
135
- STDOUT -> flush();
136
- } else {
137
- if ($attempt % 6 == 0) { # Every 30 seconds
138
- say " [Waiting for SSH connection... ${attempt} 0s elapsed]" ;
139
- STDOUT -> flush();
140
- }
141
- sleep (5);
142
- }
143
- }
144
-
145
- if (!$ssh_connected ) {
146
- say " ❌ Failed to establish SSH connection to " . $ssh_connection -> host . " after " . ($max_attempts * 5 / 60) . " minutes" ;
147
- STDOUT -> flush();
148
- $self -> _print_cloud_init_logs($ssh_connection );
149
- die " SSH connection failed" ;
150
- }
151
-
152
- # Step 2: Wait until cloud-init completion marker is created
153
- say " ⏳ Waiting for cloud-init to complete..." ;
154
- STDOUT -> flush();
155
-
156
- $attempt = 0;
157
- my $consecutive_ssh_failures = 0;
158
- while ($attempt < $max_attempts ) {
159
- $attempt ++;
160
-
161
- my $result = $ssh_connection -> execute_command(" test -f $completion_file " );
162
-
163
- # Debug: Always show result details when exit code is 0
164
- if ($result -> exit_code == 0) {
165
- say " [DEBUG] File exists! Exit code: " . $result -> exit_code .
166
- " , Success method: " . ($result -> success ? ' true' : ' false' ) .
167
- " , Output: '" . ($result -> output // ' EMPTY' ) . " '" ;
168
- STDOUT -> flush();
169
- }
170
-
171
- if ($result -> success) {
172
- say " ✅ Cloud-init setup completed successfully!" ;
173
- STDOUT -> flush();
174
-
175
- # Show completion message
176
- my $completion_result = $ssh_connection -> execute_command(" cat $completion_file " );
177
- if ($completion_result -> success && $completion_result -> output) {
178
- chomp (my $output = $completion_result -> output);
179
- say " 📅 Completion marker: " . $output ;
180
- STDOUT -> flush();
181
- }
182
- $cloud_init_success = 1;
183
- last ;
184
- } else {
185
- # Track consecutive SSH failures (exit code 255)
186
- if ($result -> exit_code == 255) {
187
- $consecutive_ssh_failures ++;
188
- # If we have too many consecutive SSH failures, try to re-establish password connection
189
- if ($consecutive_ssh_failures >= 12) { # 1 minute of consecutive failures
190
- say " ⚠️ SSH connection lost, attempting to re-establish (VM may be rebooting)..." ;
191
- say " [Waiting 30s for VM to complete reboot...]" ;
192
- STDOUT -> flush();
193
- sleep (30); # Give VM time to fully reboot
194
-
195
- # Try to re-establish password connection (VM might have rebooted)
196
- my $reconnect_attempts = 0;
197
- while ($reconnect_attempts < 12 && !$ssh_connection -> test_password_connection()) {
198
- $reconnect_attempts ++;
199
- say " [Reconnection attempt $reconnect_attempts /12...]" ;
200
- STDOUT -> flush();
201
- sleep (15); # Wait longer between attempts
202
- }
203
-
204
- if ($ssh_connection -> test_password_connection()) {
205
- say " ✅ SSH connection re-established!" ;
206
- STDOUT -> flush();
207
- $consecutive_ssh_failures = 0; # Reset counter after successful reconnection
208
- } else {
209
- say " ❌ Failed to re-establish SSH connection after VM reboot." ;
210
- say " [DEBUG] Last error: " . $result -> output;
211
- STDOUT -> flush();
212
- last ;
213
- }
214
- }
215
- } else {
216
- # Reset counter for non-SSH failures (normal file-not-found errors)
217
- $consecutive_ssh_failures = 0;
218
- }
219
-
220
- # Debug: Show why the command failed
221
- if ($attempt % 6 == 0) { # Every 30 seconds
222
- my $elapsed_seconds = $attempt * 5;
223
- say " [DEBUG ${elapsed_seconds} s] File check failed - Exit code: " . $result -> exit_code .
224
- " (this is normal until cloud-init completes)" ;
225
- if ($consecutive_ssh_failures > 0) {
226
- say " [SSH failures: $consecutive_ssh_failures consecutive]" ;
227
- }
228
- STDOUT -> flush();
229
- }
230
- }
231
-
232
- # Show progress indicator every 2 minutes
233
- if ($attempt % 24 == 0) {
234
- my $elapsed_minutes = int ($attempt * 5 / 60);
235
- say " [Cloud-init still running... ${elapsed_minutes} minutes elapsed]" ;
236
- STDOUT -> flush();
237
- }
238
-
239
- sleep (5);
240
- }
241
-
242
- if (!$cloud_init_success ) {
243
- say " ❌ Timeout waiting for cloud-init to complete on " . $ssh_connection -> host . " after " . ($max_attempts * 5 / 60) . " minutes" ;
244
- STDOUT -> flush();
245
- $self -> _print_cloud_init_logs($ssh_connection );
246
- die " Cloud-init timeout" ;
247
- }
248
- }
249
-
250
- sub _print_cloud_init_logs {
251
- my ($self , $ssh_connection ) = @_ ;
252
-
253
- say " 📄 Cloud-init logs (for debugging):" ;
254
-
255
- # Print cloud-init-output.log
256
- say " === /var/log/cloud-init-output.log ===" ;
257
- my $output_result = $ssh_connection -> execute_command_with_sudo(' cat /var/log/cloud-init-output.log' );
258
- if ($output_result -> success) {
259
- print $output_result -> output;
260
- } else {
261
- say " Cloud-init output log not available" ;
262
- }
263
-
264
- say " === /var/log/cloud-init.log ===" ;
265
- my $main_result = $ssh_connection -> execute_command_with_sudo(' cat /var/log/cloud-init.log' );
266
- if ($main_result -> success) {
267
- print $main_result -> output;
268
- } else {
269
- say " Cloud-init main log not available" ;
270
- }
271
- }
272
-
273
- sub _run_ansible_verification {
274
- my ($self , $vm_ip , $work_dir ) = @_ ;
275
-
276
- say " " ;
277
- say " 🎭 Starting Ansible post-provision verification..." ;
278
- STDOUT -> flush();
279
-
280
- # Set up Ansible working directory
281
- my $ansible_dir = $work_dir -> child(' ansible' );
282
-
283
- # Create Ansible instance and set up configuration
284
- my $ansible = TorrustDeploy::Provision::Ansible-> new();
285
- $ansible -> copy_templates_and_generate_inventory($vm_ip , $ansible_dir );
286
-
287
- # Run verification playbook
288
- $ansible -> run_verification($ansible_dir );
289
- }
290
-
291
119
1;
292
120
293
121
__END__
@@ -299,8 +127,8 @@ TorrustDeploy::App::Command::Provision - Provision Torrust Tracker VM
299
127
=head1 DESCRIPTION
300
128
301
129
Provisions a Torrust Tracker virtual machine using OpenTofu with the libvirt provider.
302
- Creates a minimal Ubuntu 24.04 LTS VM, waits for IP assignment, and monitors cloud-init
303
- completion via SSH .
130
+ Creates a minimal Ubuntu 24.04 LTS VM, waits for cloud-init completion using Ansible,
131
+ runs post-provision verification, and performs a clean VM restart .
304
132
305
133
=head1 USAGE
306
134
@@ -309,10 +137,9 @@ completion via SSH.
309
137
=head1 REQUIREMENTS
310
138
311
139
- OpenTofu installed
312
- - Ansible installed
140
+ - Ansible installed (with community.general collection for cloud_init module)
313
141
- libvirt/KVM installed and running
314
142
- qemu-system-x86_64
315
- - sshpass installed (for password authentication during cloud-init monitoring)
316
143
- Testing SSH key pair (~/.ssh/testing_rsa)
317
144
- Default libvirt storage pool configured
318
145
- Template files in templates/ directory (main.tf, cloud-init.yml, ansible/)
0 commit comments