diff --git a/builder/yandex/access_config.go b/builder/yandex/access_config.go new file mode 100644 index 000000000..24dc53ee8 --- /dev/null +++ b/builder/yandex/access_config.go @@ -0,0 +1,67 @@ +//go:generate struct-markdown + +package yandex + +import ( + "errors" + "fmt" + "os" + + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template/interpolate" + "github.com/yandex-cloud/go-sdk/iamkey" +) + +const defaultEndpoint = "api.cloud.yandex.net:443" + +// AccessConfig is for common configuration related to Yandex.Cloud API access +type AccessConfig struct { + // Non standard API endpoint. Default is `api.cloud.yandex.net:443`. + Endpoint string `mapstructure:"endpoint" required:"false"` + // Path to file with Service Account key in json format. This + // is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable + // `YC_SERVICE_ACCOUNT_KEY_FILE`. + ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"` + // OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set + // value by environment variable `YC_TOKEN`. + Token string `mapstructure:"token" required:"true"` + // The maximum number of times an API request is being executed. + MaxRetries int `mapstructure:"max_retries"` +} + +func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { + var errs []error + + if c.Endpoint == "" { + c.Endpoint = defaultEndpoint + } + + // provision config by OS environment variables + if c.Token == "" { + c.Token = os.Getenv("YC_TOKEN") + } + + if c.ServiceAccountKeyFile == "" { + c.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE") + } + + if c.Token != "" && c.ServiceAccountKeyFile != "" { + errs = append(errs, errors.New("one of token or service account key file must be specified, not both")) + } + + if c.Token != "" { + packer.LogSecretFilter.Set(c.Token) + } + + if c.ServiceAccountKeyFile != "" { + if _, err := iamkey.ReadFromJSONFile(c.ServiceAccountKeyFile); err != nil { + errs = append(errs, fmt.Errorf("fail to read service account key file: %s", err)) + } + } + + if len(errs) > 0 { + return errs + } + + return nil +} diff --git a/builder/yandex/builder.go b/builder/yandex/builder.go index fab9d0861..481f7f57f 100644 --- a/builder/yandex/builder.go +++ b/builder/yandex/builder.go @@ -50,7 +50,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { // Run executes a yandex Packer build and returns a packer.Artifact // representing a Yandex.Cloud compute image. func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { - driver, err := NewDriverYC(ui, &b.config) + driver, err := NewDriverYC(ui, &b.config.AccessConfig) ctx = requestid.ContextWithClientTraceID(ctx, uuid.New().String()) if err != nil { diff --git a/builder/yandex/config.go b/builder/yandex/config.go index 813d46435..12b7245b3 100644 --- a/builder/yandex/config.go +++ b/builder/yandex/config.go @@ -16,11 +16,8 @@ import ( "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" - - "github.com/yandex-cloud/go-sdk/iamkey" ) -const defaultEndpoint = "api.cloud.yandex.net:443" const defaultGpuPlatformID = "gpu-standard-v1" const defaultPlatformID = "standard-v1" const defaultMaxRetries = 3 @@ -31,23 +28,15 @@ var reImageFamily = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`) type Config struct { common.PackerConfig `mapstructure:",squash"` Communicator communicator.Config `mapstructure:",squash"` + AccessConfig `mapstructure:",squash"` - // Non standard api endpoint URL. - Endpoint string `mapstructure:"endpoint" required:"false"` // The folder ID that will be used to launch instances and store images. - // Alternatively you may set value by environment variable YC_FOLDER_ID. + // Alternatively you may set value by environment variable `YC_FOLDER_ID`. // To use a different folder for looking up the source image or saving the target image to // check options 'source_image_folder_id' and 'target_image_folder_id'. FolderID string `mapstructure:"folder_id" required:"true"` - // Path to file with Service Account key in json format. This - // is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable - // YC_SERVICE_ACCOUNT_KEY_FILE. - ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"` - // Service account identifier to assign to instance + // Service account identifier to assign to instance. ServiceAccountID string `mapstructure:"service_account_id" required:"false"` - // OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set - // value by environment variable YC_TOKEN. - Token string `mapstructure:"token" required:"true"` // The name of the disk, if unset the instance name // will be used. DiskName string `mapstructure:"disk_name" required:"false"` @@ -59,8 +48,7 @@ type Config struct { ImageDescription string `mapstructure:"image_description" required:"false"` // The family name of the resulting image. ImageFamily string `mapstructure:"image_family" required:"false"` - // Key/value pair labels to - // apply to the created image. + // Key/value pair labels to apply to the created image. ImageLabels map[string]string `mapstructure:"image_labels" required:"false"` // Minimum size of the disk that will be created from built image, specified in gigabytes. // Should be more or equal to `disk_size_gb`. @@ -78,16 +66,14 @@ type Config struct { InstanceMemory int `mapstructure:"instance_mem_gb" required:"false"` // The name assigned to the instance. InstanceName string `mapstructure:"instance_name" required:"false"` - // Key/value pair labels to apply to - // the launched instance. + // Key/value pair labels to apply to the launched instance. Labels map[string]string `mapstructure:"labels" required:"false"` // Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`. PlatformID string `mapstructure:"platform_id" required:"false"` - // The maximum number of times an API request is being executed - MaxRetries int `mapstructure:"max_retries"` // Metadata applied to the launched instance. Metadata map[string]string `mapstructure:"metadata" required:"false"` - // Metadata applied to the launched instance. Value are file paths. + // Metadata applied to the launched instance. + // The values in this map are the paths to the content files for the corresponding metadata keys. MetadataFromFile map[string]string `mapstructure:"metadata_from_file"` // Launch a preemptible instance. This defaults to `false`. Preemptible bool `mapstructure:"preemptible"` @@ -95,12 +81,11 @@ type Config struct { SerialLogFile string `mapstructure:"serial_log_file" required:"false"` // The source image family to create the new image // from. You can also specify source_image_id instead. Just one of a source_image_id or - // source_image_family must be specified. Example: `ubuntu-1804-lts` + // source_image_family must be specified. Example: `ubuntu-1804-lts`. SourceImageFamily string `mapstructure:"source_image_family" required:"true"` // The ID of the folder containing the source image. SourceImageFolderID string `mapstructure:"source_image_folder_id" required:"false"` - // The source image ID to use to create the new image - // from. + // The source image ID to use to create the new image from. SourceImageID string `mapstructure:"source_image_id" required:"false"` // The source image name to use to create the new image // from. Name will be looked up in `source_image_folder_id`. @@ -142,8 +127,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { return nil, err } + // Accumulate any errors var errs *packer.MultiError + errs = packer.MultiErrorAppend(errs, c.AccessConfig.Prepare(&c.ctx)...) + if c.SerialLogFile != "" { if _, err := os.Stat(c.SerialLogFile); os.IsExist(err) { errs = packer.MultiErrorAppend(errs, @@ -236,10 +224,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { } } - if c.Endpoint == "" { - c.Endpoint = defaultEndpoint - } - if c.Zone == "" { c.Zone = defaultZone } @@ -248,35 +232,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { c.MaxRetries = defaultMaxRetries } - // provision config by OS environment variables - if c.Token == "" { - c.Token = os.Getenv("YC_TOKEN") - } - - if c.ServiceAccountKeyFile == "" { - c.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE") - } - if c.FolderID == "" { c.FolderID = os.Getenv("YC_FOLDER_ID") } - if c.Token != "" && c.ServiceAccountKeyFile != "" { - errs = packer.MultiErrorAppend( - errs, errors.New("one of token or service account key file must be specified, not both")) - } - - if c.Token != "" { - packer.LogSecretFilter.Set(c.Token) - } - - if c.ServiceAccountKeyFile != "" { - if _, err := iamkey.ReadFromJSONFile(c.ServiceAccountKeyFile); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("fail to read service account key file: %s", err)) - } - } - if c.FolderID == "" { errs = packer.MultiErrorAppend( errs, errors.New("a folder_id must be specified")) diff --git a/builder/yandex/config.hcl2spec.go b/builder/yandex/config.hcl2spec.go index d00b5cb72..b4e1df5fa 100644 --- a/builder/yandex/config.hcl2spec.go +++ b/builder/yandex/config.hcl2spec.go @@ -64,10 +64,11 @@ type FlatConfig struct { WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"` WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"` Endpoint *string `mapstructure:"endpoint" required:"false" cty:"endpoint" hcl:"endpoint"` - FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"` ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"` - ServiceAccountID *string `mapstructure:"service_account_id" required:"false" cty:"service_account_id" hcl:"service_account_id"` Token *string `mapstructure:"token" required:"true" cty:"token" hcl:"token"` + MaxRetries *int `mapstructure:"max_retries" cty:"max_retries" hcl:"max_retries"` + FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"` + ServiceAccountID *string `mapstructure:"service_account_id" required:"false" cty:"service_account_id" hcl:"service_account_id"` DiskName *string `mapstructure:"disk_name" required:"false" cty:"disk_name" hcl:"disk_name"` DiskSizeGb *int `mapstructure:"disk_size_gb" required:"false" cty:"disk_size_gb" hcl:"disk_size_gb"` DiskType *string `mapstructure:"disk_type" required:"false" cty:"disk_type" hcl:"disk_type"` @@ -83,7 +84,6 @@ type FlatConfig struct { InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"` Labels map[string]string `mapstructure:"labels" required:"false" cty:"labels" hcl:"labels"` PlatformID *string `mapstructure:"platform_id" required:"false" cty:"platform_id" hcl:"platform_id"` - MaxRetries *int `mapstructure:"max_retries" cty:"max_retries" hcl:"max_retries"` Metadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata" hcl:"metadata"` MetadataFromFile map[string]string `mapstructure:"metadata_from_file" cty:"metadata_from_file" hcl:"metadata_from_file"` Preemptible *bool `mapstructure:"preemptible" cty:"preemptible" hcl:"preemptible"` @@ -168,10 +168,11 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, "endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, Required: false}, - "folder_id": &hcldec.AttrSpec{Name: "folder_id", Type: cty.String, Required: false}, "service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false}, - "service_account_id": &hcldec.AttrSpec{Name: "service_account_id", Type: cty.String, Required: false}, "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, + "max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false}, + "folder_id": &hcldec.AttrSpec{Name: "folder_id", Type: cty.String, Required: false}, + "service_account_id": &hcldec.AttrSpec{Name: "service_account_id", Type: cty.String, Required: false}, "disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false}, "disk_size_gb": &hcldec.AttrSpec{Name: "disk_size_gb", Type: cty.Number, Required: false}, "disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false}, @@ -187,7 +188,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false}, "labels": &hcldec.AttrSpec{Name: "labels", Type: cty.Map(cty.String), Required: false}, "platform_id": &hcldec.AttrSpec{Name: "platform_id", Type: cty.String, Required: false}, - "max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false}, "metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(cty.String), Required: false}, "metadata_from_file": &hcldec.AttrSpec{Name: "metadata_from_file", Type: cty.Map(cty.String), Required: false}, "preemptible": &hcldec.AttrSpec{Name: "preemptible", Type: cty.Bool, Required: false}, diff --git a/builder/yandex/driver_yc.go b/builder/yandex/driver_yc.go index 40d6923ae..55065ae77 100644 --- a/builder/yandex/driver_yc.go +++ b/builder/yandex/driver_yc.go @@ -33,27 +33,27 @@ type driverYC struct { ui packer.Ui } -func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) { +func NewDriverYC(ui packer.Ui, ac *AccessConfig) (Driver, error) { log.Printf("[INFO] Initialize Yandex.Cloud client...") sdkConfig := ycsdk.Config{} - if config.Endpoint != "" { - sdkConfig.Endpoint = config.Endpoint + if ac.Endpoint != "" { + sdkConfig.Endpoint = ac.Endpoint } switch { - case config.Token == "" && config.ServiceAccountKeyFile == "": + case ac.Token == "" && ac.ServiceAccountKeyFile == "": log.Printf("[INFO] Use Instance Service Account for authentication") sdkConfig.Credentials = ycsdk.InstanceServiceAccount() - case config.Token != "": + case ac.Token != "": log.Printf("[INFO] Use OAuth token for authentication") - sdkConfig.Credentials = ycsdk.OAuthToken(config.Token) + sdkConfig.Credentials = ycsdk.OAuthToken(ac.Token) - case config.ServiceAccountKeyFile != "": - log.Printf("[INFO] Use Service Account key file %q for authentication", config.ServiceAccountKeyFile) - key, err := iamkey.ReadFromJSONFile(config.ServiceAccountKeyFile) + case ac.ServiceAccountKeyFile != "": + log.Printf("[INFO] Use Service Account key file %q for authentication", ac.ServiceAccountKeyFile) + key, err := iamkey.ReadFromJSONFile(ac.ServiceAccountKeyFile) if err != nil { return nil, err } @@ -69,7 +69,7 @@ func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) { requestIDInterceptor := requestid.Interceptor() retryInterceptor := retry.Interceptor( - retry.WithMax(config.MaxRetries), + retry.WithMax(ac.MaxRetries), retry.WithCodes(codes.Unavailable), retry.WithAttemptHeader(true), retry.WithBackoff(retry.BackoffExponentialWithJitter(defaultExponentialBackoffBase, defaultExponentialBackoffCap))) diff --git a/post-processor/yandex-export/post-processor.go b/post-processor/yandex-export/post-processor.go index 0fd40ca72..92fa22b12 100644 --- a/post-processor/yandex-export/post-processor.go +++ b/post-processor/yandex-export/post-processor.go @@ -11,8 +11,6 @@ import ( "strings" "time" - "github.com/yandex-cloud/go-sdk/iamkey" - "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder" "github.com/hashicorp/packer/builder/yandex" @@ -28,6 +26,7 @@ const defaultStorageEndpoint = "storage.yandexcloud.net" type Config struct { common.PackerConfig `mapstructure:",squash"` + yandex.AccessConfig `mapstructure:",squash"` // List of paths to Yandex Object Storage where exported image will be uploaded. // Please be aware that use of space char inside path not supported. @@ -36,7 +35,7 @@ type Config struct { // Paths to Yandex Object Storage where exported image will be uploaded. Paths []string `mapstructure:"paths" required:"true"` // The folder ID that will be used to launch a temporary instance. - // Alternatively you may set value by environment variable YC_FOLDER_ID. + // Alternatively you may set value by environment variable `YC_FOLDER_ID`. FolderID string `mapstructure:"folder_id" required:"true"` // Service Account ID with proper permission to modify an instance, create and attach disk and // make upload to specific Yandex Object Storage paths. @@ -53,13 +52,6 @@ type Config struct { SubnetID string `mapstructure:"subnet_id" required:"false"` // The name of the zone to launch the instance. This defaults to `ru-central1-a`. Zone string `mapstructure:"zone" required:"false"` - // OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set - // value by environment variable YC_TOKEN. - Token string `mapstructure:"token" required:"false"` - // Path to file with Service Account key in json format. This - // is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable - // YC_SERVICE_ACCOUNT_KEY_FILE. - ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"` ctx interpolate.Context } @@ -85,7 +77,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { return err } - errs := new(packer.MultiError) + // Accumulate any errors + var errs *packer.MultiError + + errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(&p.config.ctx)...) if len(p.config.Paths) == 0 { errs = packer.MultiErrorAppend( @@ -100,31 +95,6 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { } } - // provision config by OS environment variables - if p.config.Token == "" { - p.config.Token = os.Getenv("YC_TOKEN") - } - - if p.config.ServiceAccountKeyFile == "" { - p.config.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE") - } - - if p.config.Token != "" && p.config.ServiceAccountKeyFile != "" { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("one of token or service account key file must be specified, not both")) - } - - if p.config.Token != "" { - packer.LogSecretFilter.Set(p.config.Token) - } - - if p.config.ServiceAccountKeyFile != "" { - if _, err := iamkey.ReadFromJSONFile(p.config.ServiceAccountKeyFile); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("fail to read service account key file: %s", err)) - } - } - if p.config.FolderID == "" { p.config.FolderID = os.Getenv("YC_FOLDER_ID") } @@ -203,8 +173,6 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact } yandexConfig := ycSaneDefaults() - yandexConfig.Token = p.config.Token - yandexConfig.ServiceAccountKeyFile = p.config.ServiceAccountKeyFile yandexConfig.DiskName = exporterName yandexConfig.InstanceName = exporterName yandexConfig.DiskSizeGb = p.config.DiskSizeGb @@ -221,7 +189,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact yandexConfig.PlatformID = p.config.PlatformID } - driver, err := yandex.NewDriverYC(ui, &yandexConfig) + driver, err := yandex.NewDriverYC(ui, &p.config.AccessConfig) if err != nil { return nil, false, false, err } diff --git a/post-processor/yandex-export/post-processor.hcl2spec.go b/post-processor/yandex-export/post-processor.hcl2spec.go index a049ce8aa..084c8dd71 100644 --- a/post-processor/yandex-export/post-processor.hcl2spec.go +++ b/post-processor/yandex-export/post-processor.hcl2spec.go @@ -16,6 +16,10 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + Endpoint *string `mapstructure:"endpoint" required:"false" cty:"endpoint" hcl:"endpoint"` + ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"` + Token *string `mapstructure:"token" required:"true" cty:"token" hcl:"token"` + MaxRetries *int `mapstructure:"max_retries" cty:"max_retries" hcl:"max_retries"` Paths []string `mapstructure:"paths" required:"true" cty:"paths" hcl:"paths"` FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"` ServiceAccountID *string `mapstructure:"service_account_id" required:"true" cty:"service_account_id" hcl:"service_account_id"` @@ -24,8 +28,6 @@ type FlatConfig struct { PlatformID *string `mapstructure:"platform_id" required:"false" cty:"platform_id" hcl:"platform_id"` SubnetID *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` Zone *string `mapstructure:"zone" required:"false" cty:"zone" hcl:"zone"` - Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"` - ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"` } // FlatMapstructure returns a new FlatConfig. @@ -47,6 +49,10 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, Required: false}, + "service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false}, + "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, + "max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false}, "paths": &hcldec.AttrSpec{Name: "paths", Type: cty.List(cty.String), Required: false}, "folder_id": &hcldec.AttrSpec{Name: "folder_id", Type: cty.String, Required: false}, "service_account_id": &hcldec.AttrSpec{Name: "service_account_id", Type: cty.String, Required: false}, @@ -55,8 +61,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "platform_id": &hcldec.AttrSpec{Name: "platform_id", Type: cty.String, Required: false}, "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, "zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false}, - "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, - "service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false}, } return s } diff --git a/post-processor/yandex-export/post-processor_test.go b/post-processor/yandex-export/post-processor_test.go index e35381b7a..83f2cb8b8 100644 --- a/post-processor/yandex-export/post-processor_test.go +++ b/post-processor/yandex-export/post-processor_test.go @@ -3,6 +3,7 @@ package yandexexport import ( "testing" + "github.com/hashicorp/packer/builder/yandex" "github.com/stretchr/testify/require" "github.com/hashicorp/packer/helper/multistep" @@ -26,8 +27,10 @@ func TestPostProcessor_Configure(t *testing.T) { name: "no one creds", fields: fields{ config: Config{ - Token: "", - ServiceAccountKeyFile: "", + AccessConfig: yandex.AccessConfig{ + Token: "", + ServiceAccountKeyFile: "", + }, }, }, wantErr: false, @@ -36,8 +39,10 @@ func TestPostProcessor_Configure(t *testing.T) { name: "both token and sa key file", fields: fields{ config: Config{ - Token: "some-value", - ServiceAccountKeyFile: "path/not-exist.file", + AccessConfig: yandex.AccessConfig{ + Token: "some-value", + ServiceAccountKeyFile: "path/not-exist.file", + }, }, }, wantErr: true, @@ -46,8 +51,10 @@ func TestPostProcessor_Configure(t *testing.T) { name: "use sa key file", fields: fields{ config: Config{ - Token: "", - ServiceAccountKeyFile: "testdata/fake-sa-key.json", + AccessConfig: yandex.AccessConfig{ + Token: "", + ServiceAccountKeyFile: "testdata/fake-sa-key.json", + }, }, }, wantErr: false, diff --git a/post-processor/yandex-import/post-processor.go b/post-processor/yandex-import/post-processor.go index eaeab5ae8..dfbee192a 100644 --- a/post-processor/yandex-import/post-processor.go +++ b/post-processor/yandex-import/post-processor.go @@ -19,24 +19,18 @@ import ( yandexexport "github.com/hashicorp/packer/post-processor/yandex-export" "github.com/hashicorp/packer/template/interpolate" "github.com/yandex-cloud/go-genproto/yandex/cloud/iam/v1/awscompatibility" - "github.com/yandex-cloud/go-sdk/iamkey" ) type Config struct { common.PackerConfig `mapstructure:",squash"` + yandex.AccessConfig `mapstructure:",squash"` // The folder ID that will be used to store imported Image. FolderID string `mapstructure:"folder_id" required:"true"` // Service Account ID with proper permission to use Storage service - // for operations 'upload' and 'delete' object to `bucket` + // for operations 'upload' and 'delete' object to `bucket`. ServiceAccountID string `mapstructure:"service_account_id" required:"true"` - // OAuth token to use to authenticate to Yandex.Cloud. - Token string `mapstructure:"token" required:"false"` - // Path to file with Service Account key in json format. This - // is an alternative method to authenticate to Yandex.Cloud. - ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"` - // The name of the bucket where the qcow2 file will be uploaded to for import. // This bucket must exist when the post-processor is run. // @@ -49,7 +43,7 @@ type Config struct { ObjectName string `mapstructure:"object_name" required:"false"` // Whether skip removing the qcow2 file uploaded to Storage // after the import process has completed. Possible values are: `true` to - // leave it in the bucket, `false` to remove it. (Default: `false`). + // leave it in the bucket, `false` to remove it. Default is `false`. SkipClean bool `mapstructure:"skip_clean" required:"false"` // The name of the image, which contains 1-63 characters and only @@ -85,27 +79,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { return err } - errs := new(packer.MultiError) + // Accumulate any errors + var errs *packer.MultiError - // provision config by OS environment variables - if p.config.Token == "" { - p.config.Token = os.Getenv("YC_TOKEN") - } - - if p.config.ServiceAccountKeyFile == "" { - p.config.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE") - } - - if p.config.Token != "" { - packer.LogSecretFilter.Set(p.config.Token) - } - - if p.config.ServiceAccountKeyFile != "" { - if _, err := iamkey.ReadFromJSONFile(p.config.ServiceAccountKeyFile); err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("fail to read service account key file: %s", err)) - } - } + errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(&p.config.ctx)...) if p.config.FolderID == "" { p.config.FolderID = os.Getenv("YC_FOLDER_ID") @@ -160,12 +137,8 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact return nil, false, false, fmt.Errorf("error rendering object_name template: %s", err) } - cfg := &yandex.Config{ - Token: p.config.Token, - ServiceAccountKeyFile: p.config.ServiceAccountKeyFile, - } + client, err := yandex.NewDriverYC(ui, &p.config.AccessConfig) - client, err := yandex.NewDriverYC(ui, cfg) if err != nil { return nil, false, false, err } diff --git a/post-processor/yandex-import/post-processor.hcl2spec.go b/post-processor/yandex-import/post-processor.hcl2spec.go index eff818fa7..d05da3da0 100644 --- a/post-processor/yandex-import/post-processor.hcl2spec.go +++ b/post-processor/yandex-import/post-processor.hcl2spec.go @@ -16,10 +16,12 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + Endpoint *string `mapstructure:"endpoint" required:"false" cty:"endpoint" hcl:"endpoint"` + ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"` + Token *string `mapstructure:"token" required:"true" cty:"token" hcl:"token"` + MaxRetries *int `mapstructure:"max_retries" cty:"max_retries" hcl:"max_retries"` FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"` ServiceAccountID *string `mapstructure:"service_account_id" required:"true" cty:"service_account_id" hcl:"service_account_id"` - Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"` - ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"` Bucket *string `mapstructure:"bucket" required:"false" cty:"bucket" hcl:"bucket"` ObjectName *string `mapstructure:"object_name" required:"false" cty:"object_name" hcl:"object_name"` SkipClean *bool `mapstructure:"skip_clean" required:"false" cty:"skip_clean" hcl:"skip_clean"` @@ -48,10 +50,12 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "endpoint": &hcldec.AttrSpec{Name: "endpoint", Type: cty.String, Required: false}, + "service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false}, + "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, + "max_retries": &hcldec.AttrSpec{Name: "max_retries", Type: cty.Number, Required: false}, "folder_id": &hcldec.AttrSpec{Name: "folder_id", Type: cty.String, Required: false}, "service_account_id": &hcldec.AttrSpec{Name: "service_account_id", Type: cty.String, Required: false}, - "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, - "service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false}, "bucket": &hcldec.AttrSpec{Name: "bucket", Type: cty.String, Required: false}, "object_name": &hcldec.AttrSpec{Name: "object_name", Type: cty.String, Required: false}, "skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false}, diff --git a/website/pages/docs/builders/yandex.mdx b/website/pages/docs/builders/yandex.mdx index 8a6dd3052..331342c7a 100644 --- a/website/pages/docs/builders/yandex.mdx +++ b/website/pages/docs/builders/yandex.mdx @@ -67,12 +67,15 @@ can be configured for this builder. ### Required: +@include 'builder/yandex/AccessConfig-required.mdx' + @include 'builder/yandex/Config-required.mdx' ### Optional: -@include 'builder/yandex/Config-not-required.mdx' +@include 'builder/yandex/AccessConfig-not-required.mdx' +@include 'builder/yandex/Config-not-required.mdx' ## Build template data diff --git a/website/pages/docs/post-processors/yandex-export.mdx b/website/pages/docs/post-processors/yandex-export.mdx index 169f5e20a..668535275 100644 --- a/website/pages/docs/post-processors/yandex-export.mdx +++ b/website/pages/docs/post-processors/yandex-export.mdx @@ -33,10 +33,14 @@ image. ### Required: +@include 'builder/yandex/AccessConfig-required.mdx' + @include 'post-processor/yandex-export/Config-required.mdx' ### Optional: +@include 'builder/yandex/AccessConfig-not-required.mdx' + @include 'post-processor/yandex-export/Config-not-required.mdx' ## Basic Example diff --git a/website/pages/docs/post-processors/yandex-import.mdx b/website/pages/docs/post-processors/yandex-import.mdx index 75f4b957e..29b4e47bc 100644 --- a/website/pages/docs/post-processors/yandex-import.mdx +++ b/website/pages/docs/post-processors/yandex-import.mdx @@ -25,10 +25,14 @@ file. ### Required: +@include 'builder/yandex/AccessConfig-required.mdx' + @include 'post-processor/yandex-import/Config-required.mdx' ### Optional: +@include 'builder/yandex/AccessConfig-not-required.mdx' + @include 'post-processor/yandex-import/Config-not-required.mdx' ## Basic Example diff --git a/website/pages/partials/builder/yandex/AccessConfig-not-required.mdx b/website/pages/partials/builder/yandex/AccessConfig-not-required.mdx new file mode 100644 index 000000000..0ad451534 --- /dev/null +++ b/website/pages/partials/builder/yandex/AccessConfig-not-required.mdx @@ -0,0 +1,9 @@ + + +- `endpoint` (string) - Non standard API endpoint. Default is `api.cloud.yandex.net:443`. + +- `service_account_key_file` (string) - Path to file with Service Account key in json format. This + is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable + `YC_SERVICE_ACCOUNT_KEY_FILE`. + +- `max_retries` (int) - The maximum number of times an API request is being executed. diff --git a/website/pages/partials/builder/yandex/AccessConfig-required.mdx b/website/pages/partials/builder/yandex/AccessConfig-required.mdx new file mode 100644 index 000000000..0dd597dd6 --- /dev/null +++ b/website/pages/partials/builder/yandex/AccessConfig-required.mdx @@ -0,0 +1,4 @@ + + +- `token` (string) - OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set + value by environment variable `YC_TOKEN`. diff --git a/website/pages/partials/builder/yandex/AccessConfig.mdx b/website/pages/partials/builder/yandex/AccessConfig.mdx new file mode 100644 index 000000000..fa961dfd0 --- /dev/null +++ b/website/pages/partials/builder/yandex/AccessConfig.mdx @@ -0,0 +1,3 @@ + + +AccessConfig is for common configuration related to Yandex.Cloud API access diff --git a/website/pages/partials/builder/yandex/Config-not-required.mdx b/website/pages/partials/builder/yandex/Config-not-required.mdx index c8f537f15..2d933cb3e 100644 --- a/website/pages/partials/builder/yandex/Config-not-required.mdx +++ b/website/pages/partials/builder/yandex/Config-not-required.mdx @@ -1,12 +1,6 @@ -- `endpoint` (string) - Non standard api endpoint URL. - -- `service_account_key_file` (string) - Path to file with Service Account key in json format. This - is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable - YC_SERVICE_ACCOUNT_KEY_FILE. - -- `service_account_id` (string) - Service account identifier to assign to instance +- `service_account_id` (string) - Service account identifier to assign to instance. - `disk_name` (string) - The name of the disk, if unset the instance name will be used. @@ -19,8 +13,7 @@ - `image_family` (string) - The family name of the resulting image. -- `image_labels` (map[string]string) - Key/value pair labels to - apply to the created image. +- `image_labels` (map[string]string) - Key/value pair labels to apply to the created image. - `image_min_disk_size_gb` (int) - Minimum size of the disk that will be created from built image, specified in gigabytes. Should be more or equal to `disk_size_gb`. @@ -38,16 +31,14 @@ - `instance_name` (string) - The name assigned to the instance. -- `labels` (map[string]string) - Key/value pair labels to apply to - the launched instance. +- `labels` (map[string]string) - Key/value pair labels to apply to the launched instance. - `platform_id` (string) - Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`. -- `max_retries` (int) - The maximum number of times an API request is being executed - - `metadata` (map[string]string) - Metadata applied to the launched instance. -- `metadata_from_file` (map[string]string) - Metadata applied to the launched instance. Value are file paths. +- `metadata_from_file` (map[string]string) - Metadata applied to the launched instance. + The values in this map are the paths to the content files for the corresponding metadata keys. - `preemptible` (bool) - Launch a preemptible instance. This defaults to `false`. @@ -55,8 +46,7 @@ - `source_image_folder_id` (string) - The ID of the folder containing the source image. -- `source_image_id` (string) - The source image ID to use to create the new image - from. +- `source_image_id` (string) - The source image ID to use to create the new image from. - `source_image_name` (string) - The source image name to use to create the new image from. Name will be looked up in `source_image_folder_id`. diff --git a/website/pages/partials/builder/yandex/Config-required.mdx b/website/pages/partials/builder/yandex/Config-required.mdx index 831699f85..995400618 100644 --- a/website/pages/partials/builder/yandex/Config-required.mdx +++ b/website/pages/partials/builder/yandex/Config-required.mdx @@ -1,13 +1,10 @@ - `folder_id` (string) - The folder ID that will be used to launch instances and store images. - Alternatively you may set value by environment variable YC_FOLDER_ID. + Alternatively you may set value by environment variable `YC_FOLDER_ID`. To use a different folder for looking up the source image or saving the target image to check options 'source_image_folder_id' and 'target_image_folder_id'. -- `token` (string) - OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set - value by environment variable YC_TOKEN. - - `source_image_family` (string) - The source image family to create the new image from. You can also specify source_image_id instead. Just one of a source_image_id or - source_image_family must be specified. Example: `ubuntu-1804-lts` + source_image_family must be specified. Example: `ubuntu-1804-lts`. diff --git a/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx b/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx index 9b340a951..531bc012b 100644 --- a/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx +++ b/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx @@ -11,10 +11,3 @@ zone in which the VM is launched. - `zone` (string) - The name of the zone to launch the instance. This defaults to `ru-central1-a`. - -- `token` (string) - OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set - value by environment variable YC_TOKEN. - -- `service_account_key_file` (string) - Path to file with Service Account key in json format. This - is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable - YC_SERVICE_ACCOUNT_KEY_FILE. diff --git a/website/pages/partials/post-processor/yandex-export/Config-required.mdx b/website/pages/partials/post-processor/yandex-export/Config-required.mdx index 7202eaa72..170ec0adb 100644 --- a/website/pages/partials/post-processor/yandex-export/Config-required.mdx +++ b/website/pages/partials/post-processor/yandex-export/Config-required.mdx @@ -7,7 +7,7 @@ Paths to Yandex Object Storage where exported image will be uploaded. - `folder_id` (string) - The folder ID that will be used to launch a temporary instance. - Alternatively you may set value by environment variable YC_FOLDER_ID. + Alternatively you may set value by environment variable `YC_FOLDER_ID`. - `service_account_id` (string) - Service Account ID with proper permission to modify an instance, create and attach disk and make upload to specific Yandex Object Storage paths. diff --git a/website/pages/partials/post-processor/yandex-import/Config-not-required.mdx b/website/pages/partials/post-processor/yandex-import/Config-not-required.mdx index 6f5923b77..08c735a5c 100644 --- a/website/pages/partials/post-processor/yandex-import/Config-not-required.mdx +++ b/website/pages/partials/post-processor/yandex-import/Config-not-required.mdx @@ -1,10 +1,5 @@ -- `token` (string) - OAuth token to use to authenticate to Yandex.Cloud. - -- `service_account_key_file` (string) - Path to file with Service Account key in json format. This - is an alternative method to authenticate to Yandex.Cloud. - - `bucket` (string) - The name of the bucket where the qcow2 file will be uploaded to for import. This bucket must exist when the post-processor is run. @@ -17,7 +12,7 @@ - `skip_clean` (bool) - Whether skip removing the qcow2 file uploaded to Storage after the import process has completed. Possible values are: `true` to - leave it in the bucket, `false` to remove it. (Default: `false`). + leave it in the bucket, `false` to remove it. Default is `false`. - `image_name` (string) - The name of the image, which contains 1-63 characters and only supports lowercase English characters, numbers and hyphen. diff --git a/website/pages/partials/post-processor/yandex-import/Config-required.mdx b/website/pages/partials/post-processor/yandex-import/Config-required.mdx index 86bda22ce..8b665055b 100644 --- a/website/pages/partials/post-processor/yandex-import/Config-required.mdx +++ b/website/pages/partials/post-processor/yandex-import/Config-required.mdx @@ -3,4 +3,4 @@ - `folder_id` (string) - The folder ID that will be used to store imported Image. - `service_account_id` (string) - Service Account ID with proper permission to use Storage service - for operations 'upload' and 'delete' object to `bucket` + for operations 'upload' and 'delete' object to `bucket`.