This repository has been archived by the owner on Sep 12, 2020. It is now read-only.
forked from yandex-cloud/terraform-provider-yandex
-
Notifications
You must be signed in to change notification settings - Fork 6
/
config.go
199 lines (160 loc) · 5.86 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package yandex
import (
"context"
"crypto/tls"
"fmt"
"log"
"math"
"math/rand"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/google/uuid"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
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"
)
const (
defaultExponentialBackoffBase = 50 * time.Millisecond
defaultExponentialBackoffCap = 1 * time.Minute
)
type Config struct {
Endpoint string
FolderID string
CloudID string
Zone string
Token string
ServiceAccountKeyFile string
Plaintext bool
Insecure bool
MaxRetries int
StorageEndpoint string
// These storage access keys are optional and only used when
// storage data/resource doesn't have own access keys explicitly specified.
StorageAccessKey string
StorageSecretKey string
// contextWithClientTraceID is a context that has client-trace-id in its metadata
// It is initialized from stopContext at the same time as ycsdk.SDK
contextWithClientTraceID context.Context
userAgent string
sdk *ycsdk.SDK
defaultS3Client *s3.S3
}
// this function return context with added client trace id
func (c *Config) Context() context.Context {
return c.contextWithClientTraceID
}
// this function returns context with client trace id AND timeout
func (c *Config) ContextWithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) {
return context.WithTimeout(c.contextWithClientTraceID, timeout)
}
// Client configures and returns a fully initialized Yandex.Cloud sdk
func (c *Config) initAndValidate(stopContext context.Context, terraformVersion string) error {
c.contextWithClientTraceID = requestid.ContextWithClientTraceID(stopContext, uuid.New().String())
credentials, err := c.credentials()
if err != nil {
return err
}
yandexSDKConfig := &ycsdk.Config{
Credentials: credentials,
Endpoint: c.Endpoint,
Plaintext: c.Plaintext,
TLSConfig: &tls.Config{
InsecureSkipVerify: c.Insecure,
},
}
providerNameAndVersion := getProviderNameAndVersion()
terraformURL := "https://www.terraform.io"
c.userAgent = fmt.Sprintf("Terraform/%s (%s) %s", terraformVersion, terraformURL, providerNameAndVersion)
headerMD := metadata.Pairs("user-agent", c.userAgent)
requestIDInterceptor := requestid.Interceptor()
retryInterceptor := retry.Interceptor(
retry.WithMax(c.MaxRetries),
retry.WithCodes(codes.Unavailable),
retry.WithAttemptHeader(true),
retry.WithBackoff(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)
c.sdk, err = ycsdk.Build(c.contextWithClientTraceID, *yandexSDKConfig,
grpc.WithUserAgent(c.userAgent),
grpc.WithDefaultCallOptions(grpc.Header(&headerMD)),
grpc.WithUnaryInterceptor(interceptorChain))
if err == nil {
err = c.initializeDefaultS3Client()
}
return err
}
func (c *Config) initializeDefaultS3Client() (err error) {
if c.StorageEndpoint == "" || (c.StorageAccessKey == "" && c.StorageSecretKey == "") {
return nil
}
if c.StorageAccessKey == "" || c.StorageSecretKey == "" {
return fmt.Errorf("both storage access key and storage secret key should be specified or not specified")
}
c.defaultS3Client, err = newS3Client(c.StorageEndpoint, c.StorageAccessKey, c.StorageSecretKey)
return err
}
func (c *Config) credentials() (ycsdk.Credentials, error) {
if c.ServiceAccountKeyFile != "" {
key, err := iamkey.ReadFromJSONFile(c.ServiceAccountKeyFile)
if err != nil {
return nil, err
}
return ycsdk.ServiceAccountKey(key)
}
if c.Token != "" {
return ycsdk.OAuthToken(c.Token), nil
}
if sa := ycsdk.InstanceServiceAccount(); checkServiceAccountAvailable(c.Context(), sa) {
return sa, nil
}
return nil, fmt.Errorf("one of 'token' or 'service_account_key_file' should be specified; if you are inside compute instance, you can attach service account to it in order to authenticate via instance service account")
}
func backoffExponentialWithJitter(base time.Duration, cap time.Duration) retry.BackoffFunc {
return func(attempt int) time.Duration {
// First call of BackoffFunc would be with attempt arq equal 0
log.Printf("[DEBUG] API call retry attempt %d", attempt+1)
to := getExponentialTimeout(attempt, base)
// Using float types here, because exponential time can be really big, and converting it to time.Duration may
// result in undefined behaviour. Its safe conversion, when we have compared it to our 'cap' value.
if to > float64(cap) {
to = float64(cap)
}
return time.Duration(to * rand.Float64())
}
}
func getExponentialTimeout(attempt int, base time.Duration) float64 {
mult := math.Pow(2, float64(attempt))
return float64(base) * mult
}
func getProviderNameAndVersion() string {
// version is part of binary name
// https://www.terraform.io/docs/configuration/providers.html#plugin-names-and-versions
fullBinaryPath := os.Args[0]
binaryName := filepath.Base(fullBinaryPath)
parts := strings.Split(binaryName, "_")
if len(parts) < 2 {
return "unknown/unknown"
}
parts[1] = strings.TrimPrefix(parts[1], "v")
return strings.Join(parts[:2], "/")
}
func checkServiceAccountAvailable(ctx context.Context, sa ycsdk.NonExchangeableCredentials) bool {
dialer := net.Dialer{Timeout: 50 * time.Millisecond}
conn, err := dialer.Dial("tcp", net.JoinHostPort(ycsdk.InstanceMetadataAddr, "80"))
if err != nil {
return false
}
_ = conn.Close()
_, err = sa.IAMToken(ctx)
return err == nil
}