diff --git a/builder/alicloud/ecs/access_config.go b/builder/alicloud/ecs/access_config.go index bc1968dc8..e34a60ef8 100644 --- a/builder/alicloud/ecs/access_config.go +++ b/builder/alicloud/ecs/access_config.go @@ -3,32 +3,42 @@ package ecs import ( + "encoding/json" "fmt" + "io/ioutil" "os" + "runtime" "time" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/hashicorp/packer/template/interpolate" "github.com/hashicorp/packer/version" + "github.com/mitchellh/go-homedir" ) // Config of alicloud type AlicloudAccessConfig struct { - // This is the Alicloud access key. It must be provided, but it can also be + // This is the Alicloud access key. It must be provided when profile not exist, but it can also be // sourced from the ALICLOUD_ACCESS_KEY environment variable. - AlicloudAccessKey string `mapstructure:"access_key" required:"true"` - // This is the Alicloud secret key. It must be provided, but it can also be + AlicloudAccessKey string `mapstructure:"access_key" required:"false"` + // This is the Alicloud secret key. It must be provided when profile not exist, but it can also be // sourced from the ALICLOUD_SECRET_KEY environment variable. - AlicloudSecretKey string `mapstructure:"secret_key" required:"true"` - // This is the Alicloud region. It must be provided, but it can also be + AlicloudSecretKey string `mapstructure:"secret_key" required:"false"` + // This is the Alicloud region. It must be provided when profile not exist, but it can also be // sourced from the ALICLOUD_REGION environment variables. - AlicloudRegion string `mapstructure:"region" required:"true"` + AlicloudRegion string `mapstructure:"region" required:"false"` // The region validation can be skipped if this value is true, the default // value is false. AlicloudSkipValidation bool `mapstructure:"skip_region_validation" required:"false"` // The image validation can be skipped if this value is true, the default // value is false. AlicloudSkipImageValidation bool `mapstructure:"skip_image_validation" required:"false"` + // This is th Alicloud profile. If access_key not exist, is must be provided, but it can also be + // sourced from the ALICLOUD_PROFILE environment variables. + AlicloudProfile string `mapstructure:"profile" required:"false"` + // This is the Alicloud shared credentials file path. If this file path exist, os will read access key + // and secret key from this file. + AlicloudSharedCredentialsFile string `mapstructure:"shared_credentials_file" required:"false"` // STS access token, can be set through template or by exporting as // environment variable such as `export SECURITY_TOKEN=value`. SecurityToken string `mapstructure:"security_token" required:"false"` @@ -48,8 +58,22 @@ func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) { c.SecurityToken = os.Getenv("SECURITY_TOKEN") } - client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, - c.AlicloudSecretKey, c.SecurityToken) + var getProviderConfig = func(str string, key string) string { + value, err := getConfigFromProfile(c, key) + if err == nil && value != nil { + str = value.(string) + } + return str + } + + if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" { + c.AlicloudAccessKey = getProviderConfig(c.AlicloudAccessKey, "access_key_id") + c.AlicloudSecretKey = getProviderConfig(c.AlicloudSecretKey, "access_key_secret") + c.AlicloudRegion = getProviderConfig(c.AlicloudRegion, "region_id") + c.SecurityToken = getProviderConfig(c.SecurityToken, "sts_token") + } + + client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, c.AlicloudSecretKey, c.SecurityToken) if err != nil { return nil, err } @@ -89,7 +113,13 @@ func (c *AlicloudAccessConfig) Config() error { if c.AlicloudSecretKey == "" { c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY") } - if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" { + if c.AlicloudProfile == "" { + c.AlicloudProfile = os.Getenv("ALICLOUD_PROFILE") + } + if c.AlicloudSharedCredentialsFile == "" { + c.AlicloudSharedCredentialsFile = os.Getenv("ALICLOUD_SHARED_CREDENTIALS_FILE") + } + if (c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "") && c.AlicloudProfile == "" { return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.") } return nil @@ -131,3 +161,66 @@ func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) { return validRegions, nil } + +func getConfigFromProfile(c *AlicloudAccessConfig, ProfileKey string) (interface{}, error) { + providerConfig := make(map[string]interface{}) + current := c.AlicloudProfile + if current != "" { + profilePath, err := homedir.Expand(c.AlicloudSharedCredentialsFile) + if err != nil { + return nil, err + } + if profilePath == "" { + profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("HOME")) + if runtime.GOOS == "windows" { + profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("USERPROFILE")) + } + } + _, err = os.Stat(profilePath) + if !os.IsNotExist(err) { + data, err := ioutil.ReadFile(profilePath) + if err != nil { + return nil, err + } + config := map[string]interface{}{} + err = json.Unmarshal(data, &config) + if err != nil { + return nil, err + } + for _, v := range config["profiles"].([]interface{}) { + if current == v.(map[string]interface{})["name"] { + providerConfig = v.(map[string]interface{}) + } + } + } + } + mode := "" + if v, ok := providerConfig["mode"]; ok { + mode = v.(string) + } else { + return v, nil + } + switch ProfileKey { + case "access_key_id", "access_key_secret": + if mode == "EcsRamRole" { + return "", nil + } + case "ram_role_name": + if mode != "EcsRamRole" { + return "", nil + } + case "sts_token": + if mode != "StsToken" { + return "", nil + } + case "ram_role_arn", "ram_session_name": + if mode != "RamRoleArn" { + return "", nil + } + case "expired_seconds": + if mode != "RamRoleArn" { + return float64(0), nil + } + } + return providerConfig[ProfileKey], nil +} diff --git a/builder/alicloud/ecs/access_config_test.go b/builder/alicloud/ecs/access_config_test.go index 053d50a16..68c23aa14 100644 --- a/builder/alicloud/ecs/access_config_test.go +++ b/builder/alicloud/ecs/access_config_test.go @@ -32,5 +32,21 @@ func TestAlicloudAccessConfigPrepareRegion(t *testing.T) { t.Fatalf("shouldn't have err: %s", err) } + c.AlicloudAccessKey = "" + if err := c.Prepare(nil); err == nil { + t.Fatalf("should have err") + } + + c.AlicloudProfile = "default" + if err := c.Prepare(nil); err != nil { + t.Fatalf("shouldn't have err: %s", err) + } + + c.AlicloudProfile = "" + os.Setenv("ALICLOUD_PROFILE", "default") + if err := c.Prepare(nil); err != nil { + t.Fatalf("shouldn't have err: %s", err) + } + c.AlicloudSkipValidation = false } diff --git a/builder/alicloud/ecs/builder.hcl2spec.go b/builder/alicloud/ecs/builder.hcl2spec.go index d1f603845..2f5171f57 100644 --- a/builder/alicloud/ecs/builder.hcl2spec.go +++ b/builder/alicloud/ecs/builder.hcl2spec.go @@ -53,11 +53,13 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` - AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` - AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` - AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region"` + AlicloudAccessKey *string `mapstructure:"access_key" required:"false" cty:"access_key"` + AlicloudSecretKey *string `mapstructure:"secret_key" required:"false" cty:"secret_key"` + AlicloudRegion *string `mapstructure:"region" required:"false" cty:"region"` AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` AlicloudSkipImageValidation *bool `mapstructure:"skip_image_validation" required:"false" cty:"skip_image_validation"` + AlicloudProfile *string `mapstructure:"profile" required:"false" cty:"profile"` + AlicloudSharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file"` SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token"` AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"` AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"` @@ -162,6 +164,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, "skip_image_validation": &hcldec.AttrSpec{Name: "skip_image_validation", Type: cty.Bool, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false}, "security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false}, "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, "image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false}, diff --git a/post-processor/alicloud-import/post-processor.hcl2spec.go b/post-processor/alicloud-import/post-processor.hcl2spec.go index 4049af42e..f0290a185 100644 --- a/post-processor/alicloud-import/post-processor.hcl2spec.go +++ b/post-processor/alicloud-import/post-processor.hcl2spec.go @@ -17,11 +17,13 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"` - AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"` - AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"` - AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region"` + AlicloudAccessKey *string `mapstructure:"access_key" required:"false" cty:"access_key"` + AlicloudSecretKey *string `mapstructure:"secret_key" required:"false" cty:"secret_key"` + AlicloudRegion *string `mapstructure:"region" required:"false" cty:"region"` AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"` AlicloudSkipImageValidation *bool `mapstructure:"skip_image_validation" required:"false" cty:"skip_image_validation"` + AlicloudProfile *string `mapstructure:"profile" required:"false" cty:"profile"` + AlicloudSharedCredentialsFile *string `mapstructure:"shared_credentials_file" required:"false" cty:"shared_credentials_file"` SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token"` AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"` AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"` @@ -134,6 +136,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, "skip_image_validation": &hcldec.AttrSpec{Name: "skip_image_validation", Type: cty.Bool, Required: false}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "shared_credentials_file": &hcldec.AttrSpec{Name: "shared_credentials_file", Type: cty.String, Required: false}, "security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false}, "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, "image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false}, diff --git a/website/source/docs/builders/alicloud-ecs.html.md.erb b/website/source/docs/builders/alicloud-ecs.html.md.erb index 5c7b3ce58..7f067f87a 100644 --- a/website/source/docs/builders/alicloud-ecs.html.md.erb +++ b/website/source/docs/builders/alicloud-ecs.html.md.erb @@ -23,7 +23,6 @@ builder. ### Required: -<%= partial "partials/builder/alicloud/ecs/AlicloudAccessConfig-required" %> <%= partial "partials/builder/alicloud/ecs/RunConfig-required" %> <%= partial "partials/builder/alicloud/ecs/AlicloudImageConfig-required" %> diff --git a/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-not-required.html.md b/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-not-required.html.md index 7e3448a7b..6127059b2 100644 --- a/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-not-required.html.md +++ b/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-not-required.html.md @@ -1,11 +1,26 @@ +- `access_key` (string) - This is the Alicloud access key. It must be provided when profile not exist, but it can also be + sourced from the ALICLOUD_ACCESS_KEY environment variable. + +- `secret_key` (string) - This is the Alicloud secret key. It must be provided when profile not exist, but it can also be + sourced from the ALICLOUD_SECRET_KEY environment variable. + +- `region` (string) - This is the Alicloud region. It must be provided when profile not exist, but it can also be + sourced from the ALICLOUD_REGION environment variables. + - `skip_region_validation` (bool) - The region validation can be skipped if this value is true, the default value is false. - `skip_image_validation` (bool) - The image validation can be skipped if this value is true, the default value is false. +- `profile` (string) - This is th Alicloud profile. If access_key not exist, is must be provided, but it can also be + sourced from the ALICLOUD_PROFILE environment variables. + +- `shared_credentials_file` (string) - This is the Alicloud shared credentials file path. If this file path exist, os will read access key + and secret key from this file. + - `security_token` (string) - STS access token, can be set through template or by exporting as environment variable such as `export SECURITY_TOKEN=value`. \ No newline at end of file diff --git a/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-required.html.md b/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-required.html.md deleted file mode 100644 index 711822fee..000000000 --- a/website/source/partials/builder/alicloud/ecs/_AlicloudAccessConfig-required.html.md +++ /dev/null @@ -1,11 +0,0 @@ - - -- `access_key` (string) - This is the Alicloud access key. It must be provided, but it can also be - sourced from the ALICLOUD_ACCESS_KEY environment variable. - -- `secret_key` (string) - This is the Alicloud secret key. It must be provided, but it can also be - sourced from the ALICLOUD_SECRET_KEY environment variable. - -- `region` (string) - This is the Alicloud region. It must be provided, but it can also be - sourced from the ALICLOUD_REGION environment variables. - \ No newline at end of file