github.com/sneal/packer@v0.5.2/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 // DecodeConfig is a helper that handles decoding raw configuration using 49 // mapstructure. It returns the metadata and any errors that may happen. 50 // If you need extra configuration for mapstructure, you should configure 51 // it manually and not use this helper function. 52 func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) { 53 decodeHook, err := decodeConfigHook(raws) 54 if err != nil { 55 return nil, err 56 } 57 58 var md mapstructure.Metadata 59 decoderConfig := &mapstructure.DecoderConfig{ 60 DecodeHook: decodeHook, 61 Metadata: &md, 62 Result: target, 63 WeaklyTypedInput: true, 64 } 65 66 decoder, err := mapstructure.NewDecoder(decoderConfig) 67 if err != nil { 68 return nil, err 69 } 70 71 for _, raw := range raws { 72 err := decoder.Decode(raw) 73 if err != nil { 74 return nil, err 75 } 76 } 77 78 return &md, nil 79 } 80 81 // DownloadableURL processes a URL that may also be a file path and returns 82 // a completely valid URL. For example, the original URL might be "local/file.iso" 83 // which isn't a valid URL. DownloadableURL will return "file:///local/file.iso" 84 func DownloadableURL(original string) (string, error) { 85 if runtime.GOOS == "windows" { 86 // If the distance to the first ":" is just one character, assume 87 // we're dealing with a drive letter and thus a file path. 88 idx := strings.Index(original, ":") 89 if idx == 1 { 90 original = "file:///" + original 91 } 92 } 93 94 url, err := url.Parse(original) 95 if err != nil { 96 return "", err 97 } 98 99 if url.Scheme == "" { 100 url.Scheme = "file" 101 } 102 103 if url.Scheme == "file" { 104 // For Windows absolute file paths, remove leading / prior to processing 105 // since net/url turns "C:/" into "/C:/" 106 if runtime.GOOS == "windows" && url.Path[0] == '/' { 107 url.Path = url.Path[1:len(url.Path)] 108 109 // Also replace all backslashes with forwardslashes since Windows 110 // users are likely to do this but the URL should actually only 111 // contain forward slashes. 112 url.Path = strings.Replace(url.Path, `\`, `/`, -1) 113 } 114 115 // Only do the filepath transformations if the file appears 116 // to actually exist. 117 if _, err := os.Stat(url.Path); err == nil { 118 url.Path, err = filepath.Abs(url.Path) 119 if err != nil { 120 return "", err 121 } 122 123 url.Path, err = filepath.EvalSymlinks(url.Path) 124 if err != nil { 125 return "", err 126 } 127 128 url.Path = filepath.Clean(url.Path) 129 } 130 } 131 132 // Make sure it is lowercased 133 url.Scheme = strings.ToLower(url.Scheme) 134 135 // This is to work around issue #5927. This can safely be removed once 136 // we distribute with a version of Go that fixes that bug. 137 // 138 // See: https://code.google.com/p/go/issues/detail?id=5927 139 if url.Path != "" && url.Path[0] != '/' { 140 url.Path = "/" + url.Path 141 } 142 143 // Verify that the scheme is something we support in our common downloader. 144 supported := []string{"file", "http", "https"} 145 found := false 146 for _, s := range supported { 147 if url.Scheme == s { 148 found = true 149 break 150 } 151 } 152 153 if !found { 154 return "", fmt.Errorf("Unsupported URL scheme: %s", url.Scheme) 155 } 156 157 return url.String(), nil 158 } 159 160 // This returns a mapstructure.DecodeHookFunc that automatically template 161 // processes any configuration values that aren't strings but have been 162 // provided as strings. 163 // 164 // For example: "image_id" wants an int and the user uses a string with 165 // a user variable like "{{user `image_id`}}". This decode hook makes that 166 // work. 167 func decodeConfigHook(raws []interface{}) (mapstructure.DecodeHookFunc, error) { 168 // First thing we do is decode PackerConfig so that we can have access 169 // to the user variables so that we can process some templates. 170 var pc PackerConfig 171 for _, raw := range raws { 172 if err := mapstructure.Decode(raw, &pc); err != nil { 173 return nil, err 174 } 175 } 176 177 tpl, err := packer.NewConfigTemplate() 178 if err != nil { 179 return nil, err 180 } 181 tpl.UserVars = pc.PackerUserVars 182 183 return func(f reflect.Kind, t reflect.Kind, v interface{}) (interface{}, error) { 184 if t != reflect.String { 185 if sv, ok := v.(string); ok { 186 var err error 187 v, err = tpl.Process(sv, nil) 188 if err != nil { 189 return nil, err 190 } 191 } 192 } 193 194 return v, nil 195 }, nil 196 }