Skip to content

Commit a96a838

Browse files
committed
feat: add postdownload script support for agent types
1 parent 0aa6f60 commit a96a838

13 files changed

Lines changed: 694 additions & 3 deletions

File tree

.github/workflows/poc_ebpf.yml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: eBPF PoC Pipeline
2+
3+
# Runs on push to feat/poc_ebpf_package branch and can be triggered manually
4+
5+
on:
6+
workflow_dispatch:
7+
push:
8+
branches:
9+
- feat/poc_ebpf_package
10+
- main
11+
pull_request:
12+
branches:
13+
- feat/poc_ebpf_package
14+
- main
15+
schedule:
16+
# Scheduled to run at 4 a.m on every day-of-week from Monday through Friday.
17+
- cron: "0 4 * * 1-5"
18+
19+
permissions:
20+
contents: read
21+
22+
jobs:
23+
build-packages:
24+
name: Build packages
25+
uses: ./.github/workflows/component_packages.yml
26+
with:
27+
pre-release: false
28+
tag_name: 0.100.${{ github.run_id }}
29+
secrets:
30+
gh_token: ${{ secrets.GITHUB_TOKEN }}
31+
gpg_private_key_base64: ${{ secrets.OHAI_GPG_PRIVATE_KEY_BASE64 }}
32+
gpg_passphrase: ${{ secrets.OHAI_GPG_PASSPHRASE }}
33+
pfx_certificate_base64: ${{ secrets.OHAI_PFX_CERTIFICATE_BASE64 }}
34+
pfx_passphrase: ${{ secrets.OHAI_PFX_PASSPHRASE }}
35+
36+
build-image:
37+
name: Build and Push nightly image
38+
uses: ./.github/workflows/component_image.yml
39+
with:
40+
# the packages are created with 0.100.run_id, however we still push the image with nightly
41+
image-tag: nightly
42+
ac-version: nightly
43+
push: true
44+
secrets: inherit
45+
46+
security-image:
47+
name: Security scan
48+
needs: [ build-image ]
49+
uses: ./.github/workflows/component_image_security.yml
50+
with:
51+
# the packages are created with 0.100.run_id, however we still push the image with nightly
52+
image-tag: nightly
53+
secrets: inherit
54+
55+
security-source-code:
56+
uses: ./.github/workflows/component_security.yml
57+
58+
# This is currently required for canaries
59+
upload-packages-s3:
60+
runs-on: ubuntu-latest
61+
needs: [ build-packages ]
62+
name: Upload packages to testing bucket
63+
steps:
64+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
65+
with:
66+
fetch-depth: 0
67+
68+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
69+
with:
70+
name: built-binaries-0.100.${{ github.run_id }}
71+
path: ./
72+
73+
- name: Publish deb to S3 action
74+
uses: newrelic/infrastructure-publish-action@26d9d8604b2cf2e7343dc287c958e8bbc1a3f750 # v1
75+
with:
76+
tag: 0.100.${{ github.run_id }}
77+
app_name: "newrelic-agent-control"
78+
repo_name: ${{ github.repository }}
79+
schema: "custom-local"
80+
schema_path: "/srv/build/upload-schema-linux-deb-nightly.yml"
81+
aws_access_key_id: ${{ secrets.OHAI_AWS_ACCESS_KEY_ID_TESTING }}
82+
aws_secret_access_key: ${{ secrets.OHAI_AWS_SECRET_ACCESS_KEY_TESTING }}
83+
aws_s3_bucket_name: "nr-downloads-ohai-testing"
84+
aws_s3_lock_bucket_name: "onhost-ci-lock-testing"
85+
access_point_host: "testing"
86+
run_id: ${{ github.run_id }}
87+
aws_region: "us-east-1"
88+
aws_role_session_name: ${{ secrets.OHAI_AWS_ROLE_SESSION_NAME_TESTING }}
89+
aws_role_arn: ${{ secrets.OHAI_AWS_ROLE_ARN_TESTING }}
90+
# used for signing package stuff
91+
gpg_passphrase: ${{ secrets.OHAI_GPG_PASSPHRASE }}
92+
gpg_private_key_base64: ${{ secrets.OHAI_GPG_PRIVATE_KEY_BASE64 }} # base64 encoded
93+
disable_lock: false
94+
dest_prefix: "poc_ebpf/"
95+
local_packages_path: "/srv/dist/"
96+
apt_skip_mirror: true
97+

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ Remember that the keywords that you can use are the following:
1414

1515
## Unreleased
1616

