github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/commands/application/push_test.go (about) 1 package application_test 2 3 import ( 4 "os" 5 "path/filepath" 6 "syscall" 7 8 fakeactors "github.com/cloudfoundry/cli/cf/actors/fakes" 9 testApplication "github.com/cloudfoundry/cli/cf/api/applications/fakes" 10 testapi "github.com/cloudfoundry/cli/cf/api/fakes" 11 "github.com/cloudfoundry/cli/cf/api/resources" 12 testStacks "github.com/cloudfoundry/cli/cf/api/stacks/fakes" 13 fakeappfiles "github.com/cloudfoundry/cli/cf/app_files/fakes" 14 . "github.com/cloudfoundry/cli/cf/commands/application" 15 "github.com/cloudfoundry/cli/cf/configuration/core_config" 16 "github.com/cloudfoundry/cli/cf/errors" 17 "github.com/cloudfoundry/cli/cf/manifest" 18 "github.com/cloudfoundry/cli/cf/models" 19 "github.com/cloudfoundry/cli/generic" 20 testcmd "github.com/cloudfoundry/cli/testhelpers/commands" 21 testconfig "github.com/cloudfoundry/cli/testhelpers/configuration" 22 "github.com/cloudfoundry/cli/testhelpers/maker" 23 testmanifest "github.com/cloudfoundry/cli/testhelpers/manifest" 24 testreq "github.com/cloudfoundry/cli/testhelpers/requirements" 25 testterm "github.com/cloudfoundry/cli/testhelpers/terminal" 26 testwords "github.com/cloudfoundry/cli/words/generator/fakes" 27 . "github.com/onsi/ginkgo" 28 . "github.com/onsi/gomega" 29 30 . "github.com/cloudfoundry/cli/testhelpers/matchers" 31 ) 32 33 var _ = Describe("Push Command", func() { 34 var ( 35 cmd *Push 36 ui *testterm.FakeUI 37 configRepo core_config.ReadWriter 38 manifestRepo *testmanifest.FakeManifestRepository 39 starter *testcmd.FakeApplicationStarter 40 stopper *testcmd.FakeApplicationStopper 41 serviceBinder *testcmd.FakeAppBinder 42 appRepo *testApplication.FakeApplicationRepository 43 domainRepo *testapi.FakeDomainRepository 44 routeRepo *testapi.FakeRouteRepository 45 stackRepo *testStacks.FakeStackRepository 46 serviceRepo *testapi.FakeServiceRepo 47 wordGenerator *testwords.FakeWordGenerator 48 requirementsFactory *testreq.FakeReqFactory 49 authRepo *testapi.FakeAuthenticationRepository 50 actor *fakeactors.FakePushActor 51 app_files *fakeappfiles.FakeAppFiles 52 zipper *fakeappfiles.FakeZipper 53 ) 54 55 BeforeEach(func() { 56 manifestRepo = &testmanifest.FakeManifestRepository{} 57 starter = &testcmd.FakeApplicationStarter{} 58 stopper = &testcmd.FakeApplicationStopper{} 59 serviceBinder = &testcmd.FakeAppBinder{} 60 appRepo = &testApplication.FakeApplicationRepository{} 61 62 domainRepo = &testapi.FakeDomainRepository{} 63 sharedDomain := maker.NewSharedDomainFields(maker.Overrides{"name": "foo.cf-app.com", "guid": "foo-domain-guid"}) 64 domainRepo.ListDomainsForOrgDomains = []models.DomainFields{sharedDomain} 65 66 routeRepo = &testapi.FakeRouteRepository{} 67 stackRepo = &testStacks.FakeStackRepository{} 68 serviceRepo = &testapi.FakeServiceRepo{} 69 authRepo = &testapi.FakeAuthenticationRepository{} 70 wordGenerator = new(testwords.FakeWordGenerator) 71 wordGenerator.BabbleReturns("laughing-cow") 72 73 ui = new(testterm.FakeUI) 74 configRepo = testconfig.NewRepositoryWithDefaults() 75 76 requirementsFactory = &testreq.FakeReqFactory{LoginSuccess: true, TargetedSpaceSuccess: true} 77 78 zipper = &fakeappfiles.FakeZipper{} 79 app_files = &fakeappfiles.FakeAppFiles{} 80 actor = &fakeactors.FakePushActor{} 81 82 cmd = NewPush(ui, configRepo, manifestRepo, starter, stopper, serviceBinder, 83 appRepo, 84 domainRepo, 85 routeRepo, 86 stackRepo, 87 serviceRepo, 88 authRepo, 89 wordGenerator, 90 actor, 91 zipper, 92 app_files) 93 }) 94 95 callPush := func(args ...string) bool { 96 return testcmd.RunCommand(cmd, args, requirementsFactory) 97 } 98 99 Describe("requirements", func() { 100 It("passes when logged in and a space is targeted", func() { 101 Expect(callPush()).To(BeTrue()) 102 }) 103 104 It("fails when not logged in", func() { 105 requirementsFactory.LoginSuccess = false 106 Expect(callPush()).To(BeFalse()) 107 }) 108 109 It("fails when a space is not targeted", func() { 110 requirementsFactory.TargetedSpaceSuccess = false 111 Expect(callPush()).To(BeFalse()) 112 }) 113 114 // yes, we're aware that the args here should probably be provided in a different order 115 // erg: app-name -p some/path some-extra-arg 116 // but the test infrastructure for parsing args and flags is sorely lacking 117 It("fails when provided too many args", func() { 118 Expect(callPush("-p", "path", "too-much", "app-name")).To(BeFalse()) 119 }) 120 }) 121 122 Describe("when pushing a new app", func() { 123 BeforeEach(func() { 124 appRepo.ReadReturns.Error = errors.NewModelNotFoundError("App", "the-app") 125 126 zipper.ZipReturns(nil) 127 zipper.GetZipSizeReturns(9001, nil) 128 actor.GatherFilesReturns(nil, nil) 129 actor.UploadAppReturns(nil) 130 }) 131 132 Context("when the default route for the app already exists", func() { 133 BeforeEach(func() { 134 route := models.Route{} 135 route.Guid = "my-route-guid" 136 route.Host = "my-new-app" 137 route.Domain = domainRepo.ListDomainsForOrgDomains[0] 138 139 routeRepo.FindByHostAndDomainReturns.Route = route 140 }) 141 142 It("binds to existing routes", func() { 143 callPush("my-new-app") 144 145 Expect(routeRepo.CreatedHost).To(BeEmpty()) 146 Expect(routeRepo.CreatedDomainGuid).To(BeEmpty()) 147 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("my-new-app")) 148 Expect(routeRepo.BoundAppGuid).To(Equal("my-new-app-guid")) 149 Expect(routeRepo.BoundRouteGuid).To(Equal("my-route-guid")) 150 151 Expect(ui.Outputs).To(ContainSubstrings( 152 []string{"Using", "my-new-app.foo.cf-app.com"}, 153 []string{"Binding", "my-new-app.foo.cf-app.com"}, 154 []string{"OK"}, 155 )) 156 }) 157 }) 158 159 Context("when the default route for the app does not exist", func() { 160 BeforeEach(func() { 161 routeRepo.FindByHostAndDomainReturns.Error = errors.NewModelNotFoundError("Org", "couldn't find it") 162 }) 163 164 It("refreshes the auth token (so fresh)", func() { // so clean 165 callPush("fresh-prince") 166 167 Expect(authRepo.RefreshTokenCalled).To(BeTrue()) 168 }) 169 170 Context("when refreshing the auth token fails", func() { 171 BeforeEach(func() { 172 authRepo.RefreshTokenError = errors.New("I accidentally the UAA") 173 }) 174 175 It("it displays an error", func() { 176 callPush("of-bel-air") 177 178 Expect(ui.Outputs).ToNot(ContainSubstrings( 179 []string{"Error refreshing auth token"}, 180 )) 181 Expect(ui.Outputs).To(ContainSubstrings( 182 []string{"FAILED"}, 183 []string{"accidentally the UAA"}, 184 )) 185 }) 186 }) 187 188 It("creates an app", func() { 189 callPush("-t", "111", "my-new-app") 190 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"FAILED"})) 191 192 Expect(*appRepo.CreatedAppParams().Name).To(Equal("my-new-app")) 193 Expect(*appRepo.CreatedAppParams().SpaceGuid).To(Equal("my-space-guid")) 194 195 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("my-new-app")) 196 Expect(routeRepo.CreatedHost).To(Equal("my-new-app")) 197 Expect(routeRepo.CreatedDomainGuid).To(Equal("foo-domain-guid")) 198 Expect(routeRepo.BoundAppGuid).To(Equal("my-new-app-guid")) 199 Expect(routeRepo.BoundRouteGuid).To(Equal("my-new-app-route-guid")) 200 201 appGuid, _, _ := actor.UploadAppArgsForCall(0) 202 Expect(appGuid).To(Equal("my-new-app-guid")) 203 204 Expect(ui.Outputs).To(ContainSubstrings( 205 []string{"Creating app", "my-new-app", "my-org", "my-space"}, 206 []string{"OK"}, 207 []string{"Creating", "my-new-app.foo.cf-app.com"}, 208 []string{"OK"}, 209 []string{"Binding", "my-new-app.foo.cf-app.com"}, 210 []string{"OK"}, 211 []string{"Uploading my-new-app"}, 212 []string{"OK"}, 213 )) 214 215 Expect(stopper.ApplicationStopCallCount()).To(Equal(0)) 216 217 app, orgName, spaceName := starter.ApplicationStartArgsForCall(0) 218 Expect(app.Guid).To(Equal(appGuid)) 219 Expect(app.Name).To(Equal("my-new-app")) 220 Expect(orgName).To(Equal(configRepo.OrganizationFields().Name)) 221 Expect(spaceName).To(Equal(configRepo.SpaceFields().Name)) 222 Expect(starter.SetStartTimeoutInSecondsArgsForCall(0)).To(Equal(111)) 223 }) 224 225 It("strips special characters when creating a default route", func() { 226 callPush("-t", "111", "Tim's 1st-Crazy__app!") 227 Expect(*appRepo.CreatedAppParams().Name).To(Equal("Tim's 1st-Crazy__app!")) 228 229 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("tims-1st-crazy-app")) 230 Expect(routeRepo.CreatedHost).To(Equal("tims-1st-crazy-app")) 231 232 Expect(ui.Outputs).To(ContainSubstrings( 233 []string{"Creating", "tims-1st-crazy-app.foo.cf-app.com"}, 234 []string{"Binding", "tims-1st-crazy-app.foo.cf-app.com"}, 235 )) 236 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"FAILED"})) 237 }) 238 239 It("sets the app params from the flags", func() { 240 domainRepo.FindByNameInOrgDomain = models.DomainFields{ 241 Name: "bar.cf-app.com", 242 Guid: "bar-domain-guid", 243 } 244 stackRepo.FindByNameReturns(models.Stack{ 245 Name: "customLinux", 246 Guid: "custom-linux-guid", 247 }, nil) 248 249 callPush( 250 "-c", "unicorn -c config/unicorn.rb -D", 251 "-d", "bar.cf-app.com", 252 "-n", "my-hostname", 253 "-k", "4G", 254 "-i", "3", 255 "-m", "2G", 256 "-b", "https://github.com/heroku/heroku-buildpack-play.git", 257 "-s", "customLinux", 258 "-t", "1", 259 "--no-start", 260 "my-new-app", 261 ) 262 263 Expect(ui.Outputs).To(ContainSubstrings( 264 []string{"Using", "customLinux"}, 265 []string{"OK"}, 266 []string{"Creating app", "my-new-app"}, 267 []string{"OK"}, 268 []string{"Creating route", "my-hostname.bar.cf-app.com"}, 269 []string{"OK"}, 270 []string{"Binding", "my-hostname.bar.cf-app.com", "my-new-app"}, 271 []string{"Uploading", "my-new-app"}, 272 []string{"OK"}, 273 )) 274 275 Expect(stackRepo.FindByNameArgsForCall(0)).To(Equal("customLinux")) 276 277 Expect(*appRepo.CreatedAppParams().Name).To(Equal("my-new-app")) 278 Expect(*appRepo.CreatedAppParams().Command).To(Equal("unicorn -c config/unicorn.rb -D")) 279 Expect(*appRepo.CreatedAppParams().InstanceCount).To(Equal(3)) 280 Expect(*appRepo.CreatedAppParams().DiskQuota).To(Equal(int64(4096))) 281 Expect(*appRepo.CreatedAppParams().Memory).To(Equal(int64(2048))) 282 Expect(*appRepo.CreatedAppParams().StackGuid).To(Equal("custom-linux-guid")) 283 Expect(*appRepo.CreatedAppParams().HealthCheckTimeout).To(Equal(1)) 284 Expect(*appRepo.CreatedAppParams().BuildpackUrl).To(Equal("https://github.com/heroku/heroku-buildpack-play.git")) 285 286 Expect(domainRepo.FindByNameInOrgName).To(Equal("bar.cf-app.com")) 287 Expect(domainRepo.FindByNameInOrgGuid).To(Equal("my-org-guid")) 288 289 Expect(routeRepo.CreatedHost).To(Equal("my-hostname")) 290 Expect(routeRepo.CreatedDomainGuid).To(Equal("bar-domain-guid")) 291 Expect(routeRepo.BoundAppGuid).To(Equal("my-new-app-guid")) 292 Expect(routeRepo.BoundRouteGuid).To(Equal("my-hostname-route-guid")) 293 294 appGuid, _, _ := actor.UploadAppArgsForCall(0) 295 Expect(appGuid).To(Equal("my-new-app-guid")) 296 297 Expect(starter.ApplicationStartCallCount()).To(Equal(0)) 298 }) 299 300 Context("when there is a shared domain", func() { 301 It("creates a route with the shared domain and maps it to the app", func() { 302 privateDomain := models.DomainFields{ 303 Shared: false, 304 Name: "private.cf-app.com", 305 Guid: "private-domain-guid", 306 } 307 sharedDomain := models.DomainFields{ 308 Name: "shared.cf-app.com", 309 Shared: true, 310 Guid: "shared-domain-guid", 311 } 312 313 domainRepo.ListDomainsForOrgDomains = []models.DomainFields{privateDomain, sharedDomain} 314 315 callPush("-t", "111", "my-new-app") 316 317 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("my-new-app")) 318 Expect(routeRepo.CreatedHost).To(Equal("my-new-app")) 319 Expect(routeRepo.CreatedDomainGuid).To(Equal("shared-domain-guid")) 320 Expect(routeRepo.BoundAppGuid).To(Equal("my-new-app-guid")) 321 Expect(routeRepo.BoundRouteGuid).To(Equal("my-new-app-route-guid")) 322 323 Expect(ui.Outputs).To(ContainSubstrings( 324 []string{"Creating app", "my-new-app", "my-org", "my-space"}, 325 []string{"OK"}, 326 []string{"Creating", "my-new-app.shared.cf-app.com"}, 327 []string{"OK"}, 328 []string{"Binding", "my-new-app.shared.cf-app.com"}, 329 []string{"OK"}, 330 []string{"Uploading my-new-app"}, 331 []string{"OK"}, 332 )) 333 }) 334 }) 335 336 Context("when there is no shared domain but there is a private domain in the targeted org", func() { 337 It("creates a route with the private domain and maps it to the app", func() { 338 privateDomain := models.DomainFields{ 339 Shared: false, 340 Name: "private.cf-app.com", 341 Guid: "private-domain-guid", 342 } 343 344 domainRepo.ListDomainsForOrgDomains = []models.DomainFields{privateDomain} 345 appRepo.ReadReturns.Error = errors.NewModelNotFoundError("App", "the-app") 346 347 callPush("-t", "111", "my-new-app") 348 349 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("my-new-app")) 350 Expect(routeRepo.CreatedHost).To(Equal("my-new-app")) 351 Expect(routeRepo.CreatedDomainGuid).To(Equal("private-domain-guid")) 352 Expect(routeRepo.BoundAppGuid).To(Equal("my-new-app-guid")) 353 Expect(routeRepo.BoundRouteGuid).To(Equal("my-new-app-route-guid")) 354 355 Expect(ui.Outputs).To(ContainSubstrings( 356 []string{"Creating app", "my-new-app", "my-org", "my-space"}, 357 []string{"OK"}, 358 []string{"Creating", "my-new-app.private.cf-app.com"}, 359 []string{"OK"}, 360 []string{"Binding", "my-new-app.private.cf-app.com"}, 361 []string{"OK"}, 362 []string{"Uploading my-new-app"}, 363 []string{"OK"}, 364 )) 365 }) 366 }) 367 368 Describe("randomized hostnames", func() { 369 var manifestApp generic.Map 370 371 BeforeEach(func() { 372 manifest := singleAppManifest() 373 manifestApp = manifest.Data.Get("applications").([]interface{})[0].(generic.Map) 374 manifestApp.Delete("host") 375 manifestRepo.ReadManifestReturns.Manifest = manifest 376 }) 377 378 It("provides a random hostname when the --random-route flag is passed", func() { 379 callPush("--random-route", "my-new-app") 380 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("my-new-app-laughing-cow")) 381 }) 382 383 It("provides a random hostname when the random-route option is set in the manifest", func() { 384 manifestApp.Set("random-route", true) 385 386 callPush("my-new-app") 387 388 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("my-new-app-laughing-cow")) 389 }) 390 }) 391 392 It("pushes the contents of the directory specified using the -p flag", func() { 393 callPush("-p", "../some/path-to/an-app", "app-with-path") 394 395 appDir, _ := actor.GatherFilesArgsForCall(0) 396 Expect(appDir).To(Equal("../some/path-to/an-app")) 397 }) 398 399 It("pushes the contents of the current working directory by default", func() { 400 callPush("app-with-default-path") 401 dir, _ := os.Getwd() 402 403 appDir, _ := actor.GatherFilesArgsForCall(0) 404 Expect(appDir).To(Equal(dir)) 405 }) 406 407 It("fails when given a bad manifest path", func() { 408 manifestRepo.ReadManifestReturns.Manifest = manifest.NewEmptyManifest() 409 manifestRepo.ReadManifestReturns.Error = errors.New("read manifest error") 410 411 callPush("-f", "bad/manifest/path") 412 413 Expect(ui.Outputs).To(ContainSubstrings( 414 []string{"FAILED"}, 415 []string{"read manifest error"}, 416 )) 417 }) 418 419 It("does not fail when the current working directory does not contain a manifest", func() { 420 manifestRepo.ReadManifestReturns.Manifest = singleAppManifest() 421 manifestRepo.ReadManifestReturns.Error = syscall.ENOENT 422 manifestRepo.ReadManifestReturns.Manifest.Path = "" 423 424 callPush("--no-route", "app-name") 425 426 Expect(ui.Outputs).To(ContainSubstrings( 427 []string{"Creating app", "app-name"}, 428 []string{"OK"}, 429 []string{"Uploading", "app-name"}, 430 []string{"OK"}, 431 )) 432 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"FAILED"})) 433 }) 434 435 It("uses the manifest in the current directory by default", func() { 436 manifestRepo.ReadManifestReturns.Manifest = singleAppManifest() 437 manifestRepo.ReadManifestReturns.Manifest.Path = "manifest.yml" 438 439 callPush("-p", "some/relative/path") 440 441 Expect(ui.Outputs).To(ContainSubstrings([]string{"Using manifest file", "manifest.yml"})) 442 443 cwd, _ := os.Getwd() 444 Expect(manifestRepo.ReadManifestArgs.Path).To(Equal(cwd)) 445 }) 446 447 It("does not use a manifest if the 'no-manifest' flag is passed", func() { 448 callPush("--no-route", "--no-manifest", "app-name") 449 450 Expect(ui.Outputs).ToNot(ContainSubstrings( 451 []string{"FAILED"}, 452 []string{"hacker-manifesto"}, 453 )) 454 455 Expect(manifestRepo.ReadManifestArgs.Path).To(Equal("")) 456 Expect(*appRepo.CreatedAppParams().Name).To(Equal("app-name")) 457 }) 458 459 It("pushes an app when provided a manifest with one app defined", func() { 460 domainRepo.FindByNameInOrgDomain = models.DomainFields{ 461 Name: "manifest-example.com", 462 Guid: "bar-domain-guid", 463 } 464 465 manifestRepo.ReadManifestReturns.Manifest = singleAppManifest() 466 467 callPush() 468 469 Expect(ui.Outputs).To(ContainSubstrings( 470 []string{"Creating route", "manifest-host.manifest-example.com"}, 471 []string{"OK"}, 472 []string{"Binding", "manifest-host.manifest-example.com"}, 473 []string{"manifest-app-name"}, 474 )) 475 476 Expect(*appRepo.CreatedAppParams().Name).To(Equal("manifest-app-name")) 477 Expect(*appRepo.CreatedAppParams().Memory).To(Equal(int64(128))) 478 Expect(*appRepo.CreatedAppParams().InstanceCount).To(Equal(1)) 479 Expect(*appRepo.CreatedAppParams().StackName).To(Equal("custom-stack")) 480 Expect(*appRepo.CreatedAppParams().BuildpackUrl).To(Equal("some-buildpack")) 481 Expect(*appRepo.CreatedAppParams().Command).To(Equal("JAVA_HOME=$PWD/.openjdk JAVA_OPTS=\"-Xss995K\" ./bin/start.sh run")) 482 // Expect(actor.UploadedDir).To(Equal(filepath.Clean("some/path/from/manifest"))) TODO: Re-enable this once we develop a strategy 483 484 Expect(*appRepo.CreatedAppParams().EnvironmentVars).To(Equal(map[string]interface{}{ 485 "PATH": "/u/apps/my-app/bin", 486 "FOO": "baz", 487 })) 488 }) 489 490 It("pushes an app with multiple routes when multiple hosts are provided", func() { 491 domainRepo.FindByNameInOrgDomain = models.DomainFields{ 492 Name: "manifest-example.com", 493 Guid: "bar-domain-guid", 494 } 495 496 manifestRepo.ReadManifestReturns.Manifest = multipleHostManifest() 497 498 callPush() 499 500 Expect(ui.Outputs).To(ContainSubstrings( 501 []string{"Creating route", "manifest-host-1.manifest-example.com"}, 502 []string{"OK"}, 503 []string{"Binding", "manifest-host-1.manifest-example.com"}, 504 []string{"Creating route", "manifest-host-2.manifest-example.com"}, 505 []string{"OK"}, 506 []string{"Binding", "manifest-host-2.manifest-example.com"}, 507 []string{"manifest-app-name"}, 508 )) 509 }) 510 511 It("fails when parsing the manifest has errors", func() { 512 manifestRepo.ReadManifestReturns.Manifest = &manifest.Manifest{Path: "/some-path/"} 513 manifestRepo.ReadManifestReturns.Error = errors.New("buildpack should not be null") 514 515 callPush() 516 517 Expect(ui.Outputs).To(ContainSubstrings( 518 []string{"FAILED"}, 519 []string{"Error", "reading", "manifest"}, 520 []string{"buildpack should not be null"}, 521 )) 522 }) 523 524 It("does not create a route when provided the --no-route flag", func() { 525 domainRepo.FindByNameInOrgDomain = models.DomainFields{ 526 Name: "bar.cf-app.com", 527 Guid: "bar-domain-guid", 528 } 529 530 callPush("--no-route", "my-new-app") 531 532 Expect(*appRepo.CreatedAppParams().Name).To(Equal("my-new-app")) 533 Expect(routeRepo.CreatedHost).To(Equal("")) 534 Expect(routeRepo.CreatedDomainGuid).To(Equal("")) 535 }) 536 537 It("maps the root domain route to the app when given the --no-hostname flag", func() { 538 domainRepo.ListDomainsForOrgDomains = []models.DomainFields{{ 539 Name: "bar.cf-app.com", 540 Guid: "bar-domain-guid", 541 Shared: true, 542 }} 543 544 routeRepo.FindByHostAndDomainReturns.Error = errors.NewModelNotFoundError("Org", "uh oh") 545 546 callPush("--no-hostname", "my-new-app") 547 548 Expect(*appRepo.CreatedAppParams().Name).To(Equal("my-new-app")) 549 Expect(routeRepo.CreatedHost).To(Equal("")) 550 Expect(routeRepo.CreatedDomainGuid).To(Equal("bar-domain-guid")) 551 }) 552 553 It("Does not create a route when the no-route property is in the manifest", func() { 554 workerManifest := singleAppManifest() 555 workerManifest.Data.Get("applications").([]interface{})[0].(generic.Map).Set("no-route", true) 556 manifestRepo.ReadManifestReturns.Manifest = workerManifest 557 558 callPush("worker-app") 559 560 Expect(ui.Outputs).To(ContainSubstrings([]string{"worker-app", "is a worker", "skipping route creation"})) 561 Expect(routeRepo.BoundAppGuid).To(Equal("")) 562 Expect(routeRepo.BoundRouteGuid).To(Equal("")) 563 }) 564 565 It("fails when given an invalid memory limit", func() { 566 callPush("-m", "abcM", "my-new-app") 567 568 Expect(ui.Outputs).To(ContainSubstrings( 569 []string{"FAILED"}, 570 []string{"Invalid", "memory limit", "abcM"}, 571 )) 572 }) 573 574 Context("when a manifest has many apps", func() { 575 BeforeEach(func() { 576 manifestRepo.ReadManifestReturns.Manifest = manifestWithServicesAndEnv() 577 }) 578 579 It("pushes each app", func() { 580 callPush() 581 582 Expect(ui.Outputs).To(ContainSubstrings( 583 []string{"Creating", "app1"}, 584 []string{"Creating", "app2"}, 585 )) 586 Expect(len(appRepo.CreateAppParams)).To(Equal(2)) 587 588 firstApp := appRepo.CreateAppParams[0] 589 secondApp := appRepo.CreateAppParams[1] 590 Expect(*firstApp.Name).To(Equal("app1")) 591 Expect(*secondApp.Name).To(Equal("app2")) 592 593 envVars := *firstApp.EnvironmentVars 594 Expect(envVars["SOMETHING"]).To(Equal("definitely-something")) 595 596 envVars = *secondApp.EnvironmentVars 597 Expect(envVars["SOMETHING"]).To(Equal("nothing")) 598 }) 599 600 It("pushes a single app when given the name of a single app in the manifest", func() { 601 callPush("app2") 602 603 Expect(ui.Outputs).To(ContainSubstrings([]string{"Creating", "app2"})) 604 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"Creating", "app1"})) 605 Expect(len(appRepo.CreateAppParams)).To(Equal(1)) 606 Expect(*appRepo.CreateAppParams[0].Name).To(Equal("app2")) 607 }) 608 609 It("fails when given the name of an app that is not in the manifest", func() { 610 callPush("non-existant-app") 611 612 Expect(ui.Outputs).To(ContainSubstrings([]string{"FAILED"})) 613 Expect(len(appRepo.CreateAppParams)).To(Equal(0)) 614 }) 615 }) 616 }) 617 }) 618 619 Describe("re-pushing an existing app", func() { 620 var existingApp models.Application 621 622 BeforeEach(func() { 623 existingApp = models.Application{} 624 existingApp.Name = "existing-app" 625 existingApp.Guid = "existing-app-guid" 626 existingApp.Command = "unicorn -c config/unicorn.rb -D" 627 existingApp.EnvironmentVars = map[string]interface{}{ 628 "crazy": "pants", 629 "FOO": "NotYoBaz", 630 "foo": "manchu", 631 } 632 633 appRepo.ReadReturns.App = existingApp 634 appRepo.UpdateAppResult = existingApp 635 }) 636 637 It("resets the app's buildpack when the -b flag is provided as 'default'", func() { 638 callPush("-b", "default", "existing-app") 639 Expect(*appRepo.UpdateParams.BuildpackUrl).To(Equal("")) 640 }) 641 642 It("resets the app's command when the -c flag is provided as 'default'", func() { 643 callPush("-c", "default", "existing-app") 644 Expect(*appRepo.UpdateParams.Command).To(Equal("")) 645 }) 646 647 It("resets the app's buildpack when the -b flag is provided as 'null'", func() { 648 callPush("-b", "null", "existing-app") 649 Expect(*appRepo.UpdateParams.BuildpackUrl).To(Equal("")) 650 }) 651 652 It("resets the app's command when the -c flag is provided as 'null'", func() { 653 callPush("-c", "null", "existing-app") 654 Expect(*appRepo.UpdateParams.Command).To(Equal("")) 655 }) 656 657 It("merges env vars from the manifest with those from the server", func() { 658 manifestRepo.ReadManifestReturns.Manifest = singleAppManifest() 659 660 callPush("existing-app") 661 662 updatedAppEnvVars := *appRepo.UpdateParams.EnvironmentVars 663 Expect(updatedAppEnvVars["crazy"]).To(Equal("pants")) 664 Expect(updatedAppEnvVars["FOO"]).To(Equal("baz")) 665 Expect(updatedAppEnvVars["foo"]).To(Equal("manchu")) 666 Expect(updatedAppEnvVars["PATH"]).To(Equal("/u/apps/my-app/bin")) 667 }) 668 669 It("stops the app, achieving a full-downtime deploy!", func() { 670 appRepo.UpdateAppResult = existingApp 671 672 callPush("existing-app") 673 674 app, orgName, spaceName := stopper.ApplicationStopArgsForCall(0) 675 Expect(app.Guid).To(Equal(existingApp.Guid)) 676 Expect(app.Name).To(Equal("existing-app")) 677 Expect(orgName).To(Equal(configRepo.OrganizationFields().Name)) 678 Expect(spaceName).To(Equal(configRepo.SpaceFields().Name)) 679 680 appGuid, _, _ := actor.UploadAppArgsForCall(0) 681 Expect(appGuid).To(Equal(existingApp.Guid)) 682 }) 683 684 It("does not stop the app when it is already stopped", func() { 685 existingApp.State = "stopped" 686 appRepo.ReadReturns.App = existingApp 687 appRepo.UpdateAppResult = existingApp 688 689 callPush("existing-app") 690 691 Expect(stopper.ApplicationStopCallCount()).To(Equal(0)) 692 }) 693 694 It("updates the app", func() { 695 existingRoute := models.RouteSummary{} 696 existingRoute.Host = "existing-app" 697 698 existingApp.Routes = []models.RouteSummary{existingRoute} 699 appRepo.ReadReturns.App = existingApp 700 appRepo.UpdateAppResult = existingApp 701 702 stackRepo.FindByNameReturns(models.Stack{ 703 Name: "differentStack", 704 Guid: "differentStack-guid", 705 }, nil) 706 707 callPush( 708 "-c", "different start command", 709 "-i", "10", 710 "-m", "1G", 711 "-b", "https://github.com/heroku/heroku-buildpack-different.git", 712 "-s", "differentStack", 713 "existing-app", 714 ) 715 716 Expect(appRepo.UpdateAppGuid).To(Equal(existingApp.Guid)) 717 Expect(*appRepo.UpdateParams.Command).To(Equal("different start command")) 718 Expect(*appRepo.UpdateParams.InstanceCount).To(Equal(10)) 719 Expect(*appRepo.UpdateParams.Memory).To(Equal(int64(1024))) 720 Expect(*appRepo.UpdateParams.BuildpackUrl).To(Equal("https://github.com/heroku/heroku-buildpack-different.git")) 721 Expect(*appRepo.UpdateParams.StackGuid).To(Equal("differentStack-guid")) 722 }) 723 724 It("re-uploads the app", func() { 725 callPush("existing-app") 726 727 Expect(ui.Outputs).To(ContainSubstrings( 728 []string{"Uploading", "existing-app"}, 729 []string{"OK"}, 730 )) 731 }) 732 733 Describe("when the app has a route bound", func() { 734 BeforeEach(func() { 735 domain := models.DomainFields{ 736 Name: "example.com", 737 Guid: "domain-guid", 738 Shared: true, 739 } 740 741 domainRepo.ListDomainsForOrgDomains = []models.DomainFields{domain} 742 routeRepo.FindByHostAndDomainReturns.Route = models.Route{ 743 Host: "existing-app", 744 Domain: domain, 745 } 746 747 existingApp.Routes = []models.RouteSummary{models.RouteSummary{ 748 Guid: "existing-route-guid", 749 Host: "existing-app", 750 Domain: domain, 751 }} 752 753 appRepo.ReadReturns.App = existingApp 754 appRepo.UpdateAppResult = existingApp 755 }) 756 757 It("uses the existing route when an app already has it bound", func() { 758 callPush("-d", "example.com", "existing-app") 759 760 Expect(routeRepo.CreatedHost).To(Equal("")) 761 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"Creating route"})) 762 Expect(ui.Outputs).To(ContainSubstrings([]string{"Using route", "existing-app", "example.com"})) 763 }) 764 765 Context("and no route-related flags are given", func() { 766 Context("and there is no route in the manifest", func() { 767 It("does not add a route to the app", func() { 768 callPush("existing-app") 769 770 appGuid, _, _ := actor.UploadAppArgsForCall(0) 771 Expect(appGuid).To(Equal("existing-app-guid")) 772 Expect(domainRepo.FindByNameInOrgName).To(Equal("")) 773 Expect(routeRepo.FindByHostAndDomainCalledWith.Domain.Name).To(Equal("")) 774 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("")) 775 Expect(routeRepo.CreatedHost).To(Equal("")) 776 Expect(routeRepo.CreatedDomainGuid).To(Equal("")) 777 }) 778 }) 779 780 Context("and there is a route in the manifest", func() { 781 BeforeEach(func() { 782 manifestRepo.ReadManifestReturns.Manifest = existingAppManifest() 783 784 routeRepo.FindByHostAndDomainReturns.Error = errors.NewModelNotFoundError("Org", "uh oh") 785 786 domainRepo.FindByNameInOrgDomain = models.DomainFields{ 787 Name: "example.com", 788 Guid: "example-domain-guid", 789 } 790 }) 791 792 It("adds the route", func() { 793 callPush("existing-app") 794 Expect(routeRepo.CreatedHost).To(Equal("new-manifest-host")) 795 }) 796 }) 797 }) 798 799 It("creates and binds a route when a different domain is specified", func() { 800 newDomain := models.DomainFields{Guid: "domain-guid", Name: "newdomain.com"} 801 routeRepo.FindByHostAndDomainReturns.Error = errors.NewModelNotFoundError("Org", "existing-app.newdomain.com") 802 domainRepo.FindByNameInOrgDomain = newDomain 803 804 callPush("-d", "newdomain.com", "existing-app") 805 806 Expect(ui.Outputs).To(ContainSubstrings( 807 []string{"Creating route", "existing-app.newdomain.com"}, 808 []string{"OK"}, 809 []string{"Binding", "existing-app.newdomain.com"}, 810 )) 811 812 Expect(domainRepo.FindByNameInOrgName).To(Equal("newdomain.com")) 813 Expect(domainRepo.FindByNameInOrgGuid).To(Equal("my-org-guid")) 814 Expect(routeRepo.FindByHostAndDomainCalledWith.Domain.Name).To(Equal("newdomain.com")) 815 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("existing-app")) 816 Expect(routeRepo.CreatedHost).To(Equal("existing-app")) 817 Expect(routeRepo.CreatedDomainGuid).To(Equal("domain-guid")) 818 }) 819 820 It("creates and binds a route when a different hostname is specified", func() { 821 routeRepo.FindByHostAndDomainReturns.Error = errors.NewModelNotFoundError("Org", "new-host.newdomain.com") 822 823 callPush("-n", "new-host", "existing-app") 824 825 Expect(ui.Outputs).To(ContainSubstrings( 826 []string{"Creating route", "new-host.example.com"}, 827 []string{"OK"}, 828 []string{"Binding", "new-host.example.com"}, 829 )) 830 831 Expect(routeRepo.FindByHostAndDomainCalledWith.Domain.Name).To(Equal("example.com")) 832 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("new-host")) 833 Expect(routeRepo.CreatedHost).To(Equal("new-host")) 834 Expect(routeRepo.CreatedDomainGuid).To(Equal("domain-guid")) 835 }) 836 837 It("removes the route when the --no-route flag is given", func() { 838 callPush("--no-route", "existing-app") 839 840 appGuid, _, _ := actor.UploadAppArgsForCall(0) 841 Expect(appGuid).To(Equal("existing-app-guid")) 842 Expect(domainRepo.FindByNameInOrgName).To(Equal("")) 843 Expect(routeRepo.FindByHostAndDomainCalledWith.Domain.Name).To(Equal("")) 844 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("")) 845 Expect(routeRepo.CreatedHost).To(Equal("")) 846 Expect(routeRepo.CreatedDomainGuid).To(Equal("")) 847 Expect(routeRepo.UnboundRouteGuid).To(Equal("existing-route-guid")) 848 Expect(routeRepo.UnboundAppGuid).To(Equal("existing-app-guid")) 849 }) 850 851 It("binds the root domain route to an app with a pre-existing route when the --no-hostname flag is given", func() { 852 routeRepo.FindByHostAndDomainReturns.Error = errors.NewModelNotFoundError("Org", "existing-app.example.com") 853 854 callPush("--no-hostname", "existing-app") 855 856 Expect(ui.Outputs).To(ContainSubstrings( 857 []string{"Creating route", "example.com"}, 858 []string{"OK"}, 859 []string{"Binding", "example.com"}, 860 )) 861 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"existing-app.example.com"})) 862 863 Expect(routeRepo.FindByHostAndDomainCalledWith.Domain.Name).To(Equal("example.com")) 864 Expect(routeRepo.FindByHostAndDomainCalledWith.Host).To(Equal("")) 865 Expect(routeRepo.CreatedHost).To(Equal("")) 866 Expect(routeRepo.CreatedDomainGuid).To(Equal("domain-guid")) 867 }) 868 }) 869 }) 870 871 Describe("service instances", func() { 872 BeforeEach(func() { 873 serviceRepo.FindInstanceByNameMap = generic.NewMap(map[interface{}]interface{}{ 874 "global-service": maker.NewServiceInstance("global-service"), 875 "app1-service": maker.NewServiceInstance("app1-service"), 876 "app2-service": maker.NewServiceInstance("app2-service"), 877 }) 878 879 manifestRepo.ReadManifestReturns.Manifest = manifestWithServicesAndEnv() 880 }) 881 882 Context("when the service is not bound", func() { 883 BeforeEach(func() { 884 appRepo.ReadReturns.Error = errors.NewModelNotFoundError("App", "the-app") 885 }) 886 887 It("binds service instances to the app", func() { 888 callPush() 889 Expect(len(serviceBinder.AppsToBind)).To(Equal(4)) 890 Expect(serviceBinder.AppsToBind[0].Name).To(Equal("app1")) 891 Expect(serviceBinder.AppsToBind[1].Name).To(Equal("app1")) 892 Expect(serviceBinder.InstancesToBindTo[0].Name).To(Equal("app1-service")) 893 Expect(serviceBinder.InstancesToBindTo[1].Name).To(Equal("global-service")) 894 895 Expect(serviceBinder.AppsToBind[2].Name).To(Equal("app2")) 896 Expect(serviceBinder.AppsToBind[3].Name).To(Equal("app2")) 897 Expect(serviceBinder.InstancesToBindTo[2].Name).To(Equal("app2-service")) 898 Expect(serviceBinder.InstancesToBindTo[3].Name).To(Equal("global-service")) 899 900 Expect(ui.Outputs).To(ContainSubstrings( 901 []string{"Creating", "app1"}, 902 []string{"OK"}, 903 []string{"Binding service", "app1-service", "app1", "my-org", "my-space", "my-user"}, 904 []string{"OK"}, 905 []string{"Binding service", "global-service", "app1", "my-org", "my-space", "my-user"}, 906 []string{"OK"}, 907 []string{"Creating", "app2"}, 908 []string{"OK"}, 909 []string{"Binding service", "app2-service", "app2", "my-org", "my-space", "my-user"}, 910 []string{"OK"}, 911 []string{"Binding service", "global-service", "app2", "my-org", "my-space", "my-user"}, 912 []string{"OK"}, 913 )) 914 }) 915 }) 916 917 Context("when the app is already bound to the service", func() { 918 BeforeEach(func() { 919 appRepo.ReadReturns.App = maker.NewApp(maker.Overrides{}) 920 serviceBinder.BindApplicationReturns.Error = errors.NewHttpError(500, "90003", "it don't work") 921 }) 922 923 It("gracefully continues", func() { 924 callPush() 925 Expect(len(serviceBinder.AppsToBind)).To(Equal(4)) 926 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"FAILED"})) 927 }) 928 }) 929 930 Context("when the service instance can't be found", func() { 931 BeforeEach(func() { 932 // routeRepo.FindByHostAndDomainReturns.Error = errors.new("can't find service instance") 933 serviceRepo.FindInstanceByNameErr = true 934 manifestRepo.ReadManifestReturns.Manifest = manifestWithServicesAndEnv() 935 }) 936 937 It("fails with an error", func() { 938 callPush() 939 Expect(ui.Outputs).To(ContainSubstrings( 940 []string{"FAILED"}, 941 []string{"Could not find service", "app1-service", "app1"}, 942 )) 943 }) 944 }) 945 946 }) 947 948 Describe("checking for bad flags", func() { 949 It("fails when a non-numeric start timeout is given", func() { 950 appRepo.ReadReturns.Error = errors.NewModelNotFoundError("App", "the-app") 951 952 callPush("-t", "FooeyTimeout", "my-new-app") 953 954 Expect(ui.Outputs).To(ContainSubstrings( 955 []string{"FAILED"}, 956 []string{"Invalid", "timeout", "FooeyTimeout"}, 957 )) 958 }) 959 }) 960 961 Describe("displaying information about files being uploaded", func() { 962 It("displays information about the files being uploaded", func() { 963 app_files.CountFilesReturns(11) 964 zipper.ZipReturns(nil) 965 zipper.GetZipSizeReturns(6100000, nil) 966 actor.GatherFilesReturns([]resources.AppFileResource{resources.AppFileResource{Path: "path/to/app"}, resources.AppFileResource{Path: "bar"}}, nil) 967 968 curDir, err := os.Getwd() 969 Expect(err).NotTo(HaveOccurred()) 970 971 callPush("appName") 972 Expect(ui.Outputs).To(ContainSubstrings( 973 []string{"Uploading", curDir}, 974 []string{"5.8M", "11 files"}, 975 )) 976 }) 977 978 It("omits the size when there are no files being uploaded", func() { 979 app_files.CountFilesReturns(0) 980 981 callPush("appName") 982 Expect(ui.WarnOutputs).To(ContainSubstrings( 983 []string{"None of your application files have changed", "Nothing will be uploaded"}, 984 )) 985 }) 986 }) 987 988 It("fails when the app can't be uploaded", func() { 989 actor.UploadAppReturns(errors.New("Boom!")) 990 991 callPush("app") 992 993 Expect(ui.Outputs).To(ContainSubstrings( 994 []string{"Uploading"}, 995 []string{"FAILED"}, 996 )) 997 }) 998 999 Describe("when binding the route fails", func() { 1000 BeforeEach(func() { 1001 routeRepo.FindByHostAndDomainReturns.Route.Host = "existing-app" 1002 routeRepo.FindByHostAndDomainReturns.Route.Domain = models.DomainFields{Name: "foo.cf-app.com"} 1003 }) 1004 1005 It("suggests using 'random-route' if the default route is taken", func() { 1006 routeRepo.BindErr = errors.NewHttpError(400, errors.INVALID_RELATION, "The URL not available") 1007 1008 callPush("existing-app") 1009 1010 Expect(ui.Outputs).To(ContainSubstrings( 1011 []string{"FAILED"}, 1012 []string{"existing-app.foo.cf-app.com", "already in use"}, 1013 []string{"TIP", "random-route"}, 1014 )) 1015 }) 1016 1017 It("does not suggest using 'random-route' for other failures", func() { 1018 routeRepo.BindErr = errors.NewHttpError(500, "some-code", "exception happened") 1019 1020 callPush("existing-app") 1021 1022 Expect(ui.Outputs).To(ContainSubstrings([]string{"FAILED"})) 1023 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"TIP", "random-route"})) 1024 }) 1025 }) 1026 1027 It("fails when neither a manifest nor a name is given", func() { 1028 manifestRepo.ReadManifestReturns.Error = errors.New("No such manifest") 1029 callPush() 1030 Expect(ui.Outputs).To(ContainSubstrings( 1031 []string{"FAILED"}, 1032 []string{"Manifest file is not found"}, 1033 )) 1034 }) 1035 }) 1036 1037 func existingAppManifest() *manifest.Manifest { 1038 return &manifest.Manifest{ 1039 Path: "manifest.yml", 1040 Data: generic.NewMap(map[interface{}]interface{}{ 1041 "applications": []interface{}{ 1042 generic.NewMap(map[interface{}]interface{}{ 1043 "name": "manifest-app-name", 1044 "memory": "128MB", 1045 "instances": 1, 1046 "host": "new-manifest-host", 1047 "domain": "example.com", 1048 "stack": "custom-stack", 1049 "timeout": 360, 1050 "buildpack": "some-buildpack", 1051 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1052 "path": filepath.Clean("some/path/from/manifest"), 1053 "env": generic.NewMap(map[interface{}]interface{}{ 1054 "FOO": "baz", 1055 "PATH": "/u/apps/my-app/bin", 1056 }), 1057 }), 1058 }, 1059 }), 1060 } 1061 } 1062 1063 func multipleHostManifest() *manifest.Manifest { 1064 return &manifest.Manifest{ 1065 Path: "manifest.yml", 1066 Data: generic.NewMap(map[interface{}]interface{}{ 1067 "applications": []interface{}{ 1068 generic.NewMap(map[interface{}]interface{}{ 1069 "name": "manifest-app-name", 1070 "memory": "128MB", 1071 "instances": 1, 1072 "hosts": []interface{}{"manifest-host-1", "manifest-host-2"}, 1073 "domain": "manifest-example.com", 1074 "stack": "custom-stack", 1075 "timeout": 360, 1076 "buildpack": "some-buildpack", 1077 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1078 "path": filepath.Clean("some/path/from/manifest"), 1079 "env": generic.NewMap(map[interface{}]interface{}{ 1080 "FOO": "baz", 1081 "PATH": "/u/apps/my-app/bin", 1082 }), 1083 }), 1084 }, 1085 }), 1086 } 1087 } 1088 1089 func singleAppManifest() *manifest.Manifest { 1090 return &manifest.Manifest{ 1091 Path: "manifest.yml", 1092 Data: generic.NewMap(map[interface{}]interface{}{ 1093 "applications": []interface{}{ 1094 generic.NewMap(map[interface{}]interface{}{ 1095 "name": "manifest-app-name", 1096 "memory": "128MB", 1097 "instances": 1, 1098 "host": "manifest-host", 1099 "domain": "manifest-example.com", 1100 "stack": "custom-stack", 1101 "timeout": 360, 1102 "buildpack": "some-buildpack", 1103 "command": `JAVA_HOME=$PWD/.openjdk JAVA_OPTS="-Xss995K" ./bin/start.sh run`, 1104 "path": filepath.Clean("some/path/from/manifest"), 1105 "env": generic.NewMap(map[interface{}]interface{}{ 1106 "FOO": "baz", 1107 "PATH": "/u/apps/my-app/bin", 1108 }), 1109 }), 1110 }, 1111 }), 1112 } 1113 } 1114 1115 func manifestWithServicesAndEnv() *manifest.Manifest { 1116 return &manifest.Manifest{ 1117 Data: generic.NewMap(map[interface{}]interface{}{ 1118 "applications": []interface{}{ 1119 generic.NewMap(map[interface{}]interface{}{ 1120 "name": "app1", 1121 "services": []interface{}{"app1-service", "global-service"}, 1122 "env": generic.NewMap(map[interface{}]interface{}{ 1123 "SOMETHING": "definitely-something", 1124 }), 1125 }), 1126 generic.NewMap(map[interface{}]interface{}{ 1127 "name": "app2", 1128 "services": []interface{}{"app2-service", "global-service"}, 1129 "env": generic.NewMap(map[interface{}]interface{}{ 1130 "SOMETHING": "nothing", 1131 }), 1132 }), 1133 }, 1134 }), 1135 } 1136 }