Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"agent",
"aws-common",
"bin",
"bunyan",
"client",
Expand Down
9 changes: 9 additions & 0 deletions aws-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "buildomat-aws-common"
version = "0.1.0"
edition = "2024"

[dependencies]
aws-config = { workspace = true }
aws-credential-types = { workspace = true }
thiserror = { workspace = true }
68 changes: 68 additions & 0 deletions aws-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use aws_config::default_provider::credentials::DefaultCredentialsChain;
use aws_config::{BehaviorVersion, ConfigLoader, Region, SdkConfig};
use aws_credential_types::Credentials;
use aws_credential_types::provider::SharedCredentialsProvider;

pub struct AwsConfig {
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub profile: Option<String>,
pub region: String,
}

impl AwsConfig {
pub async fn into_sdk_config(self) -> Result<SdkConfig, AwsConfigError> {
let creds: SharedCredentialsProvider =
match (self.access_key_id, self.secret_access_key, self.profile) {
/*
* When an hardcoded AWS access key is present, provide it statically.
*/
(Some(aki), Some(sak), None) => SharedCredentialsProvider::new(
Credentials::new(aki, sak, None, None, "buildomat"),
),
/*
* When a profile is selected, tell the default credential chain
* to use it, dynamically fetching the access key. This could be
* used authenticate with AWS SSO on a developer machine.
*/
(None, None, Some(profile)) => SharedCredentialsProvider::new(
DefaultCredentialsChain::builder()
.profile_name(&profile)
.build()
.await,
),
/*
* When nothing is selected, use the default credential chain to
* dynamically fetch the access key. This could be used to
* authenticate an AWS instance using their metadata service.
*/
(None, None, None) => SharedCredentialsProvider::new(
DefaultCredentialsChain::builder().build().await,
),
/*
* Provide good error messages for invalid configurations.
*/
(Some(_), None, _) | (None, Some(_), _) => {
return Err(AwsConfigError::IncompleteAccessKey);
}
(Some(_), Some(_), Some(_)) => {
return Err(AwsConfigError::MultipleCredentials);
}
};

Ok(ConfigLoader::default()
.region(Region::new(self.region))
.credentials_provider(creds)
.behavior_version(BehaviorVersion::v2026_01_12())
.load()
.await)
}
}

#[derive(Debug, thiserror::Error)]
pub enum AwsConfigError {
#[error("both access_key_id and secret_access_key are required")]
IncompleteAccessKey,
#[error("cannot use both an aws profile and hardcoded credentials")]
MultipleCredentials,
}
1 change: 1 addition & 0 deletions factory/aws/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"
license = "MPL-2.0"

[dependencies]
buildomat-aws-common = { path = "../../aws-common" }
buildomat-client = { path = "../../client" }
buildomat-common = { path = "../../common" }
buildomat-types = { path = "../../types" }
Expand Down
31 changes: 9 additions & 22 deletions factory/aws/src/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ use std::time::{Duration, UNIX_EPOCH};
use std::{collections::HashMap, time::SystemTime};

use anyhow::{anyhow, bail, Result};
use aws_config::Region;
use aws_config::{meta::region::RegionProviderChain, BehaviorVersion};
use aws_sdk_ec2::config::Credentials;
use aws_sdk_ec2::types::{
BlockDeviceMapping, EbsBlockDevice, Filter,
InstanceNetworkInterfaceSpecification, InstanceType, ResourceType, Tag,
TagSpecification,
};
use base64::Engine;
use buildomat_aws_common::AwsConfig;
use buildomat_client::types::*;
use slog::{debug, error, info, o, warn, Logger};

Expand Down Expand Up @@ -589,25 +587,14 @@ async fn aws_worker_one(
pub(crate) async fn aws_worker(c: Arc<Central>) -> Result<()> {
let log = c.log.new(o!("component" => "worker"));

let region = RegionProviderChain::first_try(Region::new(
c.config.aws.region.clone(),
))
.region()
.await
.ok_or_else(|| anyhow!("could not select region"))?;
let creds = Credentials::new(
&c.config.aws.access_key_id,
&c.config.aws.secret_access_key,
None,
None,
"config-file",
);

let cfg = aws_config::defaults(BehaviorVersion::v2026_01_12())
.region(region)
.credentials_provider(creds)
.load()
.await;
let cfg = AwsConfig {
access_key_id: c.config.aws.access_key_id.clone(),
secret_access_key: c.config.aws.secret_access_key.clone(),
profile: c.config.aws.profile.clone(),
region: c.config.aws.region.clone(),
}
.into_sdk_config()
.await?;

let ec2 = aws_sdk_ec2::Client::new(&cfg);

Expand Down
5 changes: 3 additions & 2 deletions factory/aws/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ pub(crate) struct ConfigFileAwsTarget {
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct ConfigFileAws {
pub access_key_id: String,
pub secret_access_key: String,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub profile: Option<String>,
pub region: String,
pub vpc: String,
pub subnet: String,
Expand Down
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"
license = "MPL-2.0"

[dependencies]
buildomat-aws-common = { path = "../aws-common" }
buildomat-common = { path = "../common" }
buildomat-database = { path = "../database" }
buildomat-download = { path = "../download" }
Expand Down
21 changes: 3 additions & 18 deletions server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,14 @@ pub struct ConfigFileAdmin {
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ConfigFileStorage {
pub access_key_id: String,
pub secret_access_key: String,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub profile: Option<String>,
pub bucket: String,
pub prefix: String,
pub region: String,
}

impl ConfigFileStorage {
pub fn creds(&self) -> aws_credential_types::Credentials {
aws_credential_types::Credentials::new(
&self.access_key_id,
&self.secret_access_key,
None,
None,
"buildomat",
)
}

pub fn region(&self) -> aws_types::region::Region {
aws_types::region::Region::new(self.region.to_string())
}
}

pub fn load<P: AsRef<Path>>(path: P) -> Result<ConfigFile> {
read_toml(path.as_ref())
}
16 changes: 10 additions & 6 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod files;
mod jobs;
mod workers;

use buildomat_aws_common::AwsConfig;
use db::{
AuthUser, Job, JobEvent, JobFile, JobFileId, JobId, JobOutput,
JobOutputAndFile, Worker, WorkerEvent,
Expand Down Expand Up @@ -1053,12 +1054,15 @@ async fn main() -> Result<()> {
dbfile.push("data.sqlite3");
let db = db::Database::new(log.clone(), dbfile, config.sqlite.cache_kb)?;

let awscfg = aws_config::ConfigLoader::default()
.region(config.storage.region())
.credentials_provider(config.storage.creds())
.behavior_version(aws_config::BehaviorVersion::v2026_01_12())
.load()
.await;
let awscfg = AwsConfig {
access_key_id: config.storage.access_key_id.clone(),
secret_access_key: config.storage.secret_access_key.clone(),
profile: config.storage.profile.clone(),
region: config.storage.region.clone(),
}
.into_sdk_config()
.await?;

let s3 = aws_sdk_s3::Client::new(&awscfg);

let files = files::Files::new(log.new(o!("component" => "files")));
Expand Down