|
1 | | -use crate::windows::powershell::exec_powershell_cmd; |
2 | | - |
3 | | -use super::powershell::exec_powershell_command; |
4 | | -use std::path::Path; |
5 | | -use std::process::Command; |
6 | | -use tempfile::TempDir; |
7 | | -use tracing::info; |
8 | | - |
9 | | -const INSTALL_SCRIPT_NAME: &str = "install.ps1"; |
10 | | - |
11 | | -/// Extracts a zip file to a temporary directory using PowerShell's Expand-Archive, panics on failure. |
12 | | -fn unzip_to_temp(zip_path: &str) -> TempDir { |
13 | | - // Create a temporary directory |
14 | | - let temp_dir = |
15 | | - TempDir::with_prefix("agent-control-install-").expect("could not create temp dir"); |
16 | | - |
17 | | - // Use PowerShell's Expand-Archive to extract the zip |
18 | | - let cmd = format!( |
19 | | - "Expand-Archive -Path '{}' -DestinationPath '{}' -Force", |
20 | | - zip_path, |
21 | | - temp_dir.path().display() |
22 | | - ); |
| 1 | +use std::{fs, path::PathBuf, process::Command, time::Duration}; |
| 2 | + |
| 3 | +use tempfile::tempdir; |
| 4 | +use tracing::{debug, info}; |
| 5 | + |
| 6 | +use crate::{ |
| 7 | + tools::test::retry, |
| 8 | + windows::powershell::{exec_powershell_cmd, exec_powershell_command}, |
| 9 | +}; |
| 10 | + |
| 11 | +/// Arguments to be set for every test that needs Agent Control installation |
| 12 | +#[derive(Default, Debug, clap::Parser)] |
| 13 | +pub struct Args { |
| 14 | + /// Folder where zips are stored |
| 15 | + #[arg(long)] |
| 16 | + pub artifacts_package_dir: Option<PathBuf>, |
| 17 | + |
| 18 | + /// Recipes repository |
| 19 | + #[arg( |
| 20 | + long, |
| 21 | + default_value = "https://github.com/newrelic/open-install-library.git" |
| 22 | + )] |
| 23 | + pub recipes_repo: String, |
| 24 | + |
| 25 | + /// Recipes repository branch |
| 26 | + #[arg(long, default_value = "main")] |
| 27 | + pub recipes_repo_branch: String, |
| 28 | + |
| 29 | + /// New Relic license key for agent authentication |
| 30 | + #[arg(long)] |
| 31 | + pub nr_license_key: String, |
| 32 | + |
| 33 | + /// New Relic API key for programmatic access to New Relic services |
| 34 | + #[arg(long)] |
| 35 | + pub nr_api_key: String, |
| 36 | + |
| 37 | + /// New Relic account identifier for associating the agent |
| 38 | + #[arg(long)] |
| 39 | + pub nr_account_id: String, |
| 40 | + |
| 41 | + /// System Identity client id |
| 42 | + #[arg(long)] |
| 43 | + pub system_identity_client_id: String, |
| 44 | + |
| 45 | + /// System Identity private key |
| 46 | + #[arg(long)] |
| 47 | + pub agent_control_private_key: String, |
23 | 48 |
|
24 | | - exec_powershell_command(&cmd).unwrap_or_else(|err| panic!("could not unzip to temp: {err}")); |
| 49 | + /// Specific version of agent control to install |
| 50 | + #[arg(long)] |
| 51 | + pub agent_control_version: String, |
25 | 52 |
|
26 | | - temp_dir |
| 53 | + /// New Relic region |
| 54 | + #[arg(long, default_value = "US")] |
| 55 | + pub nr_region: String, |
27 | 56 | } |
28 | 57 |
|
29 | | -/// Installs agent-control using the install.ps1 PowerShell script, panics on failure |
30 | | -pub fn install_agent_control(package_path: &str, service_overwrite: bool) { |
31 | | - info!("Installing Agent Control"); |
32 | | - // Check if the package file exists |
33 | | - if !Path::new(package_path).exists() { |
34 | | - panic!("package file not found at {:?}", package_path); |
| 58 | +/// Data to set up installation |
| 59 | +pub struct RecipeData { |
| 60 | + pub args: Args, |
| 61 | + pub fleet_id: String, |
| 62 | + pub fleet_enabled: String, |
| 63 | + pub recipe_list: String, |
| 64 | + pub proxy_url: String, |
| 65 | +} |
| 66 | + |
| 67 | +impl Default for RecipeData { |
| 68 | + fn default() -> Self { |
| 69 | + Self { |
| 70 | + args: Default::default(), |
| 71 | + fleet_id: Default::default(), |
| 72 | + proxy_url: Default::default(), |
| 73 | + fleet_enabled: "false".to_string(), |
| 74 | + recipe_list: "agent-control".to_string(), |
| 75 | + } |
35 | 76 | } |
| 77 | +} |
| 78 | + |
| 79 | +/// Installs Agent Control using the recipe as configured in the provided [RecipeData]. |
| 80 | +/// |
| 81 | +/// |
| 82 | +pub fn install_agent_control_from_recipe(data: &RecipeData) { |
| 83 | + info!("Installing Agent Control from recipe"); |
| 84 | + |
| 85 | + // Obtain recipes repository |
| 86 | + let recipes_dir = tempdir().expect("failure creating temp dir"); |
| 87 | + let recipes_dir_path = recipes_dir.path().display(); |
| 88 | + let recipes_repo = data.args.recipes_repo.clone(); |
| 89 | + let recipes_branch = data.args.recipes_repo_branch.clone(); |
| 90 | + let git_command = format!( |
| 91 | + r"git clone {recipes_repo} --single-branch --branch {recipes_branch} {recipes_dir_path}" |
| 92 | + ); |
| 93 | + info!(%recipes_repo, %recipes_branch, "Checking out recipes repo"); |
| 94 | + debug!("Running command: \n{git_command}"); |
| 95 | + let _ = exec_powershell_command(&git_command) |
| 96 | + .unwrap_or_else(|err| panic!("could not checkout recipes repository: {err}")); |
| 97 | + |
| 98 | + let install_newrelic_cli_command = r#" |
| 99 | +(New-Object System.Net.WebClient).DownloadFile("https://github.com/newrelic/newrelic-cli/releases/latest/download/NewRelicCLIInstaller.msi", "$env:TEMP\NewRelicCLIInstaller.msi"); ` |
| 100 | +msiexec.exe /qn /i "$env:TEMP\NewRelicCLIInstaller.msi" | Out-Null; |
| 101 | +"#; |
| 102 | + info!("Installing newrelic cli",); |
| 103 | + debug!("Running command: \n{install_newrelic_cli_command}"); |
| 104 | + let _ = exec_powershell_command(install_newrelic_cli_command) |
| 105 | + .unwrap_or_else(|err| panic!("could not install New Relic CLI: {err}")); |
36 | 106 |
|
37 | | - // Extract the zip file to a temporary directory |
38 | | - let temp_dir = unzip_to_temp(package_path); |
| 107 | + // By default, the windows recipe will download the zip file from https://download.newrelic.com and put it |
| 108 | + // in "$env:TEMP\newrelic-agent-control-{version}.zip". If the zip file already exists, the recipe will skip the |
| 109 | + // download. We can take advantage of this behavior by placing our zip file in the expected location. |
| 110 | + // That way we avoid trying to download the artifact from the wrong place. |
| 111 | + if let Some(path) = &data.args.artifacts_package_dir { |
| 112 | + info!( |
| 113 | + "Using local artifacts package directory: {}", |
| 114 | + path.display() |
| 115 | + ); |
39 | 116 |
|
40 | | - let install_script = temp_dir.path().join(INSTALL_SCRIPT_NAME); |
| 117 | + let zip_name = PathBuf::from(path).join(format!( |
| 118 | + "newrelic-agent-control_{}_windows_amd64.zip", |
| 119 | + data.args.agent_control_version |
| 120 | + )); |
| 121 | + let extract_path = format!( |
| 122 | + "$env:TEMP/newrelic-agent-control-{}.zip", |
| 123 | + data.args.agent_control_version |
| 124 | + ); |
41 | 125 |
|
42 | | - // Build PowerShell command arguments |
43 | | - let mut args = vec![ |
44 | | - "-ExecutionPolicy".to_string(), |
45 | | - "Bypass".to_string(), |
46 | | - "-File".to_string(), |
47 | | - install_script.to_string_lossy().to_string(), |
48 | | - ]; |
49 | | - if service_overwrite { |
50 | | - args.push("-ServiceOverwrite".to_string()); |
| 126 | + let copy_zip_command = format!("cp {} {}", zip_name.display(), extract_path); |
| 127 | + info!("Copying zip file"); |
| 128 | + debug!("Running command: \n{copy_zip_command}"); |
| 129 | + let _ = exec_powershell_command(©_zip_command) |
| 130 | + .unwrap_or_else(|err| panic!("could not copy zip: {err}")); |
51 | 131 | } |
52 | 132 |
|
53 | | - // Execute PowerShell command |
54 | | - let mut cmd = Command::new("powershell.exe"); |
55 | | - let cmd = cmd.args(&args).current_dir(temp_dir.path()); |
| 133 | + // Install agent control through recipe |
| 134 | + // We need to use 2>&1 to redirect stderr to stdout for debugging. |
| 135 | + // Otherwise, newrelic cli won't show any error messages. |
| 136 | + let install_command = format!( |
| 137 | + r#" |
| 138 | +$env:NEW_RELIC_CLI_SKIP_CORE='1'; ` |
| 139 | +$env:NEW_RELIC_LICENSE_KEY='{}'; ` |
| 140 | +$env:NEW_RELIC_API_KEY='{}'; ` |
| 141 | +$env:NEW_RELIC_ACCOUNT_ID='{}'; ` |
| 142 | +$env:NEW_RELIC_AUTH_PROVISIONED_CLIENT_ID='{}'; ` |
| 143 | +$env:NEW_RELIC_AUTH_PRIVATE_KEY_PATH='{}'; ` |
| 144 | +$env:NEW_RELIC_AGENT_VERSION='{}'; ` |
| 145 | +$env:NEW_RELIC_REGION='{}'; ` |
| 146 | +$env:NR_CLI_FLEET_ID='{}'; ` |
| 147 | +$env:NEW_RELIC_AGENT_CONTROL_FLEET_ENABLED='{}'; ` |
| 148 | +$env:NEW_RELIC_AGENT_CONTROL='true'; ` |
| 149 | +$env:NEW_RELIC_AGENT_CONTROL_PROXY_URL='{}'; ` |
| 150 | +$env:HTTPS_PROXY='{}'; ` |
| 151 | +$env:NEW_RELIC_AGENT_CONTROL_SKIP_BINARY_SIGNATURE_VALIDATION='true'; ` |
| 152 | +& "C:\Program Files\New Relic\New Relic CLI\newrelic.exe" install ` |
| 153 | +-y ` |
| 154 | +--localRecipes {} ` |
| 155 | +-n {} ` |
| 156 | +--debug |
| 157 | +"#, |
| 158 | + data.args.nr_license_key, |
| 159 | + data.args.nr_api_key, |
| 160 | + data.args.nr_account_id, |
| 161 | + data.args.system_identity_client_id, |
| 162 | + data.args.agent_control_private_key, |
| 163 | + data.args.agent_control_version, |
| 164 | + data.args.nr_region, |
| 165 | + data.fleet_id, |
| 166 | + data.fleet_enabled, |
| 167 | + data.proxy_url, |
| 168 | + data.proxy_url, |
| 169 | + recipes_dir_path, |
| 170 | + data.recipe_list, |
| 171 | + ); |
| 172 | + |
| 173 | + // Create a temporary .ps1 file for the installation command |
| 174 | + // |
| 175 | + // There's an option that allows running commands directly. That is "-Command". The |
| 176 | + // issue with that is that "-ExecutionPolicy" won't bypass all the checks. It seems |
| 177 | + // to only work properly using a ps1 script. We are forced to create a temporary script file. |
| 178 | + info!("Creating install script"); |
| 179 | + debug!("Install script content: \n{install_command}"); |
| 180 | + let script_dir = tempdir().expect("failed to create temp dir for script"); |
| 181 | + let script_path = script_dir.path().join("install_command.ps1"); |
| 182 | + fs::write(&script_path, &install_command) |
| 183 | + .unwrap_or_else(|err| panic!("failed to write install script: {err}")); |
| 184 | + |
| 185 | + info!("Executing install script to install Agent Control through the recipe"); |
| 186 | + let output = retry(3, Duration::from_secs(30), "recipe installation", || { |
| 187 | + let mut cmd = Command::new("powershell.exe"); |
| 188 | + let cmd = cmd |
| 189 | + .current_dir(script_dir.path()) |
| 190 | + .arg("-ExecutionPolicy") |
| 191 | + .arg("Bypass") |
| 192 | + .arg("-File") |
| 193 | + .arg(&script_path); |
56 | 194 |
|
57 | | - let output = exec_powershell_cmd(cmd).unwrap_or_else(|err| { |
58 | | - panic!("Failure executing ps1 installation script: {err}"); |
59 | | - }); |
| 195 | + exec_powershell_cmd(cmd) |
| 196 | + }) |
| 197 | + .unwrap_or_else(|err| panic!("failure executing recipe after retries: {err}")); |
60 | 198 |
|
61 | | - info!("Installation completed successfully"); |
62 | | - info!("---\n{output}\n---"); |
| 199 | + debug!("Output:\n{output}"); |
63 | 200 | } |
0 commit comments