Skip to content

Commit a9ed918

Browse files
committed
Agent: Extract SSM logic to agent_utils
1 parent 609fe27 commit a9ed918

File tree

7 files changed

+96
-49
lines changed

7 files changed

+96
-49
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/utils/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ agent-common = { version = "0.0.5", path = "../../agent/agent-common" }
1010
aws-config = "0.54"
1111
aws-credential-types = "0.54"
1212
aws-types = "0.54"
13+
aws-sdk-iam = "0.24"
14+
aws-sdk-ssm = "0.24"
1315
aws-sdk-sts = "0.24"
1416
aws-smithy-types = "0.54"
1517
base64 = "0.20"

agent/utils/src/error.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use aws_sdk_iam::error::{AttachRolePolicyError, CreateRoleError, GetRoleError};
2+
use aws_sdk_ssm::error::{CreateActivationError, DescribeInstanceInformationError};
13
use aws_sdk_sts::error::AssumeRoleError;
24
use aws_sdk_sts::types::SdkError;
35
use snafu::Snafu;
@@ -12,18 +14,61 @@ pub enum Error {
1214
source: SdkError<AssumeRoleError>,
1315
},
1416

17+
#[snafu(display(
18+
"Failed to attach policy '{}' to role '{}': {}",
19+
policy_arn,
20+
role_name,
21+
source
22+
))]
23+
AttachRolePolicy {
24+
role_name: String,
25+
policy_arn: String,
26+
source: SdkError<AttachRolePolicyError>,
27+
},
28+
1529
#[snafu(display("Failed to decode base64 blob: {}", source))]
1630
Base64Decode { source: base64::DecodeError },
1731

18-
#[snafu(display("Failed to setup environment variables: {}", what))]
19-
EnvSetup { what: String },
20-
2132
#[snafu(display("Could not convert '{}' secret to string: {}", what, source))]
2233
Conversion { what: String, source: FromUtf8Error },
2334

35+
#[snafu(display("Failed to send create SSM command: {}", source))]
36+
CreateSsmActivation {
37+
source: SdkError<CreateActivationError>,
38+
},
39+
40+
#[snafu(display(
41+
"Unable to create role '{}' with policy '{}': {}",
42+
role_name,
43+
role_policy,
44+
source
45+
))]
46+
CreateRole {
47+
role_name: String,
48+
role_policy: String,
49+
source: SdkError<CreateRoleError>,
50+
},
51+
2452
#[snafu(display("Credentials were missing for assumed role '{}'", role_arn))]
2553
CredentialsMissing { role_arn: String },
2654

55+
#[snafu(display("Failed to setup environment variables: {}", what))]
56+
EnvSetup { what: String },
57+
58+
#[snafu(display("Unable to get managed instance information: {}", source))]
59+
GetManagedInstanceInfo {
60+
source: SdkError<DescribeInstanceInformationError>,
61+
},
62+
63+
#[snafu(display("Unable to get SSM role '{}': {}", role_name, source))]
64+
GetSSMRole {
65+
role_name: String,
66+
source: SdkError<GetRoleError>,
67+
},
68+
69+
#[snafu(display("{} was missing from {}", what, from))]
70+
Missing { what: String, from: String },
71+
2772
#[snafu(display("Secret was missing: {}", source))]
2873
SecretMissing {
2974
source: agent_common::secrets::Error,
@@ -35,3 +80,5 @@ pub enum Error {
3580
source: std::io::Error,
3681
},
3782
}
83+
84+
pub type Result<T> = std::result::Result<T, Error>;

agent/utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::{env, fs};
1919
pub mod aws;
2020
pub mod constants;
2121
mod error;
22+
pub mod ssm;
2223

