|
| 1 | +//! Integration tests for bcvk project commands |
| 2 | +//! |
| 3 | +//! ⚠️ **CRITICAL INTEGRATION TEST POLICY** ⚠️ |
| 4 | +//! |
| 5 | +//! INTEGRATION TESTS MUST NEVER "warn and continue" ON FAILURES! |
| 6 | +//! |
| 7 | +//! If something is not working: |
| 8 | +//! - Use `todo!("reason why this doesn't work yet")` |
| 9 | +//! - Use `panic!("clear error message")` |
| 10 | +//! - Use `assert!()` and `unwrap()` to fail hard |
| 11 | +//! |
| 12 | +//! NEVER use patterns like: |
| 13 | +//! - "Note: test failed - likely due to..." |
| 14 | +//! - "This is acceptable in CI/testing environments" |
| 15 | +//! - Warning and continuing on failures |
| 16 | +
|
| 17 | +use camino::Utf8PathBuf; |
| 18 | +use color_eyre::Result; |
| 19 | +use linkme::distributed_slice; |
| 20 | +use std::process::Command; |
| 21 | +use tempfile::TempDir; |
| 22 | + |
| 23 | +use crate::{get_bck_command, IntegrationTest, INTEGRATION_TESTS}; |
| 24 | + |
| 25 | +#[distributed_slice(INTEGRATION_TESTS)] |
| 26 | +static TEST_PROJECT_WORKFLOW: IntegrationTest = |
| 27 | + IntegrationTest::new("project_upgrade_workflow", test_project_upgrade_workflow); |
| 28 | + |
| 29 | +/// Test the full project workflow including upgrade |
| 30 | +/// |
| 31 | +/// This test: |
| 32 | +/// 1. Creates a custom bootc image based on centos-bootc:stream10 |
| 33 | +/// 2. Initializes a bcvk project |
| 34 | +/// 3. Starts the VM with the initial image |
| 35 | +/// 4. Modifies the Containerfile and builds v2 |
| 36 | +/// 5. Triggers manual upgrade with `bcvk project ssh -A` |
| 37 | +/// 6. Verifies the upgrade was applied in the VM |
| 38 | +fn test_project_upgrade_workflow() -> Result<()> { |
| 39 | + let temp_dir = TempDir::new().expect("Failed to create temp directory"); |
| 40 | + let project_dir = |
| 41 | + Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf()).expect("temp path is not UTF-8"); |
| 42 | + |
| 43 | + // Create initial Containerfile |
| 44 | + let containerfile_path = project_dir.join("Containerfile"); |
| 45 | + let initial_containerfile = r#"FROM quay.io/centos-bootc/centos-bootc:stream10 |
| 46 | +
|
| 47 | +# Add a marker file for version 1 |
| 48 | +RUN echo "version1" > /usr/share/test-version |
| 49 | +"#; |
| 50 | + std::fs::write(&containerfile_path, initial_containerfile) |
| 51 | + .expect("Failed to write initial Containerfile"); |
| 52 | + |
| 53 | + // Build initial image |
| 54 | + let image_name = "localhost/bcvk-test-project:latest"; |
| 55 | + println!("Building initial test image: {}", image_name); |
| 56 | + let build_output = Command::new("podman") |
| 57 | + .args(&["build", "-t", image_name, "-f"]) |
| 58 | + .arg(containerfile_path.as_str()) |
| 59 | + .arg(project_dir.as_str()) |
| 60 | + .output() |
| 61 | + .expect("Failed to run podman build"); |
| 62 | + |
| 63 | + assert!( |
| 64 | + build_output.status.success(), |
| 65 | + "Initial podman build failed: {}", |
| 66 | + String::from_utf8_lossy(&build_output.stderr) |
| 67 | + ); |
| 68 | + |
| 69 | + // Create .bcvk directory and config.toml |
| 70 | + let bcvk_dir = project_dir.join(".bcvk"); |
| 71 | + std::fs::create_dir(&bcvk_dir).expect("Failed to create .bcvk directory"); |
| 72 | + |
| 73 | + let config_content = format!( |
| 74 | + r#"[vm] |
| 75 | +image = "{}" |
| 76 | +memory = "2G" |
| 77 | +cpus = 2 |
| 78 | +disk-size = "10G" |
| 79 | +"#, |
| 80 | + image_name |
| 81 | + ); |
| 82 | + std::fs::write(bcvk_dir.join("config.toml"), config_content) |
| 83 | + .expect("Failed to write config.toml"); |
| 84 | + |
| 85 | + let bcvk = get_bck_command()?; |
| 86 | + |
| 87 | + // Start the project VM (detached) |
| 88 | + println!("Starting project VM..."); |
| 89 | + let up_output = Command::new(&bcvk) |
| 90 | + .args(&["project", "up"]) |
| 91 | + .current_dir(&project_dir) |
| 92 | + .env("BCVK_PROJECT_DIR", project_dir.as_str()) |
| 93 | + .output() |
| 94 | + .expect("Failed to run bcvk project up"); |
| 95 | + |
| 96 | + if !up_output.status.success() { |
| 97 | + eprintln!("bcvk project up failed:"); |
| 98 | + eprintln!("stdout: {}", String::from_utf8_lossy(&up_output.stdout)); |
| 99 | + eprintln!("stderr: {}", String::from_utf8_lossy(&up_output.stderr)); |
| 100 | + panic!("Failed to start project VM"); |
| 101 | + } |
| 102 | + |
| 103 | + // Give VM time to boot |
| 104 | + std::thread::sleep(std::time::Duration::from_secs(30)); |
| 105 | + |
| 106 | + // Verify version 1 is in the VM |
| 107 | + println!("Verifying initial version..."); |
| 108 | + let check_v1_output = Command::new(&bcvk) |
| 109 | + .args(&["project", "ssh", "cat", "/usr/share/test-version"]) |
| 110 | + .current_dir(&project_dir) |
| 111 | + .output() |
| 112 | + .expect("Failed to check initial version"); |
| 113 | + |
| 114 | + let v1_content = String::from_utf8_lossy(&check_v1_output.stdout); |
| 115 | + assert!( |
| 116 | + v1_content.contains("version1"), |
| 117 | + "Initial version marker not found in VM. Output: {}", |
| 118 | + v1_content |
| 119 | + ); |
| 120 | + |
| 121 | + // Update Containerfile to version 2 |
| 122 | + println!("Building updated image (v2)..."); |
| 123 | + let updated_containerfile = r#"FROM quay.io/centos-bootc/centos-bootc:stream10 |
| 124 | +
|
| 125 | +# Add a marker file for version 2 |
| 126 | +RUN echo "version2" > /usr/share/test-version |
| 127 | +"#; |
| 128 | + std::fs::write(&containerfile_path, updated_containerfile) |
| 129 | + .expect("Failed to write updated Containerfile"); |
| 130 | + |
| 131 | + // Build version 2 |
| 132 | + let build_v2_output = Command::new("podman") |
| 133 | + .args(&["build", "-t", image_name, "-f"]) |
| 134 | + .arg(containerfile_path.as_str()) |
| 135 | + .arg(project_dir.as_str()) |
| 136 | + .output() |
| 137 | + .expect("Failed to run podman build for v2"); |
| 138 | + |
| 139 | + assert!( |
| 140 | + build_v2_output.status.success(), |
| 141 | + "Version 2 podman build failed: {}", |
| 142 | + String::from_utf8_lossy(&build_v2_output.stderr) |
| 143 | + ); |
| 144 | + |
| 145 | + // Trigger upgrade with `bcvk project ssh -A` |
| 146 | + println!("Triggering upgrade with `bcvk project ssh -A`..."); |
| 147 | + let upgrade_output = Command::new(&bcvk) |
| 148 | + .args(&["project", "ssh", "-A", "echo", "upgrade-complete"]) |
| 149 | + .current_dir(&project_dir) |
| 150 | + .output() |
| 151 | + .expect("Failed to run bcvk project ssh -A"); |
| 152 | + |
| 153 | + if !upgrade_output.status.success() { |
| 154 | + eprintln!("bcvk project ssh -A failed:"); |
| 155 | + eprintln!( |
| 156 | + "stdout: {}", |
| 157 | + String::from_utf8_lossy(&upgrade_output.stdout) |
| 158 | + ); |
| 159 | + eprintln!( |
| 160 | + "stderr: {}", |
| 161 | + String::from_utf8_lossy(&upgrade_output.stderr) |
| 162 | + ); |
| 163 | + panic!("Failed to trigger upgrade"); |
| 164 | + } |
| 165 | + |
| 166 | + let upgrade_stdout = String::from_utf8_lossy(&upgrade_output.stdout); |
| 167 | + assert!( |
| 168 | + upgrade_stdout.contains("upgrade-complete"), |
| 169 | + "Upgrade command did not complete successfully" |
| 170 | + ); |
| 171 | + |
| 172 | + // Check bootc status to verify new deployment is staged |
| 173 | + println!("Checking bootc status for staged deployment..."); |
| 174 | + let status_output = Command::new(&bcvk) |
| 175 | + .args(&["project", "ssh", "bootc", "status", "--json"]) |
| 176 | + .current_dir(&project_dir) |
| 177 | + .output() |
| 178 | + .expect("Failed to run bootc status"); |
| 179 | + |
| 180 | + let status_json = String::from_utf8_lossy(&status_output.stdout); |
| 181 | + println!("bootc status output: {}", status_json); |
| 182 | + |
| 183 | + // Verify that status shows a staged deployment or that we have the new image |
| 184 | + // The exact behavior depends on bootc version, but we should see some indication |
| 185 | + // of the upgrade |
| 186 | + assert!( |
| 187 | + status_output.status.success(), |
| 188 | + "bootc status failed: {}", |
| 189 | + String::from_utf8_lossy(&status_output.stderr) |
| 190 | + ); |
| 191 | + |
| 192 | + // Clean up - destroy the VM |
| 193 | + println!("Cleaning up project VM..."); |
| 194 | + let _down_output = Command::new(&bcvk) |
| 195 | + .args(&["project", "down"]) |
| 196 | + .current_dir(&project_dir) |
| 197 | + .output() |
| 198 | + .expect("Failed to run bcvk project down"); |
| 199 | + |
| 200 | + let _rm_output = Command::new(&bcvk) |
| 201 | + .args(&["project", "rm"]) |
| 202 | + .current_dir(&project_dir) |
| 203 | + .output() |
| 204 | + .expect("Failed to run bcvk project rm"); |
| 205 | + |
| 206 | + // Clean up the test image |
| 207 | + let _rmi_output = Command::new("podman") |
| 208 | + .args(&["rmi", "-f", image_name]) |
| 209 | + .output() |
| 210 | + .ok(); |
| 211 | + |
| 212 | + Ok(()) |
| 213 | +} |
0 commit comments