github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/distgo/cmd/publish/cmd_test.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package publish 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io/ioutil" 22 "net/http" 23 "net/http/httptest" 24 "os" 25 "path" 26 "path/filepath" 27 "regexp" 28 "strings" 29 "testing" 30 31 "github.com/nmiyake/pkg/dirs" 32 "github.com/palantir/pkg/cli/cfgcli" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 36 "github.com/palantir/godel/apps/distgo/cmd/artifacts" 37 "github.com/palantir/godel/apps/distgo/cmd/build" 38 "github.com/palantir/godel/apps/distgo/config" 39 "github.com/palantir/godel/apps/distgo/pkg/git/gittest" 40 ) 41 42 const testMain = "package main; func main(){}" 43 44 func TestPublishBatchErrors(t *testing.T) { 45 var handlerFunc func(w http.ResponseWriter, r *http.Request) 46 handlerFuncPtr := &handlerFunc 47 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 48 localHandlerFunc := *handlerFuncPtr 49 localHandlerFunc(w, r) 50 })) 51 defer ts.Close() 52 53 tmp, cleanup, err := dirs.TempDir(".", "") 54 defer cleanup() 55 require.NoError(t, err) 56 57 wd, err := os.Getwd() 58 defer func() { 59 if err := os.Chdir(wd); err != nil { 60 fmt.Printf("Failed to restore working directory to %s: %v\n", wd, err) 61 } 62 }() 63 require.NoError(t, err) 64 65 for i, currCase := range []struct { 66 cfg string 67 mainFiles []string 68 publishProducts []string 69 handler func(w http.ResponseWriter, r *http.Request) 70 failFast bool 71 wantOutputRegexp string 72 notWantOutputRegexp string 73 wantErrorRegexps []string 74 }{ 75 // if failFast is false, all products should attempt publish 76 { 77 cfg: ` 78 products: 79 test-bar: 80 build: 81 main-pkg: ./bar 82 test-baz: 83 build: 84 main-pkg: ./baz 85 test-foo: 86 build: 87 main-pkg: ./foo 88 group-id: com.palantir.distgo-cmd-test`, 89 mainFiles: []string{"foo/main.go", "bar/main.go", "baz/main.go"}, 90 publishProducts: []string{"test-foo", "test-bar", "test-baz"}, 91 handler: func(w http.ResponseWriter, r *http.Request) { 92 status := http.StatusOK 93 if strings.Contains(r.URL.String(), "test-bar") || strings.Contains(r.URL.String(), "test-baz") { 94 // fail all test-bar and test-baz publishes 95 status = http.StatusNotFound 96 } 97 w.WriteHeader(status) 98 }, 99 wantOutputRegexp: `(?s).+Uploading dist/test-foo-unspecified.pom to .+`, 100 notWantOutputRegexp: `(?s).+Uploading dist/test-bar-unspecified.pom to .+`, 101 wantErrorRegexps: []string{`Publish failed for test-bar: uploading .+ to .+ resulted in response "404 Not Found"`, `Publish failed for test-baz: uploading .+ to .+ resulted in response "404 Not Found"`}, 102 }, 103 // if failFast is true, first fail should terminate publish 104 { 105 cfg: ` 106 products: 107 test-bar: 108 build: 109 main-pkg: ./bar 110 test-baz: 111 build: 112 main-pkg: ./baz 113 test-foo: 114 build: 115 main-pkg: ./foo 116 group-id: com.palantir.distgo-cmd-test`, 117 mainFiles: []string{"foo/main.go", "bar/main.go", "baz/main.go"}, 118 publishProducts: []string{"test-foo", "test-bar", "test-baz"}, 119 handler: func(w http.ResponseWriter, r *http.Request) { 120 status := http.StatusOK 121 if strings.Contains(r.URL.String(), "test-bar") || strings.Contains(r.URL.String(), "test-baz") { 122 // fail all test-bar and test-baz publishes 123 status = http.StatusNotFound 124 } 125 w.WriteHeader(status) 126 }, 127 failFast: true, 128 notWantOutputRegexp: `(?s).+Uploading dist/test-bar-unspecified.tgz to .+`, 129 wantErrorRegexps: []string{`^Publish failed for test-bar: uploading .+ to .+ resulted in response "404 Not Found"$`}, 130 }, 131 } { 132 err = os.Chdir(wd) 133 require.NoError(t, err) 134 135 handlerFunc = currCase.handler 136 137 currTmp, err := ioutil.TempDir(tmp, "") 138 require.NoError(t, err) 139 140 gittest.InitGitDir(t, currTmp) 141 142 for _, currMain := range currCase.mainFiles { 143 err = os.MkdirAll(path.Dir(path.Join(currTmp, currMain)), 0755) 144 require.NoError(t, err) 145 err = ioutil.WriteFile(path.Join(currTmp, currMain), []byte(testMain), 0644) 146 require.NoError(t, err) 147 } 148 149 err = ioutil.WriteFile(path.Join(currTmp, "dist.yml"), []byte(currCase.cfg), 0644) 150 require.NoError(t, err) 151 152 cfgcli.ConfigPath = "dist.yml" 153 154 err = os.Chdir(currTmp) 155 require.NoError(t, err) 156 157 p := &ArtifactoryConnectionInfo{ 158 BasicConnectionInfo: BasicConnectionInfo{ 159 URL: ts.URL, 160 Username: "username", 161 Password: "password", 162 }, 163 Repository: "repo", 164 } 165 166 buf := &bytes.Buffer{} 167 err = publishAction(p, currCase.publishProducts, nil, currCase.failFast, buf, ".") 168 assert.Regexp(t, regexp.MustCompile(currCase.wantOutputRegexp), buf.String(), "Case %d", i) 169 assert.NotRegexp(t, regexp.MustCompile(currCase.notWantOutputRegexp), buf.String(), "Case %d", i) 170 for _, currWantRegexp := range currCase.wantErrorRegexps { 171 assert.Regexp(t, regexp.MustCompile(currWantRegexp), err.Error(), "Case %d", i) 172 } 173 } 174 } 175 176 func TestArtifactoryPublishChecksums(t *testing.T) { 177 var handlerFunc func(w http.ResponseWriter, r *http.Request) 178 handlerFuncPtr := &handlerFunc 179 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 180 localHandlerFunc := *handlerFuncPtr 181 localHandlerFunc(w, r) 182 })) 183 defer ts.Close() 184 185 tmpDir, cleanup, err := dirs.TempDir(".", "") 186 defer cleanup() 187 require.NoError(t, err) 188 189 wd, err := os.Getwd() 190 defer func() { 191 if err := os.Chdir(wd); err != nil { 192 fmt.Printf("Failed to restore working directory to %s: %v\n", wd, err) 193 } 194 }() 195 require.NoError(t, err) 196 197 for i, currCase := range []struct { 198 cfg string 199 mainFiles []string 200 publishProducts []string 201 handler func(productToArtifact map[string]string) func(w http.ResponseWriter, r *http.Request) 202 wantRegexp func(productToArtifact map[string]string) string 203 notWantRegexp func(productToArtifact map[string]string) string 204 }{ 205 // upload for products with matching checksums are skipped 206 { 207 cfg: ` 208 products: 209 foo: 210 build: 211 main-pkg: ./foo 212 group-id: com.palantir.distgo-cmd-test`, 213 mainFiles: []string{"foo/main.go"}, 214 publishProducts: []string{"foo"}, 215 handler: func(productToArtifact map[string]string) func(w http.ResponseWriter, r *http.Request) { 216 fooArtifactPath := productToArtifact["foo"] 217 fooArtifactName := path.Base(fooArtifactPath) 218 return func(w http.ResponseWriter, r *http.Request) { 219 if strings.Contains(r.URL.String(), fooArtifactName) { 220 fileInfo, err := newFileInfo(fooArtifactPath) 221 require.NoError(t, err) 222 bytes, err := json.Marshal(map[string]checksums{ 223 "checksums": fileInfo.checksums, 224 }) 225 require.NoError(t, err) 226 _, err = w.Write(bytes) 227 require.NoError(t, err) 228 } else { 229 w.WriteHeader(http.StatusOK) 230 } 231 } 232 }, 233 wantRegexp: func(productToArtifact map[string]string) string { 234 fooArtifactName := path.Base(productToArtifact["foo"]) 235 return fmt.Sprintf("File dist/%s already exists at .+, skipping upload", fooArtifactName) 236 }, 237 }, 238 // upload for products is skipped even if only single checksum matches 239 { 240 cfg: ` 241 products: 242 foo: 243 build: 244 main-pkg: ./foo 245 group-id: com.palantir.distgo-cmd-test`, 246 mainFiles: []string{"foo/main.go"}, 247 publishProducts: []string{"foo"}, 248 handler: func(productToArtifact map[string]string) func(w http.ResponseWriter, r *http.Request) { 249 fooArtifactPath := productToArtifact["foo"] 250 fooArtifactName := path.Base(fooArtifactPath) 251 return func(w http.ResponseWriter, r *http.Request) { 252 if strings.Contains(r.URL.String(), fooArtifactName) { 253 fileInfo, err := newFileInfo(fooArtifactPath) 254 require.NoError(t, err) 255 hashes := fileInfo.checksums 256 // set 2 hashes to blank, but keep one matching hash 257 hashes.SHA256 = "" 258 hashes.MD5 = "" 259 bytes, err := json.Marshal(map[string]checksums{ 260 "checksums": hashes, 261 }) 262 require.NoError(t, err) 263 _, err = w.Write(bytes) 264 require.NoError(t, err) 265 } else { 266 w.WriteHeader(http.StatusOK) 267 } 268 } 269 }, 270 wantRegexp: func(productToArtifact map[string]string) string { 271 fooArtifactName := path.Base(productToArtifact["foo"]) 272 return fmt.Sprintf("File dist/%s already exists at .+, skipping upload", fooArtifactName) 273 }, 274 }, 275 // product is uploaded if checksum does not match 276 { 277 cfg: ` 278 products: 279 foo: 280 build: 281 main-pkg: ./foo 282 group-id: com.palantir.distgo-cmd-test`, 283 mainFiles: []string{"foo/main.go"}, 284 publishProducts: []string{"foo"}, 285 handler: func(productToArtifact map[string]string) func(w http.ResponseWriter, r *http.Request) { 286 fooArtifactPath := productToArtifact["foo"] 287 fooArtifactName := path.Base(fooArtifactPath) 288 return func(w http.ResponseWriter, r *http.Request) { 289 if strings.Contains(r.URL.String(), fooArtifactName) { 290 fileInfo, err := newFileInfo(fooArtifactPath) 291 require.NoError(t, err) 292 hashes := fileInfo.checksums 293 // 2 hashes match, but one does not 294 hashes.SHA1 = "invalid" 295 bytes, err := json.Marshal(map[string]checksums{ 296 "checksums": hashes, 297 }) 298 require.NoError(t, err) 299 _, err = w.Write(bytes) 300 require.NoError(t, err) 301 } else { 302 w.WriteHeader(http.StatusOK) 303 } 304 } 305 }, 306 notWantRegexp: func(productToArtifact map[string]string) string { 307 return "File .+ already exists at .+, skipping upload" 308 }, 309 }, 310 // product is uploaded if response does not contain checksums 311 { 312 cfg: ` 313 products: 314 foo: 315 build: 316 main-pkg: ./foo 317 group-id: com.palantir.distgo-cmd-test`, 318 mainFiles: []string{"foo/main.go"}, 319 publishProducts: []string{"foo"}, 320 handler: func(productToArtifact map[string]string) func(w http.ResponseWriter, r *http.Request) { 321 fooArtifactPath := productToArtifact["foo"] 322 fooArtifactName := path.Base(fooArtifactPath) 323 return func(w http.ResponseWriter, r *http.Request) { 324 if strings.Contains(r.URL.String(), fooArtifactName) { 325 bytes, err := json.Marshal(map[string]string{ 326 "no-checksum": "placeholder", 327 }) 328 require.NoError(t, err) 329 _, err = w.Write(bytes) 330 require.NoError(t, err) 331 } else { 332 w.WriteHeader(http.StatusOK) 333 } 334 } 335 }, 336 notWantRegexp: func(productToArtifact map[string]string) string { 337 return "File .+ already exists at .+, skipping upload" 338 }, 339 }, 340 } { 341 err = os.Chdir(wd) 342 require.NoError(t, err) 343 344 currTmp, err := ioutil.TempDir(tmpDir, "") 345 require.NoError(t, err) 346 347 currTmp, err = filepath.Abs(currTmp) 348 require.NoError(t, err) 349 350 gittest.InitGitDir(t, currTmp) 351 352 for _, currMain := range currCase.mainFiles { 353 err = os.MkdirAll(path.Dir(path.Join(currTmp, currMain)), 0755) 354 require.NoError(t, err) 355 err = ioutil.WriteFile(path.Join(currTmp, currMain), []byte(testMain), 0644) 356 require.NoError(t, err) 357 } 358 359 err = ioutil.WriteFile(path.Join(currTmp, "dist.yml"), []byte(currCase.cfg), 0644) 360 require.NoError(t, err) 361 362 cfgcli.ConfigPath = "dist.yml" 363 364 err = os.Chdir(currTmp) 365 require.NoError(t, err) 366 367 cfg, err := config.Load(cfgcli.ConfigPath, cfgcli.ConfigJSON) 368 require.NoError(t, err) 369 370 buildSpecsWithDeps, err := build.SpecsWithDepsForArgs(cfg, currCase.publishProducts, currTmp) 371 require.NoError(t, err) 372 373 artifacts, err := artifacts.DistArtifacts(buildSpecsWithDeps, true) 374 require.NoError(t, err) 375 376 productToArtifact := make(map[string]string) 377 for k, v := range artifacts { 378 require.Equal(t, 1, len(v.Keys())) 379 productToArtifact[k] = v.Get(v.Keys()[0])[0] 380 } 381 382 handlerFunc = currCase.handler(productToArtifact) 383 384 p := &ArtifactoryConnectionInfo{ 385 BasicConnectionInfo: BasicConnectionInfo{ 386 URL: ts.URL, 387 Username: "username", 388 Password: "password", 389 }, 390 Repository: "repo", 391 } 392 393 buf := &bytes.Buffer{} 394 395 err = publishAction(p, currCase.publishProducts, nil, true, buf, ".") 396 require.NoError(t, err, "Case %d", i) 397 398 if currCase.wantRegexp != nil { 399 assert.Regexp(t, regexp.MustCompile(currCase.wantRegexp(productToArtifact)), buf.String(), "Case %d", i) 400 } 401 402 if currCase.notWantRegexp != nil { 403 assert.NotRegexp(t, regexp.MustCompile(currCase.notWantRegexp(productToArtifact)), buf.String(), "Case %d", i) 404 } 405 } 406 } 407 408 func TestAlmanacPublishCheckURL(t *testing.T) { 409 var handlerFunc func(w http.ResponseWriter, r *http.Request) 410 handlerFuncPtr := &handlerFunc 411 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 412 localHandlerFunc := *handlerFuncPtr 413 localHandlerFunc(w, r) 414 })) 415 defer ts.Close() 416 417 tmpDir, cleanup, err := dirs.TempDir(".", "") 418 defer cleanup() 419 require.NoError(t, err) 420 421 wd, err := os.Getwd() 422 defer func() { 423 if err := os.Chdir(wd); err != nil { 424 fmt.Printf("Failed to restore working directory to %s: %v\n", wd, err) 425 } 426 }() 427 require.NoError(t, err) 428 429 for i, currCase := range []struct { 430 cfg string 431 mainFiles []string 432 publishProducts []string 433 handler func(w http.ResponseWriter, r *http.Request) 434 wantRegexp string 435 notWantRegexp string 436 wantErrorRegexp string 437 }{ 438 // Almanac publish for products with matching URLs are skipped 439 { 440 cfg: ` 441 products: 442 foo: 443 build: 444 main-pkg: ./foo 445 dist: 446 dist-type: 447 type: sls 448 group-id: com.palantir.distgo-cmd-test`, 449 mainFiles: []string{"foo/main.go"}, 450 publishProducts: []string{"foo"}, 451 handler: func(w http.ResponseWriter, r *http.Request) { 452 if r.URL.String() == "/v1/units/foo/unspecified/1" { 453 urlMap := map[string]string{ 454 "url": ts.URL + "/artifactory/repo/com/palantir/distgo-cmd-test/foo/unspecified/foo-unspecified.sls.tgz", 455 } 456 bytes, err := json.Marshal(urlMap) 457 require.NoError(t, err) 458 _, err = w.Write(bytes) 459 require.NoError(t, err) 460 } else { 461 w.WriteHeader(http.StatusOK) 462 } 463 }, 464 wantRegexp: fmt.Sprintf(`(?s).+Unit for product foo branch unspecified revision 1 with URL %s already exists; skipping publish.+`, ts.URL+"/artifactory/repo/com/palantir/distgo-cmd-test/foo/unspecified/foo-unspecified.sls.tgz"), 465 }, 466 // Almanac publish for product that do not exist in Almanac succeeds 467 { 468 cfg: ` 469 products: 470 foo: 471 build: 472 main-pkg: ./foo 473 dist: 474 dist-type: 475 type: sls 476 group-id: com.palantir.distgo-cmd-test`, 477 mainFiles: []string{"foo/main.go"}, 478 publishProducts: []string{"foo"}, 479 handler: func(w http.ResponseWriter, r *http.Request) { 480 status := http.StatusOK 481 if r.URL.String() == "/v1/units/foo/unspecified/1" { 482 // return error for unit 483 status = http.StatusBadRequest 484 } 485 w.WriteHeader(status) 486 }, 487 notWantRegexp: `(?s).+Unit for product .+ branch .+ revision .+ with the URL .+ already exists; skipping publish.+`, 488 }, 489 // Almanac publish for product that exists in Almanac (but has no URL) fails 490 { 491 cfg: ` 492 products: 493 foo: 494 build: 495 main-pkg: ./foo 496 dist: 497 dist-type: 498 type: sls 499 group-id: com.palantir.distgo-cmd-test`, 500 mainFiles: []string{"foo/main.go"}, 501 publishProducts: []string{"foo"}, 502 handler: func(w http.ResponseWriter, r *http.Request) { 503 w.WriteHeader(http.StatusOK) 504 }, 505 notWantRegexp: `(?s).+Unit for product .+ branch .+ revision .+ with the URL .+ already exists; skipping publish.+`, 506 wantErrorRegexp: `^Almanac publish failed for foo: unit for product foo branch unspecified revision 1 already exists; not overwriting it$`, 507 }, 508 // Almanac publish for product that exists in Almanac (but has different URL) fails 509 { 510 cfg: ` 511 products: 512 foo: 513 build: 514 main-pkg: ./foo 515 dist: 516 dist-type: 517 type: sls 518 group-id: com.palantir.distgo-cmd-test`, 519 mainFiles: []string{"foo/main.go"}, 520 publishProducts: []string{"foo"}, 521 handler: func(w http.ResponseWriter, r *http.Request) { 522 if r.URL.String() == "/v1/units/foo/unspecified/1" { 523 urlMap := map[string]string{ 524 "url": "nonMatchingURL", 525 } 526 bytes, err := json.Marshal(urlMap) 527 require.NoError(t, err) 528 _, err = w.Write(bytes) 529 require.NoError(t, err) 530 } else { 531 w.WriteHeader(http.StatusOK) 532 } 533 }, 534 notWantRegexp: `(?s).+Unit for product .+ branch .+ revision .+ with the URL .+ already exists; skipping publish.+`, 535 wantErrorRegexp: `^Almanac publish failed for foo: unit for product foo branch unspecified revision 1 already exists; not overwriting it$`, 536 }, 537 } { 538 err = os.Chdir(wd) 539 require.NoError(t, err) 540 541 currTmp, err := ioutil.TempDir(tmpDir, "") 542 require.NoError(t, err) 543 544 gittest.InitGitDir(t, currTmp) 545 546 for _, currMain := range currCase.mainFiles { 547 err = os.MkdirAll(path.Dir(path.Join(currTmp, currMain)), 0755) 548 require.NoError(t, err) 549 err = ioutil.WriteFile(path.Join(currTmp, currMain), []byte(testMain), 0644) 550 require.NoError(t, err) 551 } 552 553 err = ioutil.WriteFile(path.Join(currTmp, "dist.yml"), []byte(currCase.cfg), 0644) 554 require.NoError(t, err) 555 556 cfgcli.ConfigPath = "dist.yml" 557 558 err = os.Chdir(currTmp) 559 require.NoError(t, err) 560 561 handlerFunc = currCase.handler 562 563 p := &ArtifactoryConnectionInfo{ 564 BasicConnectionInfo: BasicConnectionInfo{ 565 URL: ts.URL, 566 Username: "username", 567 Password: "password", 568 }, 569 Repository: "repo", 570 } 571 572 a := &AlmanacInfo{ 573 URL: ts.URL, 574 AccessID: "username", 575 Secret: "password", 576 } 577 578 buf := &bytes.Buffer{} 579 580 err = publishAction(p, currCase.publishProducts, a, true, buf, ".") 581 582 if currCase.wantErrorRegexp != "" { 583 assert.Regexp(t, regexp.MustCompile(currCase.wantErrorRegexp), err.Error(), "Case %d", i) 584 } else { 585 require.NoError(t, err, "Case %d", i) 586 } 587 588 if currCase.wantRegexp != "" { 589 assert.Regexp(t, regexp.MustCompile(currCase.wantRegexp), buf.String(), "Case %d", i) 590 } 591 592 if currCase.notWantRegexp != "" { 593 assert.NotRegexp(t, regexp.MustCompile(currCase.notWantRegexp), buf.String(), "Case %d", i) 594 } 595 } 596 }