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 }