utils.go raw
1 package clientconfig
2
3 import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "os"
8 "os/user"
9 "path/filepath"
10 "reflect"
11
12 "github.com/gophercloud/gophercloud"
13 "github.com/gophercloud/utils/env"
14 )
15
16 // defaultIfEmpty is a helper function to make it cleaner to set default value
17 // for strings.
18 func defaultIfEmpty(value string, defaultValue string) string {
19 if value == "" {
20 return defaultValue
21 }
22 return value
23 }
24
25 // mergeCLouds merges two Clouds recursively (the AuthInfo also gets merged).
26 // In case both Clouds define a value, the value in the 'override' cloud takes precedence
27 func mergeClouds(override, cloud interface{}) (*Cloud, error) {
28 overrideJson, err := json.Marshal(override)
29 if err != nil {
30 return nil, err
31 }
32 cloudJson, err := json.Marshal(cloud)
33 if err != nil {
34 return nil, err
35 }
36 var overrideInterface interface{}
37 err = json.Unmarshal(overrideJson, &overrideInterface)
38 if err != nil {
39 return nil, err
40 }
41 var cloudInterface interface{}
42 err = json.Unmarshal(cloudJson, &cloudInterface)
43 if err != nil {
44 return nil, err
45 }
46 var mergedCloud Cloud
47 mergedInterface := mergeInterfaces(overrideInterface, cloudInterface)
48 mergedJson, err := json.Marshal(mergedInterface)
49 err = json.Unmarshal(mergedJson, &mergedCloud)
50 if err != nil {
51 return nil, err
52 }
53 return &mergedCloud, nil
54 }
55
56 // merges two interfaces. In cases where a value is defined for both 'overridingInterface' and
57 // 'inferiorInterface' the value in 'overridingInterface' will take precedence.
58 func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interface{} {
59 switch overriding := overridingInterface.(type) {
60 case map[string]interface{}:
61 interfaceMap, ok := inferiorInterface.(map[string]interface{})
62 if !ok {
63 return overriding
64 }
65 for k, v := range interfaceMap {
66 if overridingValue, ok := overriding[k]; ok {
67 overriding[k] = mergeInterfaces(overridingValue, v)
68 } else {
69 overriding[k] = v
70 }
71 }
72 case []interface{}:
73 list, ok := inferiorInterface.([]interface{})
74 if !ok {
75 return overriding
76 }
77 for i := range list {
78 overriding = append(overriding, list[i])
79 }
80 return overriding
81 case nil:
82 // mergeClouds(nil, map[string]interface{...}) -> map[string]interface{...}
83 v, ok := inferiorInterface.(map[string]interface{})
84 if ok {
85 return v
86 }
87 }
88 // We don't want to override with empty values
89 if reflect.DeepEqual(overridingInterface, nil) || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(overridingInterface)).Interface(), overridingInterface) {
90 return inferiorInterface
91 } else {
92 return overridingInterface
93 }
94 }
95
96 // FindAndReadCloudsYAML attempts to locate a clouds.yaml file in the following
97 // locations:
98 //
99 // 1. OS_CLIENT_CONFIG_FILE
100 // 2. Current directory.
101 // 3. unix-specific user_config_dir (~/.config/openstack/clouds.yaml)
102 // 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml)
103 //
104 // If found, the contents of the file is returned.
105 func FindAndReadCloudsYAML() (string, []byte, error) {
106 // OS_CLIENT_CONFIG_FILE
107 if v := env.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" {
108 if ok := fileExists(v); ok {
109 content, err := ioutil.ReadFile(v)
110 return v, content, err
111 }
112 }
113
114 s, b, err := FindAndReadYAML("clouds.yaml")
115 if s == "" {
116 return FindAndReadYAML("clouds.yml")
117 }
118 return s, b, err
119 }
120
121 func FindAndReadPublicCloudsYAML() (string, []byte, error) {
122 s, b, err := FindAndReadYAML("clouds-public.yaml")
123 if s == "" {
124 return FindAndReadYAML("clouds-public.yml")
125 }
126 return s, b, err
127 }
128
129 func FindAndReadSecureCloudsYAML() (string, []byte, error) {
130 s, b, err := FindAndReadYAML("secure.yaml")
131 if s == "" {
132 return FindAndReadYAML("secure.yml")
133 }
134 return s, b, err
135 }
136
137 func FindAndReadYAML(yamlFile string) (string, []byte, error) {
138 // current directory
139 cwd, err := os.Getwd()
140 if err != nil {
141 return "", nil, fmt.Errorf("unable to determine working directory: %w", err)
142 }
143
144 filename := filepath.Join(cwd, yamlFile)
145 if ok := fileExists(filename); ok {
146 content, err := ioutil.ReadFile(filename)
147 return filename, content, err
148 }
149
150 // unix user config directory: ~/.config/openstack.
151 if currentUser, err := user.Current(); err == nil {
152 homeDir := currentUser.HomeDir
153 if homeDir != "" {
154 filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile)
155 if ok := fileExists(filename); ok {
156 content, err := ioutil.ReadFile(filename)
157 return filename, content, err
158 }
159 }
160 }
161
162 // unix-specific site config directory: /etc/openstack.
163 filename = "/etc/openstack/" + yamlFile
164 if ok := fileExists(filename); ok {
165 content, err := ioutil.ReadFile(filename)
166 return filename, content, err
167 }
168
169 return "", nil, fmt.Errorf("no %s file found: %w", yamlFile, os.ErrNotExist)
170 }
171
172 // fileExists checks for the existence of a file at a given location.
173 func fileExists(filename string) bool {
174 if _, err := os.Stat(filename); err == nil {
175 return true
176 }
177 return false
178 }
179
180 // GetEndpointType is a helper method to determine the endpoint type
181 // requested by the user.
182 func GetEndpointType(endpointType string) gophercloud.Availability {
183 if endpointType == "internal" || endpointType == "internalURL" {
184 return gophercloud.AvailabilityInternal
185 }
186 if endpointType == "admin" || endpointType == "adminURL" {
187 return gophercloud.AvailabilityAdmin
188 }
189 return gophercloud.AvailabilityPublic
190 }
191