github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/allocrunner/taskrunner/getter/getter.go (about)

     1  package getter
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"sync"
    10  
    11  	gg "github.com/hashicorp/go-getter"
    12  
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  )
    15  
    16  var (
    17  	// getters is the map of getters suitable for Nomad. It is initialized once
    18  	// and the lock is used to guard access to it.
    19  	getters map[string]gg.Getter
    20  	lock    sync.Mutex
    21  
    22  	// supported is the set of download schemes supported by Nomad
    23  	supported = []string{"http", "https", "s3", "hg", "git", "gcs"}
    24  )
    25  
    26  const (
    27  	// gitSSHPrefix is the prefix for downloading via git using ssh
    28  	gitSSHPrefix = "git@github.com:"
    29  )
    30  
    31  // EnvReplacer is an interface which can interpolate environment variables and
    32  // is usually satisfied by taskenv.TaskEnv.
    33  type EnvReplacer interface {
    34  	ReplaceEnv(string) string
    35  	ClientPath(string, bool) (string, bool)
    36  }
    37  
    38  func makeGetters(headers http.Header) map[string]gg.Getter {
    39  	getters := make(map[string]gg.Getter, len(supported))
    40  	for _, getter := range supported {
    41  		switch {
    42  		case getter == "http" && len(headers) > 0:
    43  			fallthrough
    44  		case getter == "https" && len(headers) > 0:
    45  			getters[getter] = &gg.HttpGetter{
    46  				Netrc:  true,
    47  				Header: headers,
    48  			}
    49  		default:
    50  			if defaultGetter, ok := gg.Getters[getter]; ok {
    51  				getters[getter] = defaultGetter
    52  			}
    53  		}
    54  	}
    55  	return getters
    56  }
    57  
    58  // getClient returns a client that is suitable for Nomad downloading artifacts.
    59  func getClient(src string, headers http.Header, mode gg.ClientMode, dst string) *gg.Client {
    60  	client := &gg.Client{
    61  		Src:   src,
    62  		Dst:   dst,
    63  		Mode:  mode,
    64  		Umask: 060000000,
    65  	}
    66  
    67  	switch len(headers) {
    68  	case 0:
    69  		// When no headers are present use the memoized getters, creating them
    70  		// on demand if they do not exist yet.
    71  		lock.Lock()
    72  		if getters == nil {
    73  			getters = makeGetters(nil)
    74  		}
    75  		lock.Unlock()
    76  		client.Getters = getters
    77  	default:
    78  		// When there are headers present, we must create fresh gg.HttpGetter
    79  		// objects, because that is where gg stores the headers to use in its
    80  		// artifact HTTP GET requests.
    81  		client.Getters = makeGetters(headers)
    82  	}
    83  
    84  	return client
    85  }
    86  
    87  // getGetterUrl returns the go-getter URL to download the artifact.
    88  func getGetterUrl(taskEnv EnvReplacer, artifact *structs.TaskArtifact) (string, error) {
    89  	source := taskEnv.ReplaceEnv(artifact.GetterSource)
    90  
    91  	// Handle an invalid URL when given a go-getter url such as
    92  	// git@github.com:hashicorp/nomad.git
    93  	gitSSH := false
    94  	if strings.HasPrefix(source, gitSSHPrefix) {
    95  		gitSSH = true
    96  		source = source[len(gitSSHPrefix):]
    97  	}
    98  
    99  	u, err := url.Parse(source)
   100  	if err != nil {
   101  		return "", fmt.Errorf("failed to parse source URL %q: %v", artifact.GetterSource, err)
   102  	}
   103  
   104  	// Build the url
   105  	q := u.Query()
   106  	for k, v := range artifact.GetterOptions {
   107  		q.Add(k, taskEnv.ReplaceEnv(v))
   108  	}
   109  	u.RawQuery = q.Encode()
   110  
   111  	// Add the prefix back
   112  	ggURL := u.String()
   113  	if gitSSH {
   114  		ggURL = fmt.Sprintf("%s%s", gitSSHPrefix, ggURL)
   115  	}
   116  
   117  	return ggURL, nil
   118  }
   119  
   120  func getHeaders(env EnvReplacer, m map[string]string) http.Header {
   121  	if len(m) == 0 {
   122  		return nil
   123  	}
   124  
   125  	headers := make(http.Header, len(m))
   126  	for k, v := range m {
   127  		headers.Set(k, env.ReplaceEnv(v))
   128  	}
   129  	return headers
   130  }
   131  
   132  // GetArtifact downloads an artifact into the specified task directory.
   133  func GetArtifact(taskEnv EnvReplacer, artifact *structs.TaskArtifact) error {
   134  	ggURL, err := getGetterUrl(taskEnv, artifact)
   135  	if err != nil {
   136  		return newGetError(artifact.GetterSource, err, false)
   137  	}
   138  
   139  	dest, escapes := taskEnv.ClientPath(artifact.RelativeDest, true)
   140  	// Verify the destination is still in the task sandbox after interpolation
   141  	if escapes {
   142  		return newGetError(artifact.RelativeDest,
   143  			errors.New("artifact destination path escapes the alloc directory"),
   144  			false)
   145  	}
   146  
   147  	// Convert from string getter mode to go-getter const
   148  	mode := gg.ClientModeAny
   149  	switch artifact.GetterMode {
   150  	case structs.GetterModeFile:
   151  		mode = gg.ClientModeFile
   152  	case structs.GetterModeDir:
   153  		mode = gg.ClientModeDir
   154  	}
   155  
   156  	headers := getHeaders(taskEnv, artifact.GetterHeaders)
   157  	if err := getClient(ggURL, headers, mode, dest).Get(); err != nil {
   158  		return newGetError(ggURL, err, true)
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  // GetError wraps the underlying artifact fetching error with the URL. It
   165  // implements the RecoverableError interface.
   166  type GetError struct {
   167  	URL         string
   168  	Err         error
   169  	recoverable bool
   170  }
   171  
   172  func newGetError(url string, err error, recoverable bool) *GetError {
   173  	return &GetError{
   174  		URL:         url,
   175  		Err:         err,
   176  		recoverable: recoverable,
   177  	}
   178  }
   179  
   180  func (g *GetError) Error() string {
   181  	return g.Err.Error()
   182  }
   183  
   184  func (g *GetError) IsRecoverable() bool {
   185  	return g.recoverable
   186  }