Skip to content

Commit 2d09e4f

Browse files
committed
feat: add new host_monitoring cli creating infra-agent config
1 parent 299b4c5 commit 2d09e4f

File tree

12 files changed

+977
-94
lines changed

12 files changed

+977
-94
lines changed

agent-control/src/bin/main_agent_control_onhost_cli.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::process::ExitCode;
22

33
use clap::{CommandFactory, Parser, error::ErrorKind};
4+
use newrelic_agent_control::cli::on_host::host_monitoring_gen;
45
use newrelic_agent_control::cli::{logs, on_host::config_gen};
56
use tracing::{Level, error};
67

@@ -15,10 +16,13 @@ struct Cli {
1516
}
1617

1718
/// Commands supported by the cli
19+
#[allow(clippy::large_enum_variant)]
1820
#[derive(Debug, clap::Subcommand)]
1921
enum Commands {
2022
// Generate Agent Control configuration according to the provided configuration data.
2123
GenerateConfig(config_gen::Args),
24+
// Generate Host Monitoring configuration according to the provided configuration data.
25+
GenerateHostMonitoring(host_monitoring_gen::Args),
2226
}
2327

2428
fn main() -> ExitCode {
@@ -39,6 +43,9 @@ fn main() -> ExitCode {
3943
}
4044
config_gen::generate_config(args)
4145
}
46+
Commands::GenerateHostMonitoring(args) => {
47+
host_monitoring_gen::generate_host_monitoring_config(args)
48+
}
4249
};
4350

