provider/aws: Add profile to provider config
This allows specification of the profile for the shared credentials provider for AWS to be specified in Terraform configuration. This is useful if defining providers with aliases, or if you don't want to set environment variables. Example: $ aws configure --profile this_is_dog ... enter keys $ cat main.tf provider "aws" { profile = "this_is_dog" # Optionally also specify the path to the credentials file shared_credentials_file = "/tmp/credentials" } This is equivalent to specifying AWS_PROFILE or AWS_SHARED_CREDENTIALS_FILE in the environment.
This commit is contained in:
parent
e94ee6b77c
commit
ace215481a
|
@ -50,6 +50,8 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
|
CredsFilename string
|
||||||
|
Profile string
|
||||||
Token string
|
Token string
|
||||||
Region string
|
Region string
|
||||||
MaxRetries int
|
MaxRetries int
|
||||||
|
@ -113,7 +115,7 @@ func (c *Config) Client() (interface{}, error) {
|
||||||
client.region = c.Region
|
client.region = c.Region
|
||||||
|
|
||||||
log.Println("[INFO] Building AWS auth structure")
|
log.Println("[INFO] Building AWS auth structure")
|
||||||
creds := getCreds(c.AccessKey, c.SecretKey, c.Token)
|
creds := getCreds(c.AccessKey, c.SecretKey, c.Token, c.Profile, c.CredsFilename)
|
||||||
// Call Get to check for credential provider. If nothing found, we'll get an
|
// Call Get to check for credential provider. If nothing found, we'll get an
|
||||||
// error, and we can present it nicely to the user
|
// error, and we can present it nicely to the user
|
||||||
_, err = creds.Get()
|
_, err = creds.Get()
|
||||||
|
@ -341,7 +343,7 @@ func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
|
||||||
// This function is responsible for reading credentials from the
|
// This function is responsible for reading credentials from the
|
||||||
// environment in the case that they're not explicitly specified
|
// environment in the case that they're not explicitly specified
|
||||||
// in the Terraform configuration.
|
// in the Terraform configuration.
|
||||||
func getCreds(key, secret, token string) *awsCredentials.Credentials {
|
func getCreds(key, secret, token, profile, credsfile string) *awsCredentials.Credentials {
|
||||||
// build a chain provider, lazy-evaulated by aws-sdk
|
// build a chain provider, lazy-evaulated by aws-sdk
|
||||||
providers := []awsCredentials.Provider{
|
providers := []awsCredentials.Provider{
|
||||||
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
|
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
|
||||||
|
@ -350,7 +352,10 @@ func getCreds(key, secret, token string) *awsCredentials.Credentials {
|
||||||
SessionToken: token,
|
SessionToken: token,
|
||||||
}},
|
}},
|
||||||
&awsCredentials.EnvProvider{},
|
&awsCredentials.EnvProvider{},
|
||||||
&awsCredentials.SharedCredentialsProvider{},
|
&awsCredentials.SharedCredentialsProvider{
|
||||||
|
Filename: credsfile,
|
||||||
|
Profile: profile,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only look in the EC2 metadata API if we can connect
|
// We only look in the EC2 metadata API if we can connect
|
||||||
|
|
|
@ -3,6 +3,7 @@ package aws
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
@ -16,7 +17,7 @@ func TestAWSConfig_shouldError(t *testing.T) {
|
||||||
defer resetEnv()
|
defer resetEnv()
|
||||||
cfg := Config{}
|
cfg := Config{}
|
||||||
|
|
||||||
c := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token)
|
c := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
|
||||||
_, err := c.Get()
|
_, err := c.Get()
|
||||||
if awsErr, ok := err.(awserr.Error); ok {
|
if awsErr, ok := err.(awserr.Error); ok {
|
||||||
if awsErr.Code() != "NoCredentialProviders" {
|
if awsErr.Code() != "NoCredentialProviders" {
|
||||||
|
@ -49,7 +50,7 @@ func TestAWSConfig_shouldBeStatic(t *testing.T) {
|
||||||
Token: c.Token,
|
Token: c.Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token)
|
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
|
||||||
if creds == nil {
|
if creds == nil {
|
||||||
t.Fatalf("Expected a static creds provider to be returned")
|
t.Fatalf("Expected a static creds provider to be returned")
|
||||||
}
|
}
|
||||||
|
@ -84,7 +85,7 @@ func TestAWSConfig_shouldIAM(t *testing.T) {
|
||||||
// An empty config, no key supplied
|
// An empty config, no key supplied
|
||||||
cfg := Config{}
|
cfg := Config{}
|
||||||
|
|
||||||
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token)
|
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
|
||||||
if creds == nil {
|
if creds == nil {
|
||||||
t.Fatalf("Expected a static creds provider to be returned")
|
t.Fatalf("Expected a static creds provider to be returned")
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ func TestAWSConfig_shouldIgnoreIAM(t *testing.T) {
|
||||||
Token: c.Token,
|
Token: c.Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token)
|
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
|
||||||
if creds == nil {
|
if creds == nil {
|
||||||
t.Fatalf("Expected a static creds provider to be returned")
|
t.Fatalf("Expected a static creds provider to be returned")
|
||||||
}
|
}
|
||||||
|
@ -153,15 +154,65 @@ func TestAWSConfig_shouldIgnoreIAM(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var credentialsFileContents = `[myprofile]
|
||||||
|
aws_access_key_id = accesskey
|
||||||
|
aws_secret_access_key = secretkey
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestAWSConfig_shouldBeShared(t *testing.T) {
|
||||||
|
file, err := ioutil.TempFile(os.TempDir(), "terraform_aws_cred")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing temporary credentials file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = file.WriteString(credentialsFileContents)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing temporary credentials to file: %s", err)
|
||||||
|
}
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error closing temporary credentials file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
resetEnv := unsetEnv(t)
|
||||||
|
defer resetEnv()
|
||||||
|
|
||||||
|
if err := os.Setenv("AWS_PROFILE", "myprofile"); err != nil {
|
||||||
|
t.Fatalf("Error resetting env var AWS_PROFILE: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv("AWS_SHARED_CREDENTIALS_FILE", file.Name()); err != nil {
|
||||||
|
t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := getCreds("", "", "", "myprofile", file.Name())
|
||||||
|
if creds == nil {
|
||||||
|
t.Fatalf("Expected a provider chain to be returned")
|
||||||
|
}
|
||||||
|
v, err := creds.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error gettings creds: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.AccessKeyID != "accesskey" {
|
||||||
|
t.Fatalf("AccessKeyID mismatch, expected (%s), got (%s)", "accesskey", v.AccessKeyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.SecretAccessKey != "secretkey" {
|
||||||
|
t.Fatalf("SecretAccessKey mismatch, expected (%s), got (%s)", "accesskey", v.AccessKeyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAWSConfig_shouldBeENV(t *testing.T) {
|
func TestAWSConfig_shouldBeENV(t *testing.T) {
|
||||||
// need to set the environment variables to a dummy string, as we don't know
|
// need to set the environment variables to a dummy string, as we don't know
|
||||||
// what they may be at runtime without hardcoding here
|
// what they may be at runtime without hardcoding here
|
||||||
s := "some_env"
|
s := "some_env"
|
||||||
resetEnv := setEnv(s, t)
|
resetEnv := setEnv(s, t)
|
||||||
|
|
||||||
defer resetEnv()
|
defer resetEnv()
|
||||||
|
|
||||||
cfg := Config{}
|
cfg := Config{}
|
||||||
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token)
|
creds := getCreds(cfg.AccessKey, cfg.SecretKey, cfg.Token, cfg.Profile, cfg.CredsFilename)
|
||||||
if creds == nil {
|
if creds == nil {
|
||||||
t.Fatalf("Expected a static creds provider to be returned")
|
t.Fatalf("Expected a static creds provider to be returned")
|
||||||
}
|
}
|
||||||
|
@ -195,6 +246,12 @@ func unsetEnv(t *testing.T) func() {
|
||||||
if err := os.Unsetenv("AWS_SESSION_TOKEN"); err != nil {
|
if err := os.Unsetenv("AWS_SESSION_TOKEN"); err != nil {
|
||||||
t.Fatalf("Error unsetting env var AWS_SESSION_TOKEN: %s", err)
|
t.Fatalf("Error unsetting env var AWS_SESSION_TOKEN: %s", err)
|
||||||
}
|
}
|
||||||
|
if err := os.Unsetenv("AWS_PROFILE"); err != nil {
|
||||||
|
t.Fatalf("Error unsetting env var AWS_TOKEN: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE"); err != nil {
|
||||||
|
t.Fatalf("Error unsetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
// re-set all the envs we unset above
|
// re-set all the envs we unset above
|
||||||
|
@ -207,6 +264,12 @@ func unsetEnv(t *testing.T) func() {
|
||||||
if err := os.Setenv("AWS_SESSION_TOKEN", e.Token); err != nil {
|
if err := os.Setenv("AWS_SESSION_TOKEN", e.Token); err != nil {
|
||||||
t.Fatalf("Error resetting env var AWS_SESSION_TOKEN: %s", err)
|
t.Fatalf("Error resetting env var AWS_SESSION_TOKEN: %s", err)
|
||||||
}
|
}
|
||||||
|
if err := os.Setenv("AWS_PROFILE", e.Profile); err != nil {
|
||||||
|
t.Fatalf("Error resetting env var AWS_PROFILE: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv("AWS_SHARED_CREDENTIALS_FILE", e.CredsFilename); err != nil {
|
||||||
|
t.Fatalf("Error resetting env var AWS_SHARED_CREDENTIALS_FILE: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +285,12 @@ func setEnv(s string, t *testing.T) func() {
|
||||||
if err := os.Setenv("AWS_SESSION_TOKEN", s); err != nil {
|
if err := os.Setenv("AWS_SESSION_TOKEN", s); err != nil {
|
||||||
t.Fatalf("Error setting env var AWS_SESSION_TOKEN: %s", err)
|
t.Fatalf("Error setting env var AWS_SESSION_TOKEN: %s", err)
|
||||||
}
|
}
|
||||||
|
if err := os.Setenv("AWS_PROFILE", s); err != nil {
|
||||||
|
t.Fatalf("Error setting env var AWS_PROFILE: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv("AWS_SHARED_CREDENTIALS_FILE", s); err != nil {
|
||||||
|
t.Fatalf("Error setting env var AWS_SHARED_CREDENTIALS_FLE: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
// re-set all the envs we unset above
|
// re-set all the envs we unset above
|
||||||
|
@ -234,6 +303,12 @@ func setEnv(s string, t *testing.T) func() {
|
||||||
if err := os.Setenv("AWS_SESSION_TOKEN", e.Token); err != nil {
|
if err := os.Setenv("AWS_SESSION_TOKEN", e.Token); err != nil {
|
||||||
t.Fatalf("Error resetting env var AWS_SESSION_TOKEN: %s", err)
|
t.Fatalf("Error resetting env var AWS_SESSION_TOKEN: %s", err)
|
||||||
}
|
}
|
||||||
|
if err := os.Setenv("AWS_PROFILE", e.Profile); err != nil {
|
||||||
|
t.Fatalf("Error setting env var AWS_PROFILE: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv("AWS_SHARED_CREDENTIALS_FILE", s); err != nil {
|
||||||
|
t.Fatalf("Error setting env var AWS_SHARED_CREDENTIALS_FLE: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,12 +342,14 @@ func getEnv() *currentEnv {
|
||||||
Key: os.Getenv("AWS_ACCESS_KEY_ID"),
|
Key: os.Getenv("AWS_ACCESS_KEY_ID"),
|
||||||
Secret: os.Getenv("AWS_SECRET_ACCESS_KEY"),
|
Secret: os.Getenv("AWS_SECRET_ACCESS_KEY"),
|
||||||
Token: os.Getenv("AWS_SESSION_TOKEN"),
|
Token: os.Getenv("AWS_SESSION_TOKEN"),
|
||||||
|
Profile: os.Getenv("AWS_TOKEN"),
|
||||||
|
CredsFilename: os.Getenv("AWS_SHARED_CREDENTIALS_FILE"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// struct to preserve the current environment
|
// struct to preserve the current environment
|
||||||
type currentEnv struct {
|
type currentEnv struct {
|
||||||
Key, Secret, Token string
|
Key, Secret, Token, Profile, CredsFilename string
|
||||||
}
|
}
|
||||||
|
|
||||||
type routes struct {
|
type routes struct {
|
||||||
|
|
|
@ -29,6 +29,20 @@ func Provider() terraform.ResourceProvider {
|
||||||
Description: descriptions["secret_key"],
|
Description: descriptions["secret_key"],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"profile": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
Description: descriptions["profile"],
|
||||||
|
},
|
||||||
|
|
||||||
|
"shared_credentials_file": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
Description: descriptions["shared_credentials_file"],
|
||||||
|
},
|
||||||
|
|
||||||
"token": &schema.Schema{
|
"token": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -222,6 +236,12 @@ func init() {
|
||||||
"secret_key": "The secret key for API operations. You can retrieve this\n" +
|
"secret_key": "The secret key for API operations. You can retrieve this\n" +
|
||||||
"from the 'Security & Credentials' section of the AWS console.",
|
"from the 'Security & Credentials' section of the AWS console.",
|
||||||
|
|
||||||
|
"profile": "The profile for API operations. If not set, the default profile\n" +
|
||||||
|
"created with `aws configure` will be used.",
|
||||||
|
|
||||||
|
"shared_credentials_file": "The path to the shared credentials file. If not set\n" +
|
||||||
|
"this defaults to ~/.aws/credentials.",
|
||||||
|
|
||||||
"token": "session token. A session token is only required if you are\n" +
|
"token": "session token. A session token is only required if you are\n" +
|
||||||
"using temporary security credentials.",
|
"using temporary security credentials.",
|
||||||
|
|
||||||
|
@ -241,6 +261,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||||
config := Config{
|
config := Config{
|
||||||
AccessKey: d.Get("access_key").(string),
|
AccessKey: d.Get("access_key").(string),
|
||||||
SecretKey: d.Get("secret_key").(string),
|
SecretKey: d.Get("secret_key").(string),
|
||||||
|
Profile: d.Get("profile").(string),
|
||||||
|
CredsFilename: d.Get("shared_credentials_file").(string),
|
||||||
Token: d.Get("token").(string),
|
Token: d.Get("token").(string),
|
||||||
Region: d.Get("region").(string),
|
Region: d.Get("region").(string),
|
||||||
MaxRetries: d.Get("max_retries").(int),
|
MaxRetries: d.Get("max_retries").(int),
|
||||||
|
|
|
@ -34,14 +34,26 @@ resource "aws_instance" "web" {
|
||||||
|
|
||||||
The following arguments are supported in the `provider` block:
|
The following arguments are supported in the `provider` block:
|
||||||
|
|
||||||
* `access_key` - (Required) This is the AWS access key. It must be provided, but
|
* `access_key` - (Optional) This is the AWS access key. It must be provided, but
|
||||||
it can also be sourced from the `AWS_ACCESS_KEY_ID` environment variable.
|
it can also be sourced from the `AWS_ACCESS_KEY_ID` environment variable, or via
|
||||||
|
a shared credentials file if `profile` is specified.
|
||||||
|
|
||||||
* `secret_key` - (Required) This is the AWS secret key. It must be provided, but
|
* `secret_key` - (Optional) This is the AWS secret key. It must be provided, but
|
||||||
it can also be sourced from the `AWS_SECRET_ACCESS_KEY` environment variable.
|
it can also be sourced from the `AWS_SECRET_ACCESS_KEY` environment variable, or
|
||||||
|
via a shared credentials file if `profile` is specified.
|
||||||
|
|
||||||
* `region` - (Required) This is the AWS region. It must be provided, but
|
* `region` - (Required) This is the AWS region. It must be provided, but
|
||||||
it can also be sourced from the `AWS_DEFAULT_REGION` environment variables.
|
it can also be sourced from the `AWS_DEFAULT_REGION` environment variables, or
|
||||||
|
via a shared credentials file if `profile` is specified.
|
||||||
|
|
||||||
|
* `profile` - (Optional) This is the AWS profile name as set in the shared credentials
|
||||||
|
file.
|
||||||
|
|
||||||
|
* `shared_credentials_file` = (Optional) This is the path to the shared credentials file.
|
||||||
|
If this is not set and a profile is specified, ~/.aws/credentials will be used.
|
||||||
|
|
||||||
|
* `token` - (Optional) Use this to set an MFA token. It can also be sourced
|
||||||
|
from the `AWS_SECURITY_TOKEN` environment variable.
|
||||||
|
|
||||||
* `max_retries` - (Optional) This is the maximum number of times an API call is
|
* `max_retries` - (Optional) This is the maximum number of times an API call is
|
||||||
being retried in case requests are being throttled or experience transient failures.
|
being retried in case requests are being throttled or experience transient failures.
|
||||||
|
@ -55,8 +67,10 @@ The following arguments are supported in the `provider` block:
|
||||||
to prevent you mistakenly using a wrong one (and end up destroying live environment).
|
to prevent you mistakenly using a wrong one (and end up destroying live environment).
|
||||||
Conflicts with `allowed_account_ids`.
|
Conflicts with `allowed_account_ids`.
|
||||||
|
|
||||||
* `dynamodb_endpoint` - (Optional) Use this to override the default endpoint URL constructed from the `region`. It's typically used to connect to dynamodb-local.
|
* `dynamodb_endpoint` - (Optional) Use this to override the default endpoint
|
||||||
|
URL constructed from the `region`. It's typically used to connect to
|
||||||
|
dynamodb-local.
|
||||||
|
|
||||||
* `kinesis_endpoint` - (Optional) Use this to override the default endpoint URL constructed from the `region`. It's typically used to connect to kinesalite.
|
* `kinesis_endpoint` - (Optional) Use this to override the default endpoint URL
|
||||||
|
constructed from the `region`. It's typically used to connect to kinesalite.
|
||||||
|
|
||||||
* `token` - (Optional) Use this to set an MFA token. It can also be sourced from the `AWS_SECURITY_TOKEN` environment variable.
|
|
||||||
|
|
Loading…
Reference in New Issue