package yandex import ( "context" "fmt" "log" "strings" "time" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/useragent" "google.golang.org/grpc" "google.golang.org/grpc/codes" "github.com/hashicorp/packer/builder/yandex/version" "github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1" "github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint" "github.com/yandex-cloud/go-genproto/yandex/cloud/vpc/v1" 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 packersdk.Ui } func NewDriverYC(ui packersdk.Ui, ac *AccessConfig) (Driver, error) { log.Printf("[INFO] Initialize Yandex.Cloud client...") sdkConfig := ycsdk.Config{} if ac.Endpoint != "" { sdkConfig.Endpoint = ac.Endpoint } switch { case ac.Token == "" && ac.ServiceAccountKeyFile == "": log.Printf("[INFO] Use Instance Service Account for authentication") sdkConfig.Credentials = ycsdk.InstanceServiceAccount() case ac.Token != "": if strings.HasPrefix(ac.Token, "t1.") && strings.Count(ac.Token, ".") == 2 { log.Printf("[INFO] Use IAM token for authentication") sdkConfig.Credentials = ycsdk.NewIAMTokenCredentials(ac.Token) } else { log.Printf("[INFO] Use OAuth token for authentication") sdkConfig.Credentials = ycsdk.OAuthToken(ac.Token) } 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 } credentials, err := ycsdk.ServiceAccountKey(key) if err != nil { return nil, err } sdkConfig.Credentials = credentials } requestIDInterceptor := requestid.Interceptor() retryInterceptor := retry.Interceptor( retry.WithMax(ac.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) sdk, err := ycsdk.Build(context.Background(), sdkConfig, grpc.WithUserAgent(useragent.String(version.YandexPluginVersion.FormattedVersion())), grpc.WithUnaryInterceptor(interceptorChain), ) if err != nil { return nil, err } if _, err = sdk.ApiEndpoint().ApiEndpoint().List(context.Background(), &endpoint.ListApiEndpointsRequest{}); err != nil { return nil, err } return &driverYC{ sdk: sdk, ui: ui, }, nil } func (d *driverYC) GetImage(imageID string) (*Image, error) { image, err := d.sdk.Compute().Image().Get(context.Background(), &compute.GetImageRequest{ ImageId: imageID, }) if err != nil { return nil, err } return &Image{ ID: image.Id, Labels: image.Labels, Licenses: image.ProductIds, Name: image.Name, Description: image.Description, FolderID: image.FolderId, MinDiskSizeGb: toGigabytes(image.MinDiskSize), SizeGb: toGigabytes(image.StorageSize), }, nil } func (d *driverYC) GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error) { image, err := d.sdk.Compute().Image().GetLatestByFamily(ctx, &compute.GetImageLatestByFamilyRequest{ FolderId: folderID, Family: family, }) if err != nil { return nil, err } return &Image{ ID: image.Id, Labels: image.Labels, Licenses: image.ProductIds, Name: image.Name, Description: image.Description, FolderID: image.FolderId, Family: image.Family, MinDiskSizeGb: toGigabytes(image.MinDiskSize), SizeGb: toGigabytes(image.StorageSize), }, nil } func (d *driverYC) GetImageFromFolderByName(ctx context.Context, folderID string, imageName string) (*Image, error) { imageResolver := sdkresolvers.ImageResolver(imageName, sdkresolvers.FolderID(folderID)) if err := d.sdk.Resolve(ctx, imageResolver); err != nil { return nil, fmt.Errorf("failed to resolve image name: %s", err) } return d.GetImage(imageResolver.ID()) } func (d *driverYC) DeleteImage(ID string) error { ctx := context.TODO() op, err := d.sdk.WrapOperation(d.sdk.Compute().Image().Delete(ctx, &compute.DeleteImageRequest{ ImageId: ID, })) if err != nil { return err } err = op.Wait(ctx) if err != nil { return err } _, err = op.Response() return err } func (d *driverYC) SDK() *ycsdk.SDK { return d.sdk } func (d *driverYC) DeleteInstance(ctx context.Context, instanceID string) error { op, err := d.sdk.WrapOperation(d.sdk.Compute().Instance().Delete(ctx, &compute.DeleteInstanceRequest{ InstanceId: instanceID, })) if err != nil { return err } err = op.Wait(ctx) if err != nil { return err } _, err = op.Response() return err } func (d *driverYC) DeleteSubnet(ctx context.Context, subnetID string) error { op, err := d.sdk.WrapOperation(d.sdk.VPC().Subnet().Delete(ctx, &vpc.DeleteSubnetRequest{ SubnetId: subnetID, })) if err != nil { return err } err = op.Wait(ctx) if err != nil { return err } _, err = op.Response() return err } func (d *driverYC) DeleteNetwork(ctx context.Context, networkID string) error { op, err := d.sdk.WrapOperation(d.sdk.VPC().Network().Delete(ctx, &vpc.DeleteNetworkRequest{ NetworkId: networkID, })) if err != nil { return err } err = op.Wait(ctx) if err != nil { return err } _, err = op.Response() return err } func (d *driverYC) DeleteDisk(ctx context.Context, diskID string) error { op, err := d.sdk.WrapOperation(d.sdk.Compute().Disk().Delete(ctx, &compute.DeleteDiskRequest{ DiskId: diskID, })) if err != nil { return err } err = op.Wait(ctx) if err != nil { return err } _, err = op.Response() return err } func (d *driverYC) GetInstanceMetadata(ctx context.Context, instanceID string, key string) (string, error) { instance, err := d.sdk.Compute().Instance().Get(ctx, &compute.GetInstanceRequest{ InstanceId: instanceID, View: compute.InstanceView_FULL, }) if err != nil { return "", err } for k, v := range instance.GetMetadata() { if k == key { return v, nil } } return "", fmt.Errorf("Instance metadata key, %s, not found.", key) }