1122 lines
35 KiB
Go
1122 lines
35 KiB
Go
package radar_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"code.cloudfoundry.org/clock/fakeclock"
|
|
"code.cloudfoundry.org/lager"
|
|
"code.cloudfoundry.org/lager/lagertest"
|
|
"github.com/cloudfoundry/bosh-cli/director/template"
|
|
"github.com/concourse/concourse/atc"
|
|
"github.com/concourse/concourse/atc/creds"
|
|
"github.com/concourse/concourse/atc/db"
|
|
"github.com/concourse/concourse/atc/db/dbfakes"
|
|
"github.com/concourse/concourse/atc/db/lock"
|
|
"github.com/concourse/concourse/atc/db/lock/lockfakes"
|
|
"github.com/concourse/concourse/atc/radar"
|
|
"github.com/concourse/concourse/atc/worker"
|
|
"github.com/concourse/concourse/atc/worker/workerfakes"
|
|
|
|
. "github.com/concourse/concourse/atc/radar"
|
|
"github.com/concourse/concourse/atc/resource"
|
|
rfakes "github.com/concourse/concourse/atc/resource/resourcefakes"
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("ResourceScanner", func() {
|
|
var (
|
|
epoch time.Time
|
|
scanLogger lager.Logger
|
|
|
|
fakeContainer *workerfakes.FakeContainer
|
|
fakeWorker *workerfakes.FakeWorker
|
|
fakePool *workerfakes.FakePool
|
|
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
|
|
fakeResourceFactory *rfakes.FakeResourceFactory
|
|
fakeResourceConfigFactory *dbfakes.FakeResourceConfigFactory
|
|
fakeDBPipeline *dbfakes.FakePipeline
|
|
fakeClock *fakeclock.FakeClock
|
|
interval time.Duration
|
|
variables creds.Variables
|
|
|
|
fakeResourceType *dbfakes.FakeResourceType
|
|
interpolatedResourceTypes atc.VersionedResourceTypes
|
|
|
|
scanner Scanner
|
|
|
|
resourceConfig atc.ResourceConfig
|
|
fakeDBResource *dbfakes.FakeResource
|
|
fakeResourceConfig *dbfakes.FakeResourceConfig
|
|
fakeResourceConfigScope *dbfakes.FakeResourceConfigScope
|
|
|
|
fakeLock *lockfakes.FakeLock
|
|
teamID = 123
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
epoch = time.Unix(123, 456).UTC()
|
|
scanLogger = lagertest.NewTestLogger("test")
|
|
fakeLock = &lockfakes.FakeLock{}
|
|
interval = 1 * time.Minute
|
|
GlobalResourceCheckTimeout = 1 * time.Hour
|
|
variables = template.StaticVariables{
|
|
"source-params": "some-secret-sauce",
|
|
}
|
|
|
|
resourceConfig = atc.ResourceConfig{
|
|
Name: "some-resource",
|
|
Type: "git",
|
|
Source: atc.Source{"uri": "some-secret-sauce"},
|
|
Tags: atc.Tags{"some-tag"},
|
|
}
|
|
|
|
interpolatedResourceTypes = atc.VersionedResourceTypes{
|
|
{
|
|
ResourceType: atc.ResourceType{
|
|
Name: "some-custom-resource",
|
|
Type: "registry-image",
|
|
Source: atc.Source{"custom": "some-secret-sauce"},
|
|
},
|
|
Version: atc.Version{"custom": "version"},
|
|
},
|
|
}
|
|
|
|
fakeContainer = new(workerfakes.FakeContainer)
|
|
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
|
|
fakePool = new(workerfakes.FakePool)
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakeResourceFactory = new(rfakes.FakeResourceFactory)
|
|
fakeResourceConfigFactory = new(dbfakes.FakeResourceConfigFactory)
|
|
fakeResourceType = new(dbfakes.FakeResourceType)
|
|
fakeDBResource = new(dbfakes.FakeResource)
|
|
fakeDBPipeline = new(dbfakes.FakePipeline)
|
|
fakeResourceConfig = new(dbfakes.FakeResourceConfig)
|
|
fakeResourceConfig.IDReturns(123)
|
|
fakeResourceConfigScope = new(dbfakes.FakeResourceConfigScope)
|
|
fakeResourceConfigScope.IDReturns(456)
|
|
fakeResourceConfigScope.ResourceConfigReturns(fakeResourceConfig)
|
|
|
|
fakeDBPipeline.IDReturns(42)
|
|
fakeDBPipeline.NameReturns("some-pipeline")
|
|
fakeDBPipeline.TeamIDReturns(teamID)
|
|
fakeClock = fakeclock.NewFakeClock(epoch)
|
|
|
|
fakeDBPipeline.ReloadReturns(true, nil)
|
|
fakeDBPipeline.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil)
|
|
|
|
fakeResourceType.IDReturns(1)
|
|
fakeResourceType.NameReturns("some-custom-resource")
|
|
fakeResourceType.TypeReturns("registry-image")
|
|
fakeResourceType.SourceReturns(atc.Source{"custom": "((source-params))"})
|
|
fakeResourceType.VersionReturns(atc.Version{"custom": "version"})
|
|
|
|
fakeDBResource.IDReturns(39)
|
|
fakeDBResource.NameReturns("some-resource")
|
|
fakeDBResource.PipelineNameReturns("some-pipeline")
|
|
fakeDBResource.TypeReturns("git")
|
|
fakeDBResource.SourceReturns(atc.Source{"uri": "((source-params))"})
|
|
fakeDBResource.TagsReturns(atc.Tags{"some-tag"})
|
|
fakeDBResource.SetResourceConfigReturns(fakeResourceConfigScope, nil)
|
|
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
|
|
scanner = NewResourceScanner(
|
|
fakeClock,
|
|
fakePool,
|
|
fakeResourceFactory,
|
|
fakeResourceConfigFactory,
|
|
interval,
|
|
fakeDBPipeline,
|
|
"https://www.example.com",
|
|
variables,
|
|
fakeStrategy,
|
|
)
|
|
})
|
|
|
|
Describe("Run", func() {
|
|
var (
|
|
fakeResource *rfakes.FakeResource
|
|
actualInterval time.Duration
|
|
runErr error
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
|
|
fakeContainer.HandleReturns("some-handle")
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
|
|
fakeResource = new(rfakes.FakeResource)
|
|
fakeResourceFactory.NewResourceForContainerReturns(fakeResource)
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
actualInterval, runErr = scanner.Run(scanLogger, 39)
|
|
})
|
|
|
|
Context("when the lock cannot be acquired", func() {
|
|
BeforeEach(func() {
|
|
results := make(chan bool, 4)
|
|
results <- false
|
|
results <- false
|
|
results <- true
|
|
results <- true
|
|
close(results)
|
|
|
|
fakeResourceConfigScope.AcquireResourceCheckingLockStub = func(logger lager.Logger) (lock.Lock, bool, error) {
|
|
if <-results {
|
|
return fakeLock, true, nil
|
|
} else {
|
|
// allow the sleep to continue
|
|
go fakeClock.WaitForWatcherAndIncrement(time.Second)
|
|
return nil, false, nil
|
|
}
|
|
}
|
|
})
|
|
|
|
It("retries every second until it is", func() {
|
|
Expect(fakeResourceConfigScope.AcquireResourceCheckingLockCallCount()).To(Equal(3))
|
|
Expect(fakeLock.ReleaseCallCount()).To(Equal(1))
|
|
})
|
|
})
|
|
|
|
Context("when the lock can be acquired", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.AcquireResourceCheckingLockReturns(fakeLock, true, nil)
|
|
})
|
|
|
|
Context("when the last checked is not able to be updated", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.UpdateLastCheckStartTimeReturns(false, nil)
|
|
})
|
|
|
|
It("does not check", func() {
|
|
Expect(fakeResource.CheckCallCount()).To(Equal(0))
|
|
})
|
|
|
|
It("returns the configured interval", func() {
|
|
Expect(runErr).To(Equal(ErrFailedToAcquireLock))
|
|
Expect(actualInterval).To(Equal(interval))
|
|
})
|
|
})
|
|
|
|
Context("when the last checked fails to update", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.UpdateLastCheckStartTimeReturns(false, errors.New("woops"))
|
|
})
|
|
|
|
It("does not check", func() {
|
|
Expect(fakeResource.CheckCallCount()).To(Equal(0))
|
|
})
|
|
|
|
It("returns the configured interval", func() {
|
|
Expect(runErr).To(Equal(errors.New("woops")))
|
|
Expect(actualInterval).To(Equal(interval))
|
|
})
|
|
})
|
|
|
|
Context("when the last checked is updated", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.UpdateLastCheckStartTimeReturns(true, nil)
|
|
})
|
|
|
|
It("checks immediately", func() {
|
|
Expect(fakeResource.CheckCallCount()).To(Equal(1))
|
|
})
|
|
|
|
It("constructs the resource of the correct type", func() {
|
|
Expect(fakeDBResource.SetResourceConfigCallCount()).To(Equal(1))
|
|
resourceSource, resourceTypes := fakeDBResource.SetResourceConfigArgsForCall(0)
|
|
Expect(resourceSource).To(Equal(atc.Source{"uri": "some-secret-sauce"}))
|
|
Expect(resourceTypes).To(Equal(interpolatedResourceTypes))
|
|
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
err := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(err).To(BeNil())
|
|
|
|
_, _, owner, containerSpec, metadata, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(owner).To(Equal(db.NewResourceConfigCheckSessionContainerOwner(fakeResourceConfig, radar.ContainerExpiries)))
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ResourceType: "git",
|
|
}))
|
|
Expect(containerSpec.Tags).To(Equal([]string{"some-tag"}))
|
|
Expect(containerSpec.TeamID).To(Equal(123))
|
|
Expect(containerSpec.Env).To(Equal([]string{
|
|
"ATC_EXTERNAL_URL=https://www.example.com",
|
|
"RESOURCE_PIPELINE_NAME=some-pipeline",
|
|
"RESOURCE_NAME=some-resource",
|
|
}))
|
|
Expect(metadata).To(Equal(db.ContainerMetadata{
|
|
Type: db.ContainerTypeCheck,
|
|
}))
|
|
Expect(workerSpec).To(Equal(worker.WorkerSpec{
|
|
ResourceType: "git",
|
|
Tags: atc.Tags{"some-tag"},
|
|
ResourceTypes: interpolatedResourceTypes,
|
|
TeamID: 123,
|
|
}))
|
|
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, _, _, owner, containerSpec, resourceTypes = fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(owner).To(Equal(db.NewResourceConfigCheckSessionContainerOwner(fakeResourceConfig, radar.ContainerExpiries)))
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ResourceType: "git",
|
|
}))
|
|
Expect(containerSpec.Tags).To(Equal([]string{"some-tag"}))
|
|
Expect(containerSpec.TeamID).To(Equal(123))
|
|
Expect(containerSpec.Env).To(Equal([]string{
|
|
"ATC_EXTERNAL_URL=https://www.example.com",
|
|
"RESOURCE_PIPELINE_NAME=some-pipeline",
|
|
"RESOURCE_NAME=some-resource",
|
|
}))
|
|
Expect(resourceTypes).To(Equal(interpolatedResourceTypes))
|
|
})
|
|
|
|
Context("when the resource config has a specified check interval", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CheckEveryReturns("10ms")
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
})
|
|
|
|
It("leases for the configured interval", func() {
|
|
Expect(fakeResourceConfigScope.AcquireResourceCheckingLockCallCount()).To(Equal(1))
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckStartTimeCallCount()).To(Equal(1))
|
|
|
|
leaseInterval, immediate := fakeResourceConfigScope.UpdateLastCheckStartTimeArgsForCall(0)
|
|
Expect(leaseInterval).To(Equal(10 * time.Millisecond))
|
|
Expect(immediate).To(BeFalse())
|
|
|
|
Eventually(fakeLock.ReleaseCallCount).Should(Equal(1))
|
|
})
|
|
|
|
It("returns configured interval", func() {
|
|
Expect(actualInterval).To(Equal(10 * time.Millisecond))
|
|
})
|
|
|
|
Context("when the interval cannot be parsed", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CheckEveryReturns("bad-value")
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
})
|
|
|
|
It("sets the check error", func() {
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
|
|
resourceErr := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(resourceErr).To(MatchError("time: invalid duration bad-value"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
})
|
|
})
|
|
})
|
|
|
|
It("grabs a periodic resource checking lock before checking, breaks lock after done", func() {
|
|
Expect(fakeResourceConfigScope.AcquireResourceCheckingLockCallCount()).To(Equal(1))
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckStartTimeCallCount()).To(Equal(1))
|
|
|
|
leaseInterval, immediate := fakeResourceConfigScope.UpdateLastCheckStartTimeArgsForCall(0)
|
|
Expect(leaseInterval).To(Equal(interval))
|
|
Expect(immediate).To(BeFalse())
|
|
|
|
Eventually(fakeLock.ReleaseCallCount).Should(Equal(1))
|
|
})
|
|
|
|
Context("when the resource uses a custom type", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.TypeReturns("some-custom-resource")
|
|
})
|
|
|
|
Context("and the custom type has a version", func() {
|
|
It("doesn't check for check error of custom type", func() {
|
|
Expect(fakeResourceType.CheckErrorCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
|
|
Context("and the custom type does not have a version", func() {
|
|
BeforeEach(func() {
|
|
results := make(chan bool, 4)
|
|
results <- false
|
|
results <- false
|
|
results <- true
|
|
results <- true
|
|
close(results)
|
|
|
|
fakeResourceType.VersionStub = func() atc.Version {
|
|
if <-results {
|
|
return atc.Version{"version": "1"}
|
|
} else {
|
|
// allow the sleep to continue
|
|
go fakeClock.WaitForWatcherAndIncrement(10 * time.Second)
|
|
return nil
|
|
}
|
|
}
|
|
})
|
|
|
|
Context("when the custom type has a check error", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceType.CheckErrorReturns(errors.New("oops"))
|
|
})
|
|
|
|
It("sets the resource check error to the custom type's check error and does not run a check", func() {
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
err := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(err).To(Equal(errors.New("oops")))
|
|
|
|
Expect(fakeResource.CheckCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
|
|
Context("when the custom type has a nil check error", func() {
|
|
Context("when the resource type sucessfully reloads", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceType.ReloadReturns(true, nil)
|
|
})
|
|
|
|
It("retries every second until version is not nil", func() {
|
|
Expect(fakeResourceType.VersionCallCount()).To(Equal(4))
|
|
})
|
|
})
|
|
|
|
Context("when the resource type fails to reload", func() {
|
|
disaster := errors.New("oops")
|
|
|
|
BeforeEach(func() {
|
|
fakeResourceType.ReloadReturns(false, disaster)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
Expect(runErr).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when the resource type is not found", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceType.ReloadReturns(false, nil)
|
|
})
|
|
|
|
It("returns ErrResourceTypeNotFound error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
Expect(runErr).To(Equal(radar.ErrResourceTypeNotFound))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when there is no current version", func() {
|
|
It("checks from nil", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("when there is a current version", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigVersion := new(dbfakes.FakeResourceConfigVersion)
|
|
fakeResourceConfigVersion.IDReturns(1)
|
|
fakeResourceConfigVersion.VersionReturns(db.Version{"version": "1"})
|
|
|
|
fakeResourceConfigScope.LatestVersionReturns(fakeResourceConfigVersion, true, nil)
|
|
})
|
|
|
|
It("checks from it", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(Equal(atc.Version{"version": "1"}))
|
|
})
|
|
})
|
|
|
|
Context("when the check returns versions", func() {
|
|
var checkedFrom chan atc.Version
|
|
|
|
var nextVersions []atc.Version
|
|
|
|
BeforeEach(func() {
|
|
checkedFrom = make(chan atc.Version, 100)
|
|
|
|
nextVersions = []atc.Version{
|
|
{"version": "1"},
|
|
{"version": "2"},
|
|
{"version": "3"},
|
|
}
|
|
|
|
checkResults := map[int][]atc.Version{
|
|
0: nextVersions,
|
|
}
|
|
|
|
check := 0
|
|
fakeResource.CheckStub = func(ctx context.Context, source atc.Source, from atc.Version) ([]atc.Version, error) {
|
|
defer GinkgoRecover()
|
|
|
|
Expect(source).To(Equal(resourceConfig.Source))
|
|
|
|
checkedFrom <- from
|
|
result := checkResults[check]
|
|
check++
|
|
|
|
return result, nil
|
|
}
|
|
})
|
|
|
|
It("saves them all, in order", func() {
|
|
Eventually(fakeResourceConfigScope.SaveVersionsCallCount).Should(Equal(1))
|
|
|
|
versions := fakeResourceConfigScope.SaveVersionsArgsForCall(0)
|
|
Expect(versions).To(Equal([]atc.Version{
|
|
{"version": "1"},
|
|
{"version": "2"},
|
|
{"version": "3"},
|
|
}))
|
|
})
|
|
|
|
Context("when saving versions fails", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.SaveVersionsReturns(errors.New("failed"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when checking fails internally", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns(nil, disaster)
|
|
})
|
|
|
|
It("exits with the failure", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
Expect(runErr).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when checking fails with ErrResourceScriptFailed", func() {
|
|
scriptFail := resource.ErrResourceScriptFailed{}
|
|
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns(nil, scriptFail)
|
|
})
|
|
|
|
It("returns no error", func() {
|
|
Expect(runErr).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when the pipeline is paused", func() {
|
|
BeforeEach(func() {
|
|
fakeDBPipeline.CheckPausedReturns(true, nil)
|
|
})
|
|
|
|
It("does not check", func() {
|
|
Expect(fakeResource.CheckCallCount()).To(BeZero())
|
|
})
|
|
|
|
It("returns the default interval", func() {
|
|
Expect(actualInterval).To(Equal(interval))
|
|
})
|
|
|
|
It("does not return an error", func() {
|
|
Expect(runErr).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when checking if the pipeline is paused fails", func() {
|
|
disaster := errors.New("disaster")
|
|
|
|
BeforeEach(func() {
|
|
fakeDBPipeline.CheckPausedReturns(false, disaster)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
Expect(runErr).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when finding the resource fails", func() {
|
|
disaster := errors.New("disaster")
|
|
|
|
BeforeEach(func() {
|
|
fakeDBPipeline.ResourceByIDReturns(nil, false, disaster)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
Expect(runErr).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when the resource is not in the database", func() {
|
|
BeforeEach(func() {
|
|
fakeDBPipeline.ResourceByIDReturns(nil, false, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(runErr).To(HaveOccurred())
|
|
Expect(runErr.Error()).To(ContainSubstring("resource '39' not found"))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Scan", func() {
|
|
var (
|
|
fakeResource *rfakes.FakeResource
|
|
|
|
scanErr error
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
|
|
fakeContainer.HandleReturns("some-handle")
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
|
|
fakeResource = new(rfakes.FakeResource)
|
|
fakeResourceFactory.NewResourceForContainerReturns(fakeResource)
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
scanErr = scanner.Scan(lagertest.NewTestLogger("test"), 39)
|
|
})
|
|
|
|
Context("if the lock can be acquired and last checked updated", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.AcquireResourceCheckingLockReturns(fakeLock, true, nil)
|
|
fakeResourceConfigScope.UpdateLastCheckStartTimeReturns(true, nil)
|
|
})
|
|
|
|
Context("Parent resource has no version and check fails", func() {
|
|
BeforeEach(func() {
|
|
var fakeGitResourceType *dbfakes.FakeResourceType
|
|
fakeGitResourceType = new(dbfakes.FakeResourceType)
|
|
|
|
fakeDBPipeline.ResourceTypesReturns([]db.ResourceType{fakeGitResourceType}, nil)
|
|
|
|
fakeGitResourceType.IDReturns(5)
|
|
fakeGitResourceType.NameReturns("git")
|
|
fakeGitResourceType.TypeReturns("registry-image")
|
|
fakeGitResourceType.SourceReturns(atc.Source{"custom": "((source-params))"})
|
|
fakeGitResourceType.VersionReturns(nil)
|
|
fakeGitResourceType.CheckErrorReturns(errors.New("oops"))
|
|
})
|
|
|
|
It("fails and returns error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(scanErr).To(Equal(radar.ErrResourceTypeCheckError))
|
|
})
|
|
|
|
It("saves the error to check_error on resource row in db", func() {
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
|
|
err := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(Equal(errors.New("oops")))
|
|
})
|
|
})
|
|
|
|
Context("Parent resource has a version and but check is failing", func() {
|
|
BeforeEach(func() {
|
|
var fakeGitResourceType *dbfakes.FakeResourceType
|
|
fakeGitResourceType = new(dbfakes.FakeResourceType)
|
|
|
|
fakeDBPipeline.ResourceTypesReturns([]db.ResourceType{fakeGitResourceType}, nil)
|
|
|
|
fakeGitResourceType.IDReturns(5)
|
|
fakeGitResourceType.NameReturns("git")
|
|
fakeGitResourceType.TypeReturns("registry-image")
|
|
fakeGitResourceType.SourceReturns(atc.Source{"custom": "((source-params))"})
|
|
fakeGitResourceType.VersionReturns(atc.Version{"version": "1"})
|
|
fakeGitResourceType.CheckErrorReturns(errors.New("oops"))
|
|
})
|
|
|
|
It("continues to scan", func() {
|
|
Expect(scanErr).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("succeeds", func() {
|
|
Expect(scanErr).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("constructs the resource of the correct type", func() {
|
|
Expect(fakeDBResource.SetResourceConfigCallCount()).To(Equal(1))
|
|
resourceSource, resourceTypes := fakeDBResource.SetResourceConfigArgsForCall(0)
|
|
Expect(resourceSource).To(Equal(atc.Source{"uri": "some-secret-sauce"}))
|
|
Expect(resourceTypes).To(Equal(interpolatedResourceTypes))
|
|
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
err := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(err).To(BeNil())
|
|
|
|
_, _, owner, containerSpec, metadata, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(owner).To(Equal(db.NewResourceConfigCheckSessionContainerOwner(fakeResourceConfig, radar.ContainerExpiries)))
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ResourceType: "git",
|
|
}))
|
|
Expect(containerSpec.Tags).To(Equal([]string{"some-tag"}))
|
|
Expect(containerSpec.TeamID).To(Equal(123))
|
|
Expect(containerSpec.Env).To(Equal([]string{
|
|
"ATC_EXTERNAL_URL=https://www.example.com",
|
|
"RESOURCE_PIPELINE_NAME=some-pipeline",
|
|
"RESOURCE_NAME=some-resource",
|
|
}))
|
|
Expect(metadata).To(Equal(db.ContainerMetadata{
|
|
Type: db.ContainerTypeCheck,
|
|
}))
|
|
Expect(workerSpec).To(Equal(worker.WorkerSpec{
|
|
ResourceType: "git",
|
|
Tags: atc.Tags{"some-tag"},
|
|
ResourceTypes: interpolatedResourceTypes,
|
|
TeamID: 123,
|
|
}))
|
|
|
|
_, _, _, owner, containerSpec, resourceTypes = fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(owner).To(Equal(db.NewResourceConfigCheckSessionContainerOwner(fakeResourceConfig, radar.ContainerExpiries)))
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ResourceType: "git",
|
|
}))
|
|
Expect(containerSpec.Tags).To(Equal([]string{"some-tag"}))
|
|
Expect(containerSpec.TeamID).To(Equal(123))
|
|
Expect(containerSpec.Env).To(Equal([]string{
|
|
"ATC_EXTERNAL_URL=https://www.example.com",
|
|
"RESOURCE_PIPELINE_NAME=some-pipeline",
|
|
"RESOURCE_NAME=some-resource",
|
|
}))
|
|
Expect(resourceTypes).To(Equal(interpolatedResourceTypes))
|
|
})
|
|
|
|
It("grabs an immediate resource checking lock before checking, breaks lock after done", func() {
|
|
Expect(fakeResourceConfigScope.AcquireResourceCheckingLockCallCount()).To(Equal(1))
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckStartTimeCallCount()).To(Equal(1))
|
|
|
|
leaseInterval, immediate := fakeResourceConfigScope.UpdateLastCheckStartTimeArgsForCall(0)
|
|
Expect(leaseInterval).To(Equal(interval))
|
|
Expect(immediate).To(BeTrue())
|
|
|
|
Expect(fakeLock.ReleaseCallCount()).To(Equal(1))
|
|
})
|
|
|
|
Context("when setting the resource config on the resource fails", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.SetResourceConfigReturns(nil, errors.New("catastrophe"))
|
|
})
|
|
|
|
It("sets the check error and returns the error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
|
|
resourceErr := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(resourceErr).To(MatchError("catastrophe"))
|
|
})
|
|
})
|
|
|
|
Context("when creating the container fails", func() {
|
|
BeforeEach(func() {
|
|
fakeWorker.FindOrCreateContainerReturns(nil, errors.New("catastrophe"))
|
|
})
|
|
|
|
It("sets the check error and returns the error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(fakeResourceConfigScope.SetCheckErrorCallCount()).To(Equal(1))
|
|
|
|
resourceErr := fakeResourceConfigScope.SetCheckErrorArgsForCall(0)
|
|
Expect(resourceErr).To(MatchError("catastrophe"))
|
|
})
|
|
})
|
|
|
|
Context("when find or choosing the worker fails", func() {
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerForContainerReturns(nil, errors.New("catastrophe"))
|
|
})
|
|
|
|
It("sets the check error and returns the error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(fakeResourceConfigScope.SetCheckErrorCallCount()).To(Equal(1))
|
|
|
|
resourceErr := fakeResourceConfigScope.SetCheckErrorArgsForCall(0)
|
|
Expect(resourceErr).To(MatchError("catastrophe"))
|
|
})
|
|
})
|
|
|
|
Context("when the resource config has a specified check interval", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CheckEveryReturns("10ms")
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
})
|
|
|
|
It("leases for the configured interval", func() {
|
|
Expect(fakeResourceConfigScope.AcquireResourceCheckingLockCallCount()).To(Equal(1))
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckStartTimeCallCount()).To(Equal(1))
|
|
|
|
leaseInterval, immediate := fakeResourceConfigScope.UpdateLastCheckStartTimeArgsForCall(0)
|
|
Expect(leaseInterval).To(Equal(10 * time.Millisecond))
|
|
Expect(immediate).To(BeTrue())
|
|
|
|
Eventually(fakeLock.ReleaseCallCount).Should(Equal(1))
|
|
})
|
|
|
|
Context("when the interval cannot be parsed", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CheckEveryReturns("bad-value")
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
})
|
|
|
|
It("sets the check error and returns the error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
|
|
resourceErr := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(resourceErr).To(MatchError("time: invalid duration bad-value"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the resource has a specified timeout", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CheckTimeoutReturns("10s")
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
})
|
|
|
|
It("times out after the specified timeout", func() {
|
|
now := time.Now()
|
|
ctx, _, _ := fakeResource.CheckArgsForCall(0)
|
|
deadline, _ := ctx.Deadline()
|
|
Expect(deadline).Should(BeTemporally("~", now.Add(10*time.Second), time.Second))
|
|
})
|
|
|
|
Context("when the timeout cannot be parsed", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CheckTimeoutReturns("bad-value")
|
|
fakeDBPipeline.ResourceByIDReturns(fakeDBResource, true, nil)
|
|
})
|
|
|
|
It("fails to parse the timeout and returns the error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(fakeDBResource.SetCheckSetupErrorCallCount()).To(Equal(1))
|
|
|
|
resourceErr := fakeDBResource.SetCheckSetupErrorArgsForCall(0)
|
|
Expect(resourceErr).To(MatchError("time: invalid duration bad-value"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the resource has a pinned version", func() {
|
|
BeforeEach(func() {
|
|
fakeDBResource.CurrentPinnedVersionReturns(atc.Version{"version": "1"})
|
|
})
|
|
|
|
It("tries to find the version in the database", func() {
|
|
Expect(fakeResourceConfigScope.FindVersionCallCount()).To(Equal(1))
|
|
Expect(fakeResourceConfigScope.FindVersionArgsForCall(0)).To(Equal(atc.Version{"version": "1"}))
|
|
})
|
|
|
|
Context("when finding the version succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigVersion := new(dbfakes.FakeResourceConfigVersion)
|
|
fakeResourceConfigVersion.IDReturns(1)
|
|
fakeResourceConfigVersion.VersionReturns(db.Version{"version": "1"})
|
|
fakeResourceConfigScope.FindVersionReturns(fakeResourceConfigVersion, true, nil)
|
|
})
|
|
|
|
It("does not check", func() {
|
|
Expect(fakeResource.CheckCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
|
|
Context("when the version is not found", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.FindVersionReturns(nil, false, nil)
|
|
})
|
|
|
|
It("checks from the pinned version", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(Equal(atc.Version{"version": "1"}))
|
|
})
|
|
})
|
|
|
|
Context("when finding the version fails", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.FindVersionReturns(nil, false, errors.New("ah"))
|
|
})
|
|
|
|
It("sets the check error on the resource config", func() {
|
|
Expect(fakeResourceConfigScope.SetCheckErrorCallCount()).To(Equal(1))
|
|
|
|
err := fakeResourceConfigScope.SetCheckErrorArgsForCall(0)
|
|
Expect(err).To(Equal(errors.New("ah")))
|
|
})
|
|
})
|
|
})
|
|
|
|
It("clears the resource's check error", func() {
|
|
Expect(fakeResourceConfigScope.SetCheckErrorCallCount()).To(Equal(1))
|
|
|
|
err := fakeResourceConfigScope.SetCheckErrorArgsForCall(0)
|
|
Expect(err).To(BeNil())
|
|
})
|
|
|
|
Context("when there is no current version", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.LatestVersionReturns(nil, false, nil)
|
|
})
|
|
|
|
It("checks from nil", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("when getting the current version fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.LatestVersionReturns(nil, false, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(scanErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("does not check", func() {
|
|
Expect(fakeResource.CheckCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
|
|
Context("when there is a current version", func() {
|
|
var latestVersion db.Version
|
|
BeforeEach(func() {
|
|
latestVersion = db.Version{"version": "1"}
|
|
fakeResourceConfigVersion := new(dbfakes.FakeResourceConfigVersion)
|
|
fakeResourceConfigVersion.IDReturns(1)
|
|
fakeResourceConfigVersion.VersionReturns(db.Version(latestVersion))
|
|
|
|
fakeResourceConfigScope.LatestVersionReturns(fakeResourceConfigVersion, true, nil)
|
|
})
|
|
|
|
It("checks from it", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(Equal(atc.Version{"version": "1"}))
|
|
})
|
|
|
|
Context("when the check returns only the latest version", func() {
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns([]atc.Version{atc.Version(latestVersion)}, nil)
|
|
})
|
|
|
|
It("does not save it", func() {
|
|
Expect(fakeResourceConfigScope.SaveVersionsCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the check returns versions", func() {
|
|
var checkedFrom chan atc.Version
|
|
|
|
var nextVersions []atc.Version
|
|
|
|
BeforeEach(func() {
|
|
checkedFrom = make(chan atc.Version, 100)
|
|
|
|
nextVersions = []atc.Version{
|
|
{"version": "1"},
|
|
{"version": "2"},
|
|
{"version": "3"},
|
|
}
|
|
|
|
checkResults := map[int][]atc.Version{
|
|
0: nextVersions,
|
|
}
|
|
|
|
check := 0
|
|
fakeResource.CheckStub = func(ctx context.Context, source atc.Source, from atc.Version) ([]atc.Version, error) {
|
|
defer GinkgoRecover()
|
|
|
|
Expect(source).To(Equal(resourceConfig.Source))
|
|
|
|
checkedFrom <- from
|
|
result := checkResults[check]
|
|
check++
|
|
|
|
return result, nil
|
|
}
|
|
})
|
|
|
|
It("saves them all, in order", func() {
|
|
Expect(fakeResourceConfigScope.SaveVersionsCallCount()).To(Equal(1))
|
|
|
|
versions := fakeResourceConfigScope.SaveVersionsArgsForCall(0)
|
|
Expect(versions).To(Equal([]atc.Version{
|
|
{"version": "1"},
|
|
{"version": "2"},
|
|
{"version": "3"},
|
|
}))
|
|
})
|
|
|
|
It("updates last check finished", func() {
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(Equal(1))
|
|
})
|
|
|
|
Context("when saving fails", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.SaveVersionsReturns(errors.New("some-error"))
|
|
})
|
|
|
|
It("does not update last check finished", func() {
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(BeZero())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the check does not return any new versions", func() {
|
|
BeforeEach(func() {
|
|
fakeResource.CheckStub = func(ctx context.Context, source atc.Source, from atc.Version) ([]atc.Version, error) {
|
|
return []atc.Version{}, nil
|
|
}
|
|
})
|
|
|
|
It("updates last check finished", func() {
|
|
Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(Equal(1))
|
|
})
|
|
})
|
|
|
|
Context("when checking fails internally", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(scanErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("sets the resource's check error", func() {
|
|
Expect(fakeResourceConfigScope.SetCheckErrorCallCount()).To(Equal(1))
|
|
|
|
err := fakeResourceConfigScope.SetCheckErrorArgsForCall(0)
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when checking fails with ErrResourceScriptFailed", func() {
|
|
scriptFail := resource.ErrResourceScriptFailed{}
|
|
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns(nil, scriptFail)
|
|
})
|
|
|
|
It("returns no error", func() {
|
|
Expect(scanErr).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("sets the resource's check error", func() {
|
|
Expect(fakeResourceConfigScope.SetCheckErrorCallCount()).To(Equal(1))
|
|
|
|
err := fakeResourceConfigScope.SetCheckErrorArgsForCall(0)
|
|
Expect(err).To(Equal(scriptFail))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("ScanFromVersion", func() {
|
|
var (
|
|
fakeResource *rfakes.FakeResource
|
|
fromVersion atc.Version
|
|
|
|
scanErr error
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
|
|
fakeContainer.HandleReturns("some-handle")
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
|
|
fakeResource = new(rfakes.FakeResource)
|
|
fakeResourceFactory.NewResourceForContainerReturns(fakeResource)
|
|
|
|
fromVersion = nil
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
scanErr = scanner.ScanFromVersion(lagertest.NewTestLogger("test"), 39, fromVersion)
|
|
})
|
|
|
|
Context("if the lock can be acquired and last checked updated", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceConfigScope.AcquireResourceCheckingLockReturns(fakeLock, true, nil)
|
|
fakeResourceConfigScope.UpdateLastCheckStartTimeReturns(true, nil)
|
|
})
|
|
|
|
Context("when fromVersion is nil", func() {
|
|
It("checks from nil", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Context("when fromVersion is specified", func() {
|
|
BeforeEach(func() {
|
|
fromVersion = atc.Version{
|
|
"version": "1",
|
|
}
|
|
})
|
|
|
|
It("checks from it", func() {
|
|
_, _, version := fakeResource.CheckArgsForCall(0)
|
|
Expect(version).To(Equal(atc.Version{"version": "1"}))
|
|
})
|
|
|
|
Context("when the check returns only the latest version", func() {
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns([]atc.Version{fromVersion}, nil)
|
|
})
|
|
|
|
It("saves it", func() {
|
|
Expect(fakeResourceConfigScope.SaveVersionsCallCount()).To(Equal(1))
|
|
versions := fakeResourceConfigScope.SaveVersionsArgsForCall(0)
|
|
Expect(versions).To(Equal([]atc.Version{fromVersion}))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when checking fails with ErrResourceScriptFailed", func() {
|
|
scriptFail := resource.ErrResourceScriptFailed{}
|
|
|
|
BeforeEach(func() {
|
|
fakeResource.CheckReturns(nil, scriptFail)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(scanErr).To(Equal(scriptFail))
|
|
})
|
|
})
|
|
|
|
Context("when the resource is not in the database", func() {
|
|
BeforeEach(func() {
|
|
fakeDBPipeline.ResourceByIDReturns(nil, false, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(scanErr).To(HaveOccurred())
|
|
Expect(scanErr.Error()).To(ContainSubstring("resource '39' not found"))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|