2324
/// Decode base64 blob and write to a file at the specified path
2425
pub async fn base64_decode_write_file(
Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use crate::error::{self, Result};
12
use aws_sdk_iam::types::SdkError;
23
use aws_sdk_ssm::model::{InstanceInformation, InstanceInformationStringFilter, Tag};
34
use log::info;
4-
use resource_agent::provider::{IntoProviderError, ProviderError, ProviderResult, Resources};
55
use serde_json::json;
6+
use snafu::{OptionExt, ResultExt};
67
use std::thread::sleep;
78
use std::time::Duration;
89

@@ -12,9 +13,7 @@ const SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME: &str = "BR-SSMServiceRole";
1213
const SSM_MANAGED_INSTANCE_POLICY_ARN: &str =
1314
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore";
1415

15-
pub(crate) async fn ensure_ssm_service_role(
16-
iam_client: &aws_sdk_iam::Client,
17-
) -> ProviderResult<()> {
16+
pub async fn ensure_ssm_service_role(iam_client: &aws_sdk_iam::Client) -> Result<()> {
1817
let get_role_result = iam_client
1918
.get_role()
2019
.role_name(SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME)
@@ -38,23 +37,19 @@ pub(crate) async fn ensure_ssm_service_role(
3837
iam_client
3938
.create_role()
4039
.role_name(SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME)
41-
.assume_role_policy_document(assume_role_doc)
40+
.assume_role_policy_document(&assume_role_doc)
4241
.send()
4342
.await
44-
.context(
45-
Resources::Clear,
46-
"Could not create SSM managed instance service role",
47-
)?;
43+
.context(error::CreateRoleSnafu {
44+
role_name: SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME,
45+
role_policy: assume_role_doc,
46+
})?;
4847
}
49-
_ => {
50-
return Err(ProviderError::new_with_source_and_context(
51-
Resources::Clear,
52-
format!(
53-
"Failed to determine whether SSM '{}' service role exists",
54-
SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME
55-
),
56-
sdk_err,
57-
));
48+
e => {
49+
return Err(error::Error::GetSSMRole {
50+
role_name: SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME.to_string(),
51+
source: e,
52+
});
5853
}
5954
}
6055
}
@@ -66,20 +61,19 @@ pub(crate) async fn ensure_ssm_service_role(
6661
.policy_arn(SSM_MANAGED_INSTANCE_POLICY_ARN)
6762
.send()
6863
.await
69-
.context(
70-
Resources::Clear,
71-
"Could not attach SSM managed instance policy to the service role",
72-
)?;
64+
.context(error::AttachRolePolicySnafu {
65+
role_name: SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME,
66+
policy_arn: SSM_MANAGED_INSTANCE_POLICY_ARN,
67+
})?;
7368

7469
Ok(())
7570
}
7671

77-
pub(crate) async fn create_ssm_activation(
78-
resources: Resources,
72+
pub async fn create_ssm_activation(
7973
cluster_name: &str,
8074
num_registration: i32,
8175
ssm_client: &aws_sdk_ssm::Client,
82-
) -> ProviderResult<(String, String)> {
76+
) -> Result<(String, String)> {
8377
let activations = ssm_client
8478
.create_activation()
8579
.iam_role(SSM_MANAGED_INSTANCE_SERVICE_ROLE_NAME)
@@ -92,23 +86,24 @@ pub(crate) async fn create_ssm_activation(
9286
)
9387
.send()
9488
.await
95-
.context(resources, "Failed to send SSM create activation command")?;
96-
let activation_id = activations
97-
.activation_id
98-
.context(resources, "Unable to fetch SSM activation ID")?;
99-
let activation_code = activations
100-
.activation_code
101-
.context(resources, "Unable to generate SSM activation code")?;
89+
.context(error::CreateSsmActivationSnafu {})?;
90+
let activation_id = activations.activation_id.context(error::MissingSnafu {
91+
what: "activation id",
92+
from: "activations",
93+
})?;
94+
let activation_code = activations.activation_code.context(error::MissingSnafu {
95+
what: "activation code",
96+
from: "activations",
97+
})?;
10298
Ok((activation_id, activation_code))
10399
}
104100

105101
// Waits for the SSM agent to be ready for a particular instance, returns the instance information
106-
pub(crate) async fn wait_for_ssm_ready(
107-
resources: Resources,
102+
pub async fn wait_for_ssm_ready(
108103
ssm_client: &aws_sdk_ssm::Client,
109104
activation_id: &str,
110105
ip: &str,
111-
) -> ProviderResult<InstanceInformation> {
106+
) -> Result<InstanceInformation> {
112107
let seconds_between_checks = Duration::from_secs(5);
113108
loop {
114109
let instance_info = ssm_client
@@ -121,10 +116,7 @@ pub(crate) async fn wait_for_ssm_ready(
121116
)
122117
.send()
123118
.await
124-
.context(
125-
resources,
126-
"Failed to get registered managed instance information",
127-
)?;
119+
.context(error::GetManagedInstanceInfoSnafu {})?;
128120
if let Some(info) = instance_info.instance_information_list().and_then(|list| {
129121
list.iter()
130122
.find(|info| info.ip_address == Some(ip.to_string()))

bottlerocket/agents/src/bin/vsphere-vm-resource-agent/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Provides Bottlerocket VMWare vSphere VMs to serve as Kubernetes nodes via `govc`
44
55
!*/
66

7-
mod aws;
87
mod vsphere_vm_provider;
98

109
use crate::vsphere_vm_provider::{VMCreator, VMDestroyer};

bottlerocket/agents/src/bin/vsphere-vm-resource-agent/vsphere_vm_provider.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use crate::aws::{create_ssm_activation, ensure_ssm_service_role, wait_for_ssm_ready};
21
use agent_utils::aws::aws_config;
32
use agent_utils::base64_decode_write_file;
3+
use agent_utils::ssm::{create_ssm_activation, ensure_ssm_service_role, wait_for_ssm_ready};
44
use bottlerocket_agents::constants::TEST_CLUSTER_KUBECONFIG_PATH;
55
use bottlerocket_agents::tuf::{download_target, tuf_repo_urls};
66
use bottlerocket_agents::userdata::{decode_to_string, merge_values};
@@ -139,7 +139,9 @@ impl Create for VMCreator {
139139
.context(resources, "Error sending cluster creation message")?;
140140

141141
// Ensure we have a SSM service role we can attach to the VMs
142-
ensure_ssm_service_role(&iam_client).await?;
142+
ensure_ssm_service_role(&iam_client)
143+
.await
144+
.context(Resources::Clear, "Unable to check for SSM service role")?;
143145

144146
info!("Getting vSphere secret");
145147
memo.current_status = "Getting vSphere secret".to_string();
@@ -343,8 +345,9 @@ impl Create for VMCreator {
343345

344346
let vm_count = spec.configuration.vm_count.unwrap_or(DEFAULT_VM_COUNT);
345347
// Generate SSM activation codes and IDs
346-
let activation =
347-
create_ssm_activation(resources, &vsphere_cluster.name, vm_count, &ssm_client).await?;
348+
let activation = create_ssm_activation(&vsphere_cluster.name, vm_count, &ssm_client)
349+
.await
350+
.context(resources, "Unable to create SSM activation")?;
348351
memo.ssm_activation_id = activation.0.to_owned();
349352
let control_host_ctr_userdata = json!({"ssm":{"activation-id": activation.0.to_string(), "activation-code":activation.1.to_string(),"region":"us-west-2"}});
350353
debug!(
@@ -461,13 +464,14 @@ impl Create for VMCreator {
461464

462465
let instance_info = tokio::time::timeout(
463466
Duration::from_secs(60),
464-
wait_for_ssm_ready(resources, &ssm_client, &memo.ssm_activation_id, ip),
467+
wait_for_ssm_ready(&ssm_client, &memo.ssm_activation_id, ip),
465468
)
466469
.await
467470
.context(
468471
resources,
469472
format!("Timed out waiting for SSM agent to be ready on VM '{}'", ip),
470-
)??;
473+
)?
474+
.context(resources, "Unable to determine if SSM activation is ready")?;
471475

472476
memo.vms.push(VSphereVM {
473477
name: node_name,

0 commit comments

Comments
 (0)