options.go raw
1 // Copyright 2022-2025 The sacloud/iaas-api-go 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 "fmt"
19 "os"
20 "runtime"
21
22 client "github.com/sacloud/api-client-go"
23 "github.com/sacloud/api-client-go/profile"
24 "github.com/sacloud/iaas-api-go"
25 "github.com/sacloud/packages-go/envvar"
26 )
27
28 var UserAgent = fmt.Sprintf(
29 "sacloud/iaas-api-go/v%s (%s/%s; +https://github.com/sacloud/iaas-api-go) %s",
30 iaas.Version,
31 runtime.GOOS,
32 runtime.GOARCH,
33 client.DefaultUserAgent,
34 )
35
36 // CallerOptions iaas.APICallerを作成する際のオプション
37 type CallerOptions struct {
38 *client.Options
39
40 APIRootURL string
41 DefaultZone string
42 Zones []string
43
44 TraceAPI bool
45 FakeMode bool
46 FakeStorePath string
47 }
48
49 // DefaultOption 環境変数、プロファイルからCallerOptionsを組み立てて返す
50 //
51 // プロファイルは環境変数`SAKURACLOUD_PROFILE`または`USACLOUD_PROFILE`でプロファイル名が指定されていればそちらを優先し、
52 // 未指定の場合は通常のプロファイル処理(~/.usacloud/currentファイルから読み込み)される。
53 // 同じ項目を複数箇所で指定していた場合、環境変数->プロファイルの順で上書きされたものが返される
54 func DefaultOption() (*CallerOptions, error) {
55 return DefaultOptionWithProfile("")
56 }
57
58 // DefaultOptionWithProfile 環境変数、プロファイルからCallerOptionsを組み立てて返す
59 //
60 // プロファイルは引数を優先し、空の場合は環境変数`SAKURACLOUD_PROFILE`または`USACLOUD_PROFILE`が利用され、
61 // それも空の場合は通常のプロファイル処理(~/.usacloud/currentファイルから読み込み)される。
62 // 同じ項目を複数箇所で指定していた場合、環境変数->プロファイルの順で上書きされたものが返される
63 func DefaultOptionWithProfile(profileName string) (*CallerOptions, error) {
64 options, err := client.DefaultOptionWithProfile(profileName)
65 if err != nil {
66 return nil, err
67 }
68
69 fromEnv := OptionsFromEnv()
70 fromEnv.Options = options
71
72 fromProfile, err := OptionsFromProfile(profileName)
73 if err != nil {
74 return nil, err
75 }
76 fromProfile.Options = options
77
78 defaults := &CallerOptions{
79 APIRootURL: iaas.SakuraCloudAPIRoot,
80 DefaultZone: iaas.APIDefaultZone,
81 Zones: iaas.SakuraCloudZones,
82 Options: &client.Options{
83 UserAgent: UserAgent,
84 },
85 }
86
87 return MergeOptions(defaults, fromEnv, fromProfile), nil
88 }
89
90 // OptionsFromEnv 環境変数からCallerOptionsを組み立てて返す
91 func OptionsFromEnv() *CallerOptions {
92 return &CallerOptions{
93 Options: client.OptionsFromEnv(),
94 APIRootURL: envvar.StringFromEnv("SAKURACLOUD_API_ROOT_URL", ""),
95 DefaultZone: envvar.StringFromEnv("SAKURACLOUD_DEFAULT_ZONE", ""),
96 Zones: envvar.StringSliceFromEnv("SAKURACLOUD_ZONES", []string{}),
97
98 TraceAPI: profile.EnableAPITrace(envvar.StringFromEnv("SAKURACLOUD_TRACE", "")),
99
100 FakeMode: os.Getenv("SAKURACLOUD_FAKE_MODE") != "",
101 FakeStorePath: envvar.StringFromEnv("SAKURACLOUD_FAKE_STORE_PATH", ""),
102 }
103 }
104
105 // OptionsFromProfile 指定のプロファイルからCallerOptionsを組み立てて返す
106 // プロファイル名に空文字が指定された場合はカレントプロファイルが利用される
107 func OptionsFromProfile(profileName string) (*CallerOptions, error) {
108 options, err := client.OptionsFromProfile(profileName)
109 if err != nil {
110 return nil, err
111 }
112 config := options.ProfileConfigValue()
113 return &CallerOptions{
114 Options: options,
115 APIRootURL: config.APIRootURL,
116 DefaultZone: config.DefaultZone,
117 Zones: config.Zones,
118 TraceAPI: config.EnableAPITrace(),
119 FakeMode: config.FakeMode,
120 FakeStorePath: config.FakeStorePath,
121 }, nil
122 }
123
124 // MergeOptions 指定のCallerOptionsの非ゼロ値フィールドをoのコピーにマージして返す
125 func MergeOptions(opts ...*CallerOptions) *CallerOptions {
126 merged := &CallerOptions{}
127 for _, opt := range opts {
128 if opt.Options != nil {
129 var opts []*client.Options
130 if merged.Options != nil {
131 opts = append(opts, merged.Options)
132 }
133 opts = append(opts, opt.Options)
134 merged.Options = client.MergeOptions(opts...)
135 }
136 if opt.APIRootURL != "" {
137 merged.APIRootURL = opt.APIRootURL
138 }
139 if opt.DefaultZone != "" {
140 merged.DefaultZone = opt.DefaultZone
141 }
142 if len(opt.Zones) > 0 {
143 merged.Zones = opt.Zones
144 }
145
146 // Note: bool値は一度trueにしたらMergeでfalseになることがない
147 if opt.TraceAPI {
148 merged.TraceAPI = true
149 }
150 if opt.FakeMode {
151 merged.FakeMode = true
152 }
153 if opt.FakeStorePath != "" {
154 merged.FakeStorePath = opt.FakeStorePath
155 }
156 }
157 return merged
158 }
159