1package bosh_test 2 3import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 10 "github.com/cloudfoundry/bosh-bootloader/bosh" 11 "github.com/cloudfoundry/bosh-bootloader/fakes" 12 "github.com/cloudfoundry/bosh-bootloader/fileio" 13 "github.com/cloudfoundry/bosh-bootloader/storage" 14 "github.com/spf13/afero" 15 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18) 19 20var _ = Describe("Executor", func() { 21 var ( 22 fs *afero.Afero 23 cli *fakes.BOSHCLI 24 stateDir string 25 deploymentDir string 26 varsDir string 27 relativeDeploymentDir string 28 relativeVarsDir string 29 relativeStateDir string 30 dirInput bosh.DirInput 31 32 executor bosh.Executor 33 ) 34 35 BeforeEach(func() { 36 fs = &afero.Afero{afero.NewMemMapFs()} 37 cli = &fakes.BOSHCLI{} 38 cli.RunStub = func(stdout io.Writer, workingDirectory string, args []string) error { 39 stdout.Write([]byte("some-manifest")) 40 return nil 41 } 42 cli.GetBOSHPathCall.Returns.Path = "bosh-path" 43 44 var err error 45 stateDir, err = fs.TempDir("", "") 46 Expect(err).NotTo(HaveOccurred()) 47 48 deploymentDir = filepath.Join(stateDir, "deployment") 49 err = fs.Mkdir(deploymentDir, os.ModePerm) 50 Expect(err).NotTo(HaveOccurred()) 51 52 varsDir = filepath.Join(stateDir, "vars") 53 err = fs.Mkdir(varsDir, os.ModePerm) 54 Expect(err).NotTo(HaveOccurred()) 55 56 relativeDeploymentDir = "${BBL_STATE_DIR}/deployment" 57 relativeVarsDir = "${BBL_STATE_DIR}/vars" 58 relativeStateDir = "${BBL_STATE_DIR}" 59 60 dirInput = bosh.DirInput{ 61 VarsDir: varsDir, 62 StateDir: stateDir, 63 } 64 65 executor = bosh.NewExecutor(cli, fs) 66 }) 67 68 Describe("PlanJumpbox", func() { 69 It("writes bosh-deployment assets to the deployment dir", func() { 70 err := executor.PlanJumpbox(dirInput, deploymentDir, "aws") 71 Expect(err).NotTo(HaveOccurred()) 72 73 By("writing bosh-deployment assets to the deployment dir", func() { 74 simplePath := filepath.Join(deploymentDir, "no-external-ip.yml") 75 expectedContents := bosh.MustAsset("vendor/github.com/cloudfoundry/jumpbox-deployment/no-external-ip.yml") 76 77 contents, err := fs.ReadFile(simplePath) 78 Expect(err).NotTo(HaveOccurred()) 79 Expect(contents).To(Equal(expectedContents)) 80 81 nestedPath := filepath.Join(deploymentDir, "vsphere", "cpi.yml") 82 expectedContents = bosh.MustAsset("vendor/github.com/cloudfoundry/jumpbox-deployment/vsphere/cpi.yml") 83 84 contents, err = fs.ReadFile(nestedPath) 85 Expect(err).NotTo(HaveOccurred()) 86 Expect(contents).To(Equal(expectedContents)) 87 }) 88 89 By("writing create-env and delete-env scripts", func() { 90 expectedArgs := []string{ 91 fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), 92 "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), 93 "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), 94 "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), 95 "-o", fmt.Sprintf("%s/aws/cpi.yml", relativeDeploymentDir), 96 "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, 97 "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, 98 } 99 100 expectedScript := formatScript("create-env", stateDir, expectedArgs) 101 scriptPath := fmt.Sprintf("%s/create-jumpbox.sh", stateDir) 102 shellScript, err := fs.ReadFile(scriptPath) 103 Expect(err).NotTo(HaveOccurred()) 104 105 fileinfo, err := fs.Stat(scriptPath) 106 Expect(err).NotTo(HaveOccurred()) 107 Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) 108 Expect(string(shellScript)).To(Equal(expectedScript)) 109 110 expectedScript = formatScript("delete-env", stateDir, expectedArgs) 111 scriptPath = fmt.Sprintf("%s/delete-jumpbox.sh", stateDir) 112 shellScript, err = fs.ReadFile(scriptPath) 113 Expect(err).NotTo(HaveOccurred()) 114 115 fileinfo, err = fs.Stat(scriptPath) 116 Expect(err).NotTo(HaveOccurred()) 117 Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) 118 Expect(err).NotTo(HaveOccurred()) 119 Expect(string(shellScript)).To(Equal(expectedScript)) 120 }) 121 }) 122 123 Context("on azure", func() { 124 It("generates create-env args for jumpbox", func() { 125 err := executor.PlanJumpbox(dirInput, deploymentDir, "azure") 126 Expect(err).NotTo(HaveOccurred()) 127 128 expectedArgs := []string{ 129 fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), 130 "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), 131 "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), 132 "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), 133 "-o", fmt.Sprintf("%s/azure/cpi.yml", relativeDeploymentDir), 134 "-v", `subscription_id="${BBL_AZURE_SUBSCRIPTION_ID}"`, 135 "-v", `client_id="${BBL_AZURE_CLIENT_ID}"`, 136 "-v", `client_secret="${BBL_AZURE_CLIENT_SECRET}"`, 137 "-v", `tenant_id="${BBL_AZURE_TENANT_ID}"`, 138 } 139 140 By("writing the create-env args to a shell script", func() { 141 expectedScript := formatScript("create-env", stateDir, expectedArgs) 142 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/create-jumpbox.sh", stateDir)) 143 Expect(err).NotTo(HaveOccurred()) 144 145 Expect(string(shellScript)).To(Equal(expectedScript)) 146 }) 147 148 By("writing the delete-env args to a shell script", func() { 149 expectedScript := formatScript("delete-env", stateDir, expectedArgs) 150 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/delete-jumpbox.sh", stateDir)) 151 Expect(err).NotTo(HaveOccurred()) 152 153 Expect(string(shellScript)).To(Equal(expectedScript)) 154 }) 155 }) 156 }) 157 158 Context("on gcp", func() { 159 It("generates create-env args for jumpbox", func() { 160 err := executor.PlanJumpbox(dirInput, deploymentDir, "gcp") 161 Expect(err).NotTo(HaveOccurred()) 162 163 expectedArgs := []string{ 164 fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), 165 "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), 166 "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), 167 "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), 168 "-o", fmt.Sprintf("%s/gcp/cpi.yml", relativeDeploymentDir), 169 "--var-file", `gcp_credentials_json="${BBL_GCP_SERVICE_ACCOUNT_KEY_PATH}"`, 170 "-v", `project_id="${BBL_GCP_PROJECT_ID}"`, 171 "-v", `zone="${BBL_GCP_ZONE}"`, 172 } 173 174 By("writing the create-env args to a shell script", func() { 175 expectedScript := formatScript("create-env", stateDir, expectedArgs) 176 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/create-jumpbox.sh", stateDir)) 177 Expect(err).NotTo(HaveOccurred()) 178 179 Expect(string(shellScript)).To(Equal(expectedScript)) 180 }) 181 182 By("writing the delete-env args to a shell script", func() { 183 expectedScript := formatScript("delete-env", stateDir, expectedArgs) 184 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/delete-jumpbox.sh", stateDir)) 185 Expect(err).NotTo(HaveOccurred()) 186 187 Expect(string(shellScript)).To(Equal(expectedScript)) 188 }) 189 }) 190 }) 191 192 Context("when the iaas is vsphere", func() { 193 It("generates create-env args for jumpbox", func() { 194 err := executor.PlanJumpbox(dirInput, deploymentDir, "vsphere") 195 Expect(err).NotTo(HaveOccurred()) 196 197 expectedArgs := []string{ 198 fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), 199 "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), 200 "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), 201 "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), 202 "-o", fmt.Sprintf("%s/vsphere/cpi.yml", relativeDeploymentDir), 203 "-o", fmt.Sprintf("%s/vsphere/resource-pool.yml", relativeDeploymentDir), 204 "-o", fmt.Sprintf("%s/vsphere-jumpbox-network.yml", relativeDeploymentDir), 205 "-v", `vcenter_user="${BBL_VSPHERE_VCENTER_USER}"`, 206 "-v", `vcenter_password="${BBL_VSPHERE_VCENTER_PASSWORD}"`, 207 } 208 209 By("writing the jumpbox-network ops-file", func() { 210 opsfile, err := fs.ReadFile(fmt.Sprintf("%s/vsphere-jumpbox-network.yml", deploymentDir)) 211 Expect(err).NotTo(HaveOccurred()) 212 213 Expect(string(opsfile)).To(ContainSubstring("instance_groups/name=jumpbox/networks/name=public")) 214 }) 215 216 By("writing the create-env args to a shell script", func() { 217 expectedScript := formatScript("create-env", stateDir, expectedArgs) 218 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/create-jumpbox.sh", stateDir)) 219 Expect(err).NotTo(HaveOccurred()) 220 221 Expect(string(shellScript)).To(Equal(expectedScript)) 222 }) 223 224 By("writing the delete-env args to a shell script", func() { 225 expectedScript := formatScript("delete-env", stateDir, expectedArgs) 226 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/delete-jumpbox.sh", stateDir)) 227 Expect(err).NotTo(HaveOccurred()) 228 229 Expect(string(shellScript)).To(Equal(expectedScript)) 230 }) 231 }) 232 }) 233 234 Context("openstack", func() { 235 It("generates create-env args for jumpbox", func() { 236 err := executor.PlanJumpbox(dirInput, deploymentDir, "openstack") 237 Expect(err).NotTo(HaveOccurred()) 238 239 expectedArgs := []string{ 240 fmt.Sprintf("%s/jumpbox.yml", relativeDeploymentDir), 241 "--state", fmt.Sprintf("%s/jumpbox-state.json", relativeVarsDir), 242 "--vars-store", fmt.Sprintf("%s/jumpbox-vars-store.yml", relativeVarsDir), 243 "--vars-file", fmt.Sprintf("%s/jumpbox-vars-file.yml", relativeVarsDir), 244 "-o", fmt.Sprintf("%s/openstack/cpi.yml", relativeDeploymentDir), 245 "-v", `openstack_username="${BBL_OPENSTACK_USERNAME}"`, 246 "-v", `openstack_password="${BBL_OPENSTACK_PASSWORD}"`, 247 } 248 249 By("writing the create-env args to a shell script", func() { 250 expectedScript := formatScript("create-env", stateDir, expectedArgs) 251 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/create-jumpbox.sh", stateDir)) 252 Expect(err).NotTo(HaveOccurred()) 253 254 Expect(string(shellScript)).To(Equal(expectedScript)) 255 }) 256 257 By("writing the delete-env args to a shell script", func() { 258 expectedScript := formatScript("delete-env", stateDir, expectedArgs) 259 shellScript, err := fs.ReadFile(fmt.Sprintf("%s/delete-jumpbox.sh", stateDir)) 260 Expect(err).NotTo(HaveOccurred()) 261 262 Expect(string(shellScript)).To(Equal(expectedScript)) 263 }) 264 }) 265 }) 266 }) 267 268 Describe("PlanDirector", func() { 269 It("writes bosh-deployment assets to the deployment dir", func() { 270 err := executor.PlanDirector(dirInput, deploymentDir, "warden") 271 Expect(err).NotTo(HaveOccurred()) 272 273 simplePath := filepath.Join(deploymentDir, "LICENSE") 274 expectedContents := bosh.MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/LICENSE") 275 276 contents, err := fs.ReadFile(simplePath) 277 Expect(err).NotTo(HaveOccurred()) 278 Expect(contents).To(Equal(expectedContents)) 279 280 nestedPath := filepath.Join(deploymentDir, "vsphere", "cpi.yml") 281 expectedContents = bosh.MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/vsphere/cpi.yml") 282 283 contents, err = fs.ReadFile(nestedPath) 284 Expect(err).NotTo(HaveOccurred()) 285 Expect(contents).To(Equal(expectedContents)) 286 }) 287 288 Context("aws", func() { 289 It("writes create-director.sh and delete-director.sh", func() { 290 expectedArgs := []string{ 291 filepath.Join(relativeDeploymentDir, "bosh.yml"), 292 "--state", filepath.Join(relativeVarsDir, "bosh-state.json"), 293 "--vars-store", filepath.Join(relativeVarsDir, "director-vars-store.yml"), 294 "--vars-file", filepath.Join(relativeVarsDir, "director-vars-file.yml"), 295 "-o", filepath.Join(relativeDeploymentDir, "aws", "cpi.yml"), 296 "-o", filepath.Join(relativeDeploymentDir, "jumpbox-user.yml"), 297 "-o", filepath.Join(relativeDeploymentDir, "uaa.yml"), 298 "-o", filepath.Join(relativeDeploymentDir, "credhub.yml"), 299 "-o", filepath.Join(relativeStateDir, "bbl-ops-files", "aws", "bosh-director-ephemeral-ip-ops.yml"), 300 "-o", filepath.Join(relativeDeploymentDir, "aws", "iam-instance-profile.yml"), 301 "-o", filepath.Join(relativeDeploymentDir, "aws", "encrypted-disk.yml"), 302 "-v", `access_key_id="${BBL_AWS_ACCESS_KEY_ID}"`, 303 "-v", `secret_access_key="${BBL_AWS_SECRET_ACCESS_KEY}"`, 304 } 305 306 behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "aws", stateDir) 307 }) 308 309 It("writes aws-specific ops files", func() { 310 err := executor.PlanDirector(dirInput, deploymentDir, "aws") 311 Expect(err).NotTo(HaveOccurred()) 312 313 ipOpsFile := filepath.Join(stateDir, "bbl-ops-files", "aws", "bosh-director-ephemeral-ip-ops.yml") 314 315 ipOpsFileContents, err := fs.ReadFile(ipOpsFile) 316 Expect(err).NotTo(HaveOccurred()) 317 Expect(string(ipOpsFileContents)).To(Equal(` 318- type: replace 319 path: /resource_pools/name=vms/cloud_properties/auto_assign_public_ip? 320 value: true 321`)) 322 }) 323 }) 324 325 Context("gcp", func() { 326 It("writes create-director.sh and delete-director.sh", func() { 327 expectedArgs := []string{ 328 filepath.Join(relativeDeploymentDir, "bosh.yml"), 329 "--state", filepath.Join(relativeVarsDir, "bosh-state.json"), 330 "--vars-store", filepath.Join(relativeVarsDir, "director-vars-store.yml"), 331 "--vars-file", filepath.Join(relativeVarsDir, "director-vars-file.yml"), 332 "-o", filepath.Join(relativeDeploymentDir, "gcp", "cpi.yml"), 333 "-o", filepath.Join(relativeDeploymentDir, "jumpbox-user.yml"), 334 "-o", filepath.Join(relativeDeploymentDir, "uaa.yml"), 335 "-o", filepath.Join(relativeDeploymentDir, "credhub.yml"), 336 "-o", filepath.Join(relativeStateDir, "bbl-ops-files", "gcp", "bosh-director-ephemeral-ip-ops.yml"), 337 "--var-file", `gcp_credentials_json="${BBL_GCP_SERVICE_ACCOUNT_KEY_PATH}"`, 338 "-v", `project_id="${BBL_GCP_PROJECT_ID}"`, 339 "-v", `zone="${BBL_GCP_ZONE}"`, 340 } 341 342 behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "gcp", stateDir) 343 }) 344 345 It("writes gcp-specific ops files", func() { 346 err := executor.PlanDirector(dirInput, deploymentDir, "gcp") 347 Expect(err).NotTo(HaveOccurred()) 348 349 ipOpsFile := filepath.Join(stateDir, "bbl-ops-files", "gcp", "bosh-director-ephemeral-ip-ops.yml") 350 351 ipOpsFileContents, err := fs.ReadFile(ipOpsFile) 352 Expect(err).NotTo(HaveOccurred()) 353 Expect(string(ipOpsFileContents)).To(Equal(` 354- type: replace 355 path: /networks/name=default/subnets/0/cloud_properties/ephemeral_external_ip? 356 value: true 357`)) 358 }) 359 }) 360 361 Context("azure", func() { 362 It("writes create-director.sh and delete-director.sh", func() { 363 expectedArgs := []string{ 364 filepath.Join(relativeDeploymentDir, "bosh.yml"), 365 "--state", filepath.Join(relativeVarsDir, "bosh-state.json"), 366 "--vars-store", filepath.Join(relativeVarsDir, "director-vars-store.yml"), 367 "--vars-file", filepath.Join(relativeVarsDir, "director-vars-file.yml"), 368 "-o", filepath.Join(relativeDeploymentDir, "azure", "cpi.yml"), 369 "-o", filepath.Join(relativeDeploymentDir, "jumpbox-user.yml"), 370 "-o", filepath.Join(relativeDeploymentDir, "uaa.yml"), 371 "-o", filepath.Join(relativeDeploymentDir, "credhub.yml"), 372 "-v", `subscription_id="${BBL_AZURE_SUBSCRIPTION_ID}"`, 373 "-v", `client_id="${BBL_AZURE_CLIENT_ID}"`, 374 "-v", `client_secret="${BBL_AZURE_CLIENT_SECRET}"`, 375 "-v", `tenant_id="${BBL_AZURE_TENANT_ID}"`, 376 } 377 378 behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "azure", stateDir) 379 }) 380 }) 381 382 Context("vsphere", func() { 383 It("writes create-director.sh and delete-director.sh", func() { 384 expectedArgs := []string{ 385 filepath.Join(relativeDeploymentDir, "bosh.yml"), 386 "--state", filepath.Join(relativeVarsDir, "bosh-state.json"), 387 "--vars-store", filepath.Join(relativeVarsDir, "director-vars-store.yml"), 388 "--vars-file", filepath.Join(relativeVarsDir, "director-vars-file.yml"), 389 "-o", filepath.Join(relativeDeploymentDir, "vsphere", "cpi.yml"), 390 "-o", filepath.Join(relativeDeploymentDir, "jumpbox-user.yml"), 391 "-o", filepath.Join(relativeDeploymentDir, "uaa.yml"), 392 "-o", filepath.Join(relativeDeploymentDir, "credhub.yml"), 393 "-o", filepath.Join(relativeDeploymentDir, "vsphere", "resource-pool.yml"), 394 "-v", `vcenter_user="${BBL_VSPHERE_VCENTER_USER}"`, 395 "-v", `vcenter_password="${BBL_VSPHERE_VCENTER_PASSWORD}"`, 396 } 397 398 behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "vsphere", stateDir) 399 }) 400 }) 401 402 Context("openstack", func() { 403 It("writes create-director.sh and delete-director.sh", func() { 404 expectedArgs := []string{ 405 filepath.Join(relativeDeploymentDir, "bosh.yml"), 406 "--state", filepath.Join(relativeVarsDir, "bosh-state.json"), 407 "--vars-store", filepath.Join(relativeVarsDir, "director-vars-store.yml"), 408 "--vars-file", filepath.Join(relativeVarsDir, "director-vars-file.yml"), 409 "-o", filepath.Join(relativeDeploymentDir, "openstack", "cpi.yml"), 410 "-o", filepath.Join(relativeDeploymentDir, "jumpbox-user.yml"), 411 "-o", filepath.Join(relativeDeploymentDir, "uaa.yml"), 412 "-o", filepath.Join(relativeDeploymentDir, "credhub.yml"), 413 "-v", `openstack_username="${BBL_OPENSTACK_USERNAME}"`, 414 "-v", `openstack_password="${BBL_OPENSTACK_PASSWORD}"`, 415 } 416 417 behavesLikePlan(expectedArgs, cli, fs, executor, dirInput, deploymentDir, "openstack", stateDir) 418 }) 419 }) 420 }) 421 422 Describe("WriteDeploymentVars", func() { 423 BeforeEach(func() { 424 dirInput.Deployment = "some-deployment" 425 }) 426 427 It("writes the deployment vars yml file", func() { 428 err := executor.WriteDeploymentVars(dirInput, "some-deployment-vars") 429 Expect(err).NotTo(HaveOccurred()) 430 431 deploymentVars, err := fs.ReadFile(filepath.Join(varsDir, "some-deployment-vars-file.yml")) 432 Expect(err).NotTo(HaveOccurred()) 433 434 Expect(string(deploymentVars)).To(Equal("some-deployment-vars")) 435 }) 436 }) 437 438 Describe("CreateEnv", func() { 439 var ( 440 cli *fakes.BOSHCLI 441 executor bosh.Executor 442 443 createEnvPath string 444 varsDir string 445 stateDir string 446 447 dirInput bosh.DirInput 448 state storage.State 449 ) 450 451 BeforeEach(func() { 452 fs = &afero.Afero{afero.NewOsFs()} // real os fs so we can exec scripts... 453 cli = &fakes.BOSHCLI{} 454 455 var err error 456 varsDir, err = fs.TempDir("", "") 457 Expect(err).NotTo(HaveOccurred()) 458 stateDir, err = fs.TempDir("", "") 459 Expect(err).NotTo(HaveOccurred()) 460 461 executor = bosh.NewExecutor(cli, fs) 462 463 dirInput = bosh.DirInput{ 464 Deployment: "some-deployment", 465 StateDir: stateDir, 466 VarsDir: varsDir, 467 } 468 469 createEnvPath = filepath.Join(stateDir, "create-some-deployment.sh") 470 createEnvContents := fmt.Sprintf("#!/bin/bash\necho 'some-vars-store-contents' > %s/some-deployment-vars-store.yml\n", varsDir) 471 472 fs.WriteFile(createEnvPath, []byte(createEnvContents), storage.ScriptMode) 473 }) 474 475 AfterEach(func() { 476 fs.Remove(filepath.Join(varsDir, "some-deployment-vars-store.yml")) 477 fs.Remove(createEnvPath) 478 fs.Remove(filepath.Join(stateDir, "create-some-deployment-override.sh")) 479 os.Unsetenv("BBL_STATE_DIR") 480 }) 481 482 Context("when the user provides a create-env override", func() { 483 BeforeEach(func() { 484 overridePath := filepath.Join(stateDir, "create-some-deployment-override.sh") 485 overrideContents := fmt.Sprintf("#!/bin/bash\necho 'override-vars-store-contents' > %s/some-deployment-vars-store.yml\n", varsDir) 486 487 fs.WriteFile(overridePath, []byte(overrideContents), storage.ScriptMode) 488 }) 489 490 It("runs the create-env-override.sh script", func() { 491 vars, err := executor.CreateEnv(dirInput, state) 492 Expect(err).NotTo(HaveOccurred()) 493 494 Expect(cli.RunCallCount()).To(Equal(0)) 495 Expect(vars).To(ContainSubstring("override-vars-store-contents")) 496 }) 497 }) 498 499 Context("when the user provides a create-env override with flag-provided environment variable", func() { 500 BeforeEach(func() { 501 err := os.Unsetenv("BBL_IAAS") 502 Expect(err).NotTo(HaveOccurred()) 503 504 state.IAAS = "some-fictional-iaas" 505 overrideContents := fmt.Sprintf("#!/bin/bash\n [ \"${BBL_IAAS}\" = \"some-fictional-iaas\" ]") 506 507 overridePath := filepath.Join(stateDir, "create-some-deployment-override.sh") 508 fs.WriteFile(overridePath, []byte(overrideContents), storage.ScriptMode) 509 }) 510 511 It("runs the create-env-override.sh script", func() { 512 _, err := executor.CreateEnv(dirInput, state) 513 Expect(err).NotTo(HaveOccurred()) 514 515 Expect(cli.RunCallCount()).To(Equal(0)) 516 }) 517 }) 518 519 It("runs the create-env script and returns the resulting vars-store contents", func() { 520 vars, err := executor.CreateEnv(dirInput, state) 521 Expect(err).NotTo(HaveOccurred()) 522 523 Expect(cli.RunCallCount()).To(Equal(0)) 524 Expect(vars).To(ContainSubstring("some-vars-store-contents")) 525 526 By("setting BBL_STATE_DIR environment variable", func() { 527 bblStateDirEnv := os.Getenv("BBL_STATE_DIR") 528 Expect(bblStateDirEnv).To(Equal(stateDir)) 529 }) 530 }) 531 532 Context("when iaas credentials are provided", func() { 533 Context("on aws", func() { 534 BeforeEach(func() { 535 state.IAAS = "aws" 536 state.AWS = storage.AWS{ 537 AccessKeyID: "some-access-key-id", 538 SecretAccessKey: "some-secret-access-key", 539 } 540 }) 541 542 It("sets credentials in environment variables", func() { 543 _, err := executor.CreateEnv(dirInput, state) 544 Expect(err).NotTo(HaveOccurred()) 545 546 Expect(os.Getenv("BBL_AWS_ACCESS_KEY_ID")).To(Equal("some-access-key-id")) 547 Expect(os.Getenv("BBL_AWS_SECRET_ACCESS_KEY")).To(Equal("some-secret-access-key")) 548 }) 549 }) 550 551 Context("on azure", func() { 552 BeforeEach(func() { 553 state.IAAS = "azure" 554 state.Azure = storage.Azure{ 555 ClientID: "some-client-id", 556 ClientSecret: "some-client-secret", 557 SubscriptionID: "some-subscription-id", 558 TenantID: "some-tenant-id", 559 } 560 }) 561 562 It("sets credentials in environment variables", func() { 563 _, err := executor.CreateEnv(dirInput, state) 564 Expect(err).NotTo(HaveOccurred()) 565 566 Expect(os.Getenv("BBL_AZURE_CLIENT_ID")).To(Equal("some-client-id")) 567 Expect(os.Getenv("BBL_AZURE_CLIENT_SECRET")).To(Equal("some-client-secret")) 568 Expect(os.Getenv("BBL_AZURE_SUBSCRIPTION_ID")).To(Equal("some-subscription-id")) 569 Expect(os.Getenv("BBL_AZURE_TENANT_ID")).To(Equal("some-tenant-id")) 570 }) 571 }) 572 573 Context("on gcp", func() { 574 BeforeEach(func() { 575 state.IAAS = "gcp" 576 state.GCP = storage.GCP{ 577 ServiceAccountKeyPath: "some-service-account-key-path", 578 Zone: "some-zone", 579 ProjectID: "some-project-id", 580 } 581 }) 582 583 It("sets credentials in environment variables", func() { 584 _, err := executor.CreateEnv(dirInput, state) 585 Expect(err).NotTo(HaveOccurred()) 586 587 Expect(os.Getenv("BBL_GCP_SERVICE_ACCOUNT_KEY_PATH")).To(Equal("some-service-account-key-path")) 588 Expect(os.Getenv("BBL_GCP_ZONE")).To(Equal("some-zone")) 589 Expect(os.Getenv("BBL_GCP_PROJECT_ID")).To(Equal("some-project-id")) 590 }) 591 }) 592 593 Context("on vsphere", func() { 594 It("sets credentials in environment variables", func() { 595 _, err := executor.CreateEnv(dirInput, storage.State{ 596 IAAS: "vsphere", 597 VSphere: storage.VSphere{ 598 VCenterUser: "some-user", 599 VCenterPassword: "some-password", 600 }, 601 }) 602 Expect(err).NotTo(HaveOccurred()) 603 604 Expect(os.Getenv("BBL_VSPHERE_VCENTER_USER")).To(Equal("some-user")) 605 Expect(os.Getenv("BBL_VSPHERE_VCENTER_PASSWORD")).To(Equal("some-password")) 606 }) 607 }) 608 609 Context("on openstack", func() { 610 It("sets credentials in environment variables", func() { 611 _, err := executor.CreateEnv(dirInput, storage.State{ 612 IAAS: "openstack", 613 OpenStack: storage.OpenStack{ 614 Username: "some-user", 615 Password: "some-password", 616 }, 617 }) 618 Expect(err).NotTo(HaveOccurred()) 619 620 Expect(os.Getenv("BBL_OPENSTACK_USERNAME")).To(Equal("some-user")) 621 Expect(os.Getenv("BBL_OPENSTACK_PASSWORD")).To(Equal("some-password")) 622 }) 623 }) 624 }) 625 626 Context("when the create-env script returns an error", func() { 627 BeforeEach(func() { 628 createEnvContents := "#!/bin/bash\nexit 1\n" 629 fs.WriteFile(createEnvPath, []byte(createEnvContents), storage.ScriptMode) 630 }) 631 632 It("returns an error", func() { 633 vars, err := executor.CreateEnv(dirInput, state) 634 Expect(err).To(MatchError(fmt.Sprintf("Running %s: exit status 1", createEnvPath))) 635 Expect(vars).To(Equal("")) 636 }) 637 }) 638 }) 639 640 Describe("DeleteEnv", func() { 641 var ( 642 executor bosh.Executor 643 644 deleteEnvPath string 645 varsDir string 646 stateDir string 647 648 dirInput bosh.DirInput 649 state storage.State 650 ) 651 652 BeforeEach(func() { 653 fs = &afero.Afero{afero.NewOsFs()} // real os fs so we can exec scripts... 654 655 var err error 656 varsDir, err = fs.TempDir("", "") 657 Expect(err).NotTo(HaveOccurred()) 658 stateDir, err = fs.TempDir("", "") 659 Expect(err).NotTo(HaveOccurred()) 660 661 executor = bosh.NewExecutor(cli, fs) 662 663 dirInput = bosh.DirInput{ 664 Deployment: "director", 665 VarsDir: varsDir, 666 StateDir: stateDir, 667 } 668 669 state = storage.State{ 670 IAAS: "some-iaas", 671 } 672 673 deleteEnvPath = filepath.Join(stateDir, "delete-director.sh") 674 deleteEnvContents := "#!/bin/bash\necho delete-env > /dev/null\n" 675 fs.WriteFile(deleteEnvPath, []byte(deleteEnvContents), storage.ScriptMode) 676 677 deploymentStateJson := filepath.Join(varsDir, "bosh-state.json") 678 fs.WriteFile(deploymentStateJson, []byte("some: deployment"), storage.StateMode) 679 }) 680 681 AfterEach(func() { 682 os.Unsetenv("BBL_STATE_DIR") 683 fs.Remove(filepath.Join(stateDir, "delete-director.sh")) 684 }) 685 686 Context("when the user provides a delete-env override", func() { 687 BeforeEach(func() { 688 overridePath := filepath.Join(stateDir, "delete-director-override.sh") 689 overrideContents := fmt.Sprintf("#!/bin/bash\necho 'override' > %s/delete-env-output\n", varsDir) 690 691 fs.WriteFile(overridePath, []byte(overrideContents), storage.ScriptMode) 692 }) 693 694 AfterEach(func() { 695 fs.Remove(filepath.Join(varsDir, "delete-env-output")) 696 fs.Remove(filepath.Join(stateDir, "delete-director-override.sh")) 697 }) 698 699 It("runs the delete-env-override.sh script", func() { 700 err := executor.DeleteEnv(dirInput, state) 701 Expect(err).NotTo(HaveOccurred()) 702 703 Expect(cli.RunCallCount()).To(Equal(0)) 704 705 overrideOut, err := fs.ReadFile(filepath.Join(varsDir, "delete-env-output")) 706 Expect(err).NotTo(HaveOccurred()) 707 Expect(overrideOut).To(ContainSubstring("override")) 708 }) 709 }) 710 711 Context("when the user tries to delete a jumpbox", func() { 712 BeforeEach(func() { 713 dirInput.Deployment = "jumpbox" 714 deleteEnvPath = filepath.Join(stateDir, "delete-jumpbox.sh") 715 deleteEnvContents := "#!/bin/bash\necho delete-env > /dev/null\n" 716 fs.WriteFile(deleteEnvPath, []byte(deleteEnvContents), storage.ScriptMode) 717 718 deploymentStateJson := filepath.Join(varsDir, "jumpbox-state.json") 719 fs.WriteFile(deploymentStateJson, []byte("some: deployment"), storage.StateMode) 720 }) 721 722 AfterEach(func() { 723 fs.Remove(filepath.Join(stateDir, "delete-jumpbox.sh")) 724 fs.Remove(filepath.Join(stateDir, "jumpbox-state.json")) 725 }) 726 727 It("deletes a bosh environment with the delete-env script", func() { 728 err := executor.DeleteEnv(dirInput, state) 729 Expect(err).NotTo(HaveOccurred()) 730 731 Expect(cli.RunCallCount()).To(Equal(0)) 732 733 By("setting BBL_STATE_DIR environment variable", func() { 734 bblStateDirEnv := os.Getenv("BBL_STATE_DIR") 735 Expect(bblStateDirEnv).To(Equal(stateDir)) 736 }) 737 }) 738 }) 739 740 Context("when the user tries to delete an unfamiliar deployment-type-thing", func() { 741 BeforeEach(func() { 742 dirInput.Deployment = "garbaggio-deployment" 743 }) 744 745 It("errors reasonably", func() { 746 err := executor.DeleteEnv(dirInput, state) 747 Expect(err).To(HaveOccurred()) 748 }) 749 }) 750 751 It("deletes a bosh environment with the delete-env script", func() { 752 err := executor.DeleteEnv(dirInput, state) 753 Expect(err).NotTo(HaveOccurred()) 754 755 Expect(cli.RunCallCount()).To(Equal(0)) 756 757 By("setting BBL_STATE_DIR environment variable", func() { 758 bblStateDirEnv := os.Getenv("BBL_STATE_DIR") 759 Expect(bblStateDirEnv).To(Equal(stateDir)) 760 }) 761 }) 762 763 Context("when iaas credentials are provided", func() { 764 Context("on aws", func() { 765 BeforeEach(func() { 766 state.IAAS = "aws" 767 state.AWS = storage.AWS{ 768 AccessKeyID: "some-access-key-id", 769 SecretAccessKey: "some-secret-access-key", 770 } 771 }) 772 773 It("sets credentials in environment variables", func() { 774 err := executor.DeleteEnv(dirInput, state) 775 Expect(err).NotTo(HaveOccurred()) 776 777 Expect(os.Getenv("BBL_AWS_ACCESS_KEY_ID")).To(Equal("some-access-key-id")) 778 Expect(os.Getenv("BBL_AWS_SECRET_ACCESS_KEY")).To(Equal("some-secret-access-key")) 779 }) 780 }) 781 782 Context("on azure", func() { 783 BeforeEach(func() { 784 state.IAAS = "azure" 785 state.Azure = storage.Azure{ 786 ClientID: "some-client-id", 787 ClientSecret: "some-client-secret", 788 SubscriptionID: "some-subscription-id", 789 TenantID: "some-tenant-id", 790 } 791 }) 792 793 It("sets credentials in environment variables", func() { 794 err := executor.DeleteEnv(dirInput, state) 795 Expect(err).NotTo(HaveOccurred()) 796 797 Expect(os.Getenv("BBL_AZURE_CLIENT_ID")).To(Equal("some-client-id")) 798 Expect(os.Getenv("BBL_AZURE_CLIENT_SECRET")).To(Equal("some-client-secret")) 799 Expect(os.Getenv("BBL_AZURE_SUBSCRIPTION_ID")).To(Equal("some-subscription-id")) 800 Expect(os.Getenv("BBL_AZURE_TENANT_ID")).To(Equal("some-tenant-id")) 801 }) 802 }) 803 804 Context("on gcp", func() { 805 BeforeEach(func() { 806 state.IAAS = "gcp" 807 state.GCP = storage.GCP{ 808 ServiceAccountKeyPath: "some-service-account-key-path", 809 Zone: "some-zone", 810 ProjectID: "some-project-id", 811 } 812 }) 813 814 It("sets credentials in environment variables", func() { 815 err := executor.DeleteEnv(dirInput, state) 816 Expect(err).NotTo(HaveOccurred()) 817 818 Expect(os.Getenv("BBL_GCP_SERVICE_ACCOUNT_KEY_PATH")).To(Equal("some-service-account-key-path")) 819 Expect(os.Getenv("BBL_GCP_ZONE")).To(Equal("some-zone")) 820 Expect(os.Getenv("BBL_GCP_PROJECT_ID")).To(Equal("some-project-id")) 821 }) 822 }) 823 824 Context("on vsphere", func() { 825 BeforeEach(func() { 826 state.IAAS = "vsphere" 827 state.VSphere = storage.VSphere{ 828 VCenterUser: "some-user", 829 VCenterPassword: "some-password", 830 } 831 }) 832 833 It("sets credentials in environment variables", func() { 834 err := executor.DeleteEnv(dirInput, state) 835 Expect(err).NotTo(HaveOccurred()) 836 837 Expect(os.Getenv("BBL_VSPHERE_VCENTER_USER")).To(Equal("some-user")) 838 Expect(os.Getenv("BBL_VSPHERE_VCENTER_PASSWORD")).To(Equal("some-password")) 839 }) 840 }) 841 }) 842 843 Context("when the create-env script returns an error", func() { 844 BeforeEach(func() { 845 deleteEnvContents := "#!/bin/bash\nexit 1\n" 846 fs.WriteFile(deleteEnvPath, []byte(deleteEnvContents), storage.ScriptMode) 847 }) 848 849 It("returns an error", func() { 850 err := executor.DeleteEnv(dirInput, state) 851 Expect(err).To(MatchError("Run bosh delete-env director: exit status 1")) 852 }) 853 }) 854 }) 855 856 Describe("Version", func() { 857 var ( 858 cli *fakes.BOSHCLI 859 executor bosh.Executor 860 ) 861 BeforeEach(func() { 862 cli = &fakes.BOSHCLI{} 863 cli.RunStub = func(stdout io.Writer, workingDirectory string, args []string) error { 864 stdout.Write([]byte("some-text version 1.1.1 some-other-text")) 865 return nil 866 } 867 868 executor = bosh.NewExecutor(cli, fs) 869 }) 870 871 It("returns the correctly trimmed version", func() { 872 version, err := executor.Version() 873 Expect(err).NotTo(HaveOccurred()) 874 875 _, _, args := cli.RunArgsForCall(0) 876 Expect(args).To(Equal([]string{"-v"})) 877 878 Expect(version).To(Equal("1.1.1")) 879 }) 880 881 Context("when the run cli fails", func() { 882 BeforeEach(func() { 883 cli.RunStub = nil 884 cli.RunCall.Returns.Error = errors.New("banana") 885 }) 886 887 It("returns an error", func() { 888 _, err := executor.Version() 889 Expect(err).To(MatchError("banana")) 890 }) 891 }) 892 893 Context("when the version cannot be parsed", func() { 894 BeforeEach(func() { 895 cli.RunStub = func(stdout io.Writer, workingDirectory string, args []string) error { 896 stdout.Write([]byte("")) 897 return nil 898 } 899 }) 900 901 It("returns a bosh version error", func() { 902 _, err := executor.Version() 903 Expect(err).To(MatchError("BOSH version could not be parsed")) 904 }) 905 }) 906 }) 907}) 908 909func formatScript(command string, stateDir string, args []string) string { 910 script := fmt.Sprintf("#!/bin/sh\nbosh-path %s \\\n", command) 911 for _, arg := range args { 912 if arg[0] == '-' { 913 script = fmt.Sprintf("%s %s", script, arg) 914 } else { 915 script = fmt.Sprintf("%s %s \\\n", script, arg) 916 } 917 } 918 919 return fmt.Sprintf("%s\n", script[:len(script)-2]) 920} 921 922type behavesLikePlanFs interface { 923 fileio.FileReader 924 fileio.Stater 925} 926 927func behavesLikePlan(expectedArgs []string, cli *fakes.BOSHCLI, fs behavesLikePlanFs, executor bosh.Executor, input bosh.DirInput, deploymentDir, iaas, stateDir string) { 928 cli.RunStub = func(stdout io.Writer, workingDirectory string, args []string) error { 929 stdout.Write([]byte("some-manifest")) 930 return nil 931 } 932 933 err := executor.PlanDirector(input, deploymentDir, iaas) 934 Expect(err).NotTo(HaveOccurred()) 935 Expect(cli.RunCallCount()).To(Equal(0)) 936 937 By("writing the create-env args to a shell script", func() { 938 expectedScript := formatScript("create-env", stateDir, expectedArgs) 939 scriptPath := fmt.Sprintf("%s/create-director.sh", stateDir) 940 shellScript, err := fs.ReadFile(scriptPath) 941 Expect(err).NotTo(HaveOccurred()) 942 943 fileinfo, err := fs.Stat(scriptPath) 944 Expect(err).NotTo(HaveOccurred()) 945 Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) 946 Expect(err).NotTo(HaveOccurred()) 947 948 Expect(string(shellScript)).To(Equal(expectedScript)) 949 }) 950 951 By("writing the delete-env args to a shell script", func() { 952 expectedScript := formatScript("delete-env", stateDir, expectedArgs) 953 scriptPath := fmt.Sprintf("%s/delete-director.sh", stateDir) 954 shellScript, err := fs.ReadFile(scriptPath) 955 Expect(err).NotTo(HaveOccurred()) 956 957 fileinfo, err := fs.Stat(scriptPath) 958 Expect(err).NotTo(HaveOccurred()) 959 Expect(fileinfo.Mode().String()).To(Equal("-rwxr-x---")) 960 Expect(err).NotTo(HaveOccurred()) 961 Expect(err).NotTo(HaveOccurred()) 962 963 Expect(string(shellScript)).To(Equal(expectedScript)) 964 }) 965} 966