Skip to content

Commit 84ec3cf

Browse files
feat: install recipe sketch (#2029)
1 parent 725a844 commit 84ec3cf

File tree

10 files changed

+272
-92
lines changed

10 files changed

+272
-92
lines changed

.github/workflows/component_onhost_e2e.yaml

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ jobs:
5353
- name: "Linux e2e ${{ matrix.group }}"
5454
run: |
5555
echo "${{ secrets.NR_SYSTEM_IDENTITY_PRIVATE_KEY }}" > /tmp/newrelic-agent-control-priv.key
56-
sudo ${{ github.workspace }}/target/debug/e2e-runner ${{ matrix.group }} --deb-package-dir ./dist \
56+
sudo ${{ github.workspace }}/target/debug/e2e-runner ${{ matrix.group }} \
57+
--deb-package-dir ./dist \
5758
--nr-api-key "${{ secrets.E2E_API_KEY }}" \
5859
--nr-license-key "${{ secrets.E2E_LICENSE_KEY }}" \
5960
--nr-account-id "${{ secrets.E2E_ACCOUNT_ID }}" \
@@ -63,23 +64,34 @@ jobs:
6364
--agent-control-version "0.900.${{ github.run_id }}" \
6465
--recipes-repo-branch "main"
6566
66-
6767
on-host-windows-e2e:
68-
name: "OnHost Windows E2E tests"
68+
name: OnHost Windows E2E tests
6969
needs: [build-packages]
70+
runs-on: windows-latest
7071
strategy:
7172
fail-fast: false
7273
matrix:
73-
group: ["install"]
74-
75-
runs-on: windows-latest
74+
group: [install]
7675
steps:
7776
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
7877
- uses: ./.github/actions/install-rust-toolchain
7978
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
8079
with:
8180
name: built-binaries-0.900.${{ github.run_id }}
8281
path: ./
83-
- name: Windows e2e ${{ matrix.group }}
82+
- name: Build e2e-runner binary
83+
run: cargo build -p e2e-runner
84+
- name: "Windows e2e ${{ matrix.group }}"
8485
shell: powershell
85-
run: cargo run -p e2e-runner -- ${{ matrix.group }} --zip-package .\dist\newrelic-agent-control_0.900.${{ github.run_id }}_windows_amd64.zip
86+
run: |
87+
"${{ secrets.NR_SYSTEM_IDENTITY_PRIVATE_KEY }}" | Out-File -FilePath $env:TEMP\newrelic-agent-control-priv.key
88+
& "${{ github.workspace }}\target\debug\e2e-runner.exe" ${{ matrix.group }} `
89+
--artifacts-package-dir .\dist `
90+
--nr-api-key "${{ secrets.E2E_API_KEY }}" `
91+
--nr-license-key "${{ secrets.E2E_LICENSE_KEY }}" `
92+
--nr-account-id "${{ secrets.E2E_ACCOUNT_ID }}" `
93+
--nr-region "us" `
94+
--system-identity-client-id "${{ secrets.NR_SYSTEM_IDENTITY_CLIENT_ID }}" `
95+
--agent-control-private-key "$env:TEMP\newrelic-agent-control-priv.key" `
96+
--agent-control-version "0.900.${{ github.run_id }}" `
97+
--recipes-repo-branch "main"

test/e2e-runner/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ enum LinuxScenarios {
3939
#[derive(Debug, clap::Subcommand)]
4040
enum WindowsScenarios {
4141
/// Simple installation of Agent Control on Windows
42-
Install(windows::scenarios::installation::Args),
42+
Install(windows::install::Args),
4343
}
4444

4545
#[derive(Parser)]

test/e2e-runner/src/tools/logs.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::fs;
2-
use tracing::{error, info};
2+
use tracing::{debug, error, info};
33

44
use crate::tools::test::TestResult;
55

@@ -23,10 +23,15 @@ impl<'a> Drop for ShowLogsOnDrop<'a> {
2323
/// Shows logs from the specified path (supports glob patterns).
2424
pub fn show_logs(logs_path: &str) -> TestResult<()> {
2525
info!("Showing Agent Control logs");
26+
2627
let pattern = format!("{}*", logs_path);
28+
debug!("Listing log files with pattern: {}", pattern);
29+
2730
let paths = glob::glob(&pattern).map_err(|e| format!("failed to list log files: {}", e))?;
31+
debug!("Found log file entries: {:?}", paths);
2832

2933
for entry in paths {
34+
debug!("Processing log file entry {entry:?}");
3035
match entry {
3136
Ok(path) => {
3237
let content = fs::read_to_string(&path)

test/e2e-runner/src/windows.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
pub mod install;
12
pub mod scenarios;
23

3-
pub mod cleanup;
4-
pub mod health;
5-
pub mod install;
6-
pub mod powershell;
7-
pub mod service;
4+
mod cleanup;
5+
mod health;
6+
mod powershell;
7+
mod service;
88

9-
pub const DEFAULT_CONFIG_PATH: &str =
9+
const DEFAULT_CONFIG_PATH: &str =
1010
r"C:\Program Files\New Relic\newrelic-agent-control\local-data\agent-control\local_config.yaml";
1111

12-
pub const DEFAULT_LOG_PATH: &str =
12+
const DEFAULT_LOG_PATH: &str =
1313
r"C:\ProgramData\New Relic\newrelic-agent-control\logs\newrelic-agent-control.log";
Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1-
use crate::tools::remove_dirs;
2-
3-
use super::service;
1+
use crate::{tools::remove_dirs, windows::service};
42

53
const AGENT_CONTROL_DIRS: &[&str] = &[
64
r"C:\Program Files\New Relic\newrelic-agent-control\",
75
r"C:\ProgramData\New Relic\newrelic-agent-control\",
86
];
97

10-
/// Cleans up the agent-control installation by stopping the service and removing directories.
11-
pub fn cleanup(service_name: &str) {
12-
service::stop_service(service_name);
13-
remove_dirs(AGENT_CONTROL_DIRS).expect("expected directories to be removed");
8+
pub struct CleanAcOnDrop<'a> {
9+
service_name: &'a str,
10+
}
11+
12+
impl<'a> From<&'a str> for CleanAcOnDrop<'a> {
13+
fn from(value: &'a str) -> Self {
14+
Self {
15+
service_name: value,
16+
}
17+
}
18+
}
19+
20+
impl<'a> Drop for CleanAcOnDrop<'a> {
21+
fn drop(&mut self) {
22+
service::stop_service(self.service_name);
23+
remove_dirs(AGENT_CONTROL_DIRS).expect("expected directories to be removed");
24+
}
1425
}
Lines changed: 187 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,200 @@
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,
2348

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,
2552

26-
temp_dir
53+
/// New Relic region
54+
#[arg(long, default_value = "US")]
55+
pub nr_region: String,
2756
}
2857

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+
}
3576
}
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}"));
36106

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+
);
39116

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+
);
41125

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(&copy_zip_command)
130+
.unwrap_or_else(|err| panic!("could not copy zip: {err}"));
51131
}
52132

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);
56194

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}"));
60198

61-
info!("Installation completed successfully");
62-
info!("---\n{output}\n---");
199+
debug!("Output:\n{output}");
63200
}

test/e2e-runner/src/windows/powershell.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ pub fn exec_powershell_cmd(cmd: &mut Command) -> TestResult<String> {
2121
return Err(format!("command failed\nStdout: {stdout}\nStderr: {stderr}").into());
2222
}
2323

24-
Ok(stdout)
24+
Ok(format!(
25+
"command\n{cmd:?}\nsuccess\nStdout: {stdout}\nStderr: {stderr}"
26+
))
2527
}

0 commit comments

Comments
 (0)