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