Exemple #1
0
func (factory *gardenFactory) Get(
	logger lager.Logger,
	stepMetadata StepMetadata,
	sourceName SourceName,
	id worker.Identifier,
	delegate GetDelegate,
	resourceConfig atc.ResourceConfig,
	params atc.Params,
	tags atc.Tags,
	version atc.Version,
) StepFactory {
	id.WorkingDirectory = resource.ResourcesDir("get")
	return newGetStep(
		logger,
		sourceName,
		resourceConfig,
		version,
		params,
		resource.ResourceCacheIdentifier{
			Type:    resource.ResourceType(resourceConfig.Type),
			Source:  resourceConfig.Source,
			Params:  params,
			Version: version,
		},
		stepMetadata,
		resource.Session{
			ID:        id,
			Ephemeral: false,
		},
		tags,
		delegate,
		factory.tracker,
	)
}
Exemple #2
0
func (factory *gardenFactory) Put(id worker.Identifier, delegate PutDelegate, config atc.ResourceConfig, tags atc.Tags, params atc.Params) StepFactory {
	return resourceStep{
		Session: resource.Session{
			ID: id,
		},

		Delegate: delegate,

		Tracker: factory.resourceTracker,
		Type:    resource.ResourceType(config.Type),
		Tags:    tags,

		Action: func(r resource.Resource, s ArtifactSource, vi VersionInfo) resource.VersionedSource {
			return r.Put(resource.IOConfig{
				Stdout: delegate.Stdout(),
				Stderr: delegate.Stderr(),
			}, config.Source, params, resourceSource{s})
		},
	}
}
Exemple #3
0
func (factory *gardenFactory) Get(sourceName SourceName, id worker.Identifier, delegate GetDelegate, config atc.ResourceConfig, params atc.Params, tags atc.Tags, version atc.Version) StepFactory {
	return resourceStep{
		SourceName: sourceName,

		Session: resource.Session{
			ID:        id,
			Ephemeral: false,
		},

		Delegate: delegate,

		Tracker: factory.resourceTracker,
		Type:    resource.ResourceType(config.Type),
		Tags:    tags,

		Action: func(r resource.Resource, s ArtifactSource, vi VersionInfo) resource.VersionedSource {
			return r.Get(resource.IOConfig{
				Stdout: delegate.Stdout(),
				Stderr: delegate.Stderr(),
			}, config.Source, params, version)
		},
	}
}
// Using constructs a GetStep that will fetch the version of the resource
// determined by the VersionInfo result of the previous step.
func (step DependentGetStep) Using(prev Step, repo *SourceRepository) Step {
	var info VersionInfo
	prev.Result(&info)

	return newGetStep(
		step.logger,
		step.sourceName,
		step.resourceConfig,
		info.Version,
		step.params,
		resource.ResourceCacheIdentifier{
			Type:    resource.ResourceType(step.resourceConfig.Type),
			Source:  step.resourceConfig.Source,
			Params:  step.params,
			Version: info.Version,
		},
		step.stepMetadata,
		step.session,
		step.tags,
		step.delegate,
		step.tracker,
		step.resourceTypes,
	).Using(prev, repo)
}
Exemple #5
0
				_, sm, sid, typ, tags, sources, actualResourceTypes, delegate := fakeTracker.InitWithSourcesArgsForCall(0)
				Expect(sm).To(Equal(stepMetadata))
				Expect(sid).To(Equal(resource.Session{
					ID: worker.Identifier{
						ResourceID: 1234,
						Stage:      db.ContainerStageRun,
					},
					Metadata: worker.Metadata{
						PipelineName:     "some-pipeline",
						Type:             db.ContainerTypePut,
						StepName:         "some-step",
						WorkingDirectory: "/tmp/build/put",
					},
				}))
				Expect(typ).To(Equal(resource.ResourceType("some-resource-type")))
				Expect(tags).To(ConsistOf("some", "tags"))
				Expect(actualResourceTypes).To(Equal(atc.ResourceTypes{
					{
						Name:   "custom-resource",
						Type:   "custom-type",
						Source: atc.Source{"some-custom": "source"},
					},
				}))
				Expect(delegate).To(Equal(putDelegate))

				// TODO: Can we test the map values?
				Expect(sources).To(HaveKey("some-source"))
				Expect(sources).To(HaveKey("some-other-source"))
				Expect(sources).To(HaveKey("some-mounted-source"))
			})
				fakeVersionedSource.VersionReturns(atc.Version{"some": "version"})
				fakeVersionedSource.MetadataReturns([]atc.MetadataField{{"some", "metadata"}})

				fakeResource.GetReturns(fakeVersionedSource)
			})

			It("initializes the resource with the correct type and session id, making sure that it is not ephemeral", func() {
				Ω(fakeTracker.InitCallCount()).Should(Equal(1))

				sid, typ, tags := fakeTracker.InitArgsForCall(0)

				Ω(sid).Should(Equal(resource.Session{
					ID:        identifier,
					Ephemeral: false,
				}))
				Ω(typ).Should(Equal(resource.ResourceType("some-resource-type")))
				Ω(tags).Should(ConsistOf("some", "tags"))
			})

			It("gets the resource with the correct source, params, and version", func() {
				Ω(fakeResource.GetCallCount()).Should(Equal(1))

				_, gotSource, gotParams, gotVersion := fakeResource.GetArgsForCall(0)
				Ω(gotSource).Should(Equal(resourceConfig.Source))
				Ω(gotParams).Should(Equal(params))
				Ω(gotVersion).Should(Equal(version))
			})

			It("gets the resource with the io config forwarded", func() {
				Ω(fakeResource.GetCallCount()).Should(Equal(1))
Exemple #7
0
func (fetcher Fetcher) FetchImage(
	logger lager.Logger,
	imageConfig atc.TaskImageConfig,
	signals <-chan os.Signal,
	identifier worker.Identifier,
	metadata worker.Metadata,
	delegate worker.ImageFetchingDelegate,
	worker worker.Client,
	customTypes atc.ResourceTypes,
) (worker.Image, error) {
	tracker := fetcher.trackerFactory.TrackerFor(worker)
	resourceType := resource.ResourceType(imageConfig.Type)

	checkSess := resource.Session{
		ID:       identifier,
		Metadata: metadata,
	}

	checkSess.ID.Stage = db.ContainerStageCheck
	checkSess.ID.ImageResourceType = imageConfig.Type
	checkSess.ID.ImageResourceSource = imageConfig.Source
	checkSess.Metadata.Type = db.ContainerTypeCheck
	checkSess.Metadata.WorkingDirectory = ""
	checkSess.Metadata.EnvironmentVariables = nil

	checkingResource, err := tracker.Init(
		logger.Session("check-image"),
		resource.EmptyMetadata{},
		checkSess,
		resourceType,
		nil,
		customTypes,
		delegate,
	)
	if err != nil {
		return nil, err
	}

	defer checkingResource.Release(nil)

	versions, err := checkingResource.Check(imageConfig.Source, nil)
	if err != nil {
		return nil, err
	}

	if len(versions) == 0 {
		return nil, ErrImageUnavailable
	}

	cacheID := resource.ResourceCacheIdentifier{
		Type:    resourceType,
		Version: versions[0],
		Source:  imageConfig.Source,
	}

	volumeID := cacheID.VolumeIdentifier()

	err = delegate.ImageVersionDetermined(volumeID)
	if err != nil {
		return nil, err
	}

	getSess := resource.Session{
		ID:       identifier,
		Metadata: metadata,
	}

	getSess.ID.Stage = db.ContainerStageGet
	getSess.ID.ImageResourceType = imageConfig.Type
	getSess.ID.ImageResourceSource = imageConfig.Source
	getSess.Metadata.Type = db.ContainerTypeGet
	getSess.Metadata.WorkingDirectory = ""
	getSess.Metadata.EnvironmentVariables = nil

	getResource, cache, err := tracker.InitWithCache(
		logger.Session("init-image"),
		resource.EmptyMetadata{},
		getSess,
		resourceType,
		nil,
		cacheID,
		customTypes,
		delegate,
	)
	if err != nil {
		return nil, err
	}

	isInitialized, err := cache.IsInitialized()
	if err != nil {
		return nil, err
	}

	versionedSource := getResource.Get(
		resource.IOConfig{
			Stderr: delegate.Stderr(),
		},
		imageConfig.Source,
		nil,
		versions[0],
	)

	if !isInitialized {
		err := versionedSource.Run(signals, make(chan struct{}))
		if err != nil {
			return nil, err
		}

		err = cache.Initialize()
		if err != nil {
			return nil, err
		}
	}

	volume, found := getResource.CacheVolume()
	if !found {
		return nil, ErrImageGetDidNotProduceVolume
	}

	imageMetadata, err := loadMetadata(versionedSource)
	if err != nil {
		return nil, err
	}

	return resourceImage{
		volume:   volume,
		metadata: imageMetadata,
		resource: getResource,
	}, nil
}
Exemple #8
0
func (scanner *resourceScanner) scan(
	logger lager.Logger,
	resourceConfig atc.ResourceConfig,
	resourceTypes atc.ResourceTypes,
	savedResource db.SavedResource,
	fromVersion atc.Version,
) error {
	pipelinePaused, err := scanner.db.IsPaused()
	if err != nil {
		logger.Error("failed-to-check-if-pipeline-paused", err)
		return err
	}

	if pipelinePaused {
		logger.Debug("pipeline-paused")
		return nil
	}

	if savedResource.Paused {
		logger.Debug("resource-paused")
		return nil
	}

	pipelineID := scanner.db.GetPipelineID()

	var resourceTypeVersion atc.Version
	_, found := resourceTypes.Lookup(resourceConfig.Type)
	if found {
		savedResourceType, resourceTypeFound, err := scanner.db.GetResourceType(resourceConfig.Type)
		if err != nil {
			logger.Error("failed-to-find-resource-type", err)
			return err
		}
		if resourceTypeFound {
			resourceTypeVersion = atc.Version(savedResourceType.Version)
		}
	}

	session := resource.Session{
		ID: worker.Identifier{
			ResourceTypeVersion: resourceTypeVersion,
			ResourceID:          savedResource.ID,
			Stage:               db.ContainerStageRun,
			CheckType:           resourceConfig.Type,
			CheckSource:         resourceConfig.Source,
		},
		Metadata: worker.Metadata{
			Type:       db.ContainerTypeCheck,
			PipelineID: pipelineID,
		},
		Ephemeral: true,
	}

	res, err := scanner.tracker.Init(
		logger,
		resource.TrackerMetadata{
			ResourceName: resourceConfig.Name,
			PipelineName: savedResource.PipelineName,
			ExternalURL:  scanner.externalURL,
		},
		session,
		resource.ResourceType(resourceConfig.Type),
		[]string{},
		resourceTypes,
		worker.NoopImageFetchingDelegate{},
	)
	if err != nil {
		logger.Error("failed-to-initialize-new-resource", err)
		return err
	}

	defer res.Release(nil)

	logger.Debug("checking", lager.Data{
		"from": fromVersion,
	})

	newVersions, err := res.Check(resourceConfig.Source, fromVersion)

	setErr := scanner.db.SetResourceCheckError(savedResource, err)
	if setErr != nil {
		logger.Error("failed-to-set-check-error", err)
	}

	if err != nil {
		if rErr, ok := err.(resource.ErrResourceScriptFailed); ok {
			logger.Info("check-failed", lager.Data{"exit-status": rErr.ExitStatus})
			return rErr
		}

		logger.Error("failed-to-check", err)
		return err
	}

	if len(newVersions) == 0 {
		logger.Debug("no-new-versions")
		return nil
	}

	logger.Info("versions-found", lager.Data{
		"versions": newVersions,
		"total":    len(newVersions),
	})

	err = scanner.db.SaveResourceVersions(resourceConfig, newVersions)
	if err != nil {
		logger.Error("failed-to-save-versions", err, lager.Data{
			"versions": newVersions,
		})
	}

	return nil
}
Exemple #9
0
// Run ultimately registers the configured resource version's ArtifactSource
// under the configured SourceName. How it actually does this is determined by
// a few factors.
//
// First, a worker that supports the given resource type is chosen, and a
// container is created on the worker.
//
// If the worker has a VolumeManager, and its cache is already warmed, the
// cache will be mounted into the container, and no fetching will be performed.
// The container will be used to stream the contents of the cache to later
// steps that require the artifact but are running on a worker that does not
// have the cache.
//
// If the worker does not have a VolumeManager, or if the worker does have a
// VolumeManager but a cache for the version of the resource is not present,
// the specified version of the resource will be fetched. As long as running
// the fetch script works, Run will return nil regardless of its exit status.
//
// If the worker has a VolumeManager but did not have the cache initially, the
// fetched ArtifactSource is initialized, thus warming the worker's cache.
//
// At the end, the resulting ArtifactSource (either from using the cache or
// fetching the resource) is registered under the step's SourceName.
func (step *GetStep) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
	trackedResource, cache, err := step.tracker.InitWithCache(
		step.logger,
		step.stepMetadata,
		step.session,
		resource.ResourceType(step.resourceConfig.Type),
		step.tags,
		step.cacheIdentifier,
	)
	if err != nil {
		step.logger.Error("failed-to-initialize-resource", err)
		return err
	}

	step.resource = trackedResource

	step.versionedSource = step.resource.Get(
		resource.IOConfig{
			Stdout: step.delegate.Stdout(),
			Stderr: step.delegate.Stderr(),
		},
		step.resourceConfig.Source,
		step.params,
		step.version,
	)

	isInitialized, err := cache.IsInitialized()
	if err != nil {
		step.logger.Error("failed-to-check-if-cache-is-initialized", err)
		return err
	}

	if isInitialized {
		step.logger.Debug("cache-already-initialized")

		fmt.Fprintf(step.delegate.Stdout(), "using version of resource found in cache\n")
		close(ready)
	} else {
		step.logger.Debug("cache-not-initialized")

		err = step.versionedSource.Run(signals, ready)

		if err, ok := err.(resource.ErrResourceScriptFailed); ok {
			step.delegate.Completed(ExitStatus(err.ExitStatus), nil)
			return nil
		}

		if err == resource.ErrAborted {
			return ErrInterrupted
		}

		if err != nil {
			step.logger.Error("failed-to-run-get", err)
			return err
		}

		err = cache.Initialize()
		if err != nil {
			step.logger.Error("failed-to-initialize-cache", err)
		}
	}

	step.repository.RegisterSource(step.sourceName, step)

	step.succeeded = true
	step.delegate.Completed(ExitStatus(0), &VersionInfo{
		Version:  step.versionedSource.Version(),
		Metadata: step.versionedSource.Metadata(),
	})

	return nil
}
Exemple #10
0
func (radar *Radar) scan(logger lager.Logger, resourceConfig atc.ResourceConfig, resourceTypes atc.ResourceTypes, savedResource db.SavedResource) error {
	pipelinePaused, err := radar.db.IsPaused()
	if err != nil {
		logger.Error("failed-to-check-if-pipeline-paused", err)
		return err
	}

	if pipelinePaused {
		logger.Debug("pipeline-paused")
		return nil
	}

	if savedResource.Paused {
		logger.Debug("resource-paused")
		return nil
	}

	session := resource.Session{
		ID: worker.Identifier{
			ResourceID:  savedResource.ID,
			Stage:       db.ContainerStageRun,
			CheckType:   resourceConfig.Type,
			CheckSource: resourceConfig.Source,
		},
		Metadata: worker.Metadata{
			Type:         db.ContainerTypeCheck,
			PipelineName: radar.db.GetPipelineName(),
		},
		Ephemeral: true,
	}

	res, err := radar.tracker.Init(
		logger,
		resource.EmptyMetadata{},
		session,
		resource.ResourceType(resourceConfig.Type),
		[]string{},
		resourceTypes,
		worker.NoopImageFetchingDelegate{},
	)
	if err != nil {
		logger.Error("failed-to-initialize-new-resource", err)
		return err
	}

	defer res.Release(nil)

	vr, found, err := radar.db.GetLatestVersionedResource(savedResource)
	if err != nil {
		logger.Error("failed-to-get-current-version", err)
		return err
	}

	var from db.Version
	if found {
		from = vr.Version
	}

	logger.Debug("checking", lager.Data{
		"from": from,
	})

	newVersions, err := res.Check(resourceConfig.Source, atc.Version(from))

	setErr := radar.db.SetResourceCheckError(savedResource, err)
	if setErr != nil {
		logger.Error("failed-to-set-check-error", err)
	}

	if err != nil {
		if rErr, ok := err.(resource.ErrResourceScriptFailed); ok {
			logger.Info("check-failed", lager.Data{"exit-status": rErr.ExitStatus})
			return nil
		}

		logger.Error("failed-to-check", err)
		return err
	}

	if len(newVersions) == 0 {
		logger.Debug("no-new-versions")
		return nil
	}

	logger.Info("versions-found", lager.Data{
		"versions": newVersions,
		"total":    len(newVersions),
	})

	err = radar.db.SaveResourceVersions(resourceConfig, newVersions)
	if err != nil {
		logger.Error("failed-to-save-versions", err, lager.Data{
			"versions": newVersions,
		})
	}

	return nil
}
Exemple #11
0
// Run chooses a worker that supports the step's resource type and creates a
// container.
//
// All ArtifactSources present in the SourceRepository are then brought into
// the container, using volumes if possible, and streaming content over if not.
//
// The resource's put script is then invoked. The PutStep is ready as soon as
// the resource's script starts, and signals will be forwarded to the script.
func (step *PutStep) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
	sources := step.repository.AsMap()

	resourceSources := make(map[string]resource.ArtifactSource)
	for name, source := range sources {
		resourceSources[string(name)] = resourceSource{source}
	}

	trackedResource, missingNames, err := step.tracker.InitWithSources(
		step.logger,
		step.stepMetadata,
		step.session,
		resource.ResourceType(step.resourceConfig.Type),
		step.tags,
		resourceSources,
	)

	if err != nil {
		return err
	}

	missingSourceNames := make([]SourceName, len(missingNames))
	for i, n := range missingNames {
		missingSourceNames[i] = SourceName(n)
	}

	step.resource = trackedResource

	scopedRepo, err := step.repository.ScopedTo(missingSourceNames...)
	if err != nil {
		return err
	}

	step.versionedSource = step.resource.Put(
		resource.IOConfig{
			Stdout: step.delegate.Stdout(),
			Stderr: step.delegate.Stderr(),
		},
		step.resourceConfig.Source,
		step.params,
		resourceSource{scopedRepo},
	)

	err = step.versionedSource.Run(signals, ready)

	if err, ok := err.(resource.ErrResourceScriptFailed); ok {
		step.delegate.Completed(ExitStatus(err.ExitStatus), nil)
		return nil
	}

	if err == resource.ErrAborted {
		return ErrInterrupted
	}

	if err != nil {
		return err
	}

	step.succeeded = true
	step.delegate.Completed(ExitStatus(0), &VersionInfo{
		Version:  step.versionedSource.Version(),
		Metadata: step.versionedSource.Metadata(),
	})

	return nil
}
Exemple #12
0
			Eventually(times).Should(Receive())

			sessionID, typ, tags := fakeTracker.InitArgsForCall(0)
			Ω(sessionID).Should(Equal(resource.Session{
				ID: worker.Identifier{
					PipelineName: "some-pipeline-name",

					Name: "some-resource",
					Type: "check",

					CheckType:   "git",
					CheckSource: resourceConfig.Source,
				},
				Ephemeral: true,
			}))
			Ω(typ).Should(Equal(resource.ResourceType("git")))
			Ω(tags).Should(BeEmpty()) // This allows the check to run on any worker
		})

		It("checks on a specified interval", func() {
			var time1 time.Time
			var time2 time.Time

			Eventually(times).Should(Receive(&time1))
			Eventually(times).Should(Receive(&time2))

			Ω(time2.Sub(time1)).Should(BeNumerically("~", interval, interval/4))
		})

		It("grabs a resource checking lock before checking, releases after done", func() {
			Eventually(times).Should(Receive())
Exemple #13
0
					Metadata: worker.Metadata{
						Type:         db.ContainerTypeCheck,
						PipelineName: "some-pipeline",
					},
					Ephemeral: true,
				}))
				Expect(customTypes).To(Equal(atc.ResourceTypes{
					{
						Name:   "some-custom-resource",
						Type:   "docker-image",
						Source: atc.Source{"custom": "source"},
					},
				}))
				Expect(delegate).To(Equal(worker.NoopImageFetchingDelegate{}))

				Expect(typ).To(Equal(resource.ResourceType("git")))
				Expect(tags).To(BeEmpty()) // This allows the check to run on any worker
			})

			Context("when the resource config has a specified check interval", func() {
				BeforeEach(func() {
					resourceConfig.CheckEvery = "10ms"

					fakeRadarDB.GetConfigReturns(atc.Config{
						Resources: atc.ResourceConfigs{
							resourceConfig,
						},
					}, 1, true, nil)
				})

				It("checks using the specified interval instead of the default", func() {
Exemple #14
0
func (radar *Radar) scan(logger lager.Logger, resourceConfig atc.ResourceConfig, savedResource db.SavedResource) error {
	pipelinePaused, err := radar.db.IsPaused()
	if err != nil {
		logger.Error("failed-to-check-if-pipeline-paused", err)
		return err
	}

	if pipelinePaused {
		logger.Debug("pipeline-paused")
		return nil
	}

	if savedResource.Paused {
		logger.Debug("resource-paused")
		return nil
	}

	typ := resource.ResourceType(resourceConfig.Type)

	res, err := radar.tracker.Init(
		logger,
		resource.EmptyMetadata{},
		checkIdentifier(radar.db.GetPipelineName(), resourceConfig),
		typ,
		[]string{},
	)
	if err != nil {
		logger.Error("failed-to-initialize-new-resource", err)
		return err
	}

	defer res.Release(0)

	vr, found, err := radar.db.GetLatestVersionedResource(savedResource)
	if err != nil {
		logger.Error("failed-to-get-current-version", err)
		return err
	}

	var from db.Version
	if found {
		from = vr.Version
	}

	logger.Debug("checking", lager.Data{
		"from": from,
	})

	newVersions, err := res.Check(resourceConfig.Source, atc.Version(from))

	setErr := radar.db.SetResourceCheckError(savedResource, err)
	if setErr != nil {
		logger.Error("failed-to-set-check-error", err)
	}

	if err != nil {
		if rErr, ok := err.(resource.ErrResourceScriptFailed); ok {
			logger.Info("check-failed", lager.Data{"exit-status": rErr.ExitStatus})
			return nil
		}

		logger.Error("failed-to-check", err)
		return err
	}

	if len(newVersions) == 0 {
		logger.Debug("no-new-versions")
		return nil
	}

	logger.Info("versions-found", lager.Data{
		"versions": newVersions,
		"total":    len(newVersions),
	})

	err = radar.db.SaveResourceVersions(resourceConfig, newVersions)
	if err != nil {
		logger.Error("failed-to-save-versions", err, lager.Data{
			"versions": newVersions,
		})
	}

	return nil
}
Exemple #15
0
func (radar *Radar) scan(logger lager.Logger, resourceName string) error {
	pipelinePaused, err := radar.db.IsPaused()
	if err != nil {
		logger.Error("failed-to-check-if-pipeline-paused", err)
		return err
	}

	if pipelinePaused {
		logger.Debug("pipeline-paused")
		return nil
	}

	config, _, err := radar.db.GetConfig()
	if err != nil {
		logger.Error("failed-to-get-config", err)
		// don't propagate error; we can just retry next tick
		return nil
	}

	resourceConfig, found := config.Resources.Lookup(resourceName)
	if !found {
		logger.Info("resource-removed-from-configuration")
		// return an error so that we exit
		return resourceNotConfiguredError{ResourceName: resourceName}
	}

	savedResource, err := radar.db.GetResource(resourceName)
	if err != nil {
		return err
	}

	if savedResource.Paused {
		return nil
	}

	typ := resource.ResourceType(resourceConfig.Type)

	res, err := radar.tracker.Init(checkIdentifier(radar.db.GetPipelineName(), resourceConfig), typ, []string{})
	if err != nil {
		logger.Error("failed-to-initialize-new-resource", err)
		return err
	}

	defer res.Release()

	var from db.Version
	if vr, err := radar.db.GetLatestVersionedResource(savedResource); err == nil {
		from = vr.Version
	}

	logger.Debug("checking", lager.Data{
		"from": from,
	})

	newVersions, err := res.Check(resourceConfig.Source, atc.Version(from))
	setErr := radar.db.SetResourceCheckError(savedResource, err)
	if setErr != nil {
		logger.Error("failed-to-set-check-error", err)
	}

	if err != nil {
		logger.Error("failed-to-check", err)

		return err
	}

	if len(newVersions) == 0 {
		logger.Debug("no-new-versions")
		return nil
	}

	logger.Info("versions-found", lager.Data{
		"versions": newVersions,
		"total":    len(newVersions),
	})

	err = radar.db.SaveResourceVersions(resourceConfig, newVersions)
	if err != nil {
		logger.Error("failed-to-save-versions", err, lager.Data{
			"versions": newVersions,
		})
	}

	return nil
}