From 94e151a07b653dbd6a26f4d3b60a92c88357e318 Mon Sep 17 00:00:00 2001 From: Rajath Agasthya Date: Tue, 27 Aug 2024 16:37:28 -0700 Subject: [PATCH] Adds support for multi-Job CPIs This commit adds CLI support for deployment manifests that specify a CPI with multiple Jobs. A CPI's Job has traditionally been specified in a Director manifest with the `{cloud_provider: {template: {name: "name", release: "release"}}}` manifest construction. This commit extends the `cloud_provider` defintion with a new `templates` key whose value is an array of zero or more `{name:, release:}` hashes/mappings/dictionaries. Supporting this change required relaxing some explicit requirements in sections of the code that expected that a CPI could only ever have one Job. However, the requirement that exactly one of the CPI's Jobs have a template that creates a `bin/cpi` executable is retained. These changes are backwards-compatible, so existing manifests that use `cloud_provider: template...` syntax will work just fine. Why have we made these changes? To support upcoming work to permit the creation of "sidecar" or "plugin" Jobs for CPIs that can be used by the CPI during both CLI-driven CPI invocations (like `create-env`) and also Director-driven CPI invocations. Signed-off-by: Kenneth Lakin --- bin/gen-fakes | 3 + cloud/factory.go | 29 ++-- cmd/cmdfakes/fake_installation.go | 161 ++++++++++++++++- cmd/create_env_test.go | 14 +- cmd/deployment_deleter.go | 22 ++- cmd/deployment_deleter_test.go | 11 +- cmd/env_factory.go | 1 - cpi/release/installer.go | 48 ++++- cpi/release/installer_test.go | 2 + cpi/release/validator.go | 32 ---- cpi/release/validator_test.go | 100 ----------- installation/installation.go | 12 +- installation/installer.go | 30 +++- installation/installer_test.go | 31 ++-- installation/job_renderer.go | 4 - installation/job_renderer_test.go | 5 +- installation/job_resolver.go | 6 +- installation/job_resolver_test.go | 5 +- installation/manifest/manifest.go | 4 +- installation/manifest/parser.go | 35 +++- installation/manifest/parser_test.go | 221 ++++++++++++++++++------ installation/manifest/validator.go | 39 +++-- installation/manifest/validator_test.go | 38 ++-- integration/create_env_test.go | 9 +- 24 files changed, 560 insertions(+), 302 deletions(-) delete mode 100644 cpi/release/validator.go delete mode 100644 cpi/release/validator_test.go diff --git a/bin/gen-fakes b/bin/gen-fakes index 6efae229c..ddf07a10c 100755 --- a/bin/gen-fakes +++ b/bin/gen-fakes @@ -31,6 +31,9 @@ counterfeiter director FileReporter counterfeiter director TaskReporter counterfeiter director Event +# FakeInstallation is generated in cmd/cmdfakes because that's where it's used +counterfeiter -o cmd/cmdfakes installation Installation + counterfeiter uaa UAA counterfeiter uaa Token counterfeiter uaa AccessToken diff --git a/cloud/factory.go b/cloud/factory.go index e46bdb527..562352e39 100644 --- a/cloud/factory.go +++ b/cloud/factory.go @@ -31,19 +31,28 @@ func NewFactory( } func (f *factory) NewCloud(installation biinstall.Installation, directorID string, stemcellApiVersion int) (Cloud, error) { - cpiJob := installation.Job() - target := installation.Target() - cpi := CPI{ - JobPath: cpiJob.Path, - JobsDir: target.JobsPath(), - PackagesDir: target.PackagesPath(), + numberCpiBinariesFound := 0 + foundCPI := CPI{} + + for _, cpiJob := range installation.Jobs() { + target := installation.Target() + cpi := CPI{ + JobPath: cpiJob.Path, + JobsDir: target.JobsPath(), + PackagesDir: target.PackagesPath(), + } + + cmdPath := cpi.ExecutablePath() + if f.fs.FileExists(cmdPath) { + numberCpiBinariesFound += 1 + foundCPI = cpi + } } - cmdPath := cpi.ExecutablePath() - if !f.fs.FileExists(cmdPath) { - return nil, bosherr.Errorf("Installed CPI job '%s' does not contain the required executable '%s'", cpiJob.Name, cmdPath) + if numberCpiBinariesFound != 1 { + return nil, bosherr.Errorf("Found %d Jobs with a 'bin/cpi' binary. Expected 1.", numberCpiBinariesFound) } - cpiCmdRunner := NewCPICmdRunner(f.cmdRunner, cpi, f.logger) + cpiCmdRunner := NewCPICmdRunner(f.cmdRunner, foundCPI, f.logger) return NewCloud(cpiCmdRunner, directorID, stemcellApiVersion, f.logger), nil } diff --git a/cmd/cmdfakes/fake_installation.go b/cmd/cmdfakes/fake_installation.go index 46244229a..b614e8176 100644 --- a/cmd/cmdfakes/fake_installation.go +++ b/cmd/cmdfakes/fake_installation.go @@ -1,16 +1,167 @@ +// Code generated by counterfeiter. DO NOT EDIT. package cmdfakes import ( - biinstallation "github.com/cloudfoundry/bosh-cli/v7/installation" + "sync" + + "github.com/cloudfoundry/bosh-cli/v7/installation" ) type FakeInstallation struct { + JobsStub func() []installation.InstalledJob + jobsMutex sync.RWMutex + jobsArgsForCall []struct { + } + jobsReturns struct { + result1 []installation.InstalledJob + } + jobsReturnsOnCall map[int]struct { + result1 []installation.InstalledJob + } + TargetStub func() installation.Target + targetMutex sync.RWMutex + targetArgsForCall []struct { + } + targetReturns struct { + result1 installation.Target + } + targetReturnsOnCall map[int]struct { + result1 installation.Target + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeInstallation) Jobs() []installation.InstalledJob { + fake.jobsMutex.Lock() + ret, specificReturn := fake.jobsReturnsOnCall[len(fake.jobsArgsForCall)] + fake.jobsArgsForCall = append(fake.jobsArgsForCall, struct { + }{}) + stub := fake.JobsStub + fakeReturns := fake.jobsReturns + fake.recordInvocation("Jobs", []interface{}{}) + fake.jobsMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeInstallation) JobsCallCount() int { + fake.jobsMutex.RLock() + defer fake.jobsMutex.RUnlock() + return len(fake.jobsArgsForCall) +} + +func (fake *FakeInstallation) JobsCalls(stub func() []installation.InstalledJob) { + fake.jobsMutex.Lock() + defer fake.jobsMutex.Unlock() + fake.JobsStub = stub +} + +func (fake *FakeInstallation) JobsReturns(result1 []installation.InstalledJob) { + fake.jobsMutex.Lock() + defer fake.jobsMutex.Unlock() + fake.JobsStub = nil + fake.jobsReturns = struct { + result1 []installation.InstalledJob + }{result1} +} + +func (fake *FakeInstallation) JobsReturnsOnCall(i int, result1 []installation.InstalledJob) { + fake.jobsMutex.Lock() + defer fake.jobsMutex.Unlock() + fake.JobsStub = nil + if fake.jobsReturnsOnCall == nil { + fake.jobsReturnsOnCall = make(map[int]struct { + result1 []installation.InstalledJob + }) + } + fake.jobsReturnsOnCall[i] = struct { + result1 []installation.InstalledJob + }{result1} } -func (f *FakeInstallation) Target() biinstallation.Target { - return biinstallation.Target{} +func (fake *FakeInstallation) Target() installation.Target { + fake.targetMutex.Lock() + ret, specificReturn := fake.targetReturnsOnCall[len(fake.targetArgsForCall)] + fake.targetArgsForCall = append(fake.targetArgsForCall, struct { + }{}) + stub := fake.TargetStub + fakeReturns := fake.targetReturns + fake.recordInvocation("Target", []interface{}{}) + fake.targetMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 } -func (f *FakeInstallation) Job() biinstallation.InstalledJob { - return biinstallation.InstalledJob{} +func (fake *FakeInstallation) TargetCallCount() int { + fake.targetMutex.RLock() + defer fake.targetMutex.RUnlock() + return len(fake.targetArgsForCall) } + +func (fake *FakeInstallation) TargetCalls(stub func() installation.Target) { + fake.targetMutex.Lock() + defer fake.targetMutex.Unlock() + fake.TargetStub = stub +} + +func (fake *FakeInstallation) TargetReturns(result1 installation.Target) { + fake.targetMutex.Lock() + defer fake.targetMutex.Unlock() + fake.TargetStub = nil + fake.targetReturns = struct { + result1 installation.Target + }{result1} +} + +func (fake *FakeInstallation) TargetReturnsOnCall(i int, result1 installation.Target) { + fake.targetMutex.Lock() + defer fake.targetMutex.Unlock() + fake.TargetStub = nil + if fake.targetReturnsOnCall == nil { + fake.targetReturnsOnCall = make(map[int]struct { + result1 installation.Target + }) + } + fake.targetReturnsOnCall[i] = struct { + result1 installation.Target + }{result1} +} + +func (fake *FakeInstallation) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.jobsMutex.RLock() + defer fake.jobsMutex.RUnlock() + fake.targetMutex.RLock() + defer fake.targetMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeInstallation) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ installation.Installation = new(FakeInstallation) diff --git a/cmd/create_env_test.go b/cmd/create_env_test.go index 1156a0ca2..0e6afb5f1 100644 --- a/cmd/create_env_test.go +++ b/cmd/create_env_test.go @@ -251,9 +251,8 @@ var _ = Describe("CreateEnvCmd", func() { // parsed CPI deployment manifest installationManifest = biinstallmanifest.Manifest{ - Template: biinstallmanifest.ReleaseJobRef{ - Name: "fake-cpi-release-job-name", - Release: "fake-cpi-release-name", + Templates: []biinstallmanifest.ReleaseJobRef{ + {Name: "fake-cpi-release-job-name", Release: "fake-cpi-release-name"}, }, Mbus: mbusURL, } @@ -322,7 +321,6 @@ var _ = Describe("CreateEnvCmd", func() { cpiInstaller := bicpirel.CpiInstaller{ ReleaseManager: releaseManager, InstallerFactory: mockInstallerFactory, - Validator: bicpirel.NewValidator(), } releaseFetcher := biinstall.NewReleaseFetcher(tarballProvider, releaseReader, releaseManager) stemcellFetcher := bistemcell.Fetcher{ @@ -430,14 +428,15 @@ var _ = Describe("CreateEnvCmd", func() { mockInstallerFactory.EXPECT().NewInstaller(target).Return(mockInstaller).AnyTimes() - installation := biinstall.NewInstallation(target, installedJob, installationManifest) + installation := biinstall.NewInstallation(target, []biinstall.InstalledJob{installedJob}, + installationManifest) expectInstall = mockInstaller.EXPECT().Install(installationManifest, gomock.Any()).Do(func(_ interface{}, stage boshui.Stage) { Expect(fakeStage.SubStages).To(ContainElement(stage)) }).Return(installation, nil).AnyTimes() mockInstaller.EXPECT().Cleanup(installation).AnyTimes() - //mockDeployment := mock_deployment.NewMockDeployment(mockCtrl) + // mockDeployment := mock_deployment.NewMockDeployment(mockCtrl) expectDeploy = mockDeployer.EXPECT().Deploy( mockCloud, @@ -748,7 +747,8 @@ var _ = Describe("CreateEnvCmd", func() { It("returns error", func() { err := command.Run(fakeStage, defaultCreateEnvOpts) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("Invalid CPI release 'fake-cpi-release-name': CPI release must contain specified job 'fake-cpi-release-job-name'")) + //nolint:gosimple + Expect(err.Error()).To(Equal(fmt.Sprintf("Found 0 releases containing a template that renders to target 'bin/cpi'. Expected to find 1. Releases inspected: [fake-cpi-release-name]\nrelease 'fake-cpi-release-name' must contain specified job 'fake-cpi-release-job-name'"))) }) }) diff --git a/cmd/deployment_deleter.go b/cmd/deployment_deleter.go index 1503c3274..74a17ec7b 100644 --- a/cmd/deployment_deleter.go +++ b/cmd/deployment_deleter.go @@ -126,15 +126,23 @@ func (c *deploymentDeleter) DeleteDeployment(skipDrain bool, stage biui.Stage) ( return err } - cpiReleaseName := installationManifest.Template.Release - cpiReleaseRef, found := releaseSetManifest.FindByName(cpiReleaseName) - if !found { - return bosherr.Errorf("installation release '%s' must refer to a release in releases", cpiReleaseName) + errs := []error{} + for _, template := range installationManifest.Templates { + + cpiReleaseName := template.Release + cpiReleaseRef, found := releaseSetManifest.FindByName(cpiReleaseName) + if !found { + return bosherr.Errorf("installation release '%s' must refer to a release in releases", cpiReleaseName) + } + + err = c.releaseFetcher.DownloadAndExtract(cpiReleaseRef, stage) + if err != nil { + errs = append(errs, err) + } } - err = c.releaseFetcher.DownloadAndExtract(cpiReleaseRef, stage) - if err != nil { - return err + if len(errs) > 0 { + return bosherr.NewMultiError(errs...) } err = c.cpiInstaller.ValidateCpiRelease(installationManifest, stage) diff --git a/cmd/deployment_deleter_test.go b/cmd/deployment_deleter_test.go index 3eab01878..3416a0945 100644 --- a/cmd/deployment_deleter_test.go +++ b/cmd/deployment_deleter_test.go @@ -182,9 +182,8 @@ cloud_provider: var allowCPIToBeInstalled = func() { installationManifest := biinstallmanifest.Manifest{ Name: "test-release", - Template: biinstallmanifest.ReleaseJobRef{ - Name: "fake-cpi-release-job-name", - Release: "fake-cpi-release-name", + Templates: []biinstallmanifest.ReleaseJobRef{ + {Name: "fake-cpi-release-job-name", Release: "fake-cpi-release-name"}, }, Mbus: mbusURL, Properties: biproperty.Map{}, @@ -216,7 +215,6 @@ cloud_provider: cpiInstaller := bicpirel.CpiInstaller{ ReleaseManager: releaseManager, InstallerFactory: mockInstallerFactory, - Validator: bicpirel.NewValidator(), } releaseFetcher := biinstall.NewReleaseFetcher(tarballProvider, releaseReader, releaseManager) releaseSetAndInstallationManifestParser := cmd.ReleaseSetAndInstallationManifestParser{ @@ -526,9 +524,8 @@ cloud_provider: JustBeforeEach(func() { installationManifest := biinstallmanifest.Manifest{ Name: "test-release", - Template: biinstallmanifest.ReleaseJobRef{ - Name: "fake-cpi-release-job-name", - Release: "fake-cpi-release-name", + Templates: []biinstallmanifest.ReleaseJobRef{ + {Name: "fake-cpi-release-job-name", Release: "fake-cpi-release-name"}, }, Mbus: mbusURL, Properties: biproperty.Map{}, diff --git a/cmd/env_factory.go b/cmd/env_factory.go index c0a50f4c1..b54cb77f0 100644 --- a/cmd/env_factory.go +++ b/cmd/env_factory.go @@ -122,7 +122,6 @@ func NewEnvFactory( f.cpiInstaller = bicpirel.CpiInstaller{ ReleaseManager: f.releaseManager, InstallerFactory: installerFactory, - Validator: bicpirel.NewValidator(), } } diff --git a/cpi/release/installer.go b/cpi/release/installer.go index 10a2b32be..816259e9a 100644 --- a/cpi/release/installer.go +++ b/cpi/release/installer.go @@ -9,25 +9,55 @@ import ( biui "github.com/cloudfoundry/bosh-cli/v7/ui" ) +const ( + ReleaseBinaryName = "bin/cpi" +) + type CpiInstaller struct { ReleaseManager birel.Manager InstallerFactory biinstall.InstallerFactory - Validator Validator } func (i CpiInstaller) ValidateCpiRelease(installationManifest biinstallmanifest.Manifest, stage biui.Stage) error { return stage.Perform("Validating cpi release", func() error { - cpiReleaseName := installationManifest.Template.Release - cpiRelease, found := i.ReleaseManager.Find(cpiReleaseName) - if !found { - return bosherr.Errorf("installation release '%s' must refer to a provided release", cpiReleaseName) + var ( + errs []error + releasePackagingErrs []error + releaseNamesInspected []string + numberCpiBinariesFound = 0 + ) + + for _, template := range installationManifest.Templates { + releaseName := template.Release + releaseJobName := template.Name + release, found := i.ReleaseManager.Find(releaseName) + releaseNamesInspected = append(releaseNamesInspected, releaseName) + + if !found { + releasePackagingErrs = append(releasePackagingErrs, bosherr.Errorf("installation release '%s' must refer to a provided release", releaseName)) + continue + } + + job, ok := release.FindJobByName(releaseJobName) + + if !ok { + releasePackagingErrs = append(releasePackagingErrs, bosherr.Errorf("release '%s' must contain specified job '%s'", releaseName, releaseJobName)) + continue + } + + _, ok = job.FindTemplateByValue(ReleaseBinaryName) + if ok { + numberCpiBinariesFound += 1 + } } - err := i.Validator.Validate(cpiRelease, installationManifest.Template.Name) - if err != nil { - return bosherr.WrapErrorf(err, "Invalid CPI release '%s'", cpiReleaseName) + if numberCpiBinariesFound != 1 { + errs = append(errs, bosherr.Errorf("Found %d releases containing a template that renders to target '%s'. Expected to find 1. Releases inspected: %v", numberCpiBinariesFound, ReleaseBinaryName, releaseNamesInspected)) + errs = append(errs, releasePackagingErrs...) + return bosherr.NewMultiError(errs...) + } else { + return nil } - return nil }) } diff --git a/cpi/release/installer_test.go b/cpi/release/installer_test.go index 7f5ecec94..dc72886af 100644 --- a/cpi/release/installer_test.go +++ b/cpi/release/installer_test.go @@ -52,6 +52,8 @@ var _ = Describe("Installer", func() { expectCleanup = mockInstaller.EXPECT().Cleanup(installation).Return(nil) }) + It("should validate CPI release that include CPI and plugin releases", Pending, func() {}) + It("should install the CPI and call the passed in function with the installation", func() { cpiInstaller := release.CpiInstaller{ InstallerFactory: mockInstallerFactory, diff --git a/cpi/release/validator.go b/cpi/release/validator.go deleted file mode 100644 index d9890bd7e..000000000 --- a/cpi/release/validator.go +++ /dev/null @@ -1,32 +0,0 @@ -package release - -import ( - bosherr "github.com/cloudfoundry/bosh-utils/errors" - - birel "github.com/cloudfoundry/bosh-cli/v7/release" -) - -const ( - ReleaseBinaryName = "bin/cpi" -) - -type Validator struct { -} - -func NewValidator() Validator { - return Validator{} -} - -func (v Validator) Validate(release birel.Release, cpiReleaseJobName string) error { - job, ok := release.FindJobByName(cpiReleaseJobName) - if !ok { - return bosherr.Errorf("CPI release must contain specified job '%s'", cpiReleaseJobName) - } - - _, ok = job.FindTemplateByValue(ReleaseBinaryName) - if !ok { - return bosherr.Errorf("Specified CPI release job '%s' must contain a template that renders to target '%s'", cpiReleaseJobName, ReleaseBinaryName) - } - - return nil -} diff --git a/cpi/release/validator_test.go b/cpi/release/validator_test.go deleted file mode 100644 index 0160e68d7..000000000 --- a/cpi/release/validator_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package release_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - . "github.com/cloudfoundry/bosh-cli/v7/cpi/release" - boshrel "github.com/cloudfoundry/bosh-cli/v7/release" - boshjob "github.com/cloudfoundry/bosh-cli/v7/release/job" - fakerel "github.com/cloudfoundry/bosh-cli/v7/release/releasefakes" - . "github.com/cloudfoundry/bosh-cli/v7/release/resource" -) - -var _ = Describe("Validator", func() { - var cpiReleaseJobName = "fake-cpi-release-job-name" - - It("validates a valid release without error", func() { - job := boshjob.NewJob(NewResourceWithBuiltArchive( - "fake-cpi-release-job-name", "fake-job-1-fingerprint", "", "fake-job-1-sha")) - - job.Templates = map[string]string{"cpi.erb": "bin/cpi"} - - release := &fakerel.FakeRelease{ - NameStub: func() string { return "fake-release-name" }, - VersionStub: func() string { return "fake-release-version" }, - - FindJobByNameStub: func(name string) (boshjob.Job, bool) { - Expect(name).To(Equal(job.Name())) - return *job, true - }, - } - - validator := NewValidator() - - err := validator.Validate(release, cpiReleaseJobName) - Expect(err).NotTo(HaveOccurred()) - }) - - Context("when the cpi job is not present", func() { - var validator Validator - var release *fakerel.FakeRelease - - BeforeEach(func() { - job := boshjob.NewJob(NewResourceWithBuiltArchive( - "non-cpi-job", "fake-job-1-fingerprint", "", "fake-job-1-sha")) - - job.Templates = map[string]string{"cpi.erb": "bin/cpi"} - - release = &fakerel.FakeRelease{ - NameStub: func() string { return "fake-release-name" }, - VersionStub: func() string { return "fake-release-version" }, - - FindJobByNameStub: func(name string) (boshjob.Job, bool) { - Expect(name).To(Equal(cpiReleaseJobName)) - return boshjob.Job{}, false - }, - } - - validator = NewValidator() - }) - - It("returns an error that the cpi job is not present", func() { - err := validator.Validate(release, cpiReleaseJobName) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring( - "CPI release must contain specified job 'fake-cpi-release-job-name'")) - }) - }) - - Context("when the templates are missing a bin/cpi target", func() { - var validator Validator - var release boshrel.Release - - BeforeEach(func() { - job := boshjob.NewJob(NewResourceWithBuiltArchive( - "fake-cpi-release-job-name", "fake-job-1-fingerprint", "", "fake-job-1-sha")) - - job.Templates = map[string]string{"cpi.erb": "nonsense"} - - release = &fakerel.FakeRelease{ - NameStub: func() string { return "fake-release-name" }, - VersionStub: func() string { return "fake-release-version" }, - - FindJobByNameStub: func(name string) (boshjob.Job, bool) { - Expect(name).To(Equal(job.Name())) - return *job, true - }, - } - - validator = NewValidator() - }) - - It("returns an error that the bin/cpi template target is missing", func() { - err := validator.Validate(release, cpiReleaseJobName) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring( - "Specified CPI release job 'fake-cpi-release-job-name' must contain a template that renders to target 'bin/cpi'")) - }) - }) -}) diff --git a/installation/installation.go b/installation/installation.go index 8b1c564da..b2a34113a 100644 --- a/installation/installation.go +++ b/installation/installation.go @@ -6,23 +6,23 @@ import ( type Installation interface { Target() Target - Job() InstalledJob + Jobs() []InstalledJob } type installation struct { target Target - job InstalledJob + jobs []InstalledJob manifest biinstallmanifest.Manifest } func NewInstallation( target Target, - job InstalledJob, + jobs []InstalledJob, manifest biinstallmanifest.Manifest, ) Installation { return &installation{ target: target, - job: job, + jobs: jobs, manifest: manifest, } } @@ -31,6 +31,6 @@ func (i *installation) Target() Target { return i.target } -func (i *installation) Job() InstalledJob { - return i.job +func (i *installation) Jobs() []InstalledJob { + return i.jobs } diff --git a/installation/installer.go b/installation/installer.go index 28b2f52ad..efb6aa532 100644 --- a/installation/installer.go +++ b/installation/installer.go @@ -81,22 +81,38 @@ func (i *installer) Install(manifest biinstallmanifest.Manifest, stage biui.Stag return nil, bosherr.WrapError(err, "Rendering and uploading Jobs") } - renderedCPIJob := renderedJobRefs[0] - installedJob, err := i.installJob(renderedCPIJob, stage) - if err != nil { - return nil, bosherr.WrapErrorf(err, "Installing job '%s' for CPI release", renderedCPIJob.Name) + installedJobs := []InstalledJob{} + + for _, renderedCPIJob := range renderedJobRefs { + installedJob, err := i.installJob(renderedCPIJob, stage) + if err != nil { + return nil, bosherr.WrapErrorf(err, "Installing job '%s' for CPI release", renderedCPIJob.Name) + } + installedJobs = append(installedJobs, installedJob) + } return NewInstallation( i.target, - installedJob, + installedJobs, manifest, ), nil } func (i *installer) Cleanup(installation Installation) error { - job := installation.Job() - return i.blobExtractor.Cleanup(job.BlobstoreID, job.Path) + errs := []error{} + for _, job := range installation.Jobs() { + err := i.blobExtractor.Cleanup(job.BlobstoreID, job.Path) + if err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return bosherr.NewMultiError(errs...) + } else { + return nil + } } func (i *installer) installPackages(compiledPackages []CompiledPackageRef) error { diff --git a/installation/installer_test.go b/installation/installer_test.go index 896c99a17..32a852456 100644 --- a/installation/installer_test.go +++ b/installation/installer_test.go @@ -37,9 +37,9 @@ var _ = Describe("Installer", func() { logger boshlog.Logger - installer Installer - target Target - installedJob InstalledJob + installer Installer + target Target + installedJobs []InstalledJob ) BeforeEach(func() { @@ -56,7 +56,12 @@ var _ = Describe("Installer", func() { Properties: biproperty.Map{}, } renderedCPIJob := NewRenderedJobRef("cpi", "fake-release-job-fingerprint", "fake-rendered-job-blobstore-id", "fake-rendered-job-blobstore-id") - installedJob = NewInstalledJob(renderedCPIJob, "/extracted-release-path/cpi") + renderedCPIPluginJob := NewRenderedJobRef("cpi-plugin", "fake-release-job-fingerprint", "fake-rendered-job-blobstore-id", "fake-rendered-job-blobstore-id") + + installedJobs = make([]InstalledJob, 0) + installedJobs = append(installedJobs, NewInstalledJob(renderedCPIJob, "/extracted-release-path/cpi")) + installedJobs = append(installedJobs, NewInstalledJob(renderedCPIPluginJob, + "/extracted-release-path/cpi-plugin")) }) JustBeforeEach(func() { @@ -92,7 +97,11 @@ var _ = Describe("Installer", func() { compiledPackages := []CompiledPackageRef{ref} releaseJobs = []bireljob.Job{} - renderedJobRefs = []RenderedJobRef{installedJob.RenderedJobRef} + + renderedJobRefs = make([]RenderedJobRef, 0) + for _, installedJob := range installedJobs { + renderedJobRefs = append(renderedJobRefs, installedJob.RenderedJobRef) + } mockJobResolver.EXPECT().From(installationManifest).Return(releaseJobs, nil).AnyTimes() mockPackageCompiler.EXPECT().For(releaseJobs, fakeStage).Return(compiledPackages, nil).AnyTimes() }) @@ -137,7 +146,7 @@ var _ = Describe("Installer", func() { BeforeEach(func() { installation = NewInstallation( target, - installedJob, + installedJobs, installationManifest, ) }) @@ -146,11 +155,13 @@ var _ = Describe("Installer", func() { err := installer.Cleanup(installation) Expect(err).ToNot(HaveOccurred()) - Expect(fakeExtractor.CleanupCallCount()).To(Equal(1)) + Expect(fakeExtractor.CleanupCallCount()).To(Equal(2)) - blobstoreID, extractedBlobPath := fakeExtractor.CleanupArgsForCall(0) - Expect(blobstoreID).To(Equal(installedJob.BlobstoreID)) - Expect(extractedBlobPath).To(Equal(installedJob.Path)) + for i, installedJob := range installedJobs { + blobstoreID, extractedBlobPath := fakeExtractor.CleanupArgsForCall(i) + Expect(blobstoreID).To(Equal(installedJob.BlobstoreID)) + Expect(extractedBlobPath).To(Equal(installedJob.Path)) + } }) It("returns errors when cleaning up installed jobs", func() { diff --git a/installation/job_renderer.go b/installation/job_renderer.go index eab05e9ef..bf1b59792 100644 --- a/installation/job_renderer.go +++ b/installation/job_renderer.go @@ -61,10 +61,6 @@ func (b *jobRenderer) RenderAndUploadFrom(installationManifest biinstallmanifest 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 } diff --git a/installation/job_renderer_test.go b/installation/job_renderer_test.go index 4bae6e770..ba169226e 100644 --- a/installation/job_renderer_test.go +++ b/installation/job_renderer_test.go @@ -65,9 +65,8 @@ var _ = Describe("JobRenderer", func() { manifest = biinstallmanifest.Manifest{ Name: "fake-installation-name", - Template: biinstallmanifest.ReleaseJobRef{ - Name: "fake-cpi-job-name", - Release: "fake-cpi-release-name", + Templates: []biinstallmanifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, }, Properties: biproperty.Map{ "fake-installation-property": "fake-installation-property-value", diff --git a/installation/job_resolver.go b/installation/job_resolver.go index 275c6480e..ee08f539f 100644 --- a/installation/job_resolver.go +++ b/installation/job_resolver.go @@ -25,8 +25,10 @@ func NewJobResolver( } func (b *jobResolver) From(installationManifest biinstallmanifest.Manifest) ([]bireljob.Job, error) { - // installation only ever has one job: the cpi job. - jobsReferencesInRelease := []biinstallmanifest.ReleaseJobRef{installationManifest.Template} + jobsReferencesInRelease := []biinstallmanifest.ReleaseJobRef{} + for _, template := range installationManifest.Templates { + jobsReferencesInRelease = append(jobsReferencesInRelease, biinstallmanifest.ReleaseJobRef{Name: template.Name, Release: template.Release}) + } releaseJobs := make([]bireljob.Job, len(jobsReferencesInRelease)) for i, jobRef := range jobsReferencesInRelease { diff --git a/installation/job_resolver_test.go b/installation/job_resolver_test.go index e04e6339b..afd8612b3 100644 --- a/installation/job_resolver_test.go +++ b/installation/job_resolver_test.go @@ -38,9 +38,8 @@ var _ = Describe("JobResolver", func() { manifest = biinstallmanifest.Manifest{ Name: "fake-installation-name", - Template: biinstallmanifest.ReleaseJobRef{ - Name: "fake-cpi-job-name", - Release: "fake-cpi-release-name", + Templates: []biinstallmanifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, }, Properties: biproperty.Map{ "fake-installation-property": "fake-installation-property-value", diff --git a/installation/manifest/manifest.go b/installation/manifest/manifest.go index cb95407b4..1cd724f9e 100644 --- a/installation/manifest/manifest.go +++ b/installation/manifest/manifest.go @@ -5,8 +5,10 @@ import ( ) type Manifest struct { - Name string + Name string + // Deprecated: use Templates instead Template ReleaseJobRef + Templates []ReleaseJobRef Properties biproperty.Map Mbus string Cert Certificate diff --git a/installation/manifest/parser.go b/installation/manifest/parser.go index 04c4d7b12..4b0e08be2 100644 --- a/installation/manifest/parser.go +++ b/installation/manifest/parser.go @@ -36,6 +36,7 @@ type manifest struct { type installation struct { Template template + Templates []template Properties map[interface{}]interface{} SSHTunnel SSHTunnel `yaml:"ssh_tunnel"` Mbus string @@ -113,14 +114,40 @@ func (p *parser) Parse(path string, vars boshtpl.Variables, op patch.Op, release installationManifest := Manifest{ Name: comboManifest.Name, - Template: ReleaseJobRef{ - Name: comboManifest.CloudProvider.Template.Name, - Release: comboManifest.CloudProvider.Template.Release, - }, Mbus: comboManifest.CloudProvider.Mbus, Cert: comboManifest.CloudProvider.Cert, } + templateList := []ReleaseJobRef{} + templateNameCount := make(map[string]int) + if len(comboManifest.CloudProvider.Templates) != 0 { + for _, template := range comboManifest.CloudProvider.Templates { + templateName := template.Name + templateList = append(templateList, ReleaseJobRef{Name: templateName, Release: template.Release}) + // Check for duplicate names + templateNameCount[templateName] = templateNameCount[templateName] + 1 + } + + installationManifest.Templates = templateList + } else { + templateList = append(templateList, ReleaseJobRef{ + Name: comboManifest.CloudProvider.Template.Name, + Release: comboManifest.CloudProvider.Template.Release, + }) + installationManifest.Templates = templateList + } + + duplicateTemplateNames := []string{} + for templateName, count := range templateNameCount { + if count > 1 { + duplicateTemplateNames = append(duplicateTemplateNames, "'"+templateName+"'") + } + } + + if len(duplicateTemplateNames) != 0 { + return Manifest{}, bosherr.WrapErrorf(err, "Duplicate templates names are illegal. Duplicate template names found: %v", strings.Join(duplicateTemplateNames, ", ")) + } + properties, err := biproperty.BuildMap(comboManifest.CloudProvider.Properties) if err != nil { return Manifest{}, bosherr.WrapErrorf(err, "Parsing cloud_provider manifest properties: %#v", comboManifest.CloudProvider.Properties) diff --git a/installation/manifest/parser_test.go b/installation/manifest/parser_test.go index f722e4f95..bff40d628 100644 --- a/installation/manifest/parser_test.go +++ b/installation/manifest/parser_test.go @@ -47,9 +47,9 @@ var _ = Describe("Parser", func() { --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name mbus: http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868 properties: fake-property-name: @@ -59,9 +59,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -105,9 +105,8 @@ cloud_provider: Expect(installationManifest).To(Equal(manifest.Manifest{ Name: "fake-deployment-name", - Template: manifest.ReleaseJobRef{ - Name: "fake-cpi-job-name", - Release: "fake-cpi-release-name", + Templates: []manifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, }, Properties: biproperty.Map{ "fake-property-name": biproperty.Map{ @@ -127,9 +126,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -159,9 +158,8 @@ cloud_provider: Expect(installationManifest).To(Equal(manifest.Manifest{ Name: "fake-deployment-name", - Template: manifest.ReleaseJobRef{ - Name: "fake-cpi-job-name", - Release: "fake-cpi-release-name", + Templates: []manifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, }, Properties: biproperty.Map{}, Mbus: "http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868", @@ -175,9 +173,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -207,9 +205,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -242,9 +240,8 @@ cloud_provider: Expect(installationManifest).To(Equal(manifest.Manifest{ Name: "fake-deployment-name", - Template: manifest.ReleaseJobRef{ - Name: "fake-cpi-job-name", - Release: "fake-cpi-release-name", + Templates: []manifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, }, Properties: biproperty.Map{}, Mbus: "http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868", @@ -258,9 +255,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -290,9 +287,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -331,9 +328,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -354,9 +351,9 @@ cloud_provider: err := fakeFs.WriteFileString(comboManifestPath, `--- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -376,9 +373,9 @@ cloud_provider: err := fakeFs.WriteFileString(comboManifestPath, `--- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -401,9 +398,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -427,9 +424,9 @@ cloud_provider: err := fakeFs.WriteFileString(comboManifestPath, `--- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -470,9 +467,9 @@ cloud_provider: err := fakeFs.WriteFileString(comboManifestPath, `--- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name ssh_tunnel: host: 54.34.56.8 port: 22 @@ -496,9 +493,8 @@ cloud_provider: Expect(installationManifest).To(Equal(manifest.Manifest{ Name: "replaced-name", - Template: manifest.ReleaseJobRef{ - Name: "fake-cpi-job-name", - Release: "fake-cpi-release-name", + Templates: []manifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, }, Properties: biproperty.Map{}, Mbus: "http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868", @@ -520,9 +516,9 @@ cloud_provider: --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name mbus: http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868 cert: ca: | @@ -581,9 +577,9 @@ nH9ttalAwSLBsobVaK8mmiAdtAdx+CmHWrB4UNxCPYasrt5A6a9A9SiQ2dLd --- name: fake-deployment-name cloud_provider: - template: - name: fake-cpi-job-name - release: fake-cpi-release-name + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name mbus: http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868 cert: ca: | @@ -617,5 +613,116 @@ cloud_provider: }) }) }) + + Context("when multiple releases are specified in templates", func() { + BeforeEach(func() { + err := fakeFs.WriteFileString(comboManifestPath, ` +--- +name: fake-deployment-name +cloud_provider: + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name + - name: fake-cpi-plugin-job-name + release: fake-cpi-release-name + mbus: http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868 + properties: + fake-property-name: + nested-property: fake-property-value +`) + Expect(err).ToNot(HaveOccurred()) + }) + + It("parses installation from combo manifest", func() { + installationManifest, err := parser.Parse(comboManifestPath, boshtpl.StaticVariables{}, patch.Ops{}, releaseSetManifest) + Expect(err).ToNot(HaveOccurred()) + + Expect(installationManifest).To(Equal(manifest.Manifest{ + Name: "fake-deployment-name", + Templates: []manifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, + {Name: "fake-cpi-plugin-job-name", Release: "fake-cpi-release-name"}, + }, + Properties: biproperty.Map{ + "fake-property-name": biproperty.Map{ + "nested-property": "fake-property-value", + }, + }, + Mbus: "http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868", + })) + }) + + Context("when there are multiple Jobs with the same names in the specified templates", func() { + BeforeEach(func() { + err := fakeFs.WriteFileString(comboManifestPath, ` +--- +name: fake-deployment-name +cloud_provider: + templates: + - name: fake-cpi-job-name + release: fake-cpi-release-name + - name: fake-cpi-job-name + release: fake-cpi-release-other-name + - name: fake-cpi-job-name-not-duplicate + release: fake-cpi-release + - name: fake-cpi-job-name-number-two + release: fake-cpi-release + - name: fake-cpi-job-name-number-two + release: fake-cpi-release + mbus: http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868 + properties: + fake-property-name: + nested-property: fake-property-value +`) + Expect(err).ToNot(HaveOccurred()) + }) + + It("detects the duplicate names and returns an error containing only the duplicate names", func() { + _, err := parser.Parse(comboManifestPath, boshtpl.StaticVariables{}, patch.Ops{}, releaseSetManifest) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("'fake-cpi-job-name'")) + Expect(err.Error()).To(ContainSubstring("'fake-cpi-job-name-number-two'")) + Expect(err.Error()).ToNot(ContainSubstring("'fake-cpi-job-name-not-duplicate'")) + }) + + }) + }) + + Context("when the deprecated template key is specified instead of the templates key", func() { + BeforeEach(func() { + err := fakeFs.WriteFileString(comboManifestPath, ` +--- +name: fake-deployment-name +cloud_provider: + template: + name: fake-cpi-job-name + release: fake-cpi-release-name + mbus: http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868 + properties: + fake-property-name: + nested-property: fake-property-value +`) + Expect(err).ToNot(HaveOccurred()) + }) + + It("parses installation from combo manifest", func() { + installationManifest, err := parser.Parse(comboManifestPath, boshtpl.StaticVariables{}, patch.Ops{}, releaseSetManifest) + Expect(err).ToNot(HaveOccurred()) + + Expect(installationManifest).To(Equal(manifest.Manifest{ + Name: "fake-deployment-name", + Templates: []manifest.ReleaseJobRef{ + {Name: "fake-cpi-job-name", Release: "fake-cpi-release-name"}, + }, + Properties: biproperty.Map{ + "fake-property-name": biproperty.Map{ + "nested-property": "fake-property-value", + }, + }, + Mbus: "http://fake-mbus-user:fake-mbus-password@0.0.0.0:6868", + })) + }) + }) }) }) diff --git a/installation/manifest/validator.go b/installation/manifest/validator.go index d58839dfc..12934d4c4 100644 --- a/installation/manifest/validator.go +++ b/installation/manifest/validator.go @@ -1,6 +1,7 @@ package manifest import ( + "fmt" "strings" bosherr "github.com/cloudfoundry/bosh-utils/errors" @@ -24,21 +25,16 @@ func NewValidator(logger boshlog.Logger) Validator { } func (v *validator) Validate(manifest Manifest, releaseSetManifest birelsetmanifest.Manifest) error { - errs := []error{} + var 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")) + // When there is nothing in templates, return an error. It should have a CPI release. + if len(manifest.Templates) == 0 { + return fmt.Errorf("either cloud_provider.templates or cloud_provider.template must be provided and must contain at least one release") } - _, found := releaseSetManifest.FindByName(cpiReleaseName) - if !found { - errs = append(errs, bosherr.Errorf("cloud_provider.template.release '%s' must refer to a release in releases", cpiReleaseName)) + for _, template := range manifest.Templates { + errRet := v.validateReleaseJobRef(template, releaseSetManifest) + errs = append(errs, errRet...) } if len(errs) > 0 { @@ -48,6 +44,25 @@ func (v *validator) Validate(manifest Manifest, releaseSetManifest birelsetmanif return nil } +func (v *validator) validateReleaseJobRef(releaseJobRef ReleaseJobRef, releaseSetManifest birelsetmanifest.Manifest) []error { + var errs []error + jobName := releaseJobRef.Name + if v.isBlank(jobName) { + errs = append(errs, bosherr.Error("cloud_provider.template.name must be provided")) + } + + releaseName := releaseJobRef.Release + if v.isBlank(releaseName) { + errs = append(errs, bosherr.Error("cloud_provider.template.release must be provided")) + } + + _, found := releaseSetManifest.FindByName(releaseName) + if !found { + errs = append(errs, bosherr.Errorf("cloud_provider.template.release '%s' must refer to a release in releases", releaseName)) + } + return errs +} + func (v *validator) isBlank(str string) bool { return str == "" || strings.TrimSpace(str) == "" } diff --git a/installation/manifest/validator_test.go b/installation/manifest/validator_test.go index dae917ec5..b4cde5c79 100644 --- a/installation/manifest/validator_test.go +++ b/installation/manifest/validator_test.go @@ -30,9 +30,8 @@ var _ = Describe("Validator", func() { validManifest = Manifest{ Name: "fake-installation-name", - Template: ReleaseJobRef{ - Name: "cpi", - Release: "provided-valid-release-name", + Templates: []ReleaseJobRef{ + {Name: "cpi", Release: "provided-valid-release-name"}, }, Properties: biproperty.Map{ "fake-prop-key": "fake-prop-value", @@ -57,9 +56,21 @@ var _ = Describe("Validator", func() { Expect(err).ToNot(HaveOccurred()) }) - It("validates template must be fully specified", func() { + It("errors when validating an empty manifest", func() { manifest := Manifest{} + err := validator.Validate(manifest, releaseSetManifest) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("either cloud_provider.templates or cloud_provider.template must be provided and must contain at least one release")) + }) + + It("validates template must be fully specified", func() { + manifest := Manifest{ + Templates: []ReleaseJobRef{ + {Name: "", Release: ""}, + }, + } + err := validator.Validate(manifest, releaseSetManifest) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("cloud_provider.template.name must be provided")) @@ -68,8 +79,8 @@ var _ = Describe("Validator", func() { It("validates template.name is not blank", func() { manifest := Manifest{ - Template: ReleaseJobRef{ - Name: " ", + Templates: []ReleaseJobRef{ + {Name: " "}, }, } @@ -80,8 +91,8 @@ var _ = Describe("Validator", func() { It("validates template.release is not blank", func() { manifest := Manifest{ - Template: ReleaseJobRef{ - Release: " ", + Templates: []ReleaseJobRef{ + {Release: " "}, }, } @@ -92,8 +103,8 @@ var _ = Describe("Validator", func() { It("validates the release is available", func() { manifest := Manifest{ - Template: ReleaseJobRef{ - Release: "not-provided-valid-release-name", + Templates: []ReleaseJobRef{ + {Release: "not-provided-valid-release-name"}, }, } @@ -101,5 +112,12 @@ var _ = Describe("Validator", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("cloud_provider.template.release 'not-provided-valid-release-name' must refer to a release in releases")) }) + + It("validates the release successfully when multiple valid templates are specified", func() { + validManifest.Templates = append(validManifest.Templates, ReleaseJobRef{Name: "plugin", Release: "provided-valid-release-name"}) + + err := validator.Validate(validManifest, releaseSetManifest) + Expect(err).ToNot(HaveOccurred()) + }) }) }) diff --git a/integration/create_env_test.go b/integration/create_env_test.go index aeca93f2d..3433d7f55 100644 --- a/integration/create_env_test.go +++ b/integration/create_env_test.go @@ -291,9 +291,8 @@ cloud_provider: installationManifest := biinstallmanifest.Manifest{ Name: "test-deployment", - Template: biinstallmanifest.ReleaseJobRef{ - Name: "fake-cpi-release-job-name", - Release: "fake-cpi-release-name", + Templates: []biinstallmanifest.ReleaseJobRef{ + {Name: "fake-cpi-release-job-name", Release: "fake-cpi-release-name"}, }, Mbus: mbusURL, Cert: biinstallmanifest.Certificate{ @@ -308,7 +307,8 @@ cloud_provider: installedJob.Name = "fake-cpi-release-job-name" installedJob.Path = filepath.Join(target.JobsPath(), "fake-cpi-release-job-name") - installation := biinstall.NewInstallation(target, installedJob, installationManifest) + installation := biinstall.NewInstallation(target, []biinstall.InstalledJob{installedJob}, + installationManifest) mockInstallerFactory.EXPECT().NewInstaller(target).Return(mockInstaller).AnyTimes() @@ -425,7 +425,6 @@ cloud_provider: cpiInstaller := bicpirel.CpiInstaller{ ReleaseManager: releaseManager, InstallerFactory: mockInstallerFactory, - Validator: bicpirel.NewValidator(), } releaseFetcher := biinstall.NewReleaseFetcher(tarballProvider, releaseReader, releaseManager) stemcellFetcher := bistemcell.Fetcher{