use std::time::Duration;
use futures::{Future, Poll};
use futures::future::{FutureResult, result};
use hyper::Uri;
use {AwsCredentials, CredentialsError, ProvideAwsCredentials,
parse_credentials_from_aws_service};
use request::{HttpClient, HttpClientFuture};
const AWS_CREDENTIALS_PROVIDER_IP: &str = "169.254.169.254";
const AWS_CREDENTIALS_PROVIDER_PATH: &str = "latest/meta-data/iam/security-credentials";
#[derive(Clone, Debug)]
pub struct InstanceMetadataProvider {
client: HttpClient,
timeout: Duration
}
impl InstanceMetadataProvider {
pub fn new() -> Self {
InstanceMetadataProvider {
client: HttpClient::new(),
timeout: Duration::from_secs(30)
}
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
}
enum InstanceMetadataFutureState {
Start,
GetRoleName(HttpClientFuture),
GetCredentialsFromRole(HttpClientFuture),
Done(FutureResult<AwsCredentials, CredentialsError>)
}
pub struct InstanceMetadataProviderFuture {
state: InstanceMetadataFutureState,
client: HttpClient,
timeout: Duration
}
impl Future for InstanceMetadataProviderFuture {
type Item = AwsCredentials;
type Error = CredentialsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let new_state = match self.state {
InstanceMetadataFutureState::Start => {
let new_future = get_role_name(&self.client, self.timeout)?;
InstanceMetadataFutureState::GetRoleName(new_future)
}
InstanceMetadataFutureState::GetRoleName(ref mut future) => {
let role_name = try_ready!(future.poll());
let new_future = get_credentials_from_role(&self.client, self.timeout, &role_name)?;
InstanceMetadataFutureState::GetCredentialsFromRole(new_future)
},
InstanceMetadataFutureState::GetCredentialsFromRole(ref mut future) => {
let cred_str = try_ready!(future.poll());
let new_future = result(parse_credentials_from_aws_service(&cred_str));
InstanceMetadataFutureState::Done(new_future)
},
InstanceMetadataFutureState::Done(ref mut future) => {
return future.poll();
}
};
self.state = new_state;
self.poll()
}
}
impl ProvideAwsCredentials for InstanceMetadataProvider {
type Future = InstanceMetadataProviderFuture;
fn credentials(&self) -> Self::Future {
InstanceMetadataProviderFuture {
state: InstanceMetadataFutureState::Start,
client: self.client.clone(),
timeout: self.timeout
}
}
}
fn get_role_name(client: &HttpClient, timeout: Duration) -> Result<HttpClientFuture, CredentialsError> {
let role_name_address = format!(
"http://{}/{}/",
AWS_CREDENTIALS_PROVIDER_IP,
AWS_CREDENTIALS_PROVIDER_PATH
);
let uri = role_name_address.parse::<Uri>()
.map_err(|err| CredentialsError::new(err))?;
Ok(client.get(uri, timeout))
}
fn get_credentials_from_role(
client: &HttpClient,
timeout: Duration,
role_name: &str
) -> Result<HttpClientFuture, CredentialsError> {
let credentials_provider_url = format!(
"http://{}/{}/{}",
AWS_CREDENTIALS_PROVIDER_IP,
AWS_CREDENTIALS_PROVIDER_PATH,
role_name
);
let uri = credentials_provider_url.parse::<Uri>()
.map_err(|err| CredentialsError::new(err))?;
Ok(client.get(uri, timeout))
}