github.com/sacloud/libsacloud/v2@v2.32.3/helper/api/options.go (about) 1 // Copyright 2016-2022 The Libsacloud Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package api 16 17 import ( 18 "net/http" 19 "os" 20 "strconv" 21 "strings" 22 23 sacloudhttp "github.com/sacloud/go-http" 24 "github.com/sacloud/libsacloud/v2/sacloud" 25 "github.com/sacloud/libsacloud/v2/sacloud/profile" 26 "github.com/sacloud/libsacloud/v2/sacloud/trace/otel" 27 ) 28 29 // CallerOptions sacloud.APICallerを作成する際のオプション 30 type CallerOptions struct { 31 AccessToken string 32 AccessTokenSecret string 33 34 APIRootURL string 35 DefaultZone string 36 Zones []string 37 AcceptLanguage string 38 39 HTTPClient *http.Client 40 41 HTTPRequestTimeout int 42 HTTPRequestRateLimit int 43 44 RetryMax int 45 RetryWaitMax int 46 RetryWaitMin int 47 48 UserAgent string 49 50 TraceAPI bool 51 TraceHTTP bool 52 OpenTelemetry bool 53 OpenTelemetryOptions []otel.Option 54 55 FakeMode bool 56 FakeStorePath string 57 } 58 59 // DefaultOption 環境変数、プロファイルからCallerOptionsを組み立てて返す 60 // 61 // プロファイルは環境変数`SAKURACLOUD_PROFILE`または`USACLOUD_PROFILE`でプロファイル名が指定されていればそちらを優先し、 62 // 未指定の場合は通常のプロファイル処理(~/.usacloud/currentファイルから読み込み)される。 63 // 同じ項目を複数箇所で指定していた場合、環境変数->プロファイルの順で上書きされたものが返される 64 func DefaultOption() (*CallerOptions, error) { 65 return DefaultOptionWithProfile("") 66 } 67 68 // DefaultOptionWithProfile 環境変数、プロファイルからCallerOptionsを組み立てて返す 69 // 70 // プロファイルは引数を優先し、空の場合は環境変数`SAKURACLOUD_PROFILE`または`USACLOUD_PROFILE`が利用され、 71 // それも空の場合は通常のプロファイル処理(~/.usacloud/currentファイルから読み込み)される。 72 // 同じ項目を複数箇所で指定していた場合、環境変数->プロファイルの順で上書きされたものが返される 73 func DefaultOptionWithProfile(profileName string) (*CallerOptions, error) { 74 if profileName == "" { 75 profileName = stringFromEnvMulti([]string{"SAKURACLOUD_PROFILE", "USACLOUD_PROFILE"}, "") 76 } 77 fromProfile, err := OptionsFromProfile(profileName) 78 if err != nil { 79 return nil, err 80 } 81 return MergeOptions(OptionsFromEnv(), fromProfile, defaultOption), nil 82 } 83 84 var defaultOption = &CallerOptions{ 85 APIRootURL: sacloud.SakuraCloudAPIRoot, 86 DefaultZone: sacloud.APIDefaultZone, 87 HTTPRequestTimeout: 300, 88 HTTPRequestRateLimit: 5, 89 RetryMax: sacloudhttp.DefaultRetryMax, 90 RetryWaitMax: int(sacloudhttp.DefaultRetryWaitMax.Seconds()), 91 RetryWaitMin: int(sacloudhttp.DefaultRetryWaitMin.Seconds()), 92 } 93 94 // MergeOptions 指定のCallerOptionsの非ゼロ値フィールドをoのコピーにマージして返す 95 func MergeOptions(opts ...*CallerOptions) *CallerOptions { 96 merged := &CallerOptions{} 97 for _, opt := range opts { 98 if opt.AccessToken != "" { 99 merged.AccessToken = opt.AccessToken 100 } 101 if opt.AccessTokenSecret != "" { 102 merged.AccessTokenSecret = opt.AccessTokenSecret 103 } 104 if opt.APIRootURL != "" { 105 merged.APIRootURL = opt.APIRootURL 106 } 107 if opt.DefaultZone != "" { 108 merged.DefaultZone = opt.DefaultZone 109 } 110 if len(opt.Zones) > 0 { 111 merged.Zones = opt.Zones 112 } 113 if opt.AcceptLanguage != "" { 114 merged.AcceptLanguage = opt.AcceptLanguage 115 } 116 if opt.HTTPClient != nil { 117 merged.HTTPClient = opt.HTTPClient 118 } 119 if opt.HTTPRequestTimeout > 0 { 120 merged.HTTPRequestTimeout = opt.HTTPRequestTimeout 121 } 122 if opt.HTTPRequestRateLimit > 0 { 123 merged.HTTPRequestRateLimit = opt.HTTPRequestRateLimit 124 } 125 if opt.RetryMax > 0 { 126 merged.RetryMax = opt.RetryMax 127 } 128 if opt.RetryWaitMax > 0 { 129 merged.RetryWaitMax = opt.RetryWaitMax 130 } 131 if opt.RetryWaitMin > 0 { 132 merged.RetryWaitMin = opt.RetryWaitMin 133 } 134 if opt.UserAgent != "" { 135 merged.UserAgent = opt.UserAgent 136 } 137 138 // Note: bool値は一度trueにしたらMergeでfalseになることがない 139 if opt.TraceAPI { 140 merged.TraceAPI = true 141 } 142 if opt.TraceHTTP { 143 merged.TraceHTTP = true 144 } 145 if opt.OpenTelemetry { 146 merged.OpenTelemetry = true 147 } 148 if len(opt.OpenTelemetryOptions) > 0 { 149 merged.OpenTelemetryOptions = opt.OpenTelemetryOptions 150 } 151 if opt.FakeMode { 152 merged.FakeMode = true 153 } 154 if opt.FakeStorePath != "" { 155 merged.FakeStorePath = opt.FakeStorePath 156 } 157 } 158 return merged 159 } 160 161 // OptionsFromEnv 環境変数からCallerOptionsを組み立てて返す 162 func OptionsFromEnv() *CallerOptions { 163 return &CallerOptions{ 164 AccessToken: stringFromEnv("SAKURACLOUD_ACCESS_TOKEN", ""), 165 AccessTokenSecret: stringFromEnv("SAKURACLOUD_ACCESS_TOKEN_SECRET", ""), 166 167 APIRootURL: stringFromEnv("SAKURACLOUD_API_ROOT_URL", ""), 168 DefaultZone: stringFromEnv("SAKURACLOUD_DEFAULT_ZONE", ""), 169 Zones: stringSliceFromEnv("SAKURACLOUD_ZONES", []string{}), 170 AcceptLanguage: stringFromEnv("SAKURACLOUD_ACCEPT_LANGUAGE", ""), 171 172 HTTPRequestTimeout: intFromEnv("SAKURACLOUD_API_REQUEST_TIMEOUT", 0), 173 HTTPRequestRateLimit: intFromEnv("SAKURACLOUD_API_REQUEST_RATE_LIMIT", 0), 174 175 RetryMax: intFromEnv("SAKURACLOUD_RETRY_MAX", 0), 176 RetryWaitMax: intFromEnv("SAKURACLOUD_RETRY_WAIT_MAX", 0), 177 RetryWaitMin: intFromEnv("SAKURACLOUD_RETRY_WAIT_MIN", 0), 178 179 TraceAPI: profile.EnableAPITrace(stringFromEnv("SAKURACLOUD_TRACE", "")), 180 TraceHTTP: profile.EnableHTTPTrace(stringFromEnv("SAKURACLOUD_TRACE", "")), 181 182 FakeMode: os.Getenv("SAKURACLOUD_FAKE_MODE") != "", 183 FakeStorePath: stringFromEnv("SAKURACLOUD_FAKE_STORE_PATH", ""), 184 } 185 } 186 187 // OptionsFromProfile 指定のプロファイルからCallerOptionsを組み立てて返す 188 // プロファイル名に空文字が指定された場合はカレントプロファイルが利用される 189 func OptionsFromProfile(profileName string) (*CallerOptions, error) { 190 if profileName == "" { 191 current, err := profile.CurrentName() 192 if err != nil { 193 return nil, err 194 } 195 profileName = current 196 } 197 198 config := profile.ConfigValue{} 199 if err := profile.Load(profileName, &config); err != nil { 200 return nil, err 201 } 202 203 return &CallerOptions{ 204 AccessToken: config.AccessToken, 205 AccessTokenSecret: config.AccessTokenSecret, 206 APIRootURL: config.APIRootURL, 207 DefaultZone: config.DefaultZone, 208 Zones: config.Zones, 209 AcceptLanguage: config.AcceptLanguage, 210 HTTPRequestTimeout: config.HTTPRequestTimeout, 211 HTTPRequestRateLimit: config.HTTPRequestRateLimit, 212 RetryMax: config.RetryMax, 213 RetryWaitMax: config.RetryWaitMax, 214 RetryWaitMin: config.RetryWaitMin, 215 TraceAPI: config.EnableAPITrace(), 216 TraceHTTP: config.EnableHTTPTrace(), 217 FakeMode: config.FakeMode, 218 FakeStorePath: config.FakeStorePath, 219 }, nil 220 } 221 222 func stringFromEnv(key, defaultValue string) string { 223 v := os.Getenv(key) 224 if v == "" { 225 return defaultValue 226 } 227 return v 228 } 229 230 func stringFromEnvMulti(keys []string, defaultValue string) string { 231 for _, key := range keys { 232 v := os.Getenv(key) 233 if v != "" { 234 return v 235 } 236 } 237 return defaultValue 238 } 239 240 func stringSliceFromEnv(key string, defaultValue []string) []string { 241 v := os.Getenv(key) 242 if v == "" { 243 return defaultValue 244 } 245 values := strings.Split(v, ",") 246 for i := range values { 247 values[i] = strings.Trim(values[i], " ") 248 } 249 return values 250 } 251 252 func intFromEnv(key string, defaultValue int) int { 253 v := os.Getenv(key) 254 if v == "" { 255 return defaultValue 256 } 257 i, err := strconv.ParseInt(v, 10, 64) 258 if err != nil { 259 return defaultValue 260 } 261 return int(i) 262 }