protected Func <Task <Settings> > CreateSettingsInitializer( CommandLineApplication cmd, bool requireAwsProfile = true ) { CommandOption awsProfileOption = null; CommandOption awsRegionOption = null; // add misc options var tierOption = AddTierOption(cmd); if (requireAwsProfile) { awsProfileOption = cmd.Option("--aws-profile|-P <NAME>", "(optional) Use a specific AWS profile from the AWS credentials file", CommandOptionType.SingleValue); awsRegionOption = cmd.Option("--aws-region <NAME>", "(optional) Use a specific AWS region (default: read from AWS profile)", CommandOptionType.SingleValue); } var verboseLevelOption = cmd.Option("--verbose|-V[:<LEVEL>]", "(optional) Show verbose output (0=Quiet, 1=Normal, 2=Detailed, 3=Exceptions; Normal if LEVEL is omitted)", CommandOptionType.SingleOrNoValue); var noAnsiOutputOption = cmd.Option("--no-ansi", "Disable colored ANSI terminal output", CommandOptionType.NoValue); // add hidden testing options var awsAccountIdOption = cmd.Option("--aws-account-id <VALUE>", "(test only) Override AWS account Id (default: read from AWS profile)", CommandOptionType.SingleValue); var awsUserArnOption = cmd.Option("--aws-user-arn <ARN>", "(test only) Override AWS user ARN (default: read from AWS profile)", CommandOptionType.SingleValue); var toolVersionOption = cmd.Option("--cli-version <VALUE>", "(test only) LambdaSharp CLI version for profile", CommandOptionType.SingleValue); var deploymentBucketNameOption = cmd.Option("--deployment-bucket-name <NAME>", "(test only) S3 Bucket name used to deploy modules (default: read from LambdaSharp CLI configuration)", CommandOptionType.SingleValue); var tierVersionOption = cmd.Option("--tier-version <VERSION>", "(test only) LambdaSharp tier version (default: read from deployment tier)", CommandOptionType.SingleValue); var promptsAsErrorsOption = cmd.Option("--prompts-as-errors", "(optional) Missing parameters cause an error instead of a prompts (use for CI/CD to avoid unattended prompts)", CommandOptionType.NoValue); awsAccountIdOption.ShowInHelpText = false; awsUserArnOption.ShowInHelpText = false; toolVersionOption.ShowInHelpText = false; deploymentBucketNameOption.ShowInHelpText = false; tierVersionOption.ShowInHelpText = false; return(async() => { // check if ANSI console output needs to be disabled if (noAnsiOutputOption.HasValue()) { Settings.UseAnsiConsole = false; } // check if experimental caching feature is enabled Settings.AllowCaching = string.Equals((Environment.GetEnvironmentVariable("LAMBDASHARP_FEATURE_CACHING") ?? "false"), "true", StringComparison.OrdinalIgnoreCase); // initialize logging level if (!TryParseEnumOption(verboseLevelOption, Tool.VerboseLevel.Normal, VerboseLevel.Detailed, out Settings.VerboseLevel)) { // NOTE (2018-08-04, bjorg): no need to add an error message since it's already added by 'TryParseEnumOption' return null; } // initialize deployment tier string tier = tierOption.Value() ?? Environment.GetEnvironmentVariable("LAMBDASHARP_TIER") ?? ""; // initialize AWS profile try { AwsAccountInfo awsAccount = null; IAmazonSimpleSystemsManagement ssmClient = null; IAmazonCloudFormation cfnClient = null; IAmazonKeyManagementService kmsClient = null; IAmazonS3 s3Client = null; IAmazonAPIGateway apiGatewayClient = null; IAmazonIdentityManagementService iamClient = null; IAmazonLambda lambdaClient = null; if (requireAwsProfile) { awsAccount = await InitializeAwsProfile( awsProfileOption.Value(), awsAccountIdOption.Value(), awsRegionOption.Value(), awsUserArnOption.Value(), // TODO (2019-10-08, bjorg): provide option to disable profile caching (or at least force a reset) allowCaching : true ); if (awsAccount == null) { return null; } // create AWS clients ssmClient = new AmazonSimpleSystemsManagementClient(); cfnClient = new AmazonCloudFormationClient(); kmsClient = new AmazonKeyManagementServiceClient(); s3Client = new AmazonS3Client(); apiGatewayClient = new AmazonAPIGatewayClient(); iamClient = new AmazonIdentityManagementServiceClient(); lambdaClient = new AmazonLambdaClient(); } if (HasErrors) { return null; } // initialize LambdaSharp deployment values var tierVersion = tierVersionOption.Value(); var deploymentBucketName = deploymentBucketNameOption.Value(); // create a settings instance for each module filename return new Settings { ToolVersion = Version, TierVersion = (tierVersion != null) ? VersionInfo.Parse(tierVersion) : null, Tier = tier, AwsRegion = awsAccount?.Region, AwsAccountId = awsAccount?.AccountId, AwsUserArn = awsAccount?.UserArn, DeploymentBucketName = deploymentBucketName, SsmClient = ssmClient, CfnClient = cfnClient, KmsClient = kmsClient, S3Client = s3Client, ApiGatewayClient = apiGatewayClient, IamClient = iamClient, LambdaClient = lambdaClient, PromptsAsErrors = promptsAsErrorsOption.HasValue() }; } catch (AmazonClientException e) when(e.Message == "No RegionEndpoint or ServiceURL configured") { LogError("AWS profile is missing a region specifier"); return null; } }); }
protected Func <Task <Settings> > CreateSettingsInitializer( CommandLineApplication cmd, bool requireAwsProfile = true ) { CommandOption awsProfileOption = null; CommandOption awsRegionOption = null; // add misc options var tierOption = cmd.Option("--tier|-T <NAME>", "(optional) Name of deployment tier (default: LAMBDASHARP_TIER environment variable)", CommandOptionType.SingleValue); if (requireAwsProfile) { awsProfileOption = cmd.Option("--aws-profile|-P <NAME>", "(optional) Use a specific AWS profile from the AWS credentials file", CommandOptionType.SingleValue); awsRegionOption = cmd.Option("--aws-region <NAME>", "(optional) Use a specific AWS region (default: read from AWS profile)", CommandOptionType.SingleValue); } // add hidden testing options var awsAccountIdOption = cmd.Option("--aws-account-id <VALUE>", "(test only) Override AWS account Id (default: read from AWS profile)", CommandOptionType.SingleValue); var awsUserArnOption = cmd.Option("--aws-user-arn <ARN>", "(test only) Override AWS user ARN (default: read from AWS profile)", CommandOptionType.SingleValue); var toolVersionOption = cmd.Option("--cli-version <VALUE>", "(test only) LambdaSharp CLI version for profile", CommandOptionType.SingleValue); var deploymentBucketNameOption = cmd.Option("--deployment-bucket-name <NAME>", "(test only) S3 Bucket name used to deploy modules (default: read from LambdaSharp CLI configuration)", CommandOptionType.SingleValue); var tierVersionOption = cmd.Option("--tier-version <VERSION>", "(test only) LambdaSharp tier version (default: read from deployment tier)", CommandOptionType.SingleValue); var promptsAsErrorsOption = cmd.Option("--prompts-as-errors", "(optional) Missing parameters cause an error instead of a prompts (use for CI/CD to avoid unattended prompts)", CommandOptionType.NoValue); awsAccountIdOption.ShowInHelpText = false; awsUserArnOption.ShowInHelpText = false; toolVersionOption.ShowInHelpText = false; deploymentBucketNameOption.ShowInHelpText = false; tierVersionOption.ShowInHelpText = false; return(async() => { // check if experimental caching feature is enabled Settings.AllowCaching = string.Equals((Environment.GetEnvironmentVariable("LAMBDASHARP_FEATURE_CACHING") ?? "false"), "true", StringComparison.OrdinalIgnoreCase); // initialize deployment tier string tier = tierOption.Value() ?? Environment.GetEnvironmentVariable("LAMBDASHARP_TIER") ?? ""; // initialize AWS profile try { AwsAccountInfo awsAccount = null; IAmazonSimpleSystemsManagement ssmClient = null; IAmazonCloudFormation cfnClient = null; IAmazonKeyManagementService kmsClient = null; IAmazonS3 s3Client = null; IAmazonAPIGateway apiGatewayClient = null; IAmazonIdentityManagementService iamClient = null; IAmazonLambda lambdaClient = null; if (requireAwsProfile) { awsAccount = await InitializeAwsProfile( awsProfileOption.Value(), awsAccountIdOption.Value(), awsRegionOption.Value(), awsUserArnOption.Value() ); if (awsAccount == null) { return null; } // create AWS clients ssmClient = new AmazonSimpleSystemsManagementClient(AWSConfigs.RegionEndpoint); cfnClient = new AmazonCloudFormationClient(AWSConfigs.RegionEndpoint); kmsClient = new AmazonKeyManagementServiceClient(AWSConfigs.RegionEndpoint); s3Client = new AmazonS3Client(AWSConfigs.RegionEndpoint); apiGatewayClient = new AmazonAPIGatewayClient(AWSConfigs.RegionEndpoint); iamClient = new AmazonIdentityManagementServiceClient(AWSConfigs.RegionEndpoint); lambdaClient = new AmazonLambdaClient(AWSConfigs.RegionEndpoint); } if (HasErrors) { return null; } // initialize LambdaSharp deployment values var tierVersion = tierVersionOption.Value(); var deploymentBucketName = deploymentBucketNameOption.Value(); // initialize LambdaSharp testing values VersionInfo toolVersion = null; if (toolVersionOption.HasValue()) { toolVersion = VersionInfo.Parse(toolVersionOption.Value()); } // create a settings instance for each module filename return new Settings(toolVersion ?? Version) { TierVersion = (tierVersion != null) ? VersionInfo.Parse(tierVersion) : null, Tier = tier, AwsRegion = awsAccount?.Region, AwsAccountId = awsAccount?.AccountId, AwsUserArn = awsAccount?.UserArn, DeploymentBucketName = deploymentBucketName, SsmClient = ssmClient, CfnClient = cfnClient, KmsClient = kmsClient, S3Client = s3Client, ApiGatewayClient = apiGatewayClient, IamClient = iamClient, LambdaClient = lambdaClient, PromptsAsErrors = promptsAsErrorsOption.HasValue() }; } catch (AmazonClientException e) when(e.Message == "No RegionEndpoint or ServiceURL configured") { LogError("AWS profile is missing a region specifier"); return null; } }); }
//--- Methods --- protected async Task <AwsAccountInfo> InitializeAwsProfile( string awsProfile, string awsAccountId = null, string awsRegion = null, string awsUserArn = null, bool allowCaching = false ) { var stopwatch = Stopwatch.StartNew(); var cached = false; try { // check if .aws/credentials file exists if ( !File.Exists(CredentialsFilePath) && ( (Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID") == null) || (Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY") == null) ) ) { LogError($"IMPORTANT: run '{Settings.Lash} init' to create an AWS profile"); return(null); } // initialize AWS profile if (awsProfile == null) { awsProfile = Settings.AwsProfileEnvironmentVariable; } // consistently set the AWS profile by setting the AWS_PROFILE/AWS_DEFAULT_PROFILE environment variables AWSConfigs.AWSProfileName = awsProfile; Environment.SetEnvironmentVariable("AWS_PROFILE", awsProfile); Environment.SetEnvironmentVariable("AWS_DEFAULT_PROFILE", awsProfile); // check for cached AWS profile var cachedProfile = Path.Combine(Settings.AwsProfileCacheDirectory, "profile.json"); if (allowCaching && Settings.AllowCaching && File.Exists(cachedProfile) && ((DateTime.UtcNow - File.GetLastWriteTimeUtc(cachedProfile)) < Settings.MaxCacheAge)) { cached = true; return(JsonConvert.DeserializeObject <AwsAccountInfo>(await File.ReadAllTextAsync(cachedProfile))); } // determine default AWS region if ((awsAccountId == null) || (awsRegion == null) || (awsUserArn == null)) { // determine AWS region and account try { var stsClient = new AmazonSecurityTokenServiceClient(); var response = await stsClient.GetCallerIdentityAsync(new GetCallerIdentityRequest()); awsRegion = awsRegion ?? stsClient.Config.RegionEndpoint.SystemName ?? "us-east-1"; awsAccountId = awsAccountId ?? response.Account; awsUserArn = awsUserArn ?? response.Arn; } catch (HttpRequestException e) when(e.Message == "No such host is known") { LogError("an Internet connection is required to determine the AWS Account Id and Region"); return(null); } catch (Exception e) { LogError("unable to determine the AWS Account Id and Region", e); return(null); } } // set AWS region for library and spawned processes AWSConfigs.AWSRegion = awsRegion; Environment.SetEnvironmentVariable("AWS_REGION", awsRegion); Environment.SetEnvironmentVariable("AWS_DEFAULT_REGION", awsRegion); var result = new AwsAccountInfo { Region = awsRegion, AccountId = awsAccountId, UserArn = awsUserArn }; if (allowCaching && Settings.AllowCaching) { Directory.CreateDirectory(Path.GetDirectoryName(cachedProfile)); await File.WriteAllTextAsync(cachedProfile, JsonConvert.SerializeObject(result)); } return(result); } finally { Settings.LogInfoPerformance($"InitializeAwsProfile()", stopwatch.Elapsed, cached); } }
//--- Methods --- protected async Task <AwsAccountInfo> InitializeAwsProfile( string awsProfile, string awsAccountId = null, string awsRegion = null, string awsUserArn = null ) { Settings.StartLogPerformance("InitializeAwsProfile()"); var cached = false; try { // check if .aws/credentials file exists var hasAwsAccessKeyId = Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID") != null; var hasAwsSecretAccessKey = Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY") != null; if (!File.Exists(CredentialsFilePath) && !(hasAwsAccessKeyId && hasAwsSecretAccessKey)) { LogError($"IMPORTANT: run '{Settings.Lash} init' to create an AWS profile"); return(null); } // initialize AWS profile if (awsProfile == null) { awsProfile = Settings.AwsProfileEnvironmentVariable; } // consistently set the AWS profile by setting the AWS_PROFILE/AWS_DEFAULT_PROFILE environment variables AWSConfigs.AWSProfileName = awsProfile; Environment.SetEnvironmentVariable("AWS_PROFILE", awsProfile); Environment.SetEnvironmentVariable("AWS_DEFAULT_PROFILE", awsProfile); // neither AWS_ACCESS_KEY_ID, nor AWS_SECRET_ACCESS_KEY can be set to use the cached profile information if (!hasAwsAccessKeyId && !hasAwsSecretAccessKey) { var(cachedProfile, cachedProfileTimestamp) = GetCachedProfile(awsProfile); // the cached profile must exist and be newer than the credentials file if ( (cachedProfile != null) && (cachedProfileTimestamp > File.GetLastWriteTimeUtc(CredentialsFilePath)) ) { cached = true; return(cachedProfile); } } // determine default AWS region if ((awsAccountId == null) || (awsRegion == null) || (awsUserArn == null)) { // determine AWS region and account try { var stsClient = new AmazonSecurityTokenServiceClient(); var response = await stsClient.GetCallerIdentityAsync(new GetCallerIdentityRequest()); awsRegion = awsRegion ?? stsClient.Config.RegionEndpoint.SystemName ?? "us-east-1"; awsAccountId = awsAccountId ?? response.Account; awsUserArn = awsUserArn ?? response.Arn; } catch (HttpRequestException e) when(e.Message == "No such host is known") { LogError("an Internet connection is required to determine the AWS Account Id and Region"); return(null); } catch (Exception e) { LogError("unable to determine the AWS Account Id and Region", e); return(null); } } // set AWS region for library and spawned processes AWSConfigs.AWSRegion = awsRegion; Environment.SetEnvironmentVariable("AWS_REGION", awsRegion); Environment.SetEnvironmentVariable("AWS_DEFAULT_REGION", awsRegion); var result = new AwsAccountInfo { Region = awsRegion, AccountId = awsAccountId, UserArn = awsUserArn }; // only store profile if no AWS keys were used if (!hasAwsAccessKeyId && !hasAwsSecretAccessKey) { CacheProfile(awsProfile, result); } return(result); } finally { Settings.StopLogPerformance(cached); } }