17-
### enhancement
17+
### enhancements
18+
- add post-download script support for OCI packages.
1819
- Agent type definitions now use a flat per-platform schema (one YAML file per `(platform, operating_system)` pair).
1920
Agent type FQNs and configuration values are unchanged, so this is **not a breaking change** for end users.
2021
Internal authors of custom agent type definitions need to migrate their YAMLs — see [docs/INTEGRATING_AGENTS.md](docs/INTEGRATING_AGENTS.md).

agent-control/src/agent_control/version_updater/on_host.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ where
135135
repository: self.repository.clone(),
136136
version: new_version,
137137
public_key_url: Some(self.pub_key_url.clone()),
138+
postdownload: None,
138139
},
139140
}
140141
}

agent-control/src/agent_type/runtime_config/on_host.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ mod tests {
167167
public_key_url: Some(TemplateableValue::from_template(
168168
"${nr-var:public-key-url}".to_string(),
169169
)),
170+
postdownload: None,
170171
},
171172
},
172173
};

agent-control/src/agent_type/runtime_config/on_host/package.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,29 @@ pub struct Oci {
3333
pub version: TemplateableValue<String>,
3434
/// Public key url is expected to be a jwks.
3535
pub public_key_url: Option<TemplateableValue<String>>,
36+
/// Postdownload script to execute after downloading and extracting the package.
37+
/// All validations, checks, and installation steps should go here.
38+
pub postdownload: Option<Postdownload>,
39+
}
40+
41+
#[derive(Debug, Deserialize, Clone, PartialEq)]
42+
pub struct Postdownload {
43+
/// Arguments where first element is the command/executable (e.g., "bash", "sh", "python3"),
44+
/// followed by script path and additional arguments.
45+
/// Example: ["bash", "./scripts/postdownload.sh", "-e"]
46+
pub args: Vec<TemplateableValue<String>>,
47+
48+
/// Environmental variables passed to the process.
49+
#[serde(default)]
50+
pub env: std::collections::HashMap<String, TemplateableValue<String>>,
51+
52+
/// Maximum time to wait for the script to complete.
53+
#[serde(default = "default_postdownload_timeout")]
54+
pub timeout: TemplateableValue<String>,
55+
}
56+
57+
fn default_postdownload_timeout() -> TemplateableValue<String> {
58+
TemplateableValue::new("300s".to_string())
3659
}
3760

3861
impl Templateable for Package {
@@ -78,14 +101,52 @@ impl Templateable for Oci {
78101
AgentTypeError::OCIReferenceParsingError(format!("invalid public_key_url: {err}"))
79102
})?;
80103

104+
let postdownload = self
105+
.postdownload
106+
.map(|pd| pd.template_with(variables))
107+
.transpose()?;
108+
81109
Ok(Self::Output {
82110
repository,
83111
version,
84112
public_key_url,
113+
postdownload,
85114
})
86115
}
87116
}
88117

