Merge pull request #4632 from hashicorp/f-aws-profile

provider/aws: Add profile to provider config
This commit is contained in:
James Nugent 2016-01-14 17:08:04 +00:00
commit 6acc7a14e4
4 changed files with 143 additions and 25 deletions

View File

@ -48,11 +48,13 @@ import (
)
type Config struct {
AccessKey string
SecretKey string
Token string
Region string
MaxRetries int
AccessKey string
SecretKey string
CredsFilename string
Profile string
Token string
Region string
MaxRetries int
AllowedAccountIds []interface{}
ForbiddenAccountIds []interface{}
@ -113,7 +115,7 @@ func (c *Config) Client() (interface{}, error) {
client.region = c.Region
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
// error, and we can present it nicely to the user
_, err = creds.Get()
@ -341,7 +343,7 @@ func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
// This function is responsible for reading credentials from the
// environment in the case that they're not explicitly specified
// 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
providers := []awsCredentials.Provider{
&awsCredentials.StaticProvider{Value: awsCredentials.Value{
@ -350,7 +352,10 @@ func getCreds(key, secret, token string) *awsCredentials.Credentials {
SessionToken: token,
}},
&awsCredentials.EnvProvider{},
&awsCredentials.SharedCredentialsProvider{},
&awsCredentials.SharedCredentialsProvider{
Filename: credsfile,
Profile: profile,
},
}
// We only look in the EC2 metadata API if we can connect

View File

@ -3,6 +3,7 @@ package aws
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@ -16,7 +17,7 @@ func TestAWSConfig_shouldError(t *testing.T) {
defer resetEnv()
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()
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() != "NoCredentialProviders" {
@ -49,7 +50,7 @@ func TestAWSConfig_shouldBeStatic(t *testing.T) {
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 {
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
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 {
t.Fatalf("Expected a static creds provider to be returned")
}
@ -133,7 +134,7 @@ func TestAWSConfig_shouldIgnoreIAM(t *testing.T) {
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 {
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) {
// need to set the environment variables to a dummy string, as we don't know
// what they may be at runtime without hardcoding here
s := "some_env"
resetEnv := setEnv(s, t)
defer resetEnv()
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 {
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 {
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() {
// 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 {
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 {
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() {
// 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 {
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)
}
}
}
@ -264,15 +339,17 @@ func getEnv() *currentEnv {
// Grab any existing AWS keys and preserve. In some tests we'll unset these, so
// we need to have them and restore them after
return &currentEnv{
Key: os.Getenv("AWS_ACCESS_KEY_ID"),
Secret: os.Getenv("AWS_SECRET_ACCESS_KEY"),
Token: os.Getenv("AWS_SESSION_TOKEN"),
Key: os.Getenv("AWS_ACCESS_KEY_ID"),
Secret: os.Getenv("AWS_SECRET_ACCESS_KEY"),
Token: os.Getenv("AWS_SESSION_TOKEN"),
Profile: os.Getenv("AWS_TOKEN"),
CredsFilename: os.Getenv("AWS_SHARED_CREDENTIALS_FILE"),
}
}
// struct to preserve the current environment
type currentEnv struct {
Key, Secret, Token string
Key, Secret, Token, Profile, CredsFilename string
}
type routes struct {

View File

@ -29,6 +29,20 @@ func Provider() terraform.ResourceProvider {
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{
Type: schema.TypeString,
Optional: true,
@ -222,6 +236,12 @@ func init() {
"secret_key": "The secret key for API operations. You can retrieve this\n" +
"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" +
"using temporary security credentials.",
@ -241,6 +261,8 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
AccessKey: d.Get("access_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),
Region: d.Get("region").(string),
MaxRetries: d.Get("max_retries").(int),

View File

@ -34,14 +34,26 @@ resource "aws_instance" "web" {
The following arguments are supported in the `provider` block:
* `access_key` - (Required) This is the AWS access key. It must be provided, but
it can also be sourced from the `AWS_ACCESS_KEY_ID` environment variable.
* `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, or via
a shared credentials file if `profile` is specified.
* `secret_key` - (Required) This is the AWS secret key. It must be provided, but
it can also be sourced from the `AWS_SECRET_ACCESS_KEY` environment variable.
* `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, or
via a shared credentials file if `profile` is specified.
* `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
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).
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.