github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/getter/getter.go (about)

     1  package getter
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"path/filepath"
     7  	"strings"
     8  	"sync"
     9  
    10  	gg "github.com/hashicorp/go-getter"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  )
    13  
    14  var (
    15  	// getters is the map of getters suitable for Nomad. It is initialized once
    16  	// and the lock is used to guard access to it.
    17  	getters map[string]gg.Getter
    18  	lock    sync.Mutex
    19  
    20  	// supported is the set of download schemes supported by Nomad
    21  	supported = []string{"http", "https", "s3", "hg", "git"}
    22  )
    23  
    24  const (
    25  	// gitSSHPrefix is the prefix for dowwnloading via git using ssh
    26  	gitSSHPrefix = "git@github.com:"
    27  )
    28  
    29  // EnvReplacer is an interface which can interpolate environment variables and
    30  // is usually satisfied by env.TaskEnv.
    31  type EnvReplacer interface {
    32  	ReplaceEnv(string) string
    33  }
    34  
    35  // getClient returns a client that is suitable for Nomad downloading artifacts.
    36  func getClient(src string, mode gg.ClientMode, dst string) *gg.Client {
    37  	lock.Lock()
    38  	defer lock.Unlock()
    39  
    40  	// Return the pre-initialized client
    41  	if getters == nil {
    42  		getters = make(map[string]gg.Getter, len(supported))
    43  		for _, getter := range supported {
    44  			if impl, ok := gg.Getters[getter]; ok {
    45  				getters[getter] = impl
    46  			}
    47  		}
    48  	}
    49  
    50  	return &gg.Client{
    51  		Src:     src,
    52  		Dst:     dst,
    53  		Mode:    mode,
    54  		Getters: getters,
    55  	}
    56  }
    57  
    58  // getGetterUrl returns the go-getter URL to download the artifact.
    59  func getGetterUrl(taskEnv EnvReplacer, artifact *structs.TaskArtifact) (string, error) {
    60  	source := taskEnv.ReplaceEnv(artifact.GetterSource)
    61  
    62  	// Handle an invalid URL when given a go-getter url such as
    63  	// git@github.com:hashicorp/nomad.git
    64  	gitSSH := false
    65  	if strings.HasPrefix(source, gitSSHPrefix) {
    66  		gitSSH = true
    67  		source = source[len(gitSSHPrefix):]
    68  	}
    69  
    70  	u, err := url.Parse(source)
    71  	if err != nil {
    72  		return "", fmt.Errorf("failed to parse source URL %q: %v", artifact.GetterSource, err)
    73  	}
    74  
    75  	// Build the url
    76  	q := u.Query()
    77  	for k, v := range artifact.GetterOptions {
    78  		q.Add(k, taskEnv.ReplaceEnv(v))
    79  	}
    80  	u.RawQuery = q.Encode()
    81  
    82  	// Add the prefix back
    83  	url := u.String()
    84  	if gitSSH {
    85  		url = fmt.Sprintf("%s%s", gitSSHPrefix, url)
    86  	}
    87  
    88  	return url, nil
    89  }
    90  
    91  // GetArtifact downloads an artifact into the specified task directory.
    92  func GetArtifact(taskEnv EnvReplacer, artifact *structs.TaskArtifact, taskDir string) error {
    93  	url, err := getGetterUrl(taskEnv, artifact)
    94  	if err != nil {
    95  		return newGetError(artifact.GetterSource, err, false)
    96  	}
    97  
    98  	// Download the artifact
    99  	dest := filepath.Join(taskDir, artifact.RelativeDest)
   100  
   101  	// Convert from string getter mode to go-getter const
   102  	mode := gg.ClientModeAny
   103  	switch artifact.GetterMode {
   104  	case structs.GetterModeFile:
   105  		mode = gg.ClientModeFile
   106  	case structs.GetterModeDir:
   107  		mode = gg.ClientModeDir
   108  	}
   109  
   110  	if err := getClient(url, mode, dest).Get(); err != nil {
   111  		return newGetError(url, err, true)
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // GetError wraps the underlying artifact fetching error with the URL. It
   118  // implements the RecoverableError interface.
   119  type GetError struct {
   120  	URL         string
   121  	Err         error
   122  	recoverable bool
   123  }
   124  
   125  func newGetError(url string, err error, recoverable bool) *GetError {
   126  	return &GetError{
   127  		URL:         url,
   128  		Err:         err,
   129  		recoverable: recoverable,
   130  	}
   131  }
   132  
   133  func (g *GetError) Error() string {
   134  	return g.Err.Error()
   135  }
   136  
   137  func (g *GetError) IsRecoverable() bool {
   138  	return g.recoverable
   139  }