118+
impl Templateable for Postdownload {
119+
type Output = rendered::Postdownload;
120+
fn template_with(self, variables: &Variables) -> Result<Self::Output, AgentTypeError> {
121+
let timeout_str = self.timeout.template_with(variables)?;
122+
123+
let args: Vec<String> = self
124+
.args
125+
.into_iter()
126+
.map(|arg| arg.template_with(variables))
127+
.collect::<Result<Vec<String>, AgentTypeError>>()?;
128+
129+
if args.is_empty() {
130+
return Err(AgentTypeError::OCIReferenceParsingError(
131+
"postdownload args cannot be empty, first element must be the command/executable"
132+
.to_string(),
133+
));
134+
}
135+
136+
let env: std::collections::HashMap<String, String> = self
137+
.env
138+
.into_iter()
139+
.map(|(k, v)| v.template_with(variables).map(|templated| (k, templated)))
140+
.collect::<Result<std::collections::HashMap<_, _>, AgentTypeError>>()?;
141+
142+
let timeout = duration_str::parse(&timeout_str).map_err(|err| {
143+
AgentTypeError::OCIReferenceParsingError(format!("invalid timeout format: {err}"))
144+
})?;
145+
146+
Ok(Self::Output { args, env, timeout })
147+
}
148+
}
149+
89150
#[cfg(test)]
90151
mod tests {
91152
use std::str::FromStr;
@@ -130,6 +191,7 @@ mod tests {
130191
public_key_url: public_key_url
131192
.clone()
132193
.map(|_| TemplateableValue::from_template("${nr-var:public-key}".to_string())),
194+
postdownload: None,
133195
};
134196

135197
let rendered_oci = oci.template_with(&variables).unwrap();

agent-control/src/agent_type/runtime_config/on_host/package/rendered.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{fmt::Display, str::FromStr};
1+
use std::{fmt::Display, str::FromStr, time::Duration};
22

33
use oci_client::Reference;
44
use serde::{Deserialize, Serialize};
@@ -26,6 +26,18 @@ pub struct Oci {
2626
pub repository: Repository,
2727
pub version: Version,
2828
pub public_key_url: Option<Url>,
29+
pub postdownload: Option<Postdownload>,
30+
}
31+
32+
#[derive(Debug, Clone, PartialEq)]
33+
pub struct Postdownload {
34+
/// Arguments where first element is the command/executable, followed by arguments.
35+
/// Example: ["bash", "./scripts/postdownload.sh", "-e"]
36+
pub args: Vec<String>,
37+
/// Environmental variables
38+
pub env: std::collections::HashMap<String, String>,
39+
/// Timeout duration
40+
pub timeout: Duration,
2941
}
3042

3143
const DEFAULT_TAG: &str = "latest";
@@ -268,6 +280,7 @@ mod tests {
268280
repository: Repository::from_str(repository).unwrap(),
269281
version: Version::from_str(version).unwrap(),
270282
public_key_url: None,
283+
postdownload: None,
271284
};
272285
let reference = oci.to_reference(&registry);
273286
assert_eq!(expected_whole, reference.whole());

agent-control/src/package.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod manager;
22
pub mod oci;
3+
pub mod postdownload_executor;

agent-control/src/package/oci/downloader.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ pub mod tests {
203203
repository: Repository::from_str(REPOSITORY).unwrap(),
204204
version: Version::from_str(VERSION).unwrap(),
205205
public_key_url,
206+
postdownload: None,
206207
},
207208
}
208209
}
@@ -450,6 +451,7 @@ pub mod tests {
450451
version: Version::from_str(&format!("v1.0.0@{}", oci_mock.manifest_digest()))
451452
.unwrap(),
452453
public_key_url: None,
454+
postdownload: None,
453455
},
454456
};
455457

agent-control/src/package/oci/package_manager.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::agent_control::defaults::PACKAGES_FOLDER_NAME;
44
use crate::package::manager::{InstalledPackageData, PackageData, PackageManager};
55
use crate::package::oci::artifact_definitions::LocalAgentPackage;
66
use crate::package::oci::downloader::OCIArtifactDownloader;
7+
use crate::package::postdownload_executor::PostdownloadExecutor;
78
use fs::directory_manager::{DirectoryManager, DirectoryManagerFs};
89
use fs::file::LocalFile;
910
use fs::file::reader::FileReader;
@@ -13,7 +14,7 @@ use std::io;
1314
use std::path::{Component, Path, PathBuf};
1415
use std::sync::Mutex;
1516
use thiserror::Error;
16-
use tracing::{debug, error, warn};
17+
use tracing::{debug, error, info, warn};
1718

1819
pub type DefaultOCIPackageManager = OCIPackageManager<OCIArtifactDownloader, DirectoryManagerFs>;
1920

@@ -106,6 +107,20 @@ where
106107
self.extract_package(&downloaded_package, install_path)
107108
.inspect_err(|e| warn!("OCI package installation failed: {}", e))?;
108109

110+
if let Some(postdownload) = &package_data.oci.postdownload {
111+
info!(
112+
"Executing postdownload script for package {}",
113+
package_data.id
114+
);
115+
let executor = PostdownloadExecutor::new(install_path.to_path_buf());
116+
executor.execute(postdownload).map_err(|e| {
117+
OCIPackageManagerError::Install(io::Error::other(format!(
118+
"Postdownload script failed: {}",
119+
e
120+
)))
121+
})?;
122+
}
123+
109124
debug!("OCI package installed at {}", install_path.display());
110125
Ok(InstalledPackageData {
111126
id: package_data.id.clone(),
@@ -401,6 +416,7 @@ mod tests {
401416
)
402417
.unwrap(),
403418
public_key_url: None,
419+
postdownload: None,
404420
},
405421
}
406422
}
@@ -416,6 +432,7 @@ mod tests {
416432
repository: Repository::from_str("newrelic/fake-agent").unwrap(),
417433
version: Version::from_str(version).unwrap(),
418434
public_key_url: None,
435+
postdownload: None,
419436
},
420437
}
421438
}

0 commit comments

Comments
 (0)