Skip to content

Commit d3b2bdf

Browse files
authored
fix: validate if agent-id collides with release-name (#1742)
1 parent dd6c6ad commit d3b2bdf

File tree

5 files changed

+168
-38
lines changed

5 files changed

+168
-38
lines changed

agent-control/src/agent_control/config_validator.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use crate::agent_control::config::AgentControlDynamicConfig;
2-
use crate::agent_type::agent_type_registry::{AgentRegistry, AgentRepositoryError};
2+
use crate::agent_type::agent_type_registry::AgentRegistry;
33
use std::sync::Arc;
44
use thiserror::Error;
55

6+
pub mod k8s;
7+
68
#[derive(Error, Debug)]
7-
pub enum DynamicConfigValidatorError {
8-
#[error("{0}")]
9-
AgentRepositoryError(#[from] AgentRepositoryError),
10-
}
9+
#[error("config validation failed: {0}")]
10+
pub struct DynamicConfigValidatorError(String);
1111

1212
/// Represents a validator for dynamic config
1313
pub trait DynamicConfigValidator {
@@ -40,7 +40,12 @@ impl<R: AgentRegistry> DynamicConfigValidator for RegistryDynamicConfigValidator
4040
.try_for_each(|sub_agent_cfg| {
4141
let _ = self
4242
.agent_type_registry
43-
.get(sub_agent_cfg.agent_type.to_string().as_str())?;
43+
.get(sub_agent_cfg.agent_type.to_string().as_str())
44+
.map_err(|err| {
45+
DynamicConfigValidatorError(format!(
46+
"AgentType registry check failed: {err}"
47+
))
48+
})?;
4449
Ok(())
4550
})
4651
}
@@ -78,9 +83,7 @@ pub mod tests {
7883
if self.valid {
7984
Ok(())
8085
} else {
81-
Err(DynamicConfigValidatorError::AgentRepositoryError(
82-
AgentRepositoryError::NotFound("not-found".to_string()),
83-
))
86+
Err(DynamicConfigValidatorError("not-found".to_string()))
8487
}
8588
}
8689
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use crate::agent_control::config::AgentControlDynamicConfig;
2+
3+
use super::{DynamicConfigValidator, DynamicConfigValidatorError};
4+
5+
pub struct K8sReleaseNamesConfigValidator<V: DynamicConfigValidator> {
6+
inner: V,
7+
ac_release_name: String,
8+
cd_release_name: String,
9+
}
10+
11+
impl<V: DynamicConfigValidator> DynamicConfigValidator for K8sReleaseNamesConfigValidator<V> {
12+
fn validate(
13+
&self,
14+
dynamic_config: &AgentControlDynamicConfig,
15+
) -> Result<(), DynamicConfigValidatorError> {
16+
self.inner.validate(dynamic_config)?;
17+
18+
dynamic_config
19+
.agents
20+
.keys()
21+
.try_for_each(|agent_id| match agent_id.as_str() {
22+
agent_id if agent_id == self.ac_release_name => Err(validation_error(
23+
agent_id,
24+
"Agent Control itself (agent-control-deployment)",
25+
)),
26+
agent_id if agent_id == self.cd_release_name => Err(validation_error(
27+
agent_id,
28+
"Agent Control CD (agent-control-cd)",
29+
)),
30+
_ => Ok(()),
31+
})
32+
}
33+
}
34+
35+
impl<V: DynamicConfigValidator> K8sReleaseNamesConfigValidator<V> {
36+
pub fn new(inner: V, ac_release_name: String, cd_release_name: String) -> Self {
37+
Self {
38+
inner,
39+
ac_release_name,
40+
cd_release_name,
41+
}
42+
}
43+
}
44+
45+
fn validation_error(agent_id: &str, component: &str) -> DynamicConfigValidatorError {
46+
DynamicConfigValidatorError(format!(
47+
"agent_id '{agent_id}' collides with the release name to deploy {component}"
48+
))
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use assert_matches::assert_matches;
54+
55+
use super::*;
56+
use crate::agent_control::{
57+
config::tests::helper_get_agent_list, config_validator::tests::MockDynamicConfigValidator,
58+
};
59+
60+
#[test]
61+
fn test_validate_no_collision() {
62+
let mut inner = MockDynamicConfigValidator::new();
63+
inner.expect_validate().once().returning(|_| Ok(()));
64+
65+
let validator = K8sReleaseNamesConfigValidator {
66+
inner,
67+
ac_release_name: "agent-control-deployment".to_string(),
68+
cd_release_name: "agent-control-cd".to_string(),
69+
};
70+
71+
let dynamic_config = AgentControlDynamicConfig {
72+
agents: helper_get_agent_list(), // contains 'infra-agent' and 'nrdot' agent-ids
73+
..Default::default()
74+
};
75+
76+
let result = validator.validate(&dynamic_config);
77+
assert!(result.is_ok());
78+
}
79+
80+
#[test]
81+
fn test_validate_inner_failure() {
82+
let mut inner = MockDynamicConfigValidator::new();
83+
inner
84+
.expect_validate()
85+
.once()
86+
.returning(|_| Err(DynamicConfigValidatorError("inner failure".to_string())));
87+
88+
let validator = K8sReleaseNamesConfigValidator {
89+
inner,
90+
ac_release_name: "agent-control-deployment".to_string(),
91+
cd_release_name: "agent-control-cd".to_string(),
92+
};
93+
94+
let dynamic_config = AgentControlDynamicConfig {
95+
agents: helper_get_agent_list(), // contains 'infra-agent' and 'nrdot' agent-ids
96+
..Default::default()
97+
};
98+
99+
let result = validator.validate(&dynamic_config);
100+
assert_matches!(result, Err(DynamicConfigValidatorError(s)) => {
101+
assert!(s.contains("inner failure"));
102+
});
103+
}
104+
105+
#[test]
106+
fn test_validate_ac_release_name_collision() {
107+
let mut inner = MockDynamicConfigValidator::new();
108+
inner.expect_validate().once().returning(|_| Ok(()));
109+
110+
let validator = K8sReleaseNamesConfigValidator {
111+
inner,
112+
ac_release_name: "nrdot".to_string(),
113+
cd_release_name: "agent-control-cd".to_string(),
114+
};
115+
116+
let dynamic_config = AgentControlDynamicConfig {
117+
agents: helper_get_agent_list(), // contains 'infra-agent' and 'nrdot' agent-ids
118+
..Default::default()
119+
};
120+
121+
let result = validator.validate(&dynamic_config);
122+
assert_matches!(result, Err(DynamicConfigValidatorError(s)) => {
123+
assert!(s.contains("nrdot") && s.contains("agent-control-deployment"));
124+
});
125+
}
126+
127+
#[test]
128+
fn test_validate_cd_release_name_collision() {
129+
let mut inner = MockDynamicConfigValidator::new();
130+
inner.expect_validate().once().returning(|_| Ok(()));
131+
132+
let validator = K8sReleaseNamesConfigValidator {
133+
inner,
134+
ac_release_name: "random-release-name".to_string(),
135+
cd_release_name: "infra-agent".to_string(),
136+
};
137+
138+
let dynamic_config = AgentControlDynamicConfig {
139+
agents: helper_get_agent_list(), // contains 'infra-agent' and 'nrdot' agent-ids
140+
..Default::default()
141+
};
142+
143+
let result = validator.validate(&dynamic_config);
144+
assert_matches!(result, Err(DynamicConfigValidatorError(s)) => {
145+
assert!(s.contains("infra-agent") && s.contains("agent-control-cd"));
146+
});
147+
}
148+
}

agent-control/src/agent_control/resource_cleaner/k8s_garbage_collector.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ pub struct K8sGarbageCollector {
3232
/// The namespace where agents are running. We are garbage collecting resources here only due to Instrumentation
3333
pub namespace_agents: String,
3434
pub cr_type_meta: Vec<TypeMeta>,
35-
pub ac_release_name: String,
36-
pub cd_release_name: String,
3735
}
3836

3937
impl K8sGarbageCollector {
@@ -142,13 +140,6 @@ impl K8sGarbageCollector {
142140
.ok_or(K8sGarbageCollectorError::MissingLabels)?
143141
.as_str();
144142

145-
// TODO AgentId should be aware of the "ac" and "cd" release names.
146-
if agent_id_from_labels == self.ac_release_name
147-
|| agent_id_from_labels == self.cd_release_name
148-
{
149-
return Ok(false);
150-
}
151-
152143
let agent_id_from_labels = match AgentID::try_from(agent_id_from_labels) {
153144
Ok(id) => id,
154145
// We must not delete anything with reserved AgentIDs (currently only Agent Control)
@@ -277,8 +268,6 @@ mod tests {
277268

278269
const TEST_NAMESPACE: &str = "test-namespace";
279270
const TEST_NAMESPACE_AGENTS: &str = "test-namespace-agents";
280-
const TEST_AC_RELEASE_NAME: &str = "test-ac-release-name";
281-
const TEST_CD_RELEASE_NAME: &str = "test-cd-release-name";
282271

283272
#[test]
284273
fn errors_if_ac_id() {
@@ -293,8 +282,6 @@ mod tests {
293282
cr_type_meta: vec![],
294283
namespace: TEST_NAMESPACE.to_string(),
295284
namespace_agents: TEST_NAMESPACE_AGENTS.to_string(),
296-
ac_release_name: TEST_AC_RELEASE_NAME.to_string(),
297-
cd_release_name: TEST_CD_RELEASE_NAME.to_string(),
298285
};
299286
let ac_id = &AgentID::AgentControl;
300287
let ac_type_id =
@@ -339,8 +326,6 @@ mod tests {
339326
cr_type_meta: vec![type_meta],
340327
namespace: TEST_NAMESPACE.to_string(),
341328
namespace_agents: TEST_NAMESPACE_AGENTS.to_string(),
342-
ac_release_name: TEST_AC_RELEASE_NAME.to_string(),
343-
cd_release_name: TEST_CD_RELEASE_NAME.to_string(),
344329
};
345330
let ac_id = &AgentID::try_from("foo-agent").unwrap();
346331
let agent_type_id = &AgentTypeID::try_from("newrelic/com.example.foo:0.0.1").unwrap();

agent-control/src/agent_control/run/k8s.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::agent_control::config::{AgentControlConfigError, K8sConfig, helmrelea
33
use crate::agent_control::config_repository::repository::AgentControlConfigLoader;
44
use crate::agent_control::config_repository::store::AgentControlConfigStore;
55
use crate::agent_control::config_validator::RegistryDynamicConfigValidator;
6+
use crate::agent_control::config_validator::k8s::K8sReleaseNamesConfigValidator;
67
use crate::agent_control::defaults::{
78
AGENT_CONTROL_VERSION, FLEET_ID_ATTRIBUTE_KEY, HOST_NAME_ATTRIBUTE_KEY,
89
OPAMP_AC_CHART_VERSION_ATTRIBUTE_KEY, OPAMP_AGENT_VERSION_ATTRIBUTE_KEY,
@@ -189,8 +190,6 @@ impl AgentControlRunner {
189190
namespace: self.k8s_config.namespace.clone(),
190191
namespace_agents: self.k8s_config.namespace_agents.clone(),
191192
cr_type_meta: self.k8s_config.cr_type_meta,
192-
ac_release_name: self.k8s_config.ac_release_name.clone(),
193-
cd_release_name: self.k8s_config.cd_release_name.clone(),
194193
};
195194

196195
info!("Initiating cleanup of outdated resources from previous Agent Control executions");
@@ -202,9 +201,15 @@ impl AgentControlRunner {
202201
))
203202
.map_err(ResourceCleanerError::from)?;
204203

205-
let dynamic_config_validator =
204+
let registry_config_validator =
206205
RegistryDynamicConfigValidator::new(self.agent_type_registry);
207206

207+
let dynamic_config_validator = K8sReleaseNamesConfigValidator::new(
208+
registry_config_validator,
209+
self.k8s_config.ac_release_name.to_string(),
210+
self.k8s_config.cd_release_name.to_string(),
211+
);
212+
208213
// The http server stops on Drop. We need to keep it while the agent control is running.
209214
let _http_server = self.http_server_runner.map(Runner::start);
210215

agent-control/tests/k8s/garbage_collector.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ use newrelic_agent_control::{
4141
};
4242
use std::{collections::HashMap, sync::Arc, time::Duration};
4343

44-
const TEST_AC_RELEASE_NAME: &str = "test-ac-release-name";
45-
const TEST_CD_RELEASE_NAME: &str = "test-cd-release-name";
46-
4744
// Setup AgentControlConfigLoader mock
4845
mock! {
4946
pub AgentControlConfigLoader {}
@@ -176,8 +173,6 @@ agents:
176173
kind: "Secret".to_string(),
177174
},
178175
],
179-
ac_release_name: TEST_AC_RELEASE_NAME.to_string(),
180-
cd_release_name: TEST_CD_RELEASE_NAME.to_string(),
181176
};
182177

183178
// Expects the GC to keep the agent cr and secret from the config, event if looking for multiple kinds or that
@@ -248,8 +243,6 @@ fn k8s_garbage_collector_with_missing_and_extra_kinds() {
248243
namespace: test_ns.clone(),
249244
namespace_agents: test_ns.clone(),
250245
cr_type_meta: vec![missing_kind, foo_type_meta()],
251-
ac_release_name: TEST_AC_RELEASE_NAME.to_string(),
252-
cd_release_name: TEST_CD_RELEASE_NAME.to_string(),
253246
};
254247

255248
let agents_config = serde_yaml::from_str::<AgentControlDynamicConfig>("agents: {}")
@@ -292,8 +285,6 @@ fn k8s_garbage_collector_does_not_remove_agent_control() {
292285
namespace: test_ns.clone(),
293286
namespace_agents: test_ns.clone(),
294287
cr_type_meta: default_group_version_kinds(),
295-
ac_release_name: TEST_AC_RELEASE_NAME.to_string(),
296-
cd_release_name: TEST_CD_RELEASE_NAME.to_string(),
297288
};
298289

299290
// Expects the GC do not clean any resource related to the SA.
@@ -374,8 +365,6 @@ agents:
374365
namespace: test_ns.clone(),
375366
namespace_agents: test_ns.clone(),
376367
cr_type_meta: vec![foo_type_meta()],
377-
ac_release_name: TEST_AC_RELEASE_NAME.to_string(),
378-
cd_release_name: TEST_CD_RELEASE_NAME.to_string(),
379368
};
380369

381370
// Expects the GC do not clean any resource related to the SA, running SubAgents or unmanaged resources.

0 commit comments

Comments
 (0)