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  }