github.com/kaixiang/packer@v0.5.2-0.20140114230416-1f5786b0d7f1/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  }