Skip to content

Commit 3c55a30

Browse files
committed
wip: redefining
1 parent aad87a6 commit 3c55a30

File tree

4 files changed

+113
-232
lines changed

4 files changed

+113
-232
lines changed
Lines changed: 36 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
use std::{iter, path::PathBuf};
2-
3-
use fs::file_reader::{FileReader, FileReaderError};
41
use serde::Deserialize;
5-
use serde_yaml::Sequence;
6-
use thiserror::Error;
7-
use tracing::debug;
8-
9-
use crate::config_migrate::migration::config::DirInfo;
102

113
/// Configuration for the infrastructure agent, represented as a raw YAML value.
124
#[derive(Debug, Default, PartialEq, Clone, Deserialize)]
@@ -17,20 +9,16 @@ pub struct NewRelicInfraConfig(serde_yaml::Value);
179
#[derive(Debug, Default, PartialEq, Clone, Deserialize)]
1810
pub struct IntegrationsConfig {
1911
// Integrations must be a list of config items.
20-
#[serde(default)]
2112
integrations: Vec<ConfigItem>,
2213
// We don't perform any validations on the `discovery` key, so we represent it with `Value`.
23-
#[serde(default)]
24-
discovery: serde_yaml::Value,
14+
discovery: Option<serde_yaml::Value>,
2515
// We don't perform any validations on the `variables` key, so we represent it with `Value`.
26-
#[serde(default)]
27-
variables: serde_yaml::Value,
16+
variables: Option<serde_yaml::Value>,
2817
}
2918

3019
/// Configuration for the log forwarder, represented as a list of raw YAML mappings.
3120
#[derive(Debug, Default, PartialEq, Clone, Deserialize)]
3221
pub struct LoggingConfig {
33-
#[serde(default)]
3422
logs: Vec<ConfigItem>,
3523
}
3624

@@ -44,133 +32,6 @@ struct ConfigItem {
4432
extra_attrs: serde_yaml::Mapping,
4533
}
4634

