Unverified Commit 735fee00 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Add missing features to user agent formatting (#865)

* Add missing features to user agent formatter

* Make it possible to set the app name for the user agent

* Add integration test for app name config

* Fix `aws-config` build

* Update changelog

* Create new type for app name to share validation

* Add ability to configure app name programmatically via shared config

* Make app name configurable via profile config and environment variable

* Add functions to add metadata on user agent

* Add breaking change to changelog

* Validate user agent metadata fields

* Add missing lints to `aws-http` and fix them

* Incorporate review feedback
parent 295bdc8a
Loading
Loading
Loading
Loading
+57 −45
Original line number Diff line number Diff line
vNext (Month Day, Year)
=======================

**TODO Upon release**
- Update README & aws-sdk-rust CI for MSRV upgrade to 1.54
- Fixed links to Usage Examples (smithy-rs#862, @floric)

**Breaking Changes**

Several breaking changes around `aws_smithy_types::Instant` were introduced by smithy-rs#849:
- The `add_metadata` function was removed from `AwsUserAgent` in `aws-http`.
  Use `with_feature_metadata`, `with_config_metadata`, or `with_framework_metadata` now instead. (smithy-rs#865)
- Several breaking changes around `aws_smithy_types::Instant` were introduced by smithy-rs#849:
  - `aws_smithy_types::Instant` from was renamed to `DateTime` to avoid confusion with the standard library's monotonically nondecreasing `Instant` type.
  - `DateParseError` in `aws_smithy_types` has been renamed to `DateTimeParseError` to match the type that's being parsed.
  - The `chrono-conversions` feature and associated functions have been moved to the `aws-smithy-types-convert` crate.
@@ -51,9 +54,18 @@ Several breaking changes around `aws_smithy_types::Instant` were introduced by s
  - The `DateTime::fmt` method is now fallible and fails when a `DateTime`'s value is outside what can be represented by the desired date format.

**New this week**

- Conversions from `aws_smithy_types::DateTime` to `OffsetDateTime` from the `time` crate are now available from the `aws-smithy-types-convert` crate. (smithy-rs#849)
- Fixed links to Usage Examples (smithy-rs#862, @floric)
- Added missing features to user agent formatting, and made it possible to configure an app name for the user agent via service config. (smithy-rs#865)
- :bug: Relaxed profile name validation to allow `@` and other characters (smithy-rs#861, aws-sdk-rust#270)

**Contributions**

Thank you for your contributions! :heart:

- @floric (smithy-rs#862)

v0.0.25-alpha (November 11th, 2021)
===================================

+95 −0
Original line number Diff line number Diff line
@@ -177,6 +177,101 @@ pub mod retry_config {
    }
}

/// Default app name provider chain
pub mod app_name {
    use crate::environment::app_name::EnvironmentVariableAppNameProvider;
    use crate::profile::app_name;
    use crate::provider_config::ProviderConfig;
    use aws_types::app_name::AppName;

    /// Default App Name Provider chain
    ///
    /// This provider will check the following sources in order:
    /// 1. [Environment variables](EnvironmentVariableAppNameProvider)
    /// 2. [Profile file](crate::profile::app_name::ProfileFileAppNameProvider)
    pub fn default_provider() -> Builder {
        Builder::default()
    }

    /// Default provider builder for [`AppName`]
    #[derive(Default)]
    pub struct Builder {
        env_provider: EnvironmentVariableAppNameProvider,
        profile_file: app_name::Builder,
    }

    impl Builder {
        #[doc(hidden)]
        /// Configure the default chain
        ///
        /// Exposed for overriding the environment when unit-testing providers
        pub fn configure(mut self, configuration: &ProviderConfig) -> Self {
            self.env_provider =
                EnvironmentVariableAppNameProvider::new_with_env(configuration.env());
            self.profile_file = self.profile_file.configure(configuration);
            self
        }

        /// Override the profile name used by this provider
        pub fn profile_name(mut self, name: &str) -> Self {
            self.profile_file = self.profile_file.profile_name(name);
            self
        }

        /// Build an [`AppName`] from the default chain
        pub async fn app_name(self) -> Option<AppName> {
            self.env_provider
                .app_name()
                .or(self.profile_file.build().app_name().await)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;
        use crate::provider_config::ProviderConfig;
        use crate::test_case::no_traffic_connector;
        use aws_types::os_shim_internal::{Env, Fs};

        #[tokio::test]
        async fn prefer_env_to_profile() {
            let fs = Fs::from_slice(&[("test_config", "[default]\nsdk-ua-app-id = wrong")]);
            let env = Env::from_slice(&[
                ("AWS_CONFIG_FILE", "test_config"),
                ("AWS_SDK_UA_APP_ID", "correct"),
            ]);
            let app_name = Builder::default()
                .configure(
                    &ProviderConfig::no_configuration()
                        .with_fs(fs)
                        .with_env(env)
                        .with_http_connector(no_traffic_connector()),
                )
                .app_name()
                .await;

            assert_eq!(Some(AppName::new("correct").unwrap()), app_name);
        }

        #[tokio::test]
        async fn load_from_profile() {
            let fs = Fs::from_slice(&[("test_config", "[default]\nsdk-ua-app-id = correct")]);
            let env = Env::from_slice(&[("AWS_CONFIG_FILE", "test_config")]);
            let app_name = Builder::default()
                .configure(
                    &ProviderConfig::empty()
                        .with_fs(fs)
                        .with_env(env)
                        .with_http_connector(no_traffic_connector()),
                )
                .app_name()
                .await;

            assert_eq!(Some(AppName::new("correct").unwrap()), app_name);
        }
    }
}

/// Default credentials provider chain
pub mod credentials {
    use std::borrow::Cow;
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

use aws_types::app_name::AppName;
use aws_types::os_shim_internal::Env;

/// Load an app name from the `AWS_SDK_UA_APP_ID` environment variable.
#[derive(Debug, Default)]
pub struct EnvironmentVariableAppNameProvider {
    env: Env,
}

impl EnvironmentVariableAppNameProvider {
    /// Create a new `EnvironmentVariableAppNameProvider`
    pub fn new() -> Self {
        Self { env: Env::real() }
    }

    #[doc(hidden)]
    /// Create an region provider from a given `Env`
    ///
    /// This method is used for tests that need to override environment variables.
    pub fn new_with_env(env: Env) -> Self {
        Self { env }
    }

    /// Attempts to create an `AppName` from the `AWS_SDK_UA_APP_ID` environment variable.
    pub fn app_name(&self) -> Option<AppName> {
        if let Ok(name) = self.env.get("AWS_SDK_UA_APP_ID") {
            match AppName::new(name) {
                Ok(name) => Some(name),
                Err(err) => {
                    tracing::warn!(err = %err, "`AWS_SDK_UA_APP_ID` environment variable value was invalid");
                    None
                }
            }
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::environment::EnvironmentVariableAppNameProvider;
    use aws_types::app_name::AppName;
    use aws_types::os_shim_internal::Env;
    use std::collections::HashMap;

    #[test]
    fn env_var_not_set() {
        let provider = EnvironmentVariableAppNameProvider::new_with_env(Env::from(HashMap::new()));
        assert_eq!(None, provider.app_name());
    }

    #[test]
    fn env_var_set() {
        let provider = EnvironmentVariableAppNameProvider::new_with_env(Env::from(
            vec![("AWS_SDK_UA_APP_ID".to_string(), "something".to_string())]
                .into_iter()
                .collect::<HashMap<String, String>>(),
        ));
        assert_eq!(
            Some(AppName::new("something").unwrap()),
            provider.app_name()
        );
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -3,6 +3,9 @@
 * SPDX-License-Identifier: Apache-2.0.
 */

/// Load app name from the environment
pub mod app_name;
pub use app_name::EnvironmentVariableAppNameProvider;
/// Load credentials from the environment
pub mod credentials;
pub use credentials::EnvironmentVariableCredentialsProvider;
+5 −4
Original line number Diff line number Diff line
@@ -39,9 +39,6 @@ use crate::provider_config::{HttpSettings, ProviderConfig};
use crate::{profile, PKG_VERSION};
use tokio::sync::OnceCell;

const USER_AGENT: AwsUserAgent =
    AwsUserAgent::new_from_environment(ApiMetadata::new("imds", PKG_VERSION));

mod token;

// 6 hours
@@ -50,6 +47,10 @@ const DEFAULT_ATTEMPTS: u32 = 4;
const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(1);
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(1);

fn user_agent() -> AwsUserAgent {
    AwsUserAgent::new_from_environment(Env::real(), ApiMetadata::new("imds", PKG_VERSION))
}

/// IMDSv2 Client
///
/// Client for IMDSv2. This client handles fetching tokens, retrying on failure, and token
@@ -215,7 +216,7 @@ impl Client {
            .body(SdkBody::empty())
            .expect("valid request");
        let mut request = operation::Request::new(request);
        request.properties_mut().insert(USER_AGENT);
        request.properties_mut().insert(user_agent());
        Ok(Operation::new(request, ImdsGetResponseHandler)
            .with_metadata(Metadata::new("get", "imds"))
            .with_retry_policy(ImdsErrorPolicy))
Loading