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 }