47-
/// Wrapper around a supported configuration value that can be merged with other values of the same key.
48-
/// The inner value is a tuple where the first element is the key (e.g., "logs", "integrations") and the second element is a sequence of values.
49-
/// This structure allows merging multiple configuration entries under the same key by concatenating their sequences.
50-
///
51-
/// This is intended to model the configuration files for integrations and logs compatible with the New Relic Infrastructure Agent. For example:
52-
///
53-
/// ```yaml
54-
/// integrations:
55-
/// - name: nri-docker
56-
/// when:
57-
/// feature: docker_enabled
58-
/// file_exists: /var/run/docker.sock
59-
/// interval: 15s
60-
/// - name: nri-docker
61-
/// when:
62-
/// feature: docker_enabled
63-
/// env_exists:
64-
/// FARGATE: "true"
65-
/// interval: 15s
66-
/// ```
67-
#[derive(Debug, Default, PartialEq, Clone)]
68-
pub struct SupportedConfigValue((String, Sequence));
69-
70-
#[derive(Debug, Error)]
71-
pub enum SupportedConfigValueError {
72-
#[error("error reading file `{0}`: `{1}`")]
73-
ReadFileError(PathBuf, FileReaderError),
74-
75-
#[error("error parsing supported config value from file `{0}`: `{1}`")]
76-
ParseError(PathBuf, serde_yaml::Error),
77-
}
78-
79-
impl<'de> Deserialize<'de> for SupportedConfigValue {
80-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81-
where
82-
D: serde::Deserializer<'de>,
83-
{
84-
use serde::de::{Error, MapAccess, Visitor};
85-
use std::fmt;
86-
87-
struct SupportedConfigValueVisitor;
88-
89-
impl<'de> Visitor<'de> for SupportedConfigValueVisitor {
90-
type Value = SupportedConfigValue;
91-
92-
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
93-
formatter.write_str("a map with a single key and a sequence of values")
94-
}
95-
96-
fn visit_map<M>(self, mut map: M) -> Result<SupportedConfigValue, M::Error>
97-
where
98-
M: MapAccess<'de>,
99-
{
100-
if let Some((key, value)) = map.next_entry::<String, Sequence>()? {
101-
if map.next_key::<String>()?.is_some() {
102-
Err(M::Error::custom(
103-
"expected a single key-value pair in the map",
104-
))
105-
} else {
106-
Ok(SupportedConfigValue((key, value)))
107-
}
108-
} else {
109-
Err(M::Error::custom("expected a non-empty map"))
110-
}
111-
}
112-
}
113-
114-
deserializer.deserialize_map(SupportedConfigValueVisitor)
115-
}
116-
}
117-
118-
impl SupportedConfigValue {
119-
/// Attempts to merge another `SupportedConfigValue` into this one.
120-
/// Merging is only possible if both values have the same key (the first element of the tuple).
121-
/// The key of [`self`] is used as the key of the resulting merged value.
122-
/// If the keys match, the sequences (the second element of the tuple) are concatenated.
123-
/// If the keys do not match, we return the [`self`] value unchanged.
124-
pub fn merge(mut self, other: Self) -> Self {
125-
if self.0.0 != other.0.0 {
126-
debug!(
127-
"cannot merge incompatible config values. Left value key: `{}`, right value key: `{}`",
128-
self.0.0, other.0.0
129-
);
130-
} else {
131-
self.0.1.extend(other.0.1);
132-
}
133-
self
134-
}
135-
136-
fn default_with_key(key: impl AsRef<str>) -> Self {
137-
SupportedConfigValue((key.as_ref().to_string(), Sequence::new()))
138-
}
139-
140-
pub fn from_dir_with_key(
141-
file_reader: &impl FileReader,
142-
dir_info: &DirInfo,
143-
key: impl AsRef<str>,
144-
) -> Result<Self, SupportedConfigValueError> {
145-
file_reader
146-
.dir_entries(&dir_info.path)
147-
.unwrap_or_default()
148-
.iter()
149-
.filter(|p| dir_info.valid_filename(p))
150-
.map(|p| {
151-
let file = file_reader
152-
.read(p)
153-
.map_err(|e| SupportedConfigValueError::ReadFileError(p.to_path_buf(), e))?;
154-
serde_yaml::from_str::<SupportedConfigValue>(&file)
155-
.map_err(|e| SupportedConfigValueError::ParseError(p.to_path_buf(), e))
156-
})
157-
.try_fold(SupportedConfigValue::default_with_key(key), |acc, item| {
158-
item.map(|v| acc.merge(v))
159-
})
160-
}
161-
}
162-
163-
impl From<SupportedConfigValue> for serde_yaml::Value {
164-
fn from(value: SupportedConfigValue) -> Self {
165-
use serde_yaml::Mapping;
166-
use serde_yaml::Value::*;
167-
168-
let k = String(value.0.0);
169-
let v = Sequence(value.0.1);
170-
Mapping(Mapping::from_iter(iter::once((k, v))))
171-
}
172-
}
173-
17435
#[cfg(test)]
17536
pub mod tests {
17637
use super::*;
@@ -190,6 +51,12 @@ integrations:
19051
env_exists:
19152
FARGATE: "true"
19253
interval: 15s
54+
55+
discovery:
56+
arbitrary_key: arbitrary_value
57+
58+
variables:
59+
arbitrary_key: arbitrary_value
19360
"#;
19461

19562
pub const EXAMPLE_LOGS_CONFIG: &str = r#"
@@ -237,42 +104,38 @@ logs:
237104
pattern: WARN|ERROR
238105
"#;
239106

240-
#[test]
241-
fn test_parse_and_merge_integration_config() {
242-
let config1: SupportedConfigValue =
243-
serde_yaml::from_str(EXAMPLE_INTEGRATION_CONFIG).unwrap();
244-
let config2: SupportedConfigValue =
245-
serde_yaml::from_str(EXAMPLE_INTEGRATION_CONFIG).unwrap();
246-
let merged_config = config1.merge(config2);
247-
assert_eq!(merged_config.0.1.len(), 4);
248-
}
107+
// Not testing the parsing of infra agent config, as we
108+
// represent it as an arbitrary YAML value here for simplicity.
249109

250110
#[test]
251-
fn test_parse_and_merge_logs_config() {
252-
let config1: SupportedConfigValue = serde_yaml::from_str(EXAMPLE_LOGS_CONFIG).unwrap();
253-
let config2: SupportedConfigValue = serde_yaml::from_str(EXAMPLE_LOGS_CONFIG).unwrap();
254-
let merged_config = config1.merge(config2);
255-
assert_eq!(merged_config.0.1.len(), 12);
111+
fn serde_logs() {
112+
let config: LoggingConfig = serde_yaml::from_str(EXAMPLE_LOGS_CONFIG).unwrap();
113+
assert_eq!(config.logs.len(), 6);
114+
assert_eq!(config.logs[0].name, "basic-file");
115+
assert_eq!(config.logs[1].name, "file-with-spaces-in-path");
116+
assert_eq!(config.logs[2].name, "file-with-attributes");
117+
assert_eq!(config.logs[3].name, "log-files-in-folder");
118+
assert_eq!(config.logs[4].name, "log-file-with-long-lines");
119+
assert_eq!(config.logs[5].name, "only-records-with-warn-and-error");
120+
121+
// no logs key should fail
122+
let err = serde_yaml::from_str::<LoggingConfig>("").unwrap_err();
123+
assert!(err.to_string().contains("missing field `logs`"));
256124
}
257125

258126
#[test]
259-
fn test_uncompatible_merge() {
260-
let config1: SupportedConfigValue = serde_yaml::from_str(EXAMPLE_LOGS_CONFIG).unwrap();
261-
let config2: SupportedConfigValue =
262-
serde_yaml::from_str(EXAMPLE_INTEGRATION_CONFIG).unwrap();
263-
let result = config1.clone().merge(config2);
264-
assert_eq!(result, config1);
265-
}
266-
267-
#[test]
268-
fn bad_serde() {
269-
let result: Result<SupportedConfigValue, _> = serde_yaml::from_str(r#"{}"#);
270-
assert!(result.is_err_and(|e| e.to_string().contains("expected a non-empty map")));
271-
272-
let result: Result<SupportedConfigValue, _> = serde_yaml::from_str(r#"{"key": "value"}"#);
273-
assert!(result.is_err_and(|e| { e.to_string().contains("expected a sequence") }));
274-
275-
let result: Result<SupportedConfigValue, _> = serde_yaml::from_str(r#"{"k1":[],"k2":[]}"#);
276-
assert!(result.is_err_and(|e| e.to_string().contains("expected a single key-value pair")));
127+
fn serde_integrations() {
128+
let config: IntegrationsConfig = serde_yaml::from_str(EXAMPLE_INTEGRATION_CONFIG).unwrap();
129+
assert_eq!(config.integrations.len(), 2);
130+
assert_eq!(config.integrations[0].name, "nri-docker");
131+
assert_eq!(config.integrations[1].name, "nri-docker");
132+
133+
// only integrations key (though empty) should succeed:
134+
let config: IntegrationsConfig = serde_yaml::from_str("integrations: []").unwrap();
135+
assert_eq!(config.integrations.len(), 0);
136+
137+
// no integrations key should fail
138+
let err = serde_yaml::from_str::<IntegrationsConfig>("").unwrap_err();
139+
assert!(err.to_string().contains("missing field `integrations`"));
277140
}
278141
}

agent-control/src/config_migrate/migration/agent_value_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub fn from_fqn_and_value(
3737
fqn: AgentTypeFieldFQN,
3838
value: AgentValueSpec,
3939
) -> HashMap<String, AgentValueSpec> {
40-
let cloned_fqn = fqn.clone().as_string();
40+
let cloned_fqn = fqn.to_string();
4141
let mut parts: Vec<&str> = cloned_fqn.rsplit(FILE_SEPARATOR).collect();
4242
let first = parts.last().unwrap().to_string();
4343
parts.remove(parts.len() - 1);

agent-control/src/config_migrate/migration/config.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ pub enum MigrationConfigError {
2929
#[derive(Debug, Clone, Deserialize)]
3030
pub struct AgentTypeFieldFQN(String);
3131

32-
impl AgentTypeFieldFQN {
33-
pub fn as_string(&self) -> String {
34-
self.0.clone()
35-
}
36-
}
37-
3832
impl Display for AgentTypeFieldFQN {
3933
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
4034
write!(f, "{}", self.0.as_str())

0 commit comments

Comments
 (0)