4451
if let Err(err) = result {

agent-control/src/bin/main_config_migrate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ fn main() -> Result<(), Box<dyn Error>> {
4040
MappingType::Dir(dir_path) => {
4141
legacy_config_renamer.rename_path(dir_path.dir_path.as_path())?
4242
}
43-
MappingType::File(file_path) => {
44-
legacy_config_renamer.rename_path(file_path.as_path())?
43+
MappingType::File(file_info) => {
44+
legacy_config_renamer.rename_path(file_info.file_path.as_path())?
4545
}
4646
}
4747
}

agent-control/src/cli/on_host.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
pub mod config_gen;
2+
3+
pub mod host_monitoring_gen;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! Implementation of the generate-config command for the on-host cli.
2+
3+
use crate::cli::error::CliError;
4+
use crate::cli::on_host::config_gen::region::{Region, region_parser};
5+
use crate::cli::on_host::host_monitoring_gen::infra_config_gen::InfraConfigGenerator;
6+
use tracing::info;
7+
8+
pub mod infra_config;
9+
pub mod infra_config_gen;
10+
11+
/// Represents the hostMonitoring source.
12+
#[derive(Debug, Copy, Clone, PartialEq, clap::ValueEnum)]
13+
pub enum HostMonitoringSource {
14+
InfraAgent,
15+
Otel,
16+
}
17+
18+
/// Generates the Agent Control configuration for host environments.
19+
#[derive(Debug, clap::Parser)]
20+
pub struct Args {
21+
/// Sets which host monitoring source to be used.
22+
#[arg(long)]
23+
host_monitoring_source: HostMonitoringSource,
24+
25+
/// Custom Attributes
26+
#[arg(long)]
27+
custom_attributes: Option<String>,
28+
29+
/// Proxy configuration
30+
#[arg(long)]
31+
proxy_config: Option<String>,
32+
33+
/// New Relic region
34+
#[arg(long, value_parser = region_parser())]
35+
region: Region,
36+
}
37+
38+
/// Generates the Host monitoring values either infra-agent or otel.
39+
pub fn generate_host_monitoring_config(args: Args) -> Result<(), CliError> {
40+
info!("Generating Host monitoring values");
41+
42+
if args.host_monitoring_source == HostMonitoringSource::InfraAgent {
43+
let infra_config_generator = InfraConfigGenerator::default();
44+
45+
infra_config_generator
46+
.generate_infra_config(args.region, args.custom_attributes, args.proxy_config)
47+
.map_err(|err| CliError::Command(format!("failed generating infra config: {err}")))?;
48+
} else {
49+
// TODO: this is going to create otel config an a following PR
50+
println!(
51+
"Host monitoring source is not InfraAgent. Skipping infra configuration generation."
52+
);
53+
}
54+
55+
info!("Host monitoring values generated successfully");
56+
Ok(())
57+
}
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
use crate::cli::error::CliError;
2+
use crate::cli::on_host::config_gen::region::Region;
3+
use std::collections::HashMap;
4+
5+
const INFRA_AGENT_TYPE_FIELD: &str = "config_agent";
6+
pub const INFRA_AGENT_TYPE_VERSION: &str = "newrelic/com.newrelic.infrastructure:0.1.0";
7+
8+
/// Represents the values to create or migrate an infra-config
9+
pub struct InfraConfig {
10+
values: HashMap<String, serde_yaml::Value>,
11+
deletions: Vec<serde_yaml::Value>,
12+
}
13+
14+
impl Default for InfraConfig {
15+
fn default() -> InfraConfig {
16+
Self {
17+
values: HashMap::from([
18+
(
19+
"license_key".to_string(),
20+
serde_yaml::Value::String("{{NEW_RELIC_LICENSE_KEY}}".to_string()),
21+
),
22+
(
23+
"enable_process_metrics".to_string(),
24+
serde_yaml::Value::Bool(true),
25+
),
26+
(
27+
"status_server_enabled".to_string(),
28+
serde_yaml::Value::Bool(true),
29+
),
30+
(
31+
"status_server_port".to_string(),
32+
serde_yaml::Value::Number(18003.into()),
33+
),
34+
]),
35+
deletions: vec![
36+
serde_yaml::Value::String("staging".to_string()),
37+
serde_yaml::Value::String("enable_process_metrics".to_string()),
38+
serde_yaml::Value::String("status_server_enabled".to_string()),
39+
serde_yaml::Value::String("status_server_port".to_string()),
40+
serde_yaml::Value::String("license_key".to_string()),
41+
serde_yaml::Value::String("custom_attributes".to_string()),
42+
serde_yaml::Value::String("is_integrations_only".to_string()),
43+
],
44+
}
45+
}
46+
}
47+
48+
impl InfraConfig {
49+
#[allow(dead_code)]
50+
fn new(
51+
values: HashMap<String, serde_yaml::Value>,
52+
deletions: Vec<serde_yaml::Value>,
53+
) -> InfraConfig {
54+
InfraConfig { values, deletions }
55+
}
56+
57+
pub fn with_custom_attributes(mut self, custom_attributes: &str) -> Result<Self, CliError> {
58+
if !custom_attributes.trim().is_empty() {
59+
let custom_attributes_value: serde_yaml::Value =
60+
serde_yaml::from_str(custom_attributes).map_err(|err| {
61+
CliError::Command(format!("error parsing custom attributes: {err}"))
62+
})?;
63+
if let Some(attributes) = custom_attributes_value.as_mapping() {
64+
for (key, value) in attributes {
65+
key.as_str()
66+
.and_then(|key| self.values.insert(key.to_string(), value.clone()));
67+
}
68+
}
69+
}
70+
Ok(self)
71+
}
72+
73+
pub fn with_region(mut self, region: Region) -> Self {
74+
if region == Region::STAGING {
75+
self.values
76+
.insert("staging".to_string(), serde_yaml::Value::Bool(true));
77+
}
78+
self
79+
}
80+
81+
pub fn with_proxy(mut self, proxy: &str) -> Self {
82+
if !proxy.trim().is_empty() {
83+
self.deletions
84+
.push(serde_yaml::Value::String("proxy".to_string()));
85+
self.values.insert(
86+
"proxy".to_string(),
87+
serde_yaml::Value::String(proxy.to_string()),
88+
);
89+
}
90+
self
91+
}
92+
93+
pub fn values(&self) -> &HashMap<String, serde_yaml::Value> {
94+
&self.values
95+
}
96+
97+
pub fn generate_agent_type_config_mapping(
98+
self,
99+
config_mapping: &str,
100+
) -> Result<String, CliError> {
101+
let mut parsed_yaml: serde_yaml::Value =
102+
serde_yaml::from_str(config_mapping).map_err(|err| {
103+
CliError::Command(format!("error parsing agent type config mapping: {err}"))
104+
})?;
105+
106+
if let Some(config_agent) = parsed_yaml
107+
.get_mut("configs")
108+
.and_then(|configs| configs.as_sequence_mut())
109+
.and_then(|configs| configs.get_mut(0))
110+
.and_then(|config| config.get_mut("filesystem_mappings"))
111+
.and_then(|mappings| mappings.get_mut("config_agent"))
112+
.and_then(|agent| agent.as_mapping_mut())
113+
{
114+
config_agent.insert(
115+
serde_yaml::Value::String("overwrites".to_string()),
116+
serde_yaml::Value::Mapping(
117+
self.values
118+
.into_iter()
119+
.map(|(k, v)| (serde_yaml::Value::String(k.to_string()), v))
120+
.collect(),
121+
),
122+
);
123+
config_agent.insert(
124+
serde_yaml::Value::String("deletions".to_string()),
125+
serde_yaml::Value::Sequence(self.deletions),
126+
);
127+
}
128+
129+
serde_yaml::to_string(&parsed_yaml).map_err(|err| {
130+
CliError::Command(format!("error generating agent type config mapping: {err}"))
131+
})
132+
}
133+
134+
pub fn generate_infra_config_values(&self) -> Result<String, CliError> {
135+
let mut config_agent = serde_yaml::Mapping::new();
136+
137+
for (key, value) in &self.values {
138+
config_agent.insert(serde_yaml::Value::String(key.clone()), value.clone());
139+
}
140+
141+
let mut root_mapping = serde_yaml::Mapping::new();
142+
root_mapping.insert(
143+
serde_yaml::Value::String(INFRA_AGENT_TYPE_FIELD.to_string()),
144+
serde_yaml::Value::Mapping(config_agent),
145+
);
146+
147+
serde_yaml::to_string(&serde_yaml::Value::Mapping(root_mapping))
148+
.map_err(|err| CliError::Command(format!("error generating infra config: {err}")))
149+
}
150+
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use super::*;
155+
use crate::cli::on_host::config_gen::region::Region;
156+
use crate::config_migrate::migration::defaults::NEWRELIC_INFRA_AGENT_TYPE_CONFIG_MAPPING;
157+
use serde_yaml::Value;
158+
159+
const EXPECTED_AGENT_TYPE_CONFIG: &str = r#"configs:
160+
- agent_type_fqn: newrelic/com.newrelic.infrastructure:0.1.0
161+
filesystem_mappings:
162+
config_agent:
163+
file_path: /etc/newrelic-infra.yml
164+
overwrites:
165+
custom_attributes:
166+
test: '123'
167+
deletions:
168+
- staging
169+
- enable_process_metrics
170+
- status_server_enabled
171+
- status_server_port
172+
- license_key
173+
- custom_attributes
174+
- is_integrations_only
175+
config_integrations:
176+
dir_path: /etc/newrelic-infra/integrations.d
177+
extensions:
178+
- yml
179+
- yaml
180+
config_logging:
181+
dir_path: /etc/newrelic-infra/logging.d
182+
extensions:
183+
- yml
184+
- yaml
185+
"#;
186+
187+
#[test]
188+
fn test_generate_agent_type_config_mapping() {
189+
let mut values = HashMap::new();
190+
// Create a nested custom_attributes value
191+
let mut custom_attributes = serde_yaml::Mapping::new();
192+
custom_attributes.insert(
193+
Value::String("test".to_string()),
194+
Value::String("123".to_string()),
195+
);
196+
values.insert(
197+
"custom_attributes".to_string(),
198+
Value::Mapping(custom_attributes),
199+
);
200+
201+
let deletions = vec![
202+
Value::String("staging".to_string()),
203+
Value::String("enable_process_metrics".to_string()),
204+
Value::String("status_server_enabled".to_string()),
205+
Value::String("status_server_port".to_string()),
206+
Value::String("license_key".to_string()),
207+
Value::String("custom_attributes".to_string()),
208+
Value::String("is_integrations_only".to_string()),
209+
];
210+
211+
let infra_config = InfraConfig::new(values, deletions);
212+
let result = infra_config
213+
.generate_agent_type_config_mapping(NEWRELIC_INFRA_AGENT_TYPE_CONFIG_MAPPING)
214+
.unwrap();
215+
216+
assert_eq!(result, EXPECTED_AGENT_TYPE_CONFIG);
217+
}
218+
219+
#[test]
220+
fn test_generate_infra_config_values() {
221+
let custom_attributes = r#"custom_attributes:
222+
custom_key: custom_value
223+
"#;
224+
let infra_config = InfraConfig::default()
225+
.with_custom_attributes(custom_attributes)
226+
.unwrap()
227+
.with_region(Region::STAGING)
228+
.with_proxy("http://proxy.example.com");
229+
let result = infra_config.generate_infra_config_values().unwrap();
230+
231+
// Parse the YAML content
232+
let parsed_values: serde_yaml::Value = serde_yaml::from_str(&result).unwrap();
233+
234+
if let serde_yaml::Value::Mapping(map) = parsed_values {
235+
if let Some(serde_yaml::Value::Mapping(config_agent_map)) =
236+
map.get(serde_yaml::Value::String("config_agent".to_string()))
237+
{
238+
assert_eq!(
239+
config_agent_map.get(serde_yaml::Value::String(
240+
"status_server_enabled".to_string()
241+
)),
242+
Some(&serde_yaml::Value::Bool(true))
243+
);
244+
assert_eq!(
245+
config_agent_map.get(serde_yaml::Value::String(
246+
"enable_process_metrics".to_string()
247+
)),
248+
Some(&serde_yaml::Value::Bool(true))
249+
);
250+
assert_eq!(
251+
config_agent_map.get(serde_yaml::Value::String("license_key".to_string())),
252+
Some(&serde_yaml::Value::String(
253+
"{{NEW_RELIC_LICENSE_KEY}}".to_string()
254+
))
255+
);
256+
assert_eq!(
257+
config_agent_map
258+
.get(serde_yaml::Value::String("status_server_port".to_string())),
259+
Some(&serde_yaml::Value::Number(serde_yaml::Number::from(18003)))
260+
);
261+
assert_eq!(
262+
config_agent_map.get(serde_yaml::Value::String("staging".to_string())),
263+
Some(&serde_yaml::Value::Bool(true))
264+
);
265+
assert_eq!(
266+
config_agent_map.get(serde_yaml::Value::String("proxy".to_string())),
267+
Some(&serde_yaml::Value::String(
268+
"http://proxy.example.com".to_string()
269+
))
270+
);
271+
let mut custom_attributes = serde_yaml::Mapping::new();
272+
custom_attributes.insert(
273+
serde_yaml::Value::String("custom_key".to_string()),
274+
serde_yaml::Value::String("custom_value".to_string()),
275+
);
276+
assert_eq!(
277+
config_agent_map
278+
.get(serde_yaml::Value::String("custom_attributes".to_string())),
279+
Some(&serde_yaml::Value::Mapping(custom_attributes))
280+
);
281+
}
282+
} else {
283+
panic!("Expected a YAML mapping");
284+
}
285+
}
286+
}

0 commit comments

Comments
 (0)