packer-cn/vendor/github.com/yandex-cloud/go-sdk/sdk.go

279 lines
8.1 KiB
Go

// Copyright (c) 2017 Yandex LLC. All rights reserved.
// Author: Alexey Baranov <baranovich@yandex-team.ru>
package ycsdk
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"sort"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint"
"github.com/yandex-cloud/go-genproto/yandex/cloud/operation"
"github.com/yandex-cloud/go-sdk/dial"
apiendpoint "github.com/yandex-cloud/go-sdk/gen/apiendpoint"
"github.com/yandex-cloud/go-sdk/gen/compute"
"github.com/yandex-cloud/go-sdk/gen/iam"
gen_operation "github.com/yandex-cloud/go-sdk/gen/operation"
"github.com/yandex-cloud/go-sdk/gen/resourcemanager"
"github.com/yandex-cloud/go-sdk/gen/vpc"
sdk_operation "github.com/yandex-cloud/go-sdk/operation"
"github.com/yandex-cloud/go-sdk/pkg/grpcclient"
"github.com/yandex-cloud/go-sdk/pkg/sdkerrors"
"github.com/yandex-cloud/go-sdk/pkg/singleflight"
)
type Endpoint string
const (
ComputeServiceID Endpoint = "compute"
IAMServiceID Endpoint = "iam"
OperationServiceID Endpoint = "operation"
ResourceManagementServiceID Endpoint = "resource-manager"
StorageServiceID Endpoint = "storage"
SerialSSHServiceID Endpoint = "serialssh"
// revive:disable:var-naming
ApiEndpointServiceID Endpoint = "endpoint"
// revive:enable:var-naming
VpcServiceID Endpoint = "vpc"
)
// Config is a config that is used to create SDK instance.
type Config struct {
// Credentials are used to authenticate the client. See Credentials for more info.
Credentials Credentials
// DialContextTimeout specifies timeout of dial on API endpoint that
// is used when building an SDK instance.
DialContextTimeout time.Duration
// TLSConfig is optional tls.Config that one can use in order to tune TLS options.
TLSConfig *tls.Config
// Endpoint is an API endpoint of Yandex.Cloud against which the SDK is used.
// Most users won't need to explicitly set it.
Endpoint string
Plaintext bool
}
// SDK is a Yandex.Cloud SDK
type SDK struct {
conf Config
cc grpcclient.ConnContext
endpoints struct {
initDone bool
mu sync.Mutex
ep map[Endpoint]*endpoint.ApiEndpoint
}
initErr error
initCall singleflight.Call
muErr sync.Mutex
}
// Build creates an SDK instance
func Build(ctx context.Context, conf Config, customOpts ...grpc.DialOption) (*SDK, error) {
if conf.Credentials == nil {
return nil, errors.New("credentials required")
}
const defaultEndpoint = "api.cloud.yandex.net:443"
if conf.Endpoint == "" {
conf.Endpoint = defaultEndpoint
}
const DefaultTimeout = 10 * time.Second
if conf.DialContextTimeout == 0 {
conf.DialContextTimeout = DefaultTimeout
}
creds, ok := conf.Credentials.(ExchangeableCredentials)
if !ok {
return nil, fmt.Errorf("unsupported credentials type %T", conf.Credentials)
}
var dialOpts []grpc.DialOption
dialOpts = append(dialOpts, grpc.WithDialer(
func(target string, timeout time.Duration) (conn net.Conn, e error) {
// Remove extra wrapper when grpc.withContextDialer become exported in https://github.com/grpc/grpc-go/issues/1786
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
dialer := dial.NewProxyDialer(dial.NewDialer())
return dialer(ctx, target)
}))
rpcCreds := newRPCCredentials(creds, conf.Plaintext)
dialOpts = append(dialOpts, grpc.WithPerRPCCredentials(rpcCreds))
if conf.DialContextTimeout > 0 {
dialOpts = append(dialOpts, grpc.WithBlock(), grpc.WithTimeout(conf.DialContextTimeout)) // nolint
}
if conf.Plaintext {
dialOpts = append(dialOpts, grpc.WithInsecure())
} else {
tlsConfig := conf.TLSConfig
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
creds := credentials.NewTLS(tlsConfig)
dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
}
// Append custom options after default, to allow to customize dialer and etc.
dialOpts = append(dialOpts, customOpts...)
cc := grpcclient.NewLazyConnContext(grpcclient.DialOptions(dialOpts...))
sdk := &SDK{
cc: cc,
conf: conf,
}
rpcCreds.Init(sdk.getConn(IAMServiceID))
return sdk, nil
}
// Shutdown shutdowns SDK and closes all open connections.
func (sdk *SDK) Shutdown(ctx context.Context) error {
return sdk.cc.Shutdown(ctx)
}
// WrapOperation wraps operation proto message to
func (sdk *SDK) WrapOperation(op *operation.Operation, err error) (*sdk_operation.Operation, error) {
if err != nil {
return nil, err
}
return sdk_operation.New(sdk.Operation(), op), nil
}
// IAM returns IAM object that is used to operate on Yandex Cloud Identity and Access Manager
func (sdk *SDK) IAM() *iam.IAM {
return iam.NewIAM(sdk.getConn(IAMServiceID))
}
// Compute returns Compute object that is used to operate on Yandex Compute Cloud
func (sdk *SDK) Compute() *compute.Compute {
return compute.NewCompute(sdk.getConn(ComputeServiceID))
}
// VPC returns VPC object that is used to operate on Yandex Virtual Private Cloud
func (sdk *SDK) VPC() *vpc.VPC {
return vpc.NewVPC(sdk.getConn(VpcServiceID))
}
// MDB returns MDB object that is used to operate on Yandex Managed Databases
func (sdk *SDK) MDB() *MDB {
return &MDB{sdk: sdk}
}
// Operation gets OperationService client
func (sdk *SDK) Operation() *gen_operation.OperationServiceClient {
group := gen_operation.NewOperation(sdk.getConn(OperationServiceID))
return group.Operation()
}
// ResourceManager returns ResourceManager object that is used to operate on Folders and Clouds
func (sdk *SDK) ResourceManager() *resourcemanager.ResourceManager {
return resourcemanager.NewResourceManager(sdk.getConn(ResourceManagementServiceID))
}
// revive:disable:var-naming
// ApiEndpoint gets ApiEndpointService client
func (sdk *SDK) ApiEndpoint() *apiendpoint.APIEndpoint {
return apiendpoint.NewAPIEndpoint(sdk.getConn(ApiEndpointServiceID))
}
// revive:enable:var-naming
func (sdk *SDK) Resolve(ctx context.Context, r ...Resolver) error {
args := make([]func() error, len(r))
for k, v := range r {
resolver := v
args[k] = func() error {
return resolver.Run(ctx, sdk)
}
}
return sdkerrors.CombineGoroutines(args...)
}
type lazyConn func(ctx context.Context) (*grpc.ClientConn, error)
func (sdk *SDK) getConn(serviceID Endpoint) func(ctx context.Context) (*grpc.ClientConn, error) {
return func(ctx context.Context) (*grpc.ClientConn, error) {
if !sdk.initDone() {
sdk.initCall.Do(func() interface{} {
sdk.muErr.Lock()
sdk.initErr = sdk.initConns(ctx)
sdk.muErr.Unlock()
return nil
})
if err := sdk.InitErr(); err != nil {
return nil, err
}
}
endpoint, endpointExist := sdk.Endpoint(serviceID)
if !endpointExist {
return nil, fmt.Errorf("server doesn't know service \"%v\". Known services: %v",
serviceID,
sdk.KnownServices())
}
return sdk.cc.GetConn(ctx, endpoint.Address)
}
}
func (sdk *SDK) initDone() (b bool) {
sdk.endpoints.mu.Lock()
b = sdk.endpoints.initDone
sdk.endpoints.mu.Unlock()
return
}
func (sdk *SDK) KnownServices() []string {
sdk.endpoints.mu.Lock()
result := make([]string, 0, len(sdk.endpoints.ep))
for k := range sdk.endpoints.ep {
result = append(result, string(k))
}
sdk.endpoints.mu.Unlock()
sort.Strings(result)
return result
}
func (sdk *SDK) Endpoint(endpointName Endpoint) (ep *endpoint.ApiEndpoint, exist bool) {
sdk.endpoints.mu.Lock()
ep, exist = sdk.endpoints.ep[endpointName]
sdk.endpoints.mu.Unlock()
return
}
func (sdk *SDK) InitErr() error {
sdk.muErr.Lock()
defer sdk.muErr.Unlock()
return sdk.initErr
}
func (sdk *SDK) initConns(ctx context.Context) error {
discoveryConn, err := sdk.cc.GetConn(ctx, sdk.conf.Endpoint)
if err != nil {
return err
}
ec := endpoint.NewApiEndpointServiceClient(discoveryConn)
const defaultEndpointPageSize = 100
listResponse, err := ec.List(ctx, &endpoint.ListApiEndpointsRequest{
PageSize: defaultEndpointPageSize,
})
if err != nil {
return err
}
sdk.endpoints.mu.Lock()
sdk.endpoints.ep = make(map[Endpoint]*endpoint.ApiEndpoint, len(listResponse.Endpoints))
for _, e := range listResponse.Endpoints {
sdk.endpoints.ep[Endpoint(e.Id)] = e
}
sdk.endpoints.initDone = true
sdk.endpoints.mu.Unlock()
return nil
}