github.com/homburg/packer@v0.6.1-0.20140528012651-1dcaf1716848/common/config.go (about) 1 package common 2 3 import ( 4 "fmt" 5 "github.com/mitchellh/mapstructure" 6 "github.com/mitchellh/packer/packer" 7 "net/url" 8 "os" 9 "path/filepath" 10 "reflect" 11 "runtime" 12 "sort" 13 "strings" 14 ) 15 16 // ScrubConfig is a helper that returns a string representation of 17 // any struct with the given values stripped out. 18 func ScrubConfig(target interface{}, values ...string) string { 19 conf := fmt.Sprintf("Config: %+v", target) 20 for _, value := range values { 21 conf = strings.Replace(conf, value, "<Filtered>", -1) 22 } 23 return conf 24 } 25 26 // CheckUnusedConfig is a helper that makes sure that the there are no 27 // unused configuration keys, properly ignoring keys that don't matter. 28 func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError { 29 errs := make([]error, 0) 30 31 if md.Unused != nil && len(md.Unused) > 0 { 32 sort.Strings(md.Unused) 33 for _, unused := range md.Unused { 34 if unused != "type" && !strings.HasPrefix(unused, "packer_") { 35 errs = append( 36 errs, fmt.Errorf("Unknown configuration key: %s", unused)) 37 } 38 } 39 } 40 41 if len(errs) > 0 { 42 return &packer.MultiError{errs} 43 } 44 45 return nil 46 } 47 48 // ChooseString returns the first non-empty value. 49 func ChooseString(vals ...string) string { 50 for _, el := range vals { 51 if el != "" { 52 return el 53 } 54 } 55 56 return "" 57 } 58 59 // DecodeConfig is a helper that handles decoding raw configuration using 60 // mapstructure. It returns the metadata and any errors that may happen. 61 // If you need extra configuration for mapstructure, you should configure 62 // it manually and not use this helper function. 63 func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) { 64 decodeHook, err := decodeConfigHook(raws) 65 if err != nil { 66 return nil, err 67 } 68 69 var md mapstructure.Metadata 70 decoderConfig := &mapstructure.DecoderConfig{ 71 DecodeHook: mapstructure.ComposeDecodeHookFunc( 72 decodeHook, 73 mapstructure.StringToSliceHookFunc(","), 74 ), 75 Metadata: &md, 76 Result: target, 77 WeaklyTypedInput: true, 78 } 79 80 decoder, err := mapstructure.NewDecoder(decoderConfig) 81 if err != nil { 82 return nil, err 83 } 84 85 for _, raw := range raws { 86 err := decoder.Decode(raw) 87 if err != nil { 88 return nil, err 89 } 90 } 91 92 return &md, nil 93 } 94 95 // DownloadableURL processes a URL that may also be a file path and returns 96 // a completely valid URL. For example, the original URL might be "local/file.iso" 97 // which isn't a valid URL. DownloadableURL will return "file:///local/file.iso" 98 func DownloadableURL(original string) (string, error) { 99 if runtime.GOOS == "windows" { 100 // If the distance to the first ":" is just one character, assume 101 // we're dealing with a drive letter and thus a file path. 102 idx := strings.Index(original, ":") 103 if idx == 1 { 104 original = "file:///" + original 105 } 106 } 107 108 url, err := url.Parse(original) 109 if err != nil { 110 return "", err 111 } 112 113 if url.Scheme == "" { 114 url.Scheme = "file" 115 } 116 117 if url.Scheme == "file" { 118 // Windows file handling is all sorts of tricky... 119 if runtime.GOOS == "windows" { 120 // If the path is using Windows-style slashes, URL parses 121 // it into the host field. 122 if url.Path == "" && strings.Contains(url.Host, `\`) { 123 url.Path = url.Host 124 url.Host = "" 125 } 126 127 // For Windows absolute file paths, remove leading / prior to processing 128 // since net/url turns "C:/" into "/C:/" 129 if len(url.Path) > 0 && url.Path[0] == '/' { 130 url.Path = url.Path[1:len(url.Path)] 131 } 132 } 133 134 // Only do the filepath transformations if the file appears 135 // to actually exist. 136 if _, err := os.Stat(url.Path); err == nil { 137 url.Path, err = filepath.Abs(url.Path) 138 if err != nil { 139 return "", err 140 } 141 142 url.Path, err = filepath.EvalSymlinks(url.Path) 143 if err != nil { 144 return "", err 145 } 146 147 url.Path = filepath.Clean(url.Path) 148 } 149 150 if runtime.GOOS == "windows" { 151 // Also replace all backslashes with forwardslashes since Windows 152 // users are likely to do this but the URL should actually only 153 // contain forward slashes. 154 url.Path = strings.Replace(url.Path, `\`, `/`, -1) 155 } 156 } 157 158 // Make sure it is lowercased 159 url.Scheme = strings.ToLower(url.Scheme) 160 161 // This is to work around issue #5927. This can safely be removed once 162 // we distribute with a version of Go that fixes that bug. 163 // 164 // See: https://code.google.com/p/go/issues/detail?id=5927 165 if url.Path != "" && url.Path[0] != '/' { 166 url.Path = "/" + url.Path 167 } 168 169 // Verify that the scheme is something we support in our common downloader. 170 supported := []string{"file", "http", "https"} 171 found := false 172 for _, s := range supported { 173 if url.Scheme == s { 174 found = true 175 break 176 } 177 } 178 179 if !found { 180 return "", fmt.Errorf("Unsupported URL scheme: %s", url.Scheme) 181 } 182 183 return url.String(), nil 184 } 185 186 // This returns a mapstructure.DecodeHookFunc that automatically template 187 // processes any configuration values that aren't strings but have been 188 // provided as strings. 189 // 190 // For example: "image_id" wants an int and the user uses a string with 191 // a user variable like "{{user `image_id`}}". This decode hook makes that 192 // work. 193 func decodeConfigHook(raws []interface{}) (mapstructure.DecodeHookFunc, error) { 194 // First thing we do is decode PackerConfig so that we can have access 195 // to the user variables so that we can process some templates. 196 var pc PackerConfig 197 198 decoderConfig := &mapstructure.DecoderConfig{ 199 Result: &pc, 200 WeaklyTypedInput: true, 201 } 202 decoder, err := mapstructure.NewDecoder(decoderConfig) 203 if err != nil { 204 return nil, err 205 } 206 for _, raw := range raws { 207 if err := decoder.Decode(raw); err != nil { 208 return nil, err 209 } 210 } 211 212 tpl, err := packer.NewConfigTemplate() 213 if err != nil { 214 return nil, err 215 } 216 tpl.UserVars = pc.PackerUserVars 217 218 return func(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) { 219 if t != reflect.String { 220 // We need to convert []uint8 to string. We have to do this 221 // because internally Packer uses MsgPack for RPC and the MsgPack 222 // codec turns strings into []uint8 223 if f == reflect.Slice { 224 dataVal := reflect.ValueOf(v) 225 dataType := dataVal.Type() 226 elemKind := dataType.Elem().Kind() 227 if elemKind == reflect.Uint8 { 228 v = string(dataVal.Interface().([]uint8)) 229 } 230 } 231 232 if sv, ok := v.(string); ok { 233 var err error 234 v, err = tpl.Process(sv, nil) 235 if err != nil { 236 return nil, err 237 } 238 } 239 } 240 241 return v, nil 242 }, nil 243 }