diff --git a/builder/yandex/builder.go b/builder/yandex/builder.go index 38ea7524d..69bb9469f 100644 --- a/builder/yandex/builder.go +++ b/builder/yandex/builder.go @@ -4,12 +4,14 @@ import ( "context" "fmt" + "github.com/google/uuid" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" + "github.com/yandex-cloud/go-sdk/pkg/requestid" ) // The unique ID for this builder. @@ -35,6 +37,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // 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) + ctx = requestid.ContextWithClientTraceID(ctx, uuid.New().String()) if err != nil { return nil, err diff --git a/builder/yandex/config.go b/builder/yandex/config.go index 8c442feee..d9d25a221 100644 --- a/builder/yandex/config.go +++ b/builder/yandex/config.go @@ -1,5 +1,3 @@ -//go:generate struct-markdown - package yandex import ( @@ -22,6 +20,7 @@ import ( const defaultEndpoint = "api.cloud.yandex.net:443" const defaultGpuPlatformID = "gpu-standard-v1" const defaultPlatformID = "standard-v1" +const defaultMaxRetries = 3 const defaultZone = "ru-central1-a" var reImageFamily = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`) @@ -74,6 +73,8 @@ type Config struct { 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. @@ -219,6 +220,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.Zone = defaultZone } + if c.MaxRetries == 0 { + c.MaxRetries = defaultMaxRetries + } + // provision config by OS environment variables if c.Token == "" { c.Token = os.Getenv("YC_TOKEN") diff --git a/builder/yandex/driver_yc.go b/builder/yandex/driver_yc.go index f68916848..b9ff35665 100644 --- a/builder/yandex/driver_yc.go +++ b/builder/yandex/driver_yc.go @@ -4,10 +4,13 @@ import ( "context" "fmt" "log" + "time" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/hashicorp/packer/helper/useragent" "github.com/hashicorp/packer/packer" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" @@ -16,9 +19,15 @@ import ( ycsdk "github.com/yandex-cloud/go-sdk" "github.com/yandex-cloud/go-sdk/iamkey" "github.com/yandex-cloud/go-sdk/pkg/requestid" + "github.com/yandex-cloud/go-sdk/pkg/retry" "github.com/yandex-cloud/go-sdk/sdkresolvers" ) +const ( + defaultExponentialBackoffBase = 50 * time.Millisecond + defaultExponentialBackoffCap = 1 * time.Minute +) + type driverYC struct { sdk *ycsdk.SDK ui packer.Ui @@ -51,11 +60,23 @@ func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) { sdkConfig.Credentials = credentials } + requestIDInterceptor := requestid.Interceptor() + + retryInterceptor := retry.Interceptor( + retry.WithMax(config.MaxRetries), + retry.WithCodes(codes.Unavailable), + retry.WithAttemptHeader(true), + retry.WithBackoff(retry.BackoffExponentialWithJitter(defaultExponentialBackoffBase, defaultExponentialBackoffCap))) + + // Make sure retry interceptor is above id interceptor. + // Now we will have new request id for every retry attempt. + interceptorChain := grpc_middleware.ChainUnaryClient(retryInterceptor, requestIDInterceptor) + userAgentMD := metadata.Pairs("user-agent", useragent.String()) sdk, err := ycsdk.Build(context.Background(), sdkConfig, grpc.WithDefaultCallOptions(grpc.Header(&userAgentMD)), - grpc.WithUnaryInterceptor(requestid.Interceptor())) + grpc.WithUnaryInterceptor(interceptorChain)) if err != nil { return nil, err diff --git a/website/source/docs/builders/yandex.html.md b/website/source/docs/builders/yandex.html.md index 80f43a742..32dafbf19 100644 --- a/website/source/docs/builders/yandex.html.md +++ b/website/source/docs/builders/yandex.html.md @@ -106,6 +106,8 @@ can be configured for this builder. - `platform_id` (string) - Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`. +- `max_retries` (number) - The maximum number of times an API request is being executed. + - `metadata` (object of key/value strings) - Metadata applied to the launched instance. @@ -135,10 +137,11 @@ can be configured for this builder. - `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`. +- `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`. - `state_timeout` (string) - The time to wait for instance state changes. Defaults to `5m`.