func main() {
	sharedWriter := &logWriter{
		writer: os.Stderr,
	}

	logger := log.New(sharedWriter, "", log.LstdFlags)

	configPath := flag.String("c", "", "Path to the JSON configuration file")
	machineImagePath := flag.String("image", "", "Path to the input machine image (root.img)")
	machineImageFormat := flag.String("format", resources.VolumeRawFormat, "Format of the input machine image (RAW or vmdk). Defaults to RAW.")
	imageVolumeSize := flag.Int("volume-size", 0, "Block device size (in GB) of the input machine image")
	manifestPath := flag.String("manifest", "", "Path to the input stemcell.MF")

	flag.Parse()

	if *configPath == "" {
		usage("-c flag is required")
	}
	if *machineImagePath == "" {
		usage("--image flag is required")
	}

	if *manifestPath == "" {
		usage("--manifest flag is required")
	}

	if *imageVolumeSize == 0 && *machineImageFormat != resources.VolumeRawFormat {
		usage("--volume-size flag is required for formats other than RAW")
	}

	configFile, err := os.Open(*configPath)
	if err != nil {
		logger.Fatalf("Error opening config file: %s", err)
	}

	defer func() {
		closeErr := configFile.Close()
		if closeErr != nil {
			logger.Fatalf("Error closing config file: %s", closeErr)
		}
	}()

	if err != nil {
		logger.Fatalf("Error opening config file: %s", err)
	}

	c, err := config.NewFromReader(configFile)
	if err != nil {
		logger.Fatalf("Error parsing config file: %s. Message: %s", *configPath, err)
	}

	if _, err := os.Stat(*machineImagePath); os.IsNotExist(err) {
		logger.Fatalf("machine image not found at: %s", *machineImagePath)
	}

	if _, err := os.Stat(*manifestPath); os.IsNotExist(err) {
		logger.Fatalf("manifest not found at: %s", *manifestPath)
	}

	manifestBytes, err := ioutil.ReadFile(*manifestPath)
	if err != nil {
		logger.Fatalf("opening manifest: %s", err)
	}

	m, err := manifest.NewFromReader(bytes.NewReader(manifestBytes))
	if err != nil {
		logger.Fatalf("reading manifest: %s", err)
	}

	amiCollection := collection.Ami{}
	errCollection := collection.Error{}

	var wg sync.WaitGroup
	wg.Add(len(c.AmiRegions))

	imageConfig := publisher.MachineImageConfig{
		LocalPath:    *machineImagePath,
		FileFormat:   *machineImageFormat,
		VolumeSizeGB: int64(*imageVolumeSize),
	}

	for i := range c.AmiRegions {
		go func(regionConfig config.AmiRegion) {
			defer wg.Done()

			switch {
			case regionConfig.IsolatedRegion:
				ds := driverset.NewIsolatedRegionDriverSet(sharedWriter, regionConfig.Credentials)
				p := publisher.NewIsolatedRegionPublisher(sharedWriter, publisher.Config{
					AmiRegion:        regionConfig,
					AmiConfiguration: c.AmiConfiguration,
				})

				amis, err := p.Publish(ds, imageConfig)
				if err != nil {
					errCollection.Add(fmt.Errorf("Error publishing AMIs to %s: %s", regionConfig.RegionName, err))
				} else {
					amiCollection.Merge(amis)
				}
			default:
				ds := driverset.NewStandardRegionDriverSet(sharedWriter, regionConfig.Credentials)
				p := publisher.NewStandardRegionPublisher(sharedWriter, publisher.Config{
					AmiRegion:        regionConfig,
					AmiConfiguration: c.AmiConfiguration,
				})

				amis, err := p.Publish(ds, imageConfig)
				if err != nil {
					errCollection.Add(fmt.Errorf("Error publishing AMIs to %s: %s", regionConfig.RegionName, err))
				} else {
					amiCollection.Merge(amis)
				}
			}
		}(c.AmiRegions[i])
	}

	logger.Println("Waiting for publishers to finish...")
	wg.Wait()

	combinedErr := errCollection.Error()
	if combinedErr != nil {
		logger.Fatal(combinedErr)
	}

	m.PublishedAmis = amiCollection.GetAll()

	m.Sha1 = shasum([]byte{})

	err = m.Write(os.Stdout)
	if err != nil {
		logger.Fatalf("writing manifest: %s", err)
	}
	logger.Println("Publishing finished successfully")
}
func (p *StandardRegionPublisher) Publish(ds driverset.StandardRegionDriverSet, machineImageConfig MachineImageConfig) (*collection.Ami, error) {

	createStartTime := time.Now()
	defer func(startTime time.Time) {
		p.logger.Printf("completed Publish() in %f minutes\n", time.Since(startTime).Minutes())
	}(createStartTime)

	machineImageDriverConfig := resources.MachineImageDriverConfig{
		MachineImagePath: machineImageConfig.LocalPath,
		FileFormat:       machineImageConfig.FileFormat,
		BucketName:       p.BucketName,
	}

	machineImageDriver := ds.MachineImageDriver()
	machineImage, err := machineImageDriver.Create(machineImageDriverConfig)
	if err != nil {
		return nil, fmt.Errorf("creating machine image: %s", err)
	}
	defer func() {
		err := machineImageDriver.Delete(machineImage)
		if err != nil {
			p.logger.Printf("Failed to delete machine image %s: %s", machineImage.GetURL, err)
		}
	}()

	snapshotDriverConfig := resources.SnapshotDriverConfig{
		MachineImageURL: machineImage.GetURL,
		FileFormat:      machineImageConfig.FileFormat,
	}

	snapshotDriver := ds.CreateSnapshotDriver()
	snapshot, err := snapshotDriver.Create(snapshotDriverConfig)
	if err != nil {
		return nil, fmt.Errorf("creating snapshot: %s", err)
	}

	createAmiDriver := ds.CreateAmiDriver()
	createAmiDriverConfig := resources.AmiDriverConfig{
		SnapshotID:    snapshot.ID,
		AmiProperties: p.AmiProperties,
	}

	sourceAmi, err := createAmiDriver.Create(createAmiDriverConfig)
	if err != nil {
		return nil, fmt.Errorf("creating ami: %s", err)
	}

	amis := collection.Ami{
		VirtualizationType: p.AmiProperties.VirtualizationType,
	}
	amis.Add(sourceAmi)

	copyAmiDriver := ds.CopyAmiDriver()

	procGroup := sync.WaitGroup{}
	procGroup.Add(len(p.CopyDestinations))

	errCol := collection.Error{}

	for i := range p.CopyDestinations {
		go func(dstRegion string) {
			defer procGroup.Done()

			copyAmiDriverConfig := resources.AmiDriverConfig{
				ExistingAmiID:     sourceAmi.ID,
				DestinationRegion: dstRegion,
				AmiProperties:     p.AmiProperties,
			}

			copiedAmi, copyErr := copyAmiDriver.Create(copyAmiDriverConfig)
			if copyErr != nil {
				errCol.Add(fmt.Errorf("copying source ami: %s to destination region: %s: %s", sourceAmi.ID, dstRegion, copyErr))
				return
			}

			amis.Add(copiedAmi)
		}(p.CopyDestinations[i])
	}

	procGroup.Wait()

	return &amis, errCol.Error()
}
package collection_test

import (
	"errors"
	"light-stemcell-builder/collection"

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

var _ = Describe("Error", func() {
	It("outputs an error with expected messages for all the errors collected", func() {
		e := collection.Error{}
		e.Add(errors.New("The quick brown"))
		e.Add(errors.New("fox jumps over"))
		e.Add(errors.New("the lazy dog"))
		err := e.Error()
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("The quick brown"))
		Expect(err.Error()).To(ContainSubstring("fox jumps over"))
		Expect(err.Error()).To(ContainSubstring("the lazy dog"))
	})

	It("does not output an error when no errors have been added", func() {
		e := collection.Error{}
		err := e.Error()
		Expect(err).ToNot(HaveOccurred())
	})
})