3

How could I fallback to use credentials from a parsed file (config.yml) if no environment variables were found? For testing, I am using this example:

extern crate rusoto_core;
extern crate rusoto_s3;

use rusoto_core::credential::ChainProvider;
use rusoto_core::request::HttpClient;
use rusoto_core::Region;
use rusoto_s3::{S3, S3Client};
use std::time::{Duration, Instant};

fn main() {
    let mut chain = ChainProvider::new();
    chain.set_timeout(Duration::from_millis(200));
    let s3client = S3Client::new_with(
        HttpClient::new().expect("failed to create request dispatcher"),
        chain,
        Region::UsEast1,
    );

    let start = Instant::now();
    println!("Starting up at {:?}", start);

    match s3client.list_buckets().sync() {
        Err(e) => println!("Error listing buckets: {}", e),
        Ok(buckets) => println!("Buckets found: {:?}", buckets),
    };
    println!("Took {:?}", Instant::now().duration_since(start));
}

It works but requires the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. I would like to extend it so that If there are no defined environment variables, I could use the keys found in a parsed file:

// parse config file
let file = std::fs::File::open("config.yml").expect("Unable to open file");
let yml: Config = match serde_yaml::from_reader(file) {
    Err(err) => {
        println!("Error: {}", err);
        return;
    }
    Ok(yml) => yml,
};

The config.yml for example could be something like:

---
endpoint: s3.provider
access_key: ACCESS_KEY_ID
secret_key: SECRET_ACCESS_KEY

What could I add to the chain to use the credentials found in the config.yml, something probably like:

let config_provider = StaticProvider::new_minimal(yml.access_key, yml.secret_key);

How to give preference to the environment and if not found then use the credentials provided by the StaticProvider?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nbari
  • 25,603
  • 10
  • 76
  • 131

1 Answers1

4

The ChainProvider actually has four sources to check for AWS credentials. The third one is the AWS configuration file located in user's home directory. But its format is predetermined.

If you insist on using your own YAML file, you can chain EnvironmentProvider and StaticProvider together like so:

//# futures01 = { package = "futures", version = "0.1.28" }
//# rusoto_core = "0.41.0"
//# rusoto_s3 = "0.41.0"
//# rusoto_credential = "0.41.1"
use futures01::future::Future;
use rusoto_core::request::HttpClient;
use rusoto_core::Region;
use rusoto_credential::{
    AwsCredentials,
    CredentialsError,
    EnvironmentProvider,
    ProvideAwsCredentials,
    StaticProvider,
};
use rusoto_s3::{S3, S3Client};
use std::time::Instant;

struct MyChainProvider<'a> {
    access_key: &'a str,
    secret_key: &'a str,
}

impl<'a> ProvideAwsCredentials for MyChainProvider<'a> {
    type Future = Box<dyn Future<Item=AwsCredentials, Error=CredentialsError> + Send>;

    fn credentials(&self) -> Self::Future {
        let future = EnvironmentProvider::default().credentials()
            .or_else({
                let access_key = self.access_key.to_string();
                let secret_key = self.secret_key.to_string();

                move |_| -> Self::Future {
                    Box::new(StaticProvider::new_minimal(access_key, secret_key).credentials())
                }
            });

        Box::new(future)
    }
}

fn main() {
    let chain = MyChainProvider {
        access_key: ...,
        secret_key: ...,
    };

    ...
}
edwardw
  • 12,652
  • 3
  • 40
  • 51
  • How could I extend the `MyChainProvider` to pass as an argument the keys using something like `MyChainProvider::new(access_key, secret_key)` so that later I could use `Box::new(StaticProvider::new_minimal(self.access_key, self.secret_key).credendials())` with out getting `cannot infer an appropriate lifetime due to conflicting requirements` – nbari Oct 02 '19 at 14:54
  • I updated to `futures = "0.3.0"` and getting this error now: `type Future = Box, Error = CredentialsError> + Send>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated type \`Output\` must be specified`, any ideas ? – nbari Nov 07 '19 at 10:53
  • 1
    @nbari pretty quick to jump on the `1.39.0` bandwagon, don't you? Unfortunately, `futures-0.1.x` and `futures-0.3.0` are incompatible, and `rusoto` itself is still depended on the `0.1.x` version. I'm afraid you have to wait it to be updated first. – edwardw Nov 07 '19 at 15:55
  • @edwardw That's not entirely correct, you can use both futures 0.3.x and 0.1.x alongside each other, 0.3.x even contains an optional compatibility feature. See here for more info: https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html – Raniz Nov 12 '19 at 11:27
  • @Raniz your statement is generally true, but it doesn't apply here. `rusoto_credential` hard-codes `0.1.x` futures type in its api, I don't see a way around it. You may also be interested in what `rusoto` devs say about that [here](https://github.com/rusoto/rusoto/issues/1583#issuecomment-551140997). – edwardw Nov 12 '19 at 12:19
  • Ok, so the compat feature is out, but you can still access 0.1.x futures by aliasing the package to futures01, then `use futures01::Future` in your own code that interfaces with _rusoto_credential_. – Raniz Nov 12 '19 at 12:36
  • 2
    @Raniz good point. I renamed the dependence in the code snippet to make it clear that it uses `0.1` branch of futures. Also make it convenient to use it alongside with `0.3` version. – edwardw Nov 12 '19 at 13:32
  • @edwardw any idea about how to make this work for `rusoto 0.43`, I get `type `Future` is not a member of trait `ProvideAwsCredentials` – nbari May 21 '20 at 17:10