func (c *InCommand) Run(input concourse.InRequest) (concourse.InResponse, error) {
	productSlug := input.Source.ProductSlug

	version, fingerprint, err := versions.SplitIntoVersionAndFingerprint(input.Version.ProductVersion)
	if err != nil {
		c.logger.Info("Parsing of fingerprint failed; continuing without it")
		version = input.Version.ProductVersion
		fingerprint = ""
	}

	c.logger.Info(fmt.Sprintf(
		"Getting release for product slug: '%s' and product version: '%s'",
		productSlug,
		version,
	))

	release, err := c.pivnetClient.GetRelease(productSlug, version)
	if err != nil {
		return concourse.InResponse{}, err
	}

	if fingerprint != "" {
		actualFingerprint := release.UpdatedAt
		if actualFingerprint != fingerprint {
			return concourse.InResponse{}, fmt.Errorf(
				"provided fingerprint: '%s' does not match actual fingerprint (from pivnet): '%s' - %s",
				fingerprint,
				actualFingerprint,
				"pivnet does not support downloading old versions of a release",
			)
		}
	}

	c.logger.Info(fmt.Sprintf("Accepting EULA for release with ID: %d", release.ID))

	err = c.pivnetClient.AcceptEULA(productSlug, release.ID)
	if err != nil {
		return concourse.InResponse{}, err
	}

	c.logger.Info("Getting product files")

	releaseProductFiles, err := c.pivnetClient.ProductFilesForRelease(productSlug, release.ID)
	if err != nil {
		return concourse.InResponse{}, err
	}

	c.logger.Info("Getting file groups")

	fileGroups, err := c.pivnetClient.FileGroupsForRelease(productSlug, release.ID)
	if err != nil {
		return concourse.InResponse{}, err
	}

	allProductFiles := releaseProductFiles
	for _, fg := range fileGroups {
		allProductFiles = append(allProductFiles, fg.ProductFiles...)
	}

	// Get individual product files to obtain metadata that isn't found
	// in the endpoint for all product files.
	for i, p := range allProductFiles {
		allProductFiles[i], err = c.pivnetClient.ProductFileForRelease(
			productSlug,
			release.ID,
			p.ID,
		)
		if err != nil {
			return concourse.InResponse{}, err
		}
	}

	c.logger.Info("Getting release dependencies")

	releaseDependencies, err := c.pivnetClient.ReleaseDependencies(productSlug, release.ID)
	if err != nil {
		return concourse.InResponse{}, err
	}

	c.logger.Info("Getting release upgrade paths")

	releaseUpgradePaths, err := c.pivnetClient.ReleaseUpgradePaths(productSlug, release.ID)
	if err != nil {
		return concourse.InResponse{}, err
	}

	c.logger.Info("Downloading files")

	err = c.downloadFiles(input.Params.Globs, allProductFiles, productSlug, release.ID)
	if err != nil {
		return concourse.InResponse{}, err
	}

	c.logger.Info("Creating metadata")

	versionWithFingerprint, err := versions.CombineVersionAndFingerprint(version, fingerprint)

	mdata := metadata.Metadata{
		Release: &metadata.Release{
			ID:                    release.ID,
			Version:               release.Version,
			ReleaseType:           string(release.ReleaseType),
			ReleaseDate:           release.ReleaseDate,
			Description:           release.Description,
			ReleaseNotesURL:       release.ReleaseNotesURL,
			Availability:          release.Availability,
			Controlled:            release.Controlled,
			ECCN:                  release.ECCN,
			LicenseException:      release.LicenseException,
			EndOfSupportDate:      release.EndOfSupportDate,
			EndOfGuidanceDate:     release.EndOfGuidanceDate,
			EndOfAvailabilityDate: release.EndOfAvailabilityDate,
		},
	}

	if release.EULA != nil {
		mdata.Release.EULASlug = release.EULA.Slug
	}

	for _, pf := range releaseProductFiles {
		mdata.Release.ProductFiles = append(mdata.Release.ProductFiles, metadata.ReleaseProductFile{
			ID: pf.ID,
		})
	}

	for _, pf := range allProductFiles {
		mdata.ProductFiles = append(mdata.ProductFiles, metadata.ProductFile{
			ID:           pf.ID,
			File:         pf.Name,
			Description:  pf.Description,
			AWSObjectKey: pf.AWSObjectKey,
			FileType:     pf.FileType,
			FileVersion:  pf.FileVersion,
			MD5:          pf.MD5,
		})
	}

	for _, d := range releaseDependencies {
		mdata.Dependencies = append(mdata.Dependencies, metadata.Dependency{
			Release: metadata.DependentRelease{
				ID:      d.Release.ID,
				Version: d.Release.Version,
				Product: metadata.Product{
					ID:   d.Release.Product.ID,
					Name: d.Release.Product.Name,
				},
			},
		})
	}

	for _, d := range releaseUpgradePaths {
		mdata.UpgradePaths = append(mdata.UpgradePaths, metadata.UpgradePath{
			ID:      d.Release.ID,
			Version: d.Release.Version,
		})
	}

	for _, fg := range fileGroups {
		mfg := metadata.FileGroup{
			ID:   fg.ID,
			Name: fg.Name,
		}

		for _, pf := range fg.ProductFiles {
			mfg.ProductFiles = append(mfg.ProductFiles, metadata.FileGroupProductFile{
				ID: pf.ID,
			})
		}

		mdata.FileGroups = append(mdata.FileGroups, mfg)
	}

	c.logger.Info("Writing metadata files")

	err = c.fileWriter.WriteVersionFile(versionWithFingerprint)
	if err != nil {
		return concourse.InResponse{}, err
	}

	err = c.fileWriter.WriteMetadataYAMLFile(mdata)
	if err != nil {
		return concourse.InResponse{}, err
	}

	err = c.fileWriter.WriteMetadataJSONFile(mdata)
	if err != nil {
		return concourse.InResponse{}, err
	}

	concourseMetadata := c.addReleaseMetadata([]concourse.Metadata{}, release)

	out := concourse.InResponse{
		Version: concourse.Version{
			ProductVersion: versionWithFingerprint,
		},
		Metadata: concourseMetadata,
	}

	return out, nil
}
func main() {
	if version == "" {
		version = "dev"
	}

	logger := log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds)

	logger.Printf("PivNet Resource version: %s", version)

	if len(os.Args) < 2 {
		log.Fatalf("not enough args - usage: %s <sources directory>", os.Args[0])
	}

	sourcesDir := os.Args[1]

	outDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatalln(err)
	}

	var input concourse.OutRequest

	err = json.NewDecoder(os.Stdin).Decode(&input)
	if err != nil {
		log.Fatalln(err)
	}

	sanitized := concourse.SanitizedSource(input.Source)
	logger.SetOutput(sanitizer.NewSanitizer(sanitized, os.Stderr))

	verbose := false
	ls := logshim.NewLogShim(logger, logger, verbose)

	var endpoint string
	if input.Source.Endpoint != "" {
		endpoint = input.Source.Endpoint
	} else {
		endpoint = pivnet.DefaultHost
	}

	clientConfig := pivnet.ClientConfig{
		Host:      endpoint,
		Token:     input.Source.APIToken,
		UserAgent: useragent.UserAgent(version, "put", input.Source.ProductSlug),
	}

	client := gp.NewClient(
		clientConfig,
		ls,
	)

	bucket := input.Source.Bucket
	if bucket == "" {
		bucket = defaultBucket
	}

	region := input.Source.Region
	if region == "" {
		region = defaultRegion
	}

	s3Client := s3.NewClient(s3.NewClientConfig{
		AccessKeyID:     input.Source.AccessKeyID,
		SecretAccessKey: input.Source.SecretAccessKey,
		RegionName:      region,
		Bucket:          bucket,
		Stderr:          os.Stderr,
		Logger:          ls,
	})

	uploaderClient := uploader.NewClient(uploader.Config{
		FilepathPrefix: input.Params.FilepathPrefix,
		SourcesDir:     sourcesDir,
		Transport:      s3Client,
	})

	globber := globs.NewGlobber(globs.GlobberConfig{
		FileGlob:   input.Params.FileGlob,
		SourcesDir: sourcesDir,
		Logger:     ls,
	})

	skipUpload := input.Params.FileGlob == "" && input.Params.FilepathPrefix == ""

	var m metadata.Metadata
	if input.Params.MetadataFile == "" {
		log.Fatalf("params.metadata_file must be provided")
	}

	metadataFilepath := filepath.Join(sourcesDir, input.Params.MetadataFile)
	metadataBytes, err := ioutil.ReadFile(metadataFilepath)
	if err != nil {
		log.Fatalf("params.metadata_file could not be read: %s", err.Error())
	}

	err = yaml.Unmarshal(metadataBytes, &m)
	if err != nil {
		log.Fatalf("params.metadata_file could not be parsed: %s", err.Error())
	}

	err = m.Validate()
	if err != nil {
		log.Fatalf("params.metadata_file is invalid: %s", err.Error())
	}

	validation := validator.NewOutValidator(input)
	semverConverter := semver.NewSemverConverter(ls)
	md5summer := md5sum.NewFileSummer()

	f := filter.NewFilter(ls)

	releaseCreator := release.NewReleaseCreator(
		client,
		semverConverter,
		ls,
		m,
		input.Params,
		input.Source,
		sourcesDir,
		input.Source.ProductSlug,
	)

	asyncTimeout := 1 * time.Hour
	pollFrequency := 5 * time.Second
	releaseUploader := release.NewReleaseUploader(
		uploaderClient,
		client,
		ls,
		md5summer,
		m,
		sourcesDir,
		input.Source.ProductSlug,
		asyncTimeout,
		pollFrequency,
	)

	releaseUserGroupsUpdater := release.NewUserGroupsUpdater(
		ls,
		client,
		m,
		input.Source.ProductSlug,
	)

	releaseDependenciesAdder := release.NewReleaseDependenciesAdder(
		ls,
		client,
		m,
		input.Source.ProductSlug,
	)

	releaseUpgradePathsAdder := release.NewReleaseUpgradePathsAdder(
		ls,
		client,
		m,
		input.Source.ProductSlug,
		f,
	)

	releaseFinalizer := release.NewFinalizer(
		client,
		ls,
		input.Params,
		m,
		sourcesDir,
		input.Source.ProductSlug,
	)

	outCmd := out.NewOutCommand(out.OutCommandConfig{
		Logger:                   ls,
		OutDir:                   outDir,
		SourcesDir:               sourcesDir,
		GlobClient:               globber,
		Validation:               validation,
		Creator:                  releaseCreator,
		Uploader:                 releaseUploader,
		UserGroupsUpdater:        releaseUserGroupsUpdater,
		ReleaseDependenciesAdder: releaseDependenciesAdder,
		ReleaseUpgradePathsAdder: releaseUpgradePathsAdder,
		Finalizer:                releaseFinalizer,
		M:                        m,
		SkipUpload:               skipUpload,
	})

	response, err := outCmd.Run(input)
	if err != nil {
		log.Fatalln(err)
	}

	err = json.NewEncoder(os.Stdout).Encode(response)
	if err != nil {
		log.Fatalln(err)
	}
}
package metadata_test

