github.phpd.cn/goreleaser/goreleaser@v0.92.0/internal/pipe/put/put_test.go (about) 1 package put 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/http/httptest" 8 "os" 9 "path/filepath" 10 "sync" 11 "testing" 12 13 "github.com/goreleaser/goreleaser/internal/artifact" 14 "github.com/goreleaser/goreleaser/internal/pipe" 15 "github.com/goreleaser/goreleaser/pkg/config" 16 "github.com/goreleaser/goreleaser/pkg/context" 17 "github.com/stretchr/testify/assert" 18 ) 19 20 var ( 21 // mux is the HTTP request multiplexer used with the test server. 22 mux *http.ServeMux 23 24 // server is a test HTTP server used to provide mock API responses. 25 server *httptest.Server 26 ) 27 28 func setup() { 29 // test server 30 mux = http.NewServeMux() 31 server = httptest.NewServer(mux) 32 } 33 34 // teardown closes the test HTTP server. 35 func teardown() { 36 server.Close() 37 } 38 39 func testMethod(t *testing.T, r *http.Request, want string) { 40 if got := r.Method; got != want { 41 t.Errorf("Request method: %v, want %v", got, want) 42 } 43 } 44 45 func testHeader(t *testing.T, r *http.Request, header string, want string) { 46 if got := r.Header.Get(header); got != want { 47 t.Errorf("Header.Get(%q) returned %q, want %q", header, got, want) 48 } 49 } 50 51 // TODO: improve all tests bellow by checking wether the mocked handlers 52 // were called or not. 53 54 func TestRunPipe_ModeBinary(t *testing.T) { 55 setup() 56 defer teardown() 57 58 folder, err := ioutil.TempDir("", "archivetest") 59 assert.NoError(t, err) 60 var dist = filepath.Join(folder, "dist") 61 assert.NoError(t, os.Mkdir(dist, 0755)) 62 assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) 63 var binPath = filepath.Join(dist, "mybin", "mybin") 64 d1 := []byte("hello\ngo\n") 65 err = ioutil.WriteFile(binPath, d1, 0666) 66 assert.NoError(t, err) 67 68 // Dummy http server 69 mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) { 70 testMethod(t, r, "PUT") 71 testHeader(t, r, "Content-Length", "9") 72 // Basic auth of user "deployuser" with secret "deployuser-secret" 73 testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==") 74 75 w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin") 76 w.WriteHeader(http.StatusCreated) 77 }) 78 mux.HandleFunc("/example-repo-local/mybin/linux/amd64/mybin", func(w http.ResponseWriter, r *http.Request) { 79 testMethod(t, r, "PUT") 80 testHeader(t, r, "Content-Length", "9") 81 // Basic auth of user "deployuser" with secret "deployuser-secret" 82 testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==") 83 84 w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin") 85 w.WriteHeader(http.StatusCreated) 86 }) 87 mux.HandleFunc("/production-repo-remote/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) { 88 testMethod(t, r, "PUT") 89 testHeader(t, r, "Content-Length", "9") 90 // Basic auth of user "productionuser" with secret "productionuser-apikey" 91 testHeader(t, r, "Authorization", "Basic cHJvZHVjdGlvbnVzZXI6cHJvZHVjdGlvbnVzZXItYXBpa2V5") 92 93 w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin") 94 w.WriteHeader(http.StatusCreated) 95 }) 96 mux.HandleFunc("/production-repo-remote/mybin/linux/amd64/mybin", func(w http.ResponseWriter, r *http.Request) { 97 testMethod(t, r, "PUT") 98 testHeader(t, r, "Content-Length", "9") 99 // Basic auth of user "productionuser" with secret "productionuser-apikey" 100 testHeader(t, r, "Authorization", "Basic cHJvZHVjdGlvbnVzZXI6cHJvZHVjdGlvbnVzZXItYXBpa2V5") 101 102 w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin") 103 w.WriteHeader(http.StatusCreated) 104 }) 105 106 var ctx = context.New(config.Project{ 107 ProjectName: "mybin", 108 Dist: dist, 109 Puts: []config.Put{ 110 { 111 Name: "production-us", 112 Mode: "binary", 113 Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL), 114 Username: "deployuser", 115 }, 116 { 117 Name: "production-eu", 118 Mode: "binary", 119 Target: fmt.Sprintf("%s/production-repo-remote/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL), 120 Username: "productionuser", 121 }, 122 }, 123 }) 124 ctx.Env = map[string]string{ 125 "PUT_PRODUCTION-US_SECRET": "deployuser-secret", 126 "PUT_PRODUCTION-EU_SECRET": "productionuser-apikey", 127 } 128 for _, goos := range []string{"linux", "darwin"} { 129 ctx.Artifacts.Add(artifact.Artifact{ 130 Name: "mybin", 131 Path: binPath, 132 Goarch: "amd64", 133 Goos: goos, 134 Type: artifact.UploadableBinary, 135 }) 136 } 137 138 assert.NoError(t, Pipe{}.Publish(ctx)) 139 } 140 141 func TestRunPipe_ModeArchive(t *testing.T) { 142 setup() 143 defer teardown() 144 145 folder, err := ioutil.TempDir("", "goreleasertest") 146 assert.NoError(t, err) 147 tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz")) 148 assert.NoError(t, err) 149 debfile, err := os.Create(filepath.Join(folder, "bin.deb")) 150 assert.NoError(t, err) 151 152 var ctx = context.New(config.Project{ 153 ProjectName: "goreleaser", 154 Dist: folder, 155 Puts: []config.Put{ 156 { 157 Name: "production", 158 Mode: "archive", 159 Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Version }}/", server.URL), 160 Username: "deployuser", 161 }, 162 }, 163 }) 164 ctx.Env = map[string]string{ 165 "PUT_PRODUCTION_SECRET": "deployuser-secret", 166 } 167 ctx.Version = "1.0.0" 168 ctx.Artifacts.Add(artifact.Artifact{ 169 Type: artifact.UploadableArchive, 170 Name: "bin.tar.gz", 171 Path: tarfile.Name(), 172 }) 173 ctx.Artifacts.Add(artifact.Artifact{ 174 Type: artifact.LinuxPackage, 175 Name: "bin.deb", 176 Path: debfile.Name(), 177 }) 178 179 var uploads sync.Map 180 181 // Dummy http server 182 mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.tar.gz", func(w http.ResponseWriter, r *http.Request) { 183 testMethod(t, r, "PUT") 184 // Basic auth of user "deployuser" with secret "deployuser-secret" 185 testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==") 186 187 w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.tar.gz") 188 w.WriteHeader(http.StatusCreated) 189 uploads.Store("targz", true) 190 }) 191 mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.deb", func(w http.ResponseWriter, r *http.Request) { 192 testMethod(t, r, "PUT") 193 // Basic auth of user "deployuser" with secret "deployuser-secret" 194 testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==") 195 196 w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.deb") 197 w.WriteHeader(http.StatusCreated) 198 uploads.Store("deb", true) 199 }) 200 201 assert.NoError(t, Pipe{}.Publish(ctx)) 202 _, ok := uploads.Load("targz") 203 assert.True(t, ok, "tar.gz file was not uploaded") 204 _, ok = uploads.Load("deb") 205 assert.True(t, ok, "deb file was not uploaded") 206 } 207 208 func TestRunPipe_ArtifactoryDown(t *testing.T) { 209 folder, err := ioutil.TempDir("", "goreleasertest") 210 assert.NoError(t, err) 211 tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz")) 212 assert.NoError(t, err) 213 214 var ctx = context.New(config.Project{ 215 ProjectName: "goreleaser", 216 Dist: folder, 217 Puts: []config.Put{ 218 { 219 Name: "production", 220 Mode: "archive", 221 Target: "http://localhost:1234/example-repo-local/{{ .ProjectName }}/{{ .Version }}/", 222 Username: "deployuser", 223 }, 224 }, 225 }) 226 ctx.Version = "2.0.0" 227 ctx.Env = map[string]string{ 228 "PUT_PRODUCTION_SECRET": "deployuser-secret", 229 } 230 ctx.Artifacts.Add(artifact.Artifact{ 231 Type: artifact.UploadableArchive, 232 Name: "bin.tar.gz", 233 Path: tarfile.Name(), 234 }) 235 err = Pipe{}.Publish(ctx) 236 assert.Error(t, err) 237 assert.Contains(t, err.Error(), "connection refused") 238 } 239 240 func TestRunPipe_TargetTemplateError(t *testing.T) { 241 folder, err := ioutil.TempDir("", "archivetest") 242 assert.NoError(t, err) 243 var dist = filepath.Join(folder, "dist") 244 var binPath = filepath.Join(dist, "mybin", "mybin") 245 246 var ctx = context.New(config.Project{ 247 ProjectName: "mybin", 248 Dist: dist, 249 Puts: []config.Put{ 250 { 251 Name: "production", 252 Mode: "binary", 253 // This template is not correct and should fail 254 Target: "http://storage.company.com/example-repo-local/{{ .ProjectName /{{ .Version }}/", 255 Username: "deployuser", 256 }, 257 }, 258 }) 259 ctx.Env = map[string]string{ 260 "PUT_PRODUCTION_SECRET": "deployuser-secret", 261 } 262 ctx.Artifacts.Add(artifact.Artifact{ 263 Name: "mybin", 264 Path: binPath, 265 Goarch: "amd64", 266 Goos: "darwin", 267 Type: artifact.UploadableBinary, 268 }) 269 err = Pipe{}.Publish(ctx) 270 assert.Error(t, err) 271 assert.Contains(t, err.Error(), `put: error while building the target url: template: mybin:1: unexpected "/" in operand`) 272 } 273 274 func TestRunPipe_BadCredentials(t *testing.T) { 275 setup() 276 defer teardown() 277 278 folder, err := ioutil.TempDir("", "archivetest") 279 assert.NoError(t, err) 280 var dist = filepath.Join(folder, "dist") 281 assert.NoError(t, os.Mkdir(dist, 0755)) 282 assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) 283 var binPath = filepath.Join(dist, "mybin", "mybin") 284 d1 := []byte("hello\ngo\n") 285 err = ioutil.WriteFile(binPath, d1, 0666) 286 assert.NoError(t, err) 287 288 // Dummy http server 289 mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) { 290 testMethod(t, r, "PUT") 291 testHeader(t, r, "Content-Length", "9") 292 // Basic auth of user "deployuser" with secret "deployuser-secret" 293 testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==") 294 295 w.WriteHeader(http.StatusUnauthorized) 296 }) 297 298 var ctx = context.New(config.Project{ 299 ProjectName: "mybin", 300 Dist: dist, 301 Puts: []config.Put{ 302 { 303 Name: "production", 304 Mode: "binary", 305 Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL), 306 Username: "deployuser", 307 }, 308 }, 309 }) 310 ctx.Env = map[string]string{ 311 "PUT_PRODUCTION_SECRET": "deployuser-secret", 312 } 313 ctx.Artifacts.Add(artifact.Artifact{ 314 Name: "mybin", 315 Path: binPath, 316 Goarch: "amd64", 317 Goos: "darwin", 318 Type: artifact.UploadableBinary, 319 }) 320 321 err = Pipe{}.Publish(ctx) 322 assert.Error(t, err) 323 assert.Contains(t, err.Error(), "Unauthorized") 324 } 325 326 func TestRunPipe_FileNotFound(t *testing.T) { 327 var ctx = context.New(config.Project{ 328 ProjectName: "mybin", 329 Dist: "archivetest/dist", 330 Puts: []config.Put{ 331 { 332 Name: "production", 333 Mode: "binary", 334 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 335 Username: "deployuser", 336 }, 337 }, 338 }) 339 ctx.Env = map[string]string{ 340 "PUT_PRODUCTION_SECRET": "deployuser-secret", 341 } 342 ctx.Artifacts.Add(artifact.Artifact{ 343 Name: "mybin", 344 Path: "archivetest/dist/mybin/mybin", 345 Goarch: "amd64", 346 Goos: "darwin", 347 Type: artifact.UploadableBinary, 348 }) 349 350 assert.EqualError(t, Pipe{}.Publish(ctx), `open archivetest/dist/mybin/mybin: no such file or directory`) 351 } 352 353 func TestRunPipe_UnparsableTarget(t *testing.T) { 354 folder, err := ioutil.TempDir("", "archivetest") 355 assert.NoError(t, err) 356 var dist = filepath.Join(folder, "dist") 357 assert.NoError(t, os.Mkdir(dist, 0755)) 358 assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) 359 var binPath = filepath.Join(dist, "mybin", "mybin") 360 d1 := []byte("hello\ngo\n") 361 err = ioutil.WriteFile(binPath, d1, 0666) 362 assert.NoError(t, err) 363 364 var ctx = context.New(config.Project{ 365 ProjectName: "mybin", 366 Dist: dist, 367 Puts: []config.Put{ 368 { 369 Name: "production", 370 Mode: "binary", 371 Target: "://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 372 Username: "deployuser", 373 }, 374 }, 375 }) 376 ctx.Env = map[string]string{ 377 "PUT_PRODUCTION_SECRET": "deployuser-secret", 378 } 379 ctx.Artifacts.Add(artifact.Artifact{ 380 Name: "mybin", 381 Path: binPath, 382 Goarch: "amd64", 383 Goos: "darwin", 384 Type: artifact.UploadableBinary, 385 }) 386 387 assert.EqualError(t, Pipe{}.Publish(ctx), `put: upload failed: parse ://artifacts.company.com/example-repo-local/mybin/darwin/amd64/mybin: missing protocol scheme`) 388 } 389 390 func TestRunPipe_SkipWhenPublishFalse(t *testing.T) { 391 var ctx = context.New(config.Project{ 392 Puts: []config.Put{ 393 { 394 Name: "production", 395 Mode: "binary", 396 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 397 Username: "deployuser", 398 }, 399 }, 400 }) 401 ctx.Env = map[string]string{ 402 "PUT_PRODUCTION_SECRET": "deployuser-secret", 403 } 404 ctx.SkipPublish = true 405 406 err := Pipe{}.Publish(ctx) 407 assert.True(t, pipe.IsSkip(err)) 408 assert.EqualError(t, err, pipe.ErrSkipPublishEnabled.Error()) 409 } 410 411 func TestRunPipe_DirUpload(t *testing.T) { 412 folder, err := ioutil.TempDir("", "archivetest") 413 assert.NoError(t, err) 414 var dist = filepath.Join(folder, "dist") 415 assert.NoError(t, os.Mkdir(dist, 0755)) 416 assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) 417 var binPath = filepath.Join(dist, "mybin") 418 419 var ctx = context.New(config.Project{ 420 ProjectName: "mybin", 421 Dist: dist, 422 Puts: []config.Put{ 423 { 424 Name: "production", 425 Mode: "binary", 426 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 427 Username: "deployuser", 428 }, 429 }, 430 }) 431 ctx.Env = map[string]string{ 432 "PUT_PRODUCTION_SECRET": "deployuser-secret", 433 } 434 ctx.Artifacts.Add(artifact.Artifact{ 435 Name: "mybin", 436 Path: filepath.Dir(binPath), 437 Goarch: "amd64", 438 Goos: "darwin", 439 Type: artifact.UploadableBinary, 440 }) 441 442 assert.EqualError(t, Pipe{}.Publish(ctx), `put: upload failed: the asset to upload can't be a directory`) 443 } 444 445 func TestDescription(t *testing.T) { 446 assert.NotEmpty(t, Pipe{}.String()) 447 } 448 449 func TestNoPuts(t *testing.T) { 450 assert.True(t, pipe.IsSkip(Pipe{}.Publish(context.New(config.Project{})))) 451 } 452 453 func TestPutsWithoutTarget(t *testing.T) { 454 var ctx = &context.Context{ 455 Env: map[string]string{ 456 "PUT_PRODUCTION_SECRET": "deployuser-secret", 457 }, 458 Config: config.Project{ 459 Puts: []config.Put{ 460 { 461 Name: "production", 462 Username: "deployuser", 463 }, 464 }, 465 }, 466 } 467 468 assert.True(t, pipe.IsSkip(Pipe{}.Publish(ctx))) 469 } 470 471 func TestPutsWithoutUsername(t *testing.T) { 472 var ctx = &context.Context{ 473 Env: map[string]string{ 474 "PUT_PRODUCTION_SECRET": "deployuser-secret", 475 }, 476 Config: config.Project{ 477 Puts: []config.Put{ 478 { 479 Name: "production", 480 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 481 }, 482 }, 483 }, 484 } 485 486 assert.True(t, pipe.IsSkip(Pipe{}.Publish(ctx))) 487 } 488 489 func TestPutsWithoutName(t *testing.T) { 490 assert.True(t, pipe.IsSkip(Pipe{}.Publish(context.New(config.Project{ 491 Puts: []config.Put{ 492 { 493 Username: "deployuser", 494 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 495 }, 496 }, 497 })))) 498 } 499 500 func TestPutsWithoutSecret(t *testing.T) { 501 assert.True(t, pipe.IsSkip(Pipe{}.Publish(context.New(config.Project{ 502 Puts: []config.Put{ 503 { 504 Name: "production", 505 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 506 Username: "deployuser", 507 }, 508 }, 509 })))) 510 } 511 512 func TestPutsWithInvalidMode(t *testing.T) { 513 var ctx = &context.Context{ 514 Env: map[string]string{ 515 "PUT_PRODUCTION_SECRET": "deployuser-secret", 516 }, 517 Config: config.Project{ 518 Puts: []config.Put{ 519 { 520 Name: "production", 521 Mode: "does-not-exists", 522 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 523 Username: "deployuser", 524 }, 525 }, 526 }, 527 } 528 assert.Error(t, Pipe{}.Publish(ctx)) 529 } 530 531 func TestDefault(t *testing.T) { 532 var ctx = &context.Context{ 533 Config: config.Project{ 534 Puts: []config.Put{ 535 { 536 Name: "production", 537 Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", 538 Username: "deployuser", 539 }, 540 }, 541 }, 542 } 543 assert.NoError(t, Pipe{}.Default(ctx)) 544 assert.Len(t, ctx.Config.Puts, 1) 545 var put = ctx.Config.Puts[0] 546 assert.Equal(t, "archive", put.Mode) 547 } 548 549 func TestDefaultNoPuts(t *testing.T) { 550 var ctx = &context.Context{ 551 Config: config.Project{ 552 Puts: []config.Put{}, 553 }, 554 } 555 assert.NoError(t, Pipe{}.Default(ctx)) 556 assert.Empty(t, ctx.Config.Puts) 557 } 558 559 func TestDefaultSet(t *testing.T) { 560 var ctx = &context.Context{ 561 Config: config.Project{ 562 Puts: []config.Put{ 563 { 564 Mode: "custom", 565 }, 566 }, 567 }, 568 } 569 assert.NoError(t, Pipe{}.Default(ctx)) 570 assert.Len(t, ctx.Config.Puts, 1) 571 var put = ctx.Config.Puts[0] 572 assert.Equal(t, "custom", put.Mode) 573 }