Ejemplo n.º 1
0
func (p linux) findRootDevicePath() (string, error) {
	mounts, err := p.diskManager.GetMountsSearcher().SearchMounts()

	if err != nil {
		return "", bosherr.WrapError(err, "Searching mounts")
	}

	for _, mount := range mounts {
		if mount.MountPoint == "/" && strings.HasPrefix(mount.PartitionPath, "/dev/") {
			p.logger.Debug(logTag, "Found root partition: `%s'", mount.PartitionPath)

			stdout, _, _, err := p.cmdRunner.RunCommand("readlink", "-f", mount.PartitionPath)
			if err != nil {
				return "", bosherr.WrapError(err, "Shelling out to readlink")
			}
			rootPartition := strings.Trim(stdout, "\n")
			p.logger.Debug(logTag, "Symlink is: `%s'", rootPartition)

			validRootPartition := regexp.MustCompile(`^/dev/[a-z]+1$`)
			if !validRootPartition.MatchString(rootPartition) {
				return "", bosherr.Error("Root partition is not the first partition")
			}

			return strings.Trim(rootPartition, "1"), nil
		}
	}

	return "", bosherr.Error("Getting root partition device")
}
Ejemplo n.º 2
0
func (f SettingsSourceFactory) buildWithoutRegistry() (boshsettings.Source, error) {
	var settingsSources []boshsettings.Source

	for _, opts := range f.options.Sources {
		var settingsSource boshsettings.Source

		switch typedOpts := opts.(type) {
		case HTTPSourceOptions:
			return nil, bosherr.Error("HTTP source is not supported without registry")

		case ConfigDriveSourceOptions:
			settingsSource = NewConfigDriveSettingsSource(
				typedOpts.DiskPaths,
				typedOpts.MetaDataPath,
				typedOpts.SettingsPath,
				f.platform,
				f.logger,
			)

		case FileSourceOptions:
			return nil, bosherr.Error("File source is not supported without registry")

		case CDROMSourceOptions:
			settingsSource = NewCDROMSettingsSource(
				typedOpts.FileName,
				f.platform,
				f.logger,
			)
		}

		settingsSources = append(settingsSources, settingsSource)
	}

	return NewMultiSettingsSource(settingsSources...)
}
Ejemplo n.º 3
0
func (r defaultNetworkResolver) GetDefaultNetwork() (boshsettings.Network, error) {
	network := boshsettings.Network{}

	routes, err := r.routesSearcher.SearchRoutes()
	if err != nil {
		return network, bosherr.WrapError(err, "Searching routes")
	}

	if len(routes) == 0 {
		return network, bosherr.Error("No routes found")
	}

	for _, route := range routes {
		if !route.IsDefault() {
			continue
		}

		ip, err := r.ipResolver.GetPrimaryIPv4(route.InterfaceName)
		if err != nil {
			return network, bosherr.WrapErrorf(err, "Getting primary IPv4 for interface '%s'", route.InterfaceName)
		}

		return boshsettings.Network{
			IP:      ip.IP.String(),
			Netmask: gonet.IP(ip.Mask).String(),
			Gateway: route.Gateway,
		}, nil

	}

	return network, bosherr.Error("Failed to find default route")
}
Ejemplo n.º 4
0
func (b localBlobstore) Validate() error {
	path, found := b.options["blobstore_path"]
	if !found {
		return bosherr.Error("missing blobstore_path")
	}

	_, ok := path.(string)
	if !ok {
		return bosherr.Error("blobstore_path must be a string")
	}

	return nil
}
Ejemplo n.º 5
0
func (b retryableBlobstore) Validate() error {
	if b.maxTries < 1 {
		return bosherr.Error("Max tries must be > 0")
	}

	return b.blobstore.Validate()
}
Ejemplo n.º 6
0
// compilePackages compiles the specified packages, in the order specified, uploads them to the Blobstore, and returns the blob references
func (c *dependencyCompiler) compilePackages(requiredPackages []*birelpkg.Package, stage biui.Stage) ([]CompiledPackageRef, error) {
	packageRefs := make([]CompiledPackageRef, 0, len(requiredPackages))

	for _, pkg := range requiredPackages {
		stepName := fmt.Sprintf("Compiling package '%s/%s'", pkg.Name, pkg.Fingerprint)
		err := stage.Perform(stepName, func() error {
			compiledPackageRecord, isAlreadyCompiled, err := c.packageCompiler.Compile(pkg)
			if err != nil {
				return err
			}

			packageRef := CompiledPackageRef{
				Name:        pkg.Name,
				Version:     pkg.Fingerprint,
				BlobstoreID: compiledPackageRecord.BlobID,
				SHA1:        compiledPackageRecord.BlobSHA1,
			}
			packageRefs = append(packageRefs, packageRef)

			if isAlreadyCompiled {
				return biui.NewSkipStageError(bosherr.Error(fmt.Sprintf("Package '%s' is already compiled. Skipped compilation", pkg.Name)), "Package already compiled")
			}

			return nil
		})
		if err != nil {
			return nil, err
		}
	}

	return packageRefs, nil
}
func (ms *configDriveMetadataService) GetInstanceID() (string, error) {
	if ms.metaDataContents.InstanceID == "" {
		return "", bosherr.Error("Failed to load instance-id from config drive metadata service")
	}

	ms.logger.Debug(ms.logTag, "Getting instance id: %s", ms.metaDataContents.InstanceID)
	return ms.metaDataContents.InstanceID, nil
}
func (ms *configDriveMetadataService) GetServerName() (string, error) {
	if ms.userDataContents.Server.Name == "" {
		return "", bosherr.Error("Failed to load server name from config drive metadata service")
	}

	ms.logger.Debug(ms.logTag, "Getting server name: %s", ms.userDataContents.Server.Name)
	return ms.userDataContents.Server.Name, nil
}
Ejemplo n.º 9
0
func NewMultiSettingsSource(sources ...boshsettings.Source) (boshsettings.Source, error) {
	var err error

	if len(sources) == 0 {
		err = bosherr.Error("MultiSettingsSource requires to have at least one source")
	}

	return &MultiSettingsSource{sources: sources}, err
}
func (ms *configDriveMetadataService) GetPublicKey() (string, error) {
	if firstPublicKey, ok := ms.metaDataContents.PublicKeys["0"]; ok {
		if openSSHKey, ok := firstPublicKey["openssh-key"]; ok {
			return openSSHKey, nil
		}
	}

	return "", bosherr.Error("Failed to load openssh-key from config drive metadata service")
}
Ejemplo n.º 11
0
func (udev ConcreteUdevDevice) Trigger() (err error) {
	udev.logger.Debug(udev.logtag, "Triggering UdevDevice")
	switch {
	case udev.runner.CommandExists("udevadm"):
		_, _, _, err = udev.runner.RunCommand("udevadm", "trigger")
	case udev.runner.CommandExists("udevtrigger"):
		_, _, _, err = udev.runner.RunCommand("udevtrigger")
	default:
		err = bosherr.Error("can not find udevadm or udevtrigger commands")
	}
	return
}
Ejemplo n.º 12
0
func (s *server) Stop() error {
	if s.listener == nil {
		return bosherr.Error("Stopping not-started registry server")
	}

	s.logger.Debug(s.logTag, "Stopping registry server")
	err := s.listener.Close()
	if err != nil {
		return bosherr.WrapError(err, "Stopping registry server")
	}

	return nil
}
Ejemplo n.º 13
0
func (i *installation) StopRegistry() error {
	if !i.manifest.Registry.IsEmpty() {
		if i.registryServer == nil {
			return bosherr.Error("Registry must be started before it can be stopped")
		}
		err := i.registryServer.Stop()
		if err != nil {
			return bosherr.WrapError(err, "Stopping registry")
		}
		i.registryServer = nil
	}
	return nil
}
Ejemplo n.º 14
0
func (i *installation) StartRegistry() error {
	if !i.manifest.Registry.IsEmpty() {
		if i.registryServer != nil {
			return bosherr.Error("Registry already started")
		}
		config := i.manifest.Registry
		registryServer, err := i.registryServerManager.Start(config.Username, config.Password, config.Host, config.Port)
		if err != nil {
			return bosherr.WrapError(err, "Starting registry")
		}
		i.registryServer = registryServer
	}
	return nil
}
Ejemplo n.º 15
0
func (ms httpMetadataService) GetServerName() (string, error) {
	userData, err := ms.getUserData()
	if err != nil {
		return "", bosherr.WrapError(err, "Getting user data")
	}

	serverName := userData.Server.Name

	if len(serverName) == 0 {
		return "", bosherr.Error("Empty server name")
	}

	return serverName, nil
}
Ejemplo n.º 16
0
func (v *validator) Validate(manifest Manifest, releaseSetManifest birelsetmanifest.Manifest) error {
	errs := []error{}

	cpiJobName := manifest.Template.Name
	if v.isBlank(cpiJobName) {
		errs = append(errs, bosherr.Error("cloud_provider.template.name must be provided"))
	}

	cpiReleaseName := manifest.Template.Release
	if v.isBlank(cpiReleaseName) {
		errs = append(errs, bosherr.Error("cloud_provider.template.release must be provided"))
	}

	_, found := releaseSetManifest.FindByName(cpiReleaseName)
	if !found {
		errs = append(errs, bosherr.Errorf("cloud_provider.template.release '%s' must refer to a release in releases", cpiReleaseName))
	}

	if len(errs) > 0 {
		return bosherr.NewMultiError(errs...)
	}

	return nil
}
Ejemplo n.º 17
0
func (b *jobRenderer) RenderAndUploadFrom(installationManifest biinstallmanifest.Manifest, jobs []bireljob.Job, stage biui.Stage) ([]RenderedJobRef, error) {
	// installation jobs do not get rendered with global deployment properties, only the cloud_provider properties
	globalProperties := biproperty.Map{}
	jobProperties := installationManifest.Properties

	renderedJobRefs, err := b.renderJobTemplates(jobs, jobProperties, globalProperties, installationManifest.Name, stage)
	if err != nil {
		return nil, bosherr.WrapError(err, "Rendering job templates for installation")
	}

	if len(renderedJobRefs) != 1 {
		return nil, bosherr.Error("Too many jobs rendered... oops?")
	}

	return renderedJobRefs, nil
}
Ejemplo n.º 18
0
func (d *diskDeployer) updateCurrentDiskRecord(disk bidisk.Disk) error {
	savedDiskRecord, found, err := d.diskRepo.Find(disk.CID())
	if err != nil {
		return bosherr.WrapError(err, "Finding disk record")
	}

	if !found {
		return bosherr.Error("Failed to find disk record for new disk")
	}

	err = d.diskRepo.UpdateCurrent(savedDiskRecord.ID)
	if err != nil {
		return bosherr.WrapError(err, "Updating current disk record")
	}

	return nil
}
Ejemplo n.º 19
0
func (s *cloudStemcell) PromoteAsCurrent() error {
	stemcellRecord, found, err := s.repo.Find(s.name, s.version)
	if err != nil {
		return bosherr.WrapError(err, "Finding current stemcell")
	}

	if !found {
		return bosherr.Error("Stemcell does not exist in repo")
	}

	err = s.repo.UpdateCurrent(stemcellRecord.ID)
	if err != nil {
		return bosherr.WrapError(err, "Updating current stemcell")
	}

	return nil
}
Ejemplo n.º 20
0
func (f SettingsSourceFactory) buildWithRegistry() (boshsettings.Source, error) {
	var metadataServices []MetadataService

	digDNSResolver := NewDigDNSResolver(f.platform.GetRunner(), f.logger)
	resolver := NewRegistryEndpointResolver(digDNSResolver)

	for _, opts := range f.options.Sources {
		var metadataService MetadataService

		switch typedOpts := opts.(type) {
		case HTTPSourceOptions:
			metadataService = NewHTTPMetadataService(typedOpts.URI, resolver, f.platform, f.logger)

		case ConfigDriveSourceOptions:
			metadataService = NewConfigDriveMetadataService(
				resolver,
				f.platform,
				typedOpts.DiskPaths,
				typedOpts.MetaDataPath,
				typedOpts.UserDataPath,
				f.logger,
			)

		case FileSourceOptions:
			metadataService = NewFileMetadataService(
				typedOpts.MetaDataPath,
				typedOpts.UserDataPath,
				typedOpts.SettingsPath,
				f.platform.GetFs(),
				f.logger,
			)

		case CDROMSourceOptions:
			return nil, bosherr.Error("CDROM source is not supported when registry is used")
		}

		metadataServices = append(metadataServices, metadataService)
	}

	metadataService := NewMultiSourceMetadataService(metadataServices...)
	registryProvider := NewRegistryProvider(metadataService, f.platform, f.options.UseServerName, f.platform.GetFs(), f.logger)
	settingsSource := NewComplexSettingsSource(metadataService, registryProvider, f.logger)

	return settingsSource, nil
}
Ejemplo n.º 21
0
func (s *SourceOptionsSlice) UnmarshalJSON(data []byte) error {
	var maps []map[string]interface{}

	err := json.Unmarshal(data, &maps)
	if err != nil {
		return bosherr.WrapError(err, "Unmarshalling sources")
	}

	for _, m := range maps {
		if optType, ok := m["Type"]; ok {
			var err error
			var opts SourceOptions

			switch {
			case optType == "HTTP":
				var o HTTPSourceOptions
				err, opts = mapstruc.Decode(m, &o), o

			case optType == "ConfigDrive":
				var o ConfigDriveSourceOptions
				err, opts = mapstruc.Decode(m, &o), o

			case optType == "File":
				var o FileSourceOptions
				err, opts = mapstruc.Decode(m, &o), o

			case optType == "CDROM":
				var o CDROMSourceOptions
				err, opts = mapstruc.Decode(m, &o), o

			default:
				err = bosherr.Errorf("Unknown source type '%s'", optType)
			}

			if err != nil {
				return bosherr.WrapErrorf(err, "Unmarshalling source type '%s'", optType)
			}
			*s = append(*s, opts)
		} else {
			return bosherr.Error("Missing source type")
		}
	}

	return nil
}
Ejemplo n.º 22
0
func (p linux) GetMonitCredentials() (username, password string, err error) {
	monitUserFilePath := filepath.Join(p.dirProvider.BaseDir(), "monit", "monit.user")
	credContent, err := p.fs.ReadFileString(monitUserFilePath)
	if err != nil {
		err = bosherr.WrapError(err, "Reading monit user file")
		return
	}

	credParts := strings.SplitN(credContent, ":", 2)
	if len(credParts) != 2 {
		err = bosherr.Error("Malformated monit user file, expecting username and password separated by ':'")
		return
	}

	username = credParts[0]
	password = credParts[1]
	return
}
Ejemplo n.º 23
0
func (p *provider) Get(source Source, stage biui.Stage) (string, error) {
	if strings.HasPrefix(source.GetURL(), "file://") {
		filePath := strings.TrimPrefix(source.GetURL(), "file://")

		expandedPath, err := p.fs.ExpandPath(filePath)
		if err != nil {
			p.logger.Warn(p.logTag, "Failed to expand file path %s, using original URL", filePath)
			return filePath, nil
		}

		p.logger.Debug(p.logTag, "Using the tarball from file source: '%s'", filePath)
		return expandedPath, nil
	}

	if !strings.HasPrefix(source.GetURL(), "http") {
		return "", bosherr.Errorf("Invalid source URL: '%s', must be either file:// or http(s)://", source.GetURL())
	}

	var cachedPath string
	err := stage.Perform(fmt.Sprintf("Downloading %s", source.Description()), func() error {
		var found bool
		cachedPath, found = p.cache.Get(source)
		if found {
			p.logger.Debug(p.logTag, "Using the tarball from cache: '%s'", cachedPath)
			return biui.NewSkipStageError(bosherr.Error("Already downloaded"), "Found in local cache")
		}

		retryStrategy := boshretry.NewAttemptRetryStrategy(p.downloadAttempts, p.delayTimeout, p.downloadRetryable(source), p.logger)
		err := retryStrategy.Try()
		if err != nil {
			return bosherr.WrapErrorf(err, "Failed to download from '%s'", source.GetURL())
		}

		p.logger.Debug(p.logTag, "Using the downloaded tarball: '%s'", cachedPath)
		return nil
	})

	if err != nil {
		return "", err
	}

	return p.cache.Path(source), nil
}
func (ms *configDriveMetadataService) GetRegistryEndpoint() (string, error) {
	if ms.userDataContents.Registry.Endpoint == "" {
		return "", bosherr.Error("Failed to load registry endpoint from config drive metadata service")
	}

	endpoint := ms.userDataContents.Registry.Endpoint
	nameServers := ms.userDataContents.DNS.Nameserver

	if len(nameServers) == 0 {
		ms.logger.Debug(ms.logTag, "Getting registry endpoint %s", endpoint)
		return endpoint, nil
	}

	resolvedEndpoint, err := ms.resolver.LookupHost(nameServers, endpoint)
	if err != nil {
		return "", bosherr.WrapError(err, "Resolving registry endpoint")
	}

	ms.logger.Debug(ms.logTag, "Registry endpoint %s was resolved to %s", endpoint, resolvedEndpoint)
	return resolvedEndpoint, nil
}
Ejemplo n.º 25
0
func (udev ConcreteUdevDevice) readByte(filePath string) error {
	udev.logger.Debug(udev.logtag, "readBytes from file: %s", filePath)
	device, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer device.Close()
	udev.logger.Debug(udev.logtag, "Successfully open file: %s", filePath)

	bytes := make([]byte, 1, 1)
	read, err := device.Read(bytes)
	if err != nil {
		return err
	}
	udev.logger.Debug(udev.logtag, "Successfully read %d bytes from file: %s", read, filePath)

	if read != 1 {
		return bosherr.Error("Device readable but zero length")
	}

	return nil
}
				target := biinstall.NewTarget(filepath.Join("fake-install-dir", "fake-installation-id"))
				mockInstallerFactory.EXPECT().NewInstaller(target).Return(mockCpiInstaller).AnyTimes()

				fakeInstallation := &fakecmd.FakeInstallation{}

				expectCPIInstall = mockCpiInstaller.EXPECT().Install(installationManifest, gomock.Any()).Do(func(_ biinstallmanifest.Manifest, stage biui.Stage) {
					Expect(fakeStage.SubStages).To(ContainElement(stage))
				}).Return(fakeInstallation, nil).AnyTimes()
				mockCpiInstaller.EXPECT().Cleanup(fakeInstallation).AnyTimes()

				expectNewCloud = mockCloudFactory.EXPECT().NewCloud(fakeInstallation, directorID).Return(mockCloud, nil).AnyTimes()
			})

			Context("when the call to delete the deployment returns an error", func() {
				It("returns the error", func() {
					mockDeploymentManagerFactory.EXPECT().NewManager(mockCloud, mockAgentClient, mockBlobstore).Return(mockDeploymentManager)
					mockDeploymentManager.EXPECT().FindCurrent().Return(mockDeployment, true, nil)

					deleteError := bosherr.Error("delete error")

					mockDeployment.EXPECT().Delete(gomock.Any()).Return(deleteError)

					err := newDeploymentDeleter().DeleteDeployment(fakeStage)

					Expect(err).To(HaveOccurred())
				})
			})
		})
	})
})
			Expect(blobID).To(Equal("fake-blob-id"))
			Expect(sha1).To(Equal(fixtureSHA1))

			Expect(innerBlobstore.CreateFileNames[0]).To(Equal(fixturePath))
		})

		It("returns error if inner blobstore blob creation fails", func() {
			innerBlobstore.CreateErr = errors.New("fake-create-error")

			_, _, err := sha1VerifiableBlobstore.Create(fixturePath)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fake-create-error"))
		})
	})

	Describe("Validate", func() {
		It("delegates to inner blobstore to validate", func() {
			err := sha1VerifiableBlobstore.Validate()
			Expect(err).ToNot(HaveOccurred())
		})

		It("returns error if inner blobstore validation fails", func() {
			innerBlobstore.ValidateError = bosherr.Error("fake-validate-error")

			err := sha1VerifiableBlobstore.Validate()
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("fake-validate-error"))
		})
	})
})
Ejemplo n.º 28
0
				Expect(err).ToNot(HaveOccurred())

				Expect(fakeVM.UnmountDiskInputs).To(Equal([]fakebivm.UnmountDiskInput{
					{Disk: firstDisk},
					{Disk: secondDisk},
				}))

				Expect(fakeStage.PerformCalls[2:4]).To(Equal([]*fakebiui.PerformCall{
					{Name: "Unmounting disk 'fake-disk-1'"},
					{Name: "Unmounting disk 'fake-disk-2'"},
				}))
			})

			Context("when stopping vm fails", func() {
				var (
					stopError = bosherr.Error("fake-stop-error")
				)

				BeforeEach(func() {
					fakeVM.StopErr = stopError
				})

				It("returns an error", func() {
					err := instance.Delete(pingTimeout, pingDelay, fakeStage)
					Expect(err).To(HaveOccurred())
					Expect(err.Error()).To(ContainSubstring("fake-stop-error"))

					Expect(fakeStage.PerformCalls).To(Equal([]*fakebiui.PerformCall{
						{Name: "Waiting for the agent on VM 'fake-vm-cid'"},
						{
							Name:  "Stopping jobs on instance 'fake-job-name/0'",
Ejemplo n.º 29
0
			Expect(packages).To(ConsistOf([]installation.CompiledPackageRef{
				{
					Name:        "fake-release-package-name-1",
					Version:     "fake-release-package-fingerprint-1",
					BlobstoreID: "fake-compiled-package-blobstore-id-1",
					SHA1:        "fake-compiled-package-sha1-1",
				},
				{
					Name:        "fake-release-package-name-2",
					Version:     "fake-release-package-fingerprint-2",
					BlobstoreID: "fake-compiled-package-blobstore-id-2",
					SHA1:        "fake-compiled-package-sha1-2",
				},
			}))
		})

		Context("when package compilation fails", func() {
			JustBeforeEach(func() {
				expectCompile.Return([]bistatejob.CompiledPackageRef{}, bosherr.Error("fake-compile-package-2-error")).Times(1)
			})

			It("returns an error", func() {
				_, err := compiler.For(releaseJobs, fakeStage)

				Expect(err).To(HaveOccurred())
				Expect(err.Error()).To(ContainSubstring("fake-compile-package-2-error"))
			})
		})
	})
})
Ejemplo n.º 30
0
func describeMultilineError() {
	var err error

	It("returns a simple single-line message string (depth=0)", func() {
		err = bosherr.Error("omg")
		Expect(MultilineError(err)).To(Equal("omg"))
	})

	Context("when given a composite error", func() {
		It("returns a multi-line, indented message string (depth=1)", func() {
			err = bosherr.WrapError(bosherr.Error("inner omg"), "omg")
			Expect(MultilineError(err)).To(Equal("omg:\n  inner omg"))
		})

		It("returns a multi-line, indented message string (depth=2)", func() {
			err = bosherr.WrapError(bosherr.WrapError(bosherr.Error("inner omg"), "omg"), "outer omg")
			Expect(MultilineError(err)).To(Equal("outer omg:\n  omg:\n    inner omg"))
		})

		It("returns a multi-line, indented message string (depth=3)", func() {
			err = bosherr.WrapError(bosherr.WrapError(bosherr.WrapError(bosherr.Error("inner omg"), "almost inner omg"), "almost outer omg"), "outer omg")
			Expect(MultilineError(err)).To(Equal("outer omg:\n  almost outer omg:\n    almost inner omg:\n      inner omg"))
		})
	})

	Context("when given an explainable error", func() {
		It("returns a multi-line message string with sibling errors at the same indentation", func() {
			err = bosherr.NewMultiError(bosherr.Error("a"), bosherr.Error("b"))
			Expect(MultilineError(err)).To(Equal("a\nb"))
		})

		It("returns a multi-line message string with sibling errors at the same indentation", func() {
			complex := bosherr.WrapError(bosherr.Error("inner a"), "outer a")
			err = bosherr.NewMultiError(complex, bosherr.Error("b"))
			Expect(MultilineError(err)).To(Equal("outer a:\n  inner a\nb"))
		})

		It("returns a multi-line message string with sibling errors at the same indentation", func() {
			complex := bosherr.WrapError(bosherr.Error("inner b"), "outer b")
			err = bosherr.NewMultiError(bosherr.Error("a"), complex)
			Expect(MultilineError(err)).To(Equal("a\nouter b:\n  inner b"))
		})
	})

	Context("when given a composite err with explainable errors", func() {
		It("returns a multi-line message string with sibling errors at the same indentation", func() {
			multi := bosherr.NewMultiError(bosherr.Error("inner a"), bosherr.Error("inner b"))
			err = bosherr.WrapError(multi, "outer omg")
			Expect(MultilineError(err)).To(Equal("outer omg:\n  inner a\n  inner b"))
		})
	})

	Context("when given an ExecError", func() {
		It("returns a multi-line message string with the command, stdout, & stderr at the same indentation", func() {
			execErr := boshsys.NewExecError("fake-cmd --flag with some args", "some\nmultiline\nstdout", "some\nmultiline\nstderr")
			err = bosherr.WrapError(execErr, "outer omg")
			Expect(MultilineError(err)).To(Equal("outer omg:\n  Error Executing Command:\n    fake-cmd --flag with some args\n  StdOut:\n    some\n    multiline\n    stdout\n  StdErr:\n    some\n    multiline\n    stderr"))
		})
	})
}