import (
	"fmt"

	"github.com/pivotal-cf/pivnet-resource/metadata"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Metadata", func() {
	Describe("Validate", func() {
		var data metadata.Metadata
		BeforeEach(func() {
			data = metadata.Metadata{
				Release: &metadata.Release{
					Version:     "1.0.0",
					ReleaseType: "All In One",
					EULASlug:    "some-other-eula",
				},
				ProductFiles: []metadata.ProductFile{
					{File: "hello.txt", Description: "available"},
				},
			}
		})

		Context("when release is missing", func() {
			BeforeEach(func() {
				data.Release = nil
			})
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("ReleaseUpgradePathsAdder", func() {
	Describe("AddReleaseUpgradePaths", func() {
		var (
			fakeLogger logger.Logger

			pivnetClient *releasefakes.ReleaseUpgradePathsAdderClient
			fakeFilter   *releasefakes.FakeFilter

			existingReleases []pivnet.Release
			filteredReleases []pivnet.Release

			existingReleasesErr error
			filterErr           error

			mdata metadata.Metadata

			productSlug   string
			pivnetRelease pivnet.Release

			releaseUpgradePathsAdder release.ReleaseUpgradePathsAdder
		)

		BeforeEach(func() {
			logger := log.New(GinkgoWriter, "", log.LstdFlags)
			fakeLogger = logshim.NewLogShim(logger, logger, true)

			pivnetClient = &releasefakes.ReleaseUpgradePathsAdderClient{}