github.com/manicqin/nomad@v0.9.5/client/allocrunner/taskrunner/getter/getter_test.go (about) 1 package getter 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/http/httptest" 8 "os" 9 "path/filepath" 10 "reflect" 11 "runtime" 12 "strings" 13 "testing" 14 15 "github.com/hashicorp/nomad/client/taskenv" 16 "github.com/hashicorp/nomad/nomad/mock" 17 "github.com/hashicorp/nomad/nomad/structs" 18 "github.com/stretchr/testify/require" 19 ) 20 21 // fakeReplacer is a noop version of taskenv.TaskEnv.ReplaceEnv 22 type fakeReplacer struct{} 23 24 func (fakeReplacer) ReplaceEnv(s string) string { 25 return s 26 } 27 28 var taskEnv = fakeReplacer{} 29 30 func TestGetArtifact_FileAndChecksum(t *testing.T) { 31 // Create the test server hosting the file to download 32 ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/")))) 33 defer ts.Close() 34 35 // Create a temp directory to download into 36 taskDir, err := ioutil.TempDir("", "nomad-test") 37 if err != nil { 38 t.Fatalf("failed to make temp directory: %v", err) 39 } 40 defer os.RemoveAll(taskDir) 41 42 // Create the artifact 43 file := "test.sh" 44 artifact := &structs.TaskArtifact{ 45 GetterSource: fmt.Sprintf("%s/%s", ts.URL, file), 46 GetterOptions: map[string]string{ 47 "checksum": "md5:bce963762aa2dbfed13caf492a45fb72", 48 }, 49 } 50 51 // Download the artifact 52 if err := GetArtifact(taskEnv, artifact, taskDir); err != nil { 53 t.Fatalf("GetArtifact failed: %v", err) 54 } 55 56 // Verify artifact exists 57 if _, err := os.Stat(filepath.Join(taskDir, file)); err != nil { 58 t.Fatalf("file not found: %s", err) 59 } 60 } 61 62 func TestGetArtifact_File_RelativeDest(t *testing.T) { 63 // Create the test server hosting the file to download 64 ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/")))) 65 defer ts.Close() 66 67 // Create a temp directory to download into 68 taskDir, err := ioutil.TempDir("", "nomad-test") 69 if err != nil { 70 t.Fatalf("failed to make temp directory: %v", err) 71 } 72 defer os.RemoveAll(taskDir) 73 74 // Create the artifact 75 file := "test.sh" 76 relative := "foo/" 77 artifact := &structs.TaskArtifact{ 78 GetterSource: fmt.Sprintf("%s/%s", ts.URL, file), 79 GetterOptions: map[string]string{ 80 "checksum": "md5:bce963762aa2dbfed13caf492a45fb72", 81 }, 82 RelativeDest: relative, 83 } 84 85 // Download the artifact 86 if err := GetArtifact(taskEnv, artifact, taskDir); err != nil { 87 t.Fatalf("GetArtifact failed: %v", err) 88 } 89 90 // Verify artifact was downloaded to the correct path 91 if _, err := os.Stat(filepath.Join(taskDir, relative, file)); err != nil { 92 t.Fatalf("file not found: %s", err) 93 } 94 } 95 96 func TestGetGetterUrl_Interpolation(t *testing.T) { 97 // Create the artifact 98 artifact := &structs.TaskArtifact{ 99 GetterSource: "${NOMAD_META_ARTIFACT}", 100 } 101 102 url := "foo.com" 103 alloc := mock.Alloc() 104 task := alloc.Job.TaskGroups[0].Tasks[0] 105 task.Meta = map[string]string{"artifact": url} 106 taskEnv := taskenv.NewBuilder(mock.Node(), alloc, task, "global").Build() 107 108 act, err := getGetterUrl(taskEnv, artifact) 109 if err != nil { 110 t.Fatalf("getGetterUrl() failed: %v", err) 111 } 112 113 if act != url { 114 t.Fatalf("getGetterUrl() returned %q; want %q", act, url) 115 } 116 } 117 118 func TestGetArtifact_InvalidChecksum(t *testing.T) { 119 // Create the test server hosting the file to download 120 ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/")))) 121 defer ts.Close() 122 123 // Create a temp directory to download into 124 taskDir, err := ioutil.TempDir("", "nomad-test") 125 if err != nil { 126 t.Fatalf("failed to make temp directory: %v", err) 127 } 128 defer os.RemoveAll(taskDir) 129 130 // Create the artifact with an incorrect checksum 131 file := "test.sh" 132 artifact := &structs.TaskArtifact{ 133 GetterSource: fmt.Sprintf("%s/%s", ts.URL, file), 134 GetterOptions: map[string]string{ 135 "checksum": "md5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 136 }, 137 } 138 139 // Download the artifact and expect an error 140 if err := GetArtifact(taskEnv, artifact, taskDir); err == nil { 141 t.Fatalf("GetArtifact should have failed") 142 } 143 } 144 145 func createContents(basedir string, fileContents map[string]string, t *testing.T) { 146 for relPath, content := range fileContents { 147 folder := basedir 148 if strings.Index(relPath, "/") != -1 { 149 // Create the folder. 150 folder = filepath.Join(basedir, filepath.Dir(relPath)) 151 if err := os.Mkdir(folder, 0777); err != nil { 152 t.Fatalf("failed to make directory: %v", err) 153 } 154 } 155 156 // Create a file in the existing folder. 157 file := filepath.Join(folder, filepath.Base(relPath)) 158 if err := ioutil.WriteFile(file, []byte(content), 0777); err != nil { 159 t.Fatalf("failed to write data to file %v: %v", file, err) 160 } 161 } 162 } 163 164 func checkContents(basedir string, fileContents map[string]string, t *testing.T) { 165 for relPath, content := range fileContents { 166 path := filepath.Join(basedir, relPath) 167 actual, err := ioutil.ReadFile(path) 168 if err != nil { 169 t.Fatalf("failed to read file %q: %v", path, err) 170 } 171 172 if !reflect.DeepEqual(actual, []byte(content)) { 173 t.Fatalf("%q: expected %q; got %q", path, content, string(actual)) 174 } 175 } 176 } 177 178 func TestGetArtifact_Archive(t *testing.T) { 179 // Create the test server hosting the file to download 180 ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/")))) 181 defer ts.Close() 182 183 // Create a temp directory to download into and create some of the same 184 // files that exist in the artifact to ensure they are overridden 185 taskDir, err := ioutil.TempDir("", "nomad-test") 186 if err != nil { 187 t.Fatalf("failed to make temp directory: %v", err) 188 } 189 defer os.RemoveAll(taskDir) 190 191 create := map[string]string{ 192 "exist/my.config": "to be replaced", 193 "untouched": "existing top-level", 194 } 195 createContents(taskDir, create, t) 196 197 file := "archive.tar.gz" 198 artifact := &structs.TaskArtifact{ 199 GetterSource: fmt.Sprintf("%s/%s", ts.URL, file), 200 GetterOptions: map[string]string{ 201 "checksum": "sha1:20bab73c72c56490856f913cf594bad9a4d730f6", 202 }, 203 } 204 205 if err := GetArtifact(taskEnv, artifact, taskDir); err != nil { 206 t.Fatalf("GetArtifact failed: %v", err) 207 } 208 209 // Verify the unarchiving overrode files properly. 210 expected := map[string]string{ 211 "untouched": "existing top-level", 212 "exist/my.config": "hello world\n", 213 "new/my.config": "hello world\n", 214 "test.sh": "sleep 1\n", 215 } 216 checkContents(taskDir, expected, t) 217 } 218 219 func TestGetArtifact_Setuid(t *testing.T) { 220 // Create the test server hosting the file to download 221 ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir("./test-fixtures/")))) 222 defer ts.Close() 223 224 // Create a temp directory to download into and create some of the same 225 // files that exist in the artifact to ensure they are overridden 226 taskDir, err := ioutil.TempDir("", "nomad-test") 227 require.NoError(t, err) 228 defer os.RemoveAll(taskDir) 229 230 file := "setuid.tgz" 231 artifact := &structs.TaskArtifact{ 232 GetterSource: fmt.Sprintf("%s/%s", ts.URL, file), 233 GetterOptions: map[string]string{ 234 "checksum": "sha1:e892194748ecbad5d0f60c6c6b2db2bdaa384a90", 235 }, 236 } 237 238 require.NoError(t, GetArtifact(taskEnv, artifact, taskDir)) 239 240 var expected map[string]int 241 242 if runtime.GOOS == "windows" { 243 // windows doesn't support Chmod changing file permissions. 244 expected = map[string]int{ 245 "public": 0666, 246 "private": 0666, 247 "setuid": 0666, 248 } 249 } else { 250 // Verify the unarchiving masked files properly. 251 expected = map[string]int{ 252 "public": 0666, 253 "private": 0600, 254 "setuid": 0755, 255 } 256 } 257 258 for file, perm := range expected { 259 path := filepath.Join(taskDir, "setuid", file) 260 s, err := os.Stat(path) 261 require.NoError(t, err) 262 p := os.FileMode(perm) 263 o := s.Mode() 264 require.Equalf(t, p, o, "%s expected %o found %o", file, p, o) 265 } 266 } 267 268 func TestGetGetterUrl_Queries(t *testing.T) { 269 cases := []struct { 270 name string 271 artifact *structs.TaskArtifact 272 output string 273 }{ 274 { 275 name: "adds query parameters", 276 artifact: &structs.TaskArtifact{ 277 GetterSource: "https://foo.com?test=1", 278 GetterOptions: map[string]string{ 279 "foo": "bar", 280 "bam": "boom", 281 }, 282 }, 283 output: "https://foo.com?bam=boom&foo=bar&test=1", 284 }, 285 { 286 name: "git without http", 287 artifact: &structs.TaskArtifact{ 288 GetterSource: "github.com/hashicorp/nomad", 289 GetterOptions: map[string]string{ 290 "ref": "abcd1234", 291 }, 292 }, 293 output: "github.com/hashicorp/nomad?ref=abcd1234", 294 }, 295 { 296 name: "git using ssh", 297 artifact: &structs.TaskArtifact{ 298 GetterSource: "git@github.com:hashicorp/nomad?sshkey=1", 299 GetterOptions: map[string]string{ 300 "ref": "abcd1234", 301 }, 302 }, 303 output: "git@github.com:hashicorp/nomad?ref=abcd1234&sshkey=1", 304 }, 305 { 306 name: "s3 scheme 1", 307 artifact: &structs.TaskArtifact{ 308 GetterSource: "s3::https://s3.amazonaws.com/bucket/foo", 309 GetterOptions: map[string]string{ 310 "aws_access_key_id": "abcd1234", 311 }, 312 }, 313 output: "s3::https://s3.amazonaws.com/bucket/foo?aws_access_key_id=abcd1234", 314 }, 315 { 316 name: "s3 scheme 2", 317 artifact: &structs.TaskArtifact{ 318 GetterSource: "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo", 319 GetterOptions: map[string]string{ 320 "aws_access_key_id": "abcd1234", 321 }, 322 }, 323 output: "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo?aws_access_key_id=abcd1234", 324 }, 325 { 326 name: "s3 scheme 3", 327 artifact: &structs.TaskArtifact{ 328 GetterSource: "bucket.s3.amazonaws.com/foo", 329 GetterOptions: map[string]string{ 330 "aws_access_key_id": "abcd1234", 331 }, 332 }, 333 output: "bucket.s3.amazonaws.com/foo?aws_access_key_id=abcd1234", 334 }, 335 { 336 name: "s3 scheme 4", 337 artifact: &structs.TaskArtifact{ 338 GetterSource: "bucket.s3-eu-west-1.amazonaws.com/foo/bar", 339 GetterOptions: map[string]string{ 340 "aws_access_key_id": "abcd1234", 341 }, 342 }, 343 output: "bucket.s3-eu-west-1.amazonaws.com/foo/bar?aws_access_key_id=abcd1234", 344 }, 345 { 346 name: "gcs", 347 artifact: &structs.TaskArtifact{ 348 GetterSource: "gcs::https://www.googleapis.com/storage/v1/b/d/f", 349 }, 350 output: "gcs::https://www.googleapis.com/storage/v1/b/d/f", 351 }, 352 { 353 name: "local file", 354 artifact: &structs.TaskArtifact{ 355 GetterSource: "/foo/bar", 356 }, 357 output: "/foo/bar", 358 }, 359 } 360 361 for _, c := range cases { 362 t.Run(c.name, func(t *testing.T) { 363 act, err := getGetterUrl(taskEnv, c.artifact) 364 if err != nil { 365 t.Fatalf("want %q; got err %v", c.output, err) 366 } else if act != c.output { 367 t.Fatalf("want %q; got %q", c.output, act) 368 } 369 }) 370 } 371 }