2019-02-08 11:59:06 +01:00
|
|
|
package clientconfig
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
2020-04-25 08:41:54 +02:00
|
|
|
|
|
|
|
"github.com/gophercloud/gophercloud"
|
|
|
|
"github.com/gophercloud/utils/env"
|
2019-02-08 11:59:06 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// defaultIfEmpty is a helper function to make it cleaner to set default value
|
|
|
|
// for strings.
|
|
|
|
func defaultIfEmpty(value string, defaultValue string) string {
|
|
|
|
if value == "" {
|
|
|
|
return defaultValue
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
// mergeCLouds merges two Clouds recursively (the AuthInfo also gets merged).
|
|
|
|
// In case both Clouds define a value, the value in the 'override' cloud takes precedence
|
|
|
|
func mergeClouds(override, cloud interface{}) (*Cloud, error) {
|
|
|
|
overrideJson, err := json.Marshal(override)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cloudJson, err := json.Marshal(cloud)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var overrideInterface interface{}
|
|
|
|
err = json.Unmarshal(overrideJson, &overrideInterface)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var cloudInterface interface{}
|
|
|
|
err = json.Unmarshal(cloudJson, &cloudInterface)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var mergedCloud Cloud
|
|
|
|
mergedInterface := mergeInterfaces(overrideInterface, cloudInterface)
|
|
|
|
mergedJson, err := json.Marshal(mergedInterface)
|
|
|
|
json.Unmarshal(mergedJson, &mergedCloud)
|
|
|
|
return &mergedCloud, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// merges two interfaces. In cases where a value is defined for both 'overridingInterface' and
|
|
|
|
// 'inferiorInterface' the value in 'overridingInterface' will take precedence.
|
|
|
|
func mergeInterfaces(overridingInterface, inferiorInterface interface{}) interface{} {
|
|
|
|
switch overriding := overridingInterface.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
interfaceMap, ok := inferiorInterface.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return overriding
|
|
|
|
}
|
|
|
|
for k, v := range interfaceMap {
|
|
|
|
if overridingValue, ok := overriding[k]; ok {
|
|
|
|
overriding[k] = mergeInterfaces(overridingValue, v)
|
|
|
|
} else {
|
|
|
|
overriding[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case []interface{}:
|
|
|
|
list, ok := inferiorInterface.([]interface{})
|
|
|
|
if !ok {
|
|
|
|
return overriding
|
|
|
|
}
|
|
|
|
for i := range list {
|
|
|
|
overriding = append(overriding, list[i])
|
|
|
|
}
|
|
|
|
return overriding
|
|
|
|
case nil:
|
|
|
|
// mergeClouds(nil, map[string]interface{...}) -> map[string]interface{...}
|
|
|
|
v, ok := inferiorInterface.(map[string]interface{})
|
|
|
|
if ok {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// We don't want to override with empty values
|
|
|
|
if reflect.DeepEqual(overridingInterface, nil) || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(overridingInterface)).Interface(), overridingInterface) {
|
|
|
|
return inferiorInterface
|
|
|
|
} else {
|
|
|
|
return overridingInterface
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-25 08:41:54 +02:00
|
|
|
// FindAndReadCloudsYAML attempts to locate a clouds.yaml file in the following
|
2019-02-08 11:59:06 +01:00
|
|
|
// locations:
|
|
|
|
//
|
|
|
|
// 1. OS_CLIENT_CONFIG_FILE
|
|
|
|
// 2. Current directory.
|
|
|
|
// 3. unix-specific user_config_dir (~/.config/openstack/clouds.yaml)
|
|
|
|
// 4. unix-specific site_config_dir (/etc/openstack/clouds.yaml)
|
|
|
|
//
|
|
|
|
// If found, the contents of the file is returned.
|
2020-04-25 08:41:54 +02:00
|
|
|
func FindAndReadCloudsYAML() (string, []byte, error) {
|
2019-02-08 11:59:06 +01:00
|
|
|
// OS_CLIENT_CONFIG_FILE
|
2020-04-25 08:41:54 +02:00
|
|
|
if v := env.Getenv("OS_CLIENT_CONFIG_FILE"); v != "" {
|
2019-02-08 11:59:06 +01:00
|
|
|
if ok := fileExists(v); ok {
|
2020-04-25 08:41:54 +02:00
|
|
|
content, err := ioutil.ReadFile(v)
|
|
|
|
return v, content, err
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-25 08:41:54 +02:00
|
|
|
return FindAndReadYAML("clouds.yaml")
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
2020-04-25 08:41:54 +02:00
|
|
|
func FindAndReadPublicCloudsYAML() (string, []byte, error) {
|
|
|
|
return FindAndReadYAML("clouds-public.yaml")
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
2020-04-25 08:41:54 +02:00
|
|
|
func FindAndReadSecureCloudsYAML() (string, []byte, error) {
|
|
|
|
return FindAndReadYAML("secure.yaml")
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
2020-04-25 08:41:54 +02:00
|
|
|
func FindAndReadYAML(yamlFile string) (string, []byte, error) {
|
2019-02-08 11:59:06 +01:00
|
|
|
// current directory
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2020-04-25 08:41:54 +02:00
|
|
|
return "", nil, fmt.Errorf("unable to determine working directory: %s", err)
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
filename := filepath.Join(cwd, yamlFile)
|
|
|
|
if ok := fileExists(filename); ok {
|
2020-04-25 08:41:54 +02:00
|
|
|
content, err := ioutil.ReadFile(filename)
|
|
|
|
return filename, content, err
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// unix user config directory: ~/.config/openstack.
|
|
|
|
if currentUser, err := user.Current(); err == nil {
|
|
|
|
homeDir := currentUser.HomeDir
|
|
|
|
if homeDir != "" {
|
|
|
|
filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile)
|
|
|
|
if ok := fileExists(filename); ok {
|
2020-04-25 08:41:54 +02:00
|
|
|
content, err := ioutil.ReadFile(filename)
|
|
|
|
return filename, content, err
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// unix-specific site config directory: /etc/openstack.
|
2020-04-25 08:41:54 +02:00
|
|
|
filename = "/etc/openstack/" + yamlFile
|
|
|
|
if ok := fileExists(filename); ok {
|
|
|
|
content, err := ioutil.ReadFile(filename)
|
|
|
|
return filename, content, err
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
2020-04-25 08:41:54 +02:00
|
|
|
return "", nil, fmt.Errorf("no " + yamlFile + " file found")
|
2019-02-08 11:59:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// fileExists checks for the existence of a file at a given location.
|
|
|
|
func fileExists(filename string) bool {
|
|
|
|
if _, err := os.Stat(filename); err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2020-04-25 08:41:54 +02:00
|
|
|
|
|
|
|
// GetEndpointType is a helper method to determine the endpoint type
|
|
|
|
// requested by the user.
|
|
|
|
func GetEndpointType(endpointType string) gophercloud.Availability {
|
|
|
|
if endpointType == "internal" || endpointType == "internalURL" {
|
|
|
|
return gophercloud.AvailabilityInternal
|
|
|
|
}
|
|
|
|
if endpointType == "admin" || endpointType == "adminURL" {
|
|
|
|
return gophercloud.AvailabilityAdmin
|
|
|
|
}
|
|
|
|
return gophercloud.AvailabilityPublic
|
|
|
|
}
|