<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package store_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/store"
	"launchpad.net/juju-core/testing"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

func (s *StoreSuite) dummyBranch(c *C, suffix string) bzrDir {
	tmpDir := c.MkDir()
	if suffix != "" {
		tmpDir = filepath.Join(tmpDir, suffix)
		err := os.MkdirAll(tmpDir, 0755)
		c.Assert(err, IsNil)
	}
	branch := bzrDir(tmpDir)
	branch.init()

	copyCharmDir(branch.path(), testing.Charms.Dir("dummy"))
	branch.add()
	branch.commit("Imported charm.")
	return branch
}

var urls = []*charm.URL{
	charm.MustParseURL("cs:~joe/oneiric/dummy"),
	charm.MustParseURL("cs:oneiric/dummy"),
}

type fakePlugin struct {
	oldEnv string
}

func (p *fakePlugin) install(dir string, content string) {
	p.oldEnv = os.Getenv("BZR_PLUGINS_AT")
	err := ioutil.WriteFile(filepath.Join(dir, "__init__.py"), []byte(content), 0644)
	if err != nil {
		panic(err)
	}
	os.Setenv("BZR_PLUGINS_AT", "fakePlugin@"+dir)
}

func (p *fakePlugin) uninstall() {
	os.Setenv("BZR_PLUGINS_AT", p.oldEnv)
}

func (s *StoreSuite) TestPublish(c *C) {
	branch := s.dummyBranch(c, "")

	// Ensure that the streams are parsed separately by inserting
	// garbage on stderr. The wanted information is still there.
	plugin := fakePlugin{}
	plugin.install(c.MkDir(), `import sys; sys.stderr.write("STDERR STUFF FROM TEST\n")`)
	defer plugin.uninstall()

	err := store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, IsNil)

	for _, url := range urls {
		info, rc, err := s.store.OpenCharm(url)
		c.Assert(err, IsNil)
		defer rc.Close()
		c.Assert(info.Revision(), Equals, 0)
		c.Assert(info.Meta().Name, Equals, "dummy")

		data, err := ioutil.ReadAll(rc)
		c.Assert(err, IsNil)

		bundle, err := charm.ReadBundleBytes(data)
		c.Assert(err, IsNil)
		c.Assert(bundle.Revision(), Equals, 0)
		c.Assert(bundle.Meta().Name, Equals, "dummy")
	}

	// Attempt to publish the same content again while providing the wrong
	// tip revision. It must pick the real revision from the branch and
	// note this was previously published.
	err = store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, Equals, store.ErrRedundantUpdate)

	// Bump the content revision and lie again about the known tip revision.
	// This time, though, pretend it's the same as the real branch revision
	// previously published. It must error and not publish the new revision
	// because it will use the revision provided as a parameter to check if
	// publishing was attempted before. This is the mechanism that enables
	// stopping fast without having to download every single branch. Real
	// revision is picked in the next scan.
	digest1 := branch.digest()
	branch.change()
	err = store.PublishBazaarBranch(s.store, urls, branch.path(), digest1)
	c.Assert(err, Equals, store.ErrRedundantUpdate)

	// Now allow it to publish the new content by providing an unseen revision.
	err = store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, IsNil)
	digest2 := branch.digest()

	info, err := s.store.CharmInfo(urls[0])
	c.Assert(err, IsNil)
	c.Assert(info.Revision(), Equals, 1)
	c.Assert(info.Meta().Name, Equals, "dummy")

	// There are two events published, for each of the successful attempts.
	// The failures are ignored given that they are artifacts of the
	// publishing mechanism rather than actual problems.
	_, err = s.store.CharmEvent(urls[0], "wrong-rev")
	c.Assert(err, Equals, store.ErrNotFound)
	for i, digest := range []string{digest1, digest2} {
		event, err := s.store.CharmEvent(urls[0], digest)
		c.Assert(err, IsNil)
		c.Assert(event.Kind, Equals, store.EventPublished)
		c.Assert(event.Revision, Equals, i)
		c.Assert(event.Errors, IsNil)
		c.Assert(event.Warnings, IsNil)
	}
}

func (s *StoreSuite) TestPublishErrorFromBzr(c *C) {
	branch := s.dummyBranch(c, "")

	// In TestPublish we ensure that the streams are parsed
	// separately by inserting garbage on stderr. Now make
	// sure that stderr isn't simply trashed, as we want to
	// know about what a real error tells us.
	plugin := fakePlugin{}
	plugin.install(c.MkDir(), `import sys; sys.stderr.write("STDERR STUFF FROM TEST\n"); sys.exit(1)`)
	defer plugin.uninstall()

	err := store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, ErrorMatches, "(?s).*STDERR STUFF.*")
}

func (s *StoreSuite) TestPublishErrorInCharm(c *C) {
	branch := s.dummyBranch(c, "")

	// Corrupt the charm.
	branch.remove("metadata.yaml")
	branch.commit("Removed metadata.yaml.")

	// Attempt to publish the erroneous content.
	err := store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, ErrorMatches, ".*/metadata.yaml: no such file or directory")

	// The event should be logged as well, since this was an error in the charm
	// that won't go away and must be communicated to the author.
	event, err := s.store.CharmEvent(urls[0], branch.digest())
	c.Assert(err, IsNil)
	c.Assert(event.Kind, Equals, store.EventPublishError)
	c.Assert(event.Revision, Equals, 0)
	c.Assert(event.Errors, NotNil)
	c.Assert(event.Errors[0], Matches, ".*/metadata.yaml: no such file or directory")
	c.Assert(event.Warnings, IsNil)
}

type bzrDir string

func (dir bzrDir) path(args ...string) string {
	return filepath.Join(append([]string{string(dir)}, args...)...)
}

func (dir bzrDir) run(args ...string) []byte {
	cmd := exec.Command("bzr", args...)
	oldemail := os.Getenv("EMAIL")
	defer os.Setenv("EMAIL", oldemail)
	// bzr will complain if bzr whoami has not been run previously,
	// avoid this by passing $EMAIL into the environment.
	os.Setenv("EMAIL", "nobody@example.com")
	cmd.Dir = string(dir)
	output, err := cmd.Output()
	if err != nil {
		panic(fmt.Sprintf("command failed: bzr %s\n%s", strings.Join(args, " "), output))
	}
	return output
}

func (dir bzrDir) init() {
	dir.run("init")
}

func (dir bzrDir) add(paths ...string) {
	dir.run(append([]string{"add"}, paths...)...)
}

func (dir bzrDir) remove(paths ...string) {
	dir.run(append([]string{"rm"}, paths...)...)
}

func (dir bzrDir) commit(msg string) {
	dir.run("commit", "-m", msg)
}

func (dir bzrDir) write(path string, data string) {
	err := ioutil.WriteFile(dir.path(path), []byte(data), 0644)
	if err != nil {
		panic(err)
	}
}

func (dir bzrDir) change() {
	t := time.Now().String()
	dir.write("timestamp", t)
	dir.add("timestamp")
	dir.commit("Revision bumped at " + t)
}

func (dir bzrDir) digest() string {
	output := dir.run("revision-info")
	f := bytes.Fields(output)
	if len(f) != 2 {
		panic("revision-info returned bad output: " + string(output))
	}
	return string(f[1])
}

func copyCharmDir(dst string, dir *charm.Dir) {
	var b bytes.Buffer
	err := dir.BundleTo(&b)
	if err != nil {
		panic(err)
	}
	bundle, err := charm.ReadBundleBytes(b.Bytes())
	if err != nil {
		panic(err)
	}
	err = bundle.ExpandTo(dst)
	if err != nil {
		panic(err)
	}
}
<commit_msg>Use testing.invalid rather than example.com for testcase urls<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package store_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/store"
	"launchpad.net/juju-core/testing"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

func (s *StoreSuite) dummyBranch(c *C, suffix string) bzrDir {
	tmpDir := c.MkDir()
	if suffix != "" {
		tmpDir = filepath.Join(tmpDir, suffix)
		err := os.MkdirAll(tmpDir, 0755)
		c.Assert(err, IsNil)
	}
	branch := bzrDir(tmpDir)
	branch.init()

	copyCharmDir(branch.path(), testing.Charms.Dir("dummy"))
	branch.add()
	branch.commit("Imported charm.")
	return branch
}

var urls = []*charm.URL{
	charm.MustParseURL("cs:~joe/oneiric/dummy"),
	charm.MustParseURL("cs:oneiric/dummy"),
}

type fakePlugin struct {
	oldEnv string
}

func (p *fakePlugin) install(dir string, content string) {
	p.oldEnv = os.Getenv("BZR_PLUGINS_AT")
	err := ioutil.WriteFile(filepath.Join(dir, "__init__.py"), []byte(content), 0644)
	if err != nil {
		panic(err)
	}
	os.Setenv("BZR_PLUGINS_AT", "fakePlugin@"+dir)
}

func (p *fakePlugin) uninstall() {
	os.Setenv("BZR_PLUGINS_AT", p.oldEnv)
}

func (s *StoreSuite) TestPublish(c *C) {
	branch := s.dummyBranch(c, "")

	// Ensure that the streams are parsed separately by inserting
	// garbage on stderr. The wanted information is still there.
	plugin := fakePlugin{}
	plugin.install(c.MkDir(), `import sys; sys.stderr.write("STDERR STUFF FROM TEST\n")`)
	defer plugin.uninstall()

	err := store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, IsNil)

	for _, url := range urls {
		info, rc, err := s.store.OpenCharm(url)
		c.Assert(err, IsNil)
		defer rc.Close()
		c.Assert(info.Revision(), Equals, 0)
		c.Assert(info.Meta().Name, Equals, "dummy")

		data, err := ioutil.ReadAll(rc)
		c.Assert(err, IsNil)

		bundle, err := charm.ReadBundleBytes(data)
		c.Assert(err, IsNil)
		c.Assert(bundle.Revision(), Equals, 0)
		c.Assert(bundle.Meta().Name, Equals, "dummy")
	}

	// Attempt to publish the same content again while providing the wrong
	// tip revision. It must pick the real revision from the branch and
	// note this was previously published.
	err = store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, Equals, store.ErrRedundantUpdate)

	// Bump the content revision and lie again about the known tip revision.
	// This time, though, pretend it's the same as the real branch revision
	// previously published. It must error and not publish the new revision
	// because it will use the revision provided as a parameter to check if
	// publishing was attempted before. This is the mechanism that enables
	// stopping fast without having to download every single branch. Real
	// revision is picked in the next scan.
	digest1 := branch.digest()
	branch.change()
	err = store.PublishBazaarBranch(s.store, urls, branch.path(), digest1)
	c.Assert(err, Equals, store.ErrRedundantUpdate)

	// Now allow it to publish the new content by providing an unseen revision.
	err = store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, IsNil)
	digest2 := branch.digest()

	info, err := s.store.CharmInfo(urls[0])
	c.Assert(err, IsNil)
	c.Assert(info.Revision(), Equals, 1)
	c.Assert(info.Meta().Name, Equals, "dummy")

	// There are two events published, for each of the successful attempts.
	// The failures are ignored given that they are artifacts of the
	// publishing mechanism rather than actual problems.
	_, err = s.store.CharmEvent(urls[0], "wrong-rev")
	c.Assert(err, Equals, store.ErrNotFound)
	for i, digest := range []string{digest1, digest2} {
		event, err := s.store.CharmEvent(urls[0], digest)
		c.Assert(err, IsNil)
		c.Assert(event.Kind, Equals, store.EventPublished)
		c.Assert(event.Revision, Equals, i)
		c.Assert(event.Errors, IsNil)
		c.Assert(event.Warnings, IsNil)
	}
}

func (s *StoreSuite) TestPublishErrorFromBzr(c *C) {
	branch := s.dummyBranch(c, "")

	// In TestPublish we ensure that the streams are parsed
	// separately by inserting garbage on stderr. Now make
	// sure that stderr isn't simply trashed, as we want to
	// know about what a real error tells us.
	plugin := fakePlugin{}
	plugin.install(c.MkDir(), `import sys; sys.stderr.write("STDERR STUFF FROM TEST\n"); sys.exit(1)`)
	defer plugin.uninstall()

	err := store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, ErrorMatches, "(?s).*STDERR STUFF.*")
}

func (s *StoreSuite) TestPublishErrorInCharm(c *C) {
	branch := s.dummyBranch(c, "")

	// Corrupt the charm.
	branch.remove("metadata.yaml")
	branch.commit("Removed metadata.yaml.")

	// Attempt to publish the erroneous content.
	err := store.PublishBazaarBranch(s.store, urls, branch.path(), "wrong-rev")
	c.Assert(err, ErrorMatches, ".*/metadata.yaml: no such file or directory")

	// The event should be logged as well, since this was an error in the charm
	// that won't go away and must be communicated to the author.
	event, err := s.store.CharmEvent(urls[0], branch.digest())
	c.Assert(err, IsNil)
	c.Assert(event.Kind, Equals, store.EventPublishError)
	c.Assert(event.Revision, Equals, 0)
	c.Assert(event.Errors, NotNil)
	c.Assert(event.Errors[0], Matches, ".*/metadata.yaml: no such file or directory")
	c.Assert(event.Warnings, IsNil)
}

type bzrDir string

func (dir bzrDir) path(args ...string) string {
	return filepath.Join(append([]string{string(dir)}, args...)...)
}

func (dir bzrDir) run(args ...string) []byte {
	cmd := exec.Command("bzr", args...)
	oldemail := os.Getenv("EMAIL")
	defer os.Setenv("EMAIL", oldemail)
	// bzr will complain if bzr whoami has not been run previously,
	// avoid this by passing $EMAIL into the environment.
	os.Setenv("EMAIL", "nobody@testing.invalid")
	cmd.Dir = string(dir)
	output, err := cmd.Output()
	if err != nil {
		panic(fmt.Sprintf("command failed: bzr %s\n%s", strings.Join(args, " "), output))
	}
	return output
}

func (dir bzrDir) init() {
	dir.run("init")
}

func (dir bzrDir) add(paths ...string) {
	dir.run(append([]string{"add"}, paths...)...)
}

func (dir bzrDir) remove(paths ...string) {
	dir.run(append([]string{"rm"}, paths...)...)
}

func (dir bzrDir) commit(msg string) {
	dir.run("commit", "-m", msg)
}

func (dir bzrDir) write(path string, data string) {
	err := ioutil.WriteFile(dir.path(path), []byte(data), 0644)
	if err != nil {
		panic(err)
	}
}

func (dir bzrDir) change() {
	t := time.Now().String()
	dir.write("timestamp", t)
	dir.add("timestamp")
	dir.commit("Revision bumped at " + t)
}

func (dir bzrDir) digest() string {
	output := dir.run("revision-info")
	f := bytes.Fields(output)
	if len(f) != 2 {
		panic("revision-info returned bad output: " + string(output))
	}
	return string(f[1])
}

func copyCharmDir(dst string, dir *charm.Dir) {
	var b bytes.Buffer
	err := dir.BundleTo(&b)
	if err != nil {
		panic(err)
	}
	bundle, err := charm.ReadBundleBytes(b.Bytes())
	if err != nil {
		panic(err)
	}
	err = bundle.ExpandTo(dst)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The storage stage is responsible for partitioning disks, creating RAID
// arrays, formatting partitions, writing files, writing systemd units, and
// writing network units.

package disks

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/coreos/ignition/v2/config/v3_2_experimental/types"
	"github.com/coreos/ignition/v2/internal/exec/util"
	"github.com/coreos/ignition/v2/internal/sgdisk"
)

var (
	ErrBadSgdiskOutput = errors.New("sgdisk had unexpected output")
)

// createPartitions creates the partitions described in config.Storage.Disks.
func (s stage) createPartitions(config types.Config) error {
	if len(config.Storage.Disks) == 0 {
		return nil
	}
	s.Logger.PushPrefix("createPartitions")
	defer s.Logger.PopPrefix()

	devs := []string{}
	for _, disk := range config.Storage.Disks {
		devs = append(devs, string(disk.Device))
	}

	if err := s.waitOnDevicesAndCreateAliases(devs, "disks"); err != nil {
		return err
	}

	for _, dev := range config.Storage.Disks {
		devAlias := util.DeviceAlias(string(dev.Device))

		err := s.Logger.LogOp(func() error {
			return s.partitionDisk(dev, devAlias)
		}, "partitioning %q", devAlias)
		if err != nil {
			return err
		}
	}

	return nil
}

// partitionMatches determines if the existing partition matches the spec given. See docs/operator notes for what
// what it means for an existing partition to match the spec. spec must have non-zero Start and Size. existing must
// also have non-zero start and size and non-nil start and size and label.
// n.b. existing.{Size,Start}MiB must be converted to sectors first (yes the variable name becomes misleading)
func partitionMatches(existing util.PartitionInfo, spec sgdisk.Partition) error {
	if spec.Number != existing.Number {
		return fmt.Errorf("partition numbers did not match (specified %d, got %d). This should not happen, please file a bug.", spec.Number, existing.Number)
	}
	if spec.StartSector != nil && *spec.StartSector != existing.StartSector {
		return fmt.Errorf("starting sector did not match (specified %d, got %d)", *spec.StartSector, existing.StartSector)
	}
	if spec.SizeInSectors != nil && *spec.SizeInSectors != existing.SizeInSectors {
		return fmt.Errorf("size did not match (specified %d, got %d)", *spec.SizeInSectors, existing.SizeInSectors)
	}
	if spec.GUID != nil && *spec.GUID != "" && strings.ToLower(*spec.GUID) != strings.ToLower(existing.GUID) {
		return fmt.Errorf("GUID did not match (specified %q, got %q)", *spec.GUID, existing.GUID)
	}
	if spec.TypeGUID != nil && *spec.TypeGUID != "" && strings.ToLower(*spec.TypeGUID) != strings.ToLower(existing.TypeGUID) {
		return fmt.Errorf("type GUID did not match (specified %q, got %q)", *spec.TypeGUID, existing.TypeGUID)
	}
	if spec.Label != nil && *spec.Label != existing.Label {
		return fmt.Errorf("label did not match (specified %q, got %q)", *spec.Label, existing.Label)
	}
	return nil
}

// partitionShouldBeInspected returns if the partition has zeroes that need to be resolved to sectors.
func partitionShouldBeInspected(part sgdisk.Partition) bool {
	if part.Number == 0 {
		return false
	}
	return (part.StartSector != nil && *part.StartSector == 0) ||
		(part.SizeInSectors != nil && *part.SizeInSectors == 0)
}

func convertMiBToSectors(mib *int, sectorSize int) *int64 {
	if mib != nil {
		v := int64(*mib) * (1024 * 1024 / int64(sectorSize))
		return &v
	} else {
		return nil
	}
}

// getRealStartAndSize returns a map of partition numbers to a struct that contains what their real start
// and end sector should be. It runs sgdisk --pretend to determine what the partitions would look like if
// everything specified were to be (re)created.
func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo util.DiskInfo) ([]sgdisk.Partition, error) {
	partitions := []sgdisk.Partition{}
	for _, cpart := range dev.Partitions {
		partitions = append(partitions, sgdisk.Partition{
			Partition:     cpart,
			StartSector:   convertMiBToSectors(cpart.StartMiB, diskInfo.LogicalSectorSize),
			SizeInSectors: convertMiBToSectors(cpart.SizeMiB, diskInfo.LogicalSectorSize),
		})
	}

	op := sgdisk.Begin(s.Logger, devAlias)
	for _, part := range partitions {
		if info, exists := diskInfo.GetPartition(part.Number); exists {
			// delete all existing partitions
			op.DeletePartition(part.Number)
			if part.StartSector == nil && (part.WipePartitionEntry == nil || !*part.WipePartitionEntry) {
				// don't care means keep the same if we can't wipe, otherwise stick it at start 0
				part.StartSector = &info.StartSector
			}
			if part.SizeInSectors == nil && (part.WipePartitionEntry == nil || !*part.WipePartitionEntry) {
				part.SizeInSectors = &info.SizeInSectors
			}
		}
		if partitionShouldExist(part) {
			// Clear the label. sgdisk doesn't escape control characters. This makes parsing easier
			part.Label = nil
			op.CreatePartition(part)
		}
	}

	// We only care to examine partitions that have start or size 0.
	partitionsToInspect := []int{}
	for _, part := range partitions {
		if partitionShouldBeInspected(part) {
			op.Info(part.Number)
			partitionsToInspect = append(partitionsToInspect, part.Number)
		}
	}

	output, err := op.Pretend()
	if err != nil {
		return nil, err
	}

	realDimensions, err := parseSgdiskPretend(output, partitionsToInspect)
	if err != nil {
		return nil, err
	}

	result := []sgdisk.Partition{}
	for _, part := range partitions {
		if dims, ok := realDimensions[part.Number]; ok {
			if part.StartSector != nil {
				part.StartSector = &dims.start
			}
			if part.SizeInSectors != nil {
				part.SizeInSectors = &dims.size
			}
		}
		result = append(result, part)
	}
	return result, nil
}

type sgdiskOutput struct {
	start int64
	size  int64
}

// parseLine takes a regexp that captures an int64 and a string to match on. On success it returns
// the captured int64 and nil. If the regexp does not match it returns -1 and nil. If it encountered
// an error it returns 0 and the error.
func parseLine(r *regexp.Regexp, line string) (int64, error) {
	matches := r.FindStringSubmatch(line)
	switch len(matches) {
	case 0:
		return -1, nil
	case 2:
		return strconv.ParseInt(matches[1], 10, 64)
	default:
		return 0, ErrBadSgdiskOutput
	}
}

// parseSgdiskPretend parses the output of running sgdisk pretend with --info specified for each partition
// number specified in partitionNumbers. E.g. if paritionNumbers is [1,4,5], it is expected that the sgdisk
// output was from running `sgdisk --pretend <commands> --info=1 --info=4 --info=5`. It assumes the the
// partition labels are well behaved (i.e. contain no control characters). It returns a list of partitions
// matching the partition numbers specified, but with the start and size information as determined by sgdisk.
// The partition numbers need to passed in because sgdisk includes them in its output.
func parseSgdiskPretend(sgdiskOut string, partitionNumbers []int) (map[int]sgdiskOutput, error) {
	if len(partitionNumbers) == 0 {
		return nil, nil
	}
	startRegex := regexp.MustCompile("^First sector: (\\d*) \\(.*\\)$")
	endRegex := regexp.MustCompile("^Last sector: (\\d*) \\(.*\\)$")
	const (
		START             = iota
		END               = iota
		FAIL_ON_START_END = iota
	)

	output := map[int]sgdiskOutput{}
	state := START
	current := sgdiskOutput{}
	i := 0

	lines := strings.Split(sgdiskOut, "\n")
	for _, line := range lines {
		switch state {
		case START:
			start, err := parseLine(startRegex, line)
			if err != nil {
				return nil, err
			}
			if start != -1 {
				current.start = start
				state = END
			}
		case END:
			end, err := parseLine(endRegex, line)
			if err != nil {
				return nil, err
			}
			if end != -1 {
				current.size = 1 + end - current.start
				output[partitionNumbers[i]] = current
				i++
				if i == len(partitionNumbers) {
					state = FAIL_ON_START_END
				} else {
					current = sgdiskOutput{}
					state = START
				}
			}
		case FAIL_ON_START_END:
			if len(startRegex.FindStringSubmatch(line)) != 0 ||
				len(endRegex.FindStringSubmatch(line)) != 0 {
				return nil, ErrBadSgdiskOutput
			}
		}
	}

	if state != FAIL_ON_START_END {
		// We stopped parsing in the middle of a info block. Something is wrong
		return nil, ErrBadSgdiskOutput
	}

	return output, nil
}

// partitionShouldExist returns whether a bool is indicating if a partition should exist or not.
// nil (unspecified in json) is treated the same as true.
func partitionShouldExist(part sgdisk.Partition) bool {
	return part.ShouldExist == nil || *part.ShouldExist
}

// getPartitionMap returns a map of partitions on device, indexed by partition number
func (s stage) getPartitionMap(device string) (util.DiskInfo, error) {
	info := util.DiskInfo{}
	err := s.Logger.LogOp(
		func() error {
			var err error
			info, err = util.DumpDisk(device)
			return err
		}, "reading partition table of %q", device)
	if err != nil {
		return util.DiskInfo{}, err
	}
	return info, nil
}

// Allow sorting partitions (must be a stable sort) so partition number 0 happens last
// regardless of where it was in the list.
type PartitionList []types.Partition

func (p PartitionList) Len() int {
	return len(p)
}

// We only care about partitions with number 0 being considered the "largest" elements
// so they are processed last.
func (p PartitionList) Less(i, j int) bool {
	return p[i].Number != 0 && p[j].Number == 0
}

func (p PartitionList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

// partitionDisk partitions devAlias according to the spec given by dev
func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
	if dev.WipeTable != nil && *dev.WipeTable {
		op := sgdisk.Begin(s.Logger, devAlias)
		s.Logger.Info("wiping partition table requested on %q", devAlias)
		op.WipeTable(true)
		op.Commit()
	}

	// Ensure all partitions with number 0 are last
	sort.Stable(PartitionList(dev.Partitions))

	op := sgdisk.Begin(s.Logger, devAlias)

	diskInfo, err := s.getPartitionMap(devAlias)
	if err != nil {
		return err
	}

	// get a list of parititions that have size and start 0 replaced with the real sizes
	// that would be used if all specified partitions were to be created anew.
	// Also calculate sectors for all of the start/size values.
	resolvedPartitions, err := s.getRealStartAndSize(dev, devAlias, diskInfo)
	if err != nil {
		return err
	}

	for _, part := range resolvedPartitions {
		shouldExist := partitionShouldExist(part)
		info, exists := diskInfo.GetPartition(part.Number)
		var matchErr error
		if exists {
			matchErr = partitionMatches(info, part)
		}
		matches := exists && matchErr == nil
		wipeEntry := part.WipePartitionEntry != nil && *part.WipePartitionEntry

		// This is a translation of the matrix in the operator notes.
		switch {
		case !exists && !shouldExist:
			s.Logger.Info("partition %d specified as nonexistant and no partition was found. Success.", part.Number)
		case !exists && shouldExist:
			op.CreatePartition(part)
		case exists && !shouldExist && !wipeEntry:
			return fmt.Errorf("partition %d exists but is specified as nonexistant and wipePartitionEntry is false", part.Number)
		case exists && !shouldExist && wipeEntry:
			op.DeletePartition(part.Number)
		case exists && shouldExist && matches:
			s.Logger.Info("partition %d found with correct specifications", part.Number)
		case exists && shouldExist && !wipeEntry && !matches:
			return fmt.Errorf("Partition %d didn't match: %v", part.Number, matchErr)
		case exists && shouldExist && wipeEntry && !matches:
			s.Logger.Info("partition %d did not meet specifications, wiping partition entry and recreating", part.Number)
			op.DeletePartition(part.Number)
			op.CreatePartition(part)
		default:
			// unfortunatey, golang doesn't check that all cases are handled exhaustively
			return fmt.Errorf("Unreachable code reached when processing partition %d. golang--", part.Number)
		}
	}

	if err := op.Commit(); err != nil {
		return fmt.Errorf("commit failure: %v", err)
	}
	return nil
}
<commit_msg>stages/disks: support resizing an existing partition<commit_after>// Copyright 2018 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The storage stage is responsible for partitioning disks, creating RAID
// arrays, formatting partitions, writing files, writing systemd units, and
// writing network units.

package disks

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/coreos/ignition/v2/config/v3_2_experimental/types"
	"github.com/coreos/ignition/v2/internal/exec/util"
	"github.com/coreos/ignition/v2/internal/sgdisk"
)

var (
	ErrBadSgdiskOutput = errors.New("sgdisk had unexpected output")
)

// createPartitions creates the partitions described in config.Storage.Disks.
func (s stage) createPartitions(config types.Config) error {
	if len(config.Storage.Disks) == 0 {
		return nil
	}
	s.Logger.PushPrefix("createPartitions")
	defer s.Logger.PopPrefix()

	devs := []string{}
	for _, disk := range config.Storage.Disks {
		devs = append(devs, string(disk.Device))
	}

	if err := s.waitOnDevicesAndCreateAliases(devs, "disks"); err != nil {
		return err
	}

	for _, dev := range config.Storage.Disks {
		devAlias := util.DeviceAlias(string(dev.Device))

		err := s.Logger.LogOp(func() error {
			return s.partitionDisk(dev, devAlias)
		}, "partitioning %q", devAlias)
		if err != nil {
			return err
		}
	}

	return nil
}

// partitionMatches determines if the existing partition matches the spec given. See doc/operator notes for what
// what it means for an existing partition to match the spec. spec must have non-zero Start and Size.
func partitionMatches(existing util.PartitionInfo, spec sgdisk.Partition) error {
	if err := partitionMatchesCommon(existing, spec); err != nil {
		return err
	}
	if spec.SizeInSectors != nil && *spec.SizeInSectors != existing.SizeInSectors {
		return fmt.Errorf("size did not match (specified %d, got %d)", *spec.SizeInSectors, existing.SizeInSectors)
	}
	return nil
}

// partitionMatchesResize returns if the existing partition should be resized by evaluating if
// `resize`field is true and partition matches in all respects except size.
func partitionMatchesResize(existing util.PartitionInfo, spec sgdisk.Partition) bool {
	return spec.Resize != nil && *spec.Resize && partitionMatchesCommon(existing, spec) == nil
}

// partitionMatchesCommon handles the common tests (excluding the partition size) to determine
// if the existing partition matches the spec given.
func partitionMatchesCommon(existing util.PartitionInfo, spec sgdisk.Partition) error {
	if spec.Number != existing.Number {
		return fmt.Errorf("partition numbers did not match (specified %d, got %d). This should not happen, please file a bug.", spec.Number, existing.Number)
	}
	if spec.StartSector != nil && *spec.StartSector != existing.StartSector {
		return fmt.Errorf("starting sector did not match (specified %d, got %d)", *spec.StartSector, existing.StartSector)
	}
	if spec.GUID != nil && *spec.GUID != "" && strings.ToLower(*spec.GUID) != strings.ToLower(existing.GUID) {
		return fmt.Errorf("GUID did not match (specified %q, got %q)", *spec.GUID, existing.GUID)
	}
	if spec.TypeGUID != nil && *spec.TypeGUID != "" && strings.ToLower(*spec.TypeGUID) != strings.ToLower(existing.TypeGUID) {
		return fmt.Errorf("type GUID did not match (specified %q, got %q)", *spec.TypeGUID, existing.TypeGUID)
	}
	if spec.Label != nil && *spec.Label != existing.Label {
		return fmt.Errorf("label did not match (specified %q, got %q)", *spec.Label, existing.Label)
	}
	return nil
}

// partitionShouldBeInspected returns if the partition has zeroes that need to be resolved to sectors.
func partitionShouldBeInspected(part sgdisk.Partition) bool {
	if part.Number == 0 {
		return false
	}
	return (part.StartSector != nil && *part.StartSector == 0) ||
		(part.SizeInSectors != nil && *part.SizeInSectors == 0)
}

func convertMiBToSectors(mib *int, sectorSize int) *int64 {
	if mib != nil {
		v := int64(*mib) * (1024 * 1024 / int64(sectorSize))
		return &v
	} else {
		return nil
	}
}

// getRealStartAndSize returns a map of partition numbers to a struct that contains what their real start
// and end sector should be. It runs sgdisk --pretend to determine what the partitions would look like if
// everything specified were to be (re)created.
func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo util.DiskInfo) ([]sgdisk.Partition, error) {
	partitions := []sgdisk.Partition{}
	for _, cpart := range dev.Partitions {
		partitions = append(partitions, sgdisk.Partition{
			Partition:     cpart,
			StartSector:   convertMiBToSectors(cpart.StartMiB, diskInfo.LogicalSectorSize),
			SizeInSectors: convertMiBToSectors(cpart.SizeMiB, diskInfo.LogicalSectorSize),
		})
	}

	op := sgdisk.Begin(s.Logger, devAlias)
	for _, part := range partitions {
		if info, exists := diskInfo.GetPartition(part.Number); exists {
			// delete all existing partitions
			op.DeletePartition(part.Number)
			if part.StartSector == nil && (part.WipePartitionEntry == nil || !*part.WipePartitionEntry) {
				// don't care means keep the same if we can't wipe, otherwise stick it at start 0
				part.StartSector = &info.StartSector
			}
			if part.SizeInSectors == nil && (part.WipePartitionEntry == nil || !*part.WipePartitionEntry) {
				part.SizeInSectors = &info.SizeInSectors
			}
		}
		if partitionShouldExist(part) {
			// Clear the label. sgdisk doesn't escape control characters. This makes parsing easier
			part.Label = nil
			op.CreatePartition(part)
		}
	}

	// We only care to examine partitions that have start or size 0.
	partitionsToInspect := []int{}
	for _, part := range partitions {
		if partitionShouldBeInspected(part) {
			op.Info(part.Number)
			partitionsToInspect = append(partitionsToInspect, part.Number)
		}
	}

	output, err := op.Pretend()
	if err != nil {
		return nil, err
	}

	realDimensions, err := parseSgdiskPretend(output, partitionsToInspect)
	if err != nil {
		return nil, err
	}

	result := []sgdisk.Partition{}
	for _, part := range partitions {
		if dims, ok := realDimensions[part.Number]; ok {
			if part.StartSector != nil {
				part.StartSector = &dims.start
			}
			if part.SizeInSectors != nil {
				part.SizeInSectors = &dims.size
			}
		}
		result = append(result, part)
	}
	return result, nil
}

type sgdiskOutput struct {
	start int64
	size  int64
}

// parseLine takes a regexp that captures an int64 and a string to match on. On success it returns
// the captured int64 and nil. If the regexp does not match it returns -1 and nil. If it encountered
// an error it returns 0 and the error.
func parseLine(r *regexp.Regexp, line string) (int64, error) {
	matches := r.FindStringSubmatch(line)
	switch len(matches) {
	case 0:
		return -1, nil
	case 2:
		return strconv.ParseInt(matches[1], 10, 64)
	default:
		return 0, ErrBadSgdiskOutput
	}
}

// parseSgdiskPretend parses the output of running sgdisk pretend with --info specified for each partition
// number specified in partitionNumbers. E.g. if paritionNumbers is [1,4,5], it is expected that the sgdisk
// output was from running `sgdisk --pretend <commands> --info=1 --info=4 --info=5`. It assumes the the
// partition labels are well behaved (i.e. contain no control characters). It returns a list of partitions
// matching the partition numbers specified, but with the start and size information as determined by sgdisk.
// The partition numbers need to passed in because sgdisk includes them in its output.
func parseSgdiskPretend(sgdiskOut string, partitionNumbers []int) (map[int]sgdiskOutput, error) {
	if len(partitionNumbers) == 0 {
		return nil, nil
	}
	startRegex := regexp.MustCompile("^First sector: (\\d*) \\(.*\\)$")
	endRegex := regexp.MustCompile("^Last sector: (\\d*) \\(.*\\)$")
	const (
		START             = iota
		END               = iota
		FAIL_ON_START_END = iota
	)

	output := map[int]sgdiskOutput{}
	state := START
	current := sgdiskOutput{}
	i := 0

	lines := strings.Split(sgdiskOut, "\n")
	for _, line := range lines {
		switch state {
		case START:
			start, err := parseLine(startRegex, line)
			if err != nil {
				return nil, err
			}
			if start != -1 {
				current.start = start
				state = END
			}
		case END:
			end, err := parseLine(endRegex, line)
			if err != nil {
				return nil, err
			}
			if end != -1 {
				current.size = 1 + end - current.start
				output[partitionNumbers[i]] = current
				i++
				if i == len(partitionNumbers) {
					state = FAIL_ON_START_END
				} else {
					current = sgdiskOutput{}
					state = START
				}
			}
		case FAIL_ON_START_END:
			if len(startRegex.FindStringSubmatch(line)) != 0 ||
				len(endRegex.FindStringSubmatch(line)) != 0 {
				return nil, ErrBadSgdiskOutput
			}
		}
	}

	if state != FAIL_ON_START_END {
		// We stopped parsing in the middle of a info block. Something is wrong
		return nil, ErrBadSgdiskOutput
	}

	return output, nil
}

// partitionShouldExist returns whether a bool is indicating if a partition should exist or not.
// nil (unspecified in json) is treated the same as true.
func partitionShouldExist(part sgdisk.Partition) bool {
	return part.ShouldExist == nil || *part.ShouldExist
}

// getPartitionMap returns a map of partitions on device, indexed by partition number
func (s stage) getPartitionMap(device string) (util.DiskInfo, error) {
	info := util.DiskInfo{}
	err := s.Logger.LogOp(
		func() error {
			var err error
			info, err = util.DumpDisk(device)
			return err
		}, "reading partition table of %q", device)
	if err != nil {
		return util.DiskInfo{}, err
	}
	return info, nil
}

// Allow sorting partitions (must be a stable sort) so partition number 0 happens last
// regardless of where it was in the list.
type PartitionList []types.Partition

func (p PartitionList) Len() int {
	return len(p)
}

// We only care about partitions with number 0 being considered the "largest" elements
// so they are processed last.
func (p PartitionList) Less(i, j int) bool {
	return p[i].Number != 0 && p[j].Number == 0
}

func (p PartitionList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

// partitionDisk partitions devAlias according to the spec given by dev
func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
	if dev.WipeTable != nil && *dev.WipeTable {
		op := sgdisk.Begin(s.Logger, devAlias)
		s.Logger.Info("wiping partition table requested on %q", devAlias)
		op.WipeTable(true)
		op.Commit()
	}

	// Ensure all partitions with number 0 are last
	sort.Stable(PartitionList(dev.Partitions))

	op := sgdisk.Begin(s.Logger, devAlias)

	diskInfo, err := s.getPartitionMap(devAlias)
	if err != nil {
		return err
	}

	// get a list of parititions that have size and start 0 replaced with the real sizes
	// that would be used if all specified partitions were to be created anew.
	// Also calculate sectors for all of the start/size values.
	resolvedPartitions, err := s.getRealStartAndSize(dev, devAlias, diskInfo)
	if err != nil {
		return err
	}

	for _, part := range resolvedPartitions {
		shouldExist := partitionShouldExist(part)
		info, exists := diskInfo.GetPartition(part.Number)
		var matchErr error
		if exists {
			matchErr = partitionMatches(info, part)
		}
		matches := exists && matchErr == nil
		wipeEntry := part.WipePartitionEntry != nil && *part.WipePartitionEntry

		// This is a translation of the matrix in the operator notes.
		switch {
		case !exists && !shouldExist:
			s.Logger.Info("partition %d specified as nonexistant and no partition was found. Success.", part.Number)
		case !exists && shouldExist:
			op.CreatePartition(part)
		case exists && !shouldExist && !wipeEntry:
			return fmt.Errorf("partition %d exists but is specified as nonexistant and wipePartitionEntry is false", part.Number)
		case exists && !shouldExist && wipeEntry:
			op.DeletePartition(part.Number)
		case exists && shouldExist && matches:
			s.Logger.Info("partition %d found with correct specifications", part.Number)
		case exists && shouldExist && !wipeEntry && !matches:
			if partitionMatchesResize(info, part) {
				s.Logger.Info("resizing partition %d", part.Number)
				op.DeletePartition(part.Number)
				part.Number = info.Number
				part.GUID = &info.GUID
				part.TypeGUID = &info.TypeGUID
				part.Label = &info.Label
				part.StartSector = &info.StartSector
				op.CreatePartition(part)
			} else {
				return fmt.Errorf("Partition %d didn't match: %v", part.Number, matchErr)
			}
		case exists && shouldExist && wipeEntry && !matches:
			s.Logger.Info("partition %d did not meet specifications, wiping partition entry and recreating", part.Number)
			op.DeletePartition(part.Number)
			op.CreatePartition(part)
		default:
			// unfortunatey, golang doesn't check that all cases are handled exhaustively
			return fmt.Errorf("Unreachable code reached when processing partition %d. golang--", part.Number)
		}
	}

	if err := op.Commit(); err != nil {
		return fmt.Errorf("commit failure: %v", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package command

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/cajun/shoehorn/logger"
	"os"
	"os/exec"
	"strings"
)

var (
	available Available
	root      = "."
)

type Ports struct {
	tcp string
	udp string
}

type Network struct {
	Ip      string
	Public  Ports
	Private Ports
}

type Available struct {
	daemonized  map[string]Executor
	info        map[string]Executor
	interactive map[string]Executor
}

func Root() string {
	return root
}

func (a *Available) addDaemon(name string, exe Executor) {
	if a.daemonized == nil {
		a.daemonized = make(map[string]Executor)
	}
	a.daemonized[name] = exe
}

func (a *Available) addInfo(name string, exe Executor) {
	if a.info == nil {
		a.info = make(map[string]Executor)
	}
	a.info[name] = exe
}

func (a *Available) addInteractive(name string, exe Executor) {
	if a.interactive == nil {
		a.interactive = make(map[string]Executor)
	}
	a.interactive[name] = exe
}

func init() {
	flag.StringVar(&root, "root", ".", "which dir at the apps located")

	available.addDaemon("start", Executor{
		description: "start the given process",
		run:         Start})
	available.addDaemon("stop", Executor{
		description: "stop the given process",
		run:         Stop})
	available.addDaemon("kill", Executor{
		description: "kill the given process",
		run:         Kill})
	available.addDaemon("restart", Executor{
		description: "restrat the givne process",
		run:         Restart})

	available.addInfo("status", Executor{
		description: "view the status of the process",
		run:         Status})
	available.addInfo("logs", Executor{
		description: "see logs for the process",
		run:         Logs})
	available.addInfo("params", Executor{
		description: "view the params that will be used in the docker command",
		run:         PrintParams})

	available.addInteractive("build", Executor{
		description: "build a container from a file or url",
		run:         Build})
	available.addInteractive("console", Executor{
		description: "execute the console command from the config",
		run:         Console})
	available.addInteractive("bash", Executor{
		description: "execute a bash shell for the process",
		run:         Bash})
	available.addInteractive("ssh", Executor{
		description: "ssh into the container",
		run:         Ssh})
}

// DaemonizedCommands are commands that will be daemonized or manage daemonized
// commands
func DaemonizedCommands() map[string]Executor {
	return available.daemonized
}

// InfoCommands are commands the will pull out information about the
// given process
func InfoCommands() map[string]Executor {
	return available.info
}

// InteractiveCommands will turn over some kind of command back to the user
func InteractiveCommands() map[string]Executor {
	return available.interactive
}

// Build will create the container if nessary
func Build(args ...string) {
	cmd := exec.Command("docker", "-t", cfg.Container, cfg.BuildFile)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Run()
}

// Start will run the standard start command
func Start(args ...string) {
	runInstances("Start", func(i int, id string) error {
		return runDaemon("run", settingsToParams(i, true)...)
	})

	if cfg.UseNginx {
		UpdateNginxConf()
	}
}

// Stop will stop all the process if this type.  If the 'Kill' setting is turned
// on then the stop will kill the process instead
func Stop(args ...string) {
	switch {
	case cfg.Kill:
		Kill(args...)
	default:
		runInstances("Stopping", func(i int, id string) error {
			defer os.Remove(pidFileName(i))
			return run("stop", id)
		})
	}
}

// Restart will call stop then start for this process
func Restart(args ...string) {
	logger.Log(fmt.Sprintf("Restarting %v\n", process))
	Stop(args...)
	Start(args...)
}

// Kill will kill the given process
func Kill(args ...string) {
	runInstances("Killing", func(i int, id string) error {
		defer os.Remove(pidFileName(i))
		return run("kill", id)
	})
}

// Console will run an interactive command for the given console command
func Console(args ...string) {
	cfg.StartCmd = "/bin/bash -c"
	cfg.QuotedOpts = "'" + cfg.Console + "'"
	runInteractive("run", settingsToParams(0, false)...)
}

// Bash will execute a bash command against the given container
func Bash(args ...string) {
	cfg.StartCmd = "/bin/bash"
	runInteractive("run", settingsToParams(0, false)...)
}

func Install(args ...string) {
	go func() {
		os.Chdir(root)
		opts := []string{"clone", args[0]}

		logger.Log("Cloning " + args[0])
		cmd := exec.Command("git", opts...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()

		parts := strings.Split(args[0], "/")
		path := parts[len(parts):][0]
		logger.Log("Building Images here: " + path)
		os.Chdir(path)
		Build(path)
	}()
}

func ip(instance int) string {
	return networkSettings(instance).Ip
}

func ports(instance int, settings map[string]interface{}) (public, private Ports) {

	if settings["PortMapping"] != nil {
		s := settings["PortMapping"].(map[string]interface{})

		if s["Tcp"] != nil {
			for private_port, public_port := range s["Tcp"].(map[string]interface{}) {
				private.tcp = private_port
				public.tcp = public_port.(string)
			}

		}

		if s["Udp"] != nil {
			for private_port, public_port := range s["Udp"].(map[string]interface{}) {
				private.tcp = private_port
				public.tcp = public_port.(string)
			}
		}

	}
	return
}

func Ssh(args ...string) {
}

func networkSettings(instance int) (net Network) {
	settings, err := inspect(instance)
	settings = settings["NetworkSettings"].(map[string]interface{})

	net.Ip = settings["IPAddress"].(string)

	net.Public, net.Private = ports(instance, settings)
	return
}

func inspect(instance int) (u map[string]interface{}, err error) {
	id, err := pid(instance)
	out, err := exec.Command("docker", "inspect", id).Output()
	if err != nil {
		return
	}

	all := []map[string]interface{}{}
	err = json.Unmarshal(out, &all)
	if len(all) > 0 {
		u = all[0]
	}
	return
}

// Running determines if the given process is running.
func running(args ...string) (found bool) {
	found = false
	cmd := exec.Command("docker", "ps")

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		logger.Log(fmt.Sprintln(err))
	}

	_, err = cmd.StderrPipe()
	if err != nil {
		logger.Log(fmt.Sprintln(err))
	}

	err = cmd.Start()
	if err != nil {
		logger.Log(fmt.Sprintln(err))
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(stdout)
	s := buf.String()

	cmd.Wait()

	for _, id := range pids() {
		if !found {
			found = strings.Contains(s, id)
		}
	}

	return
}

// Logs will print out all of the logs for each of the instances
func Logs(args ...string) {
	runInstances("Logs", func(i int, id string) error {
		return run("log", id)
	})

}

// Status will list out the statuses for the given process
func Status(args ...string) {
	runInstances("Status", func(i int, id string) error {
		return run("ps", id)
	})
}

// run will execute a command against docker with the given
// options as a daemon. run also sets the pid. run will not
// execute if there is an existing pid
func runDaemon(command string, inOpts ...string) error {
	base := []string{"-d"}
	opts := append(base, inOpts...)

	return run(command, opts...)
}

// run will execute a command against docker with the given
// options as a daemon. run also sets the pid. run will not
// execute if there is an existing pid
func run(command string, inOpts ...string) error {
	base := []string{command}
	opts := append(base, inOpts...)
	outOpts(opts)

	cmd := exec.Command("docker", opts...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// runInteractive will give the user the option for input into the
// docker command. examples would be running bash or ssh
func runInteractive(command string, inOpts ...string) error {
	base := []string{command, "-i", "-t"}
	opts := append(base, inOpts...)
	outOpts(opts)

	cmd := exec.Command("docker", opts...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	return cmd.Run()
}

// runner is an interface to a function that will execute
// the run command.
type runner func(instance int, pid string) error

// runInstances wraps the given function and execute
// the number of instances requested by the config file
// for the command given.
func runInstances(message string, fn runner) {
	logger.Log(fmt.Sprintf("%s %v\n", message, process))
	for i := 0; i < cfg.Instances; i++ {
		logger.Log(fmt.Sprintf("...Instance %d of %d %s\n", i, cfg.Instances, process))
		id, err := pid(i)
		if err != nil {
			logger.Log(fmt.Sprintln(err))
		} else {
			fn(i, id)
		}
	}

}

func runExec(cmd string, args ...string) {
	joined := strings.Join(args, " ")
	cfg.StartCmd = "/bin/bash -c"
	cfg.QuotedOpts = fmt.Sprintf("'%s %s'", cmd, joined)

	runInteractive("run", settingsToParams(0, false)...)
}
<commit_msg>Adding in Instal and Update<commit_after>package command

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/cajun/shoehorn/logger"
	"os"
	"os/exec"
	"strings"
)

var (
	available Available
	root      = "."
)

type Ports struct {
	tcp string
	udp string
}

type Network struct {
	Ip      string
	Public  Ports
	Private Ports
}

type Available struct {
	daemonized  map[string]Executor
	info        map[string]Executor
	interactive map[string]Executor
}

func Root() string {
	return root
}

func (a *Available) addDaemon(name string, exe Executor) {
	if a.daemonized == nil {
		a.daemonized = make(map[string]Executor)
	}
	a.daemonized[name] = exe
}

func (a *Available) addInfo(name string, exe Executor) {
	if a.info == nil {
		a.info = make(map[string]Executor)
	}
	a.info[name] = exe
}

func (a *Available) addInteractive(name string, exe Executor) {
	if a.interactive == nil {
		a.interactive = make(map[string]Executor)
	}
	a.interactive[name] = exe
}

func init() {
	flag.StringVar(&root, "root", ".", "which dir at the apps located")

	available.addDaemon("start", Executor{
		description: "start the given process",
		run:         Start})
	available.addDaemon("stop", Executor{
		description: "stop the given process",
		run:         Stop})
	available.addDaemon("kill", Executor{
		description: "kill the given process",
		run:         Kill})
	available.addDaemon("restart", Executor{
		description: "restrat the givne process",
		run:         Restart})

	available.addInfo("status", Executor{
		description: "view the status of the process",
		run:         Status})
	available.addInfo("logs", Executor{
		description: "see logs for the process",
		run:         Logs})
	available.addInfo("params", Executor{
		description: "view the params that will be used in the docker command",
		run:         PrintParams})

	available.addInteractive("build", Executor{
		description: "build a container from a file or url",
		run:         Build})
	available.addInteractive("console", Executor{
		description: "execute the console command from the config",
		run:         Console})
	available.addInteractive("bash", Executor{
		description: "execute a bash shell for the process",
		run:         Bash})
	available.addInteractive("get", Executor{
		description: "clone a git repo and then build the images",
		run:         Install})
	available.addInteractive("update", Executor{
		description: "pull and then build the images",
		run:         Update})
	//available.addInteractive("ssh", Executor{
	//description: "ssh into the container",
	//run:         Ssh})
}

// DaemonizedCommands are commands that will be daemonized or manage daemonized
// commands
func DaemonizedCommands() map[string]Executor {
	return available.daemonized
}

// InfoCommands are commands the will pull out information about the
// given process
func InfoCommands() map[string]Executor {
	return available.info
}

// InteractiveCommands will turn over some kind of command back to the user
func InteractiveCommands() map[string]Executor {
	return available.interactive
}

// Build will create the container if nessary
func Build(args ...string) {
	cmd := exec.Command("docker", "-t", cfg.Container, cfg.BuildFile)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.Run()
}

// Start will run the standard start command
func Start(args ...string) {
	runInstances("Start", func(i int, id string) error {
		return runDaemon("run", settingsToParams(i, true)...)
	})

	if cfg.UseNginx {
		UpdateNginxConf()
	}
}

// Stop will stop all the process if this type.  If the 'Kill' setting is turned
// on then the stop will kill the process instead
func Stop(args ...string) {
	switch {
	case cfg.Kill:
		Kill(args...)
	default:
		runInstances("Stopping", func(i int, id string) error {
			defer os.Remove(pidFileName(i))
			return run("stop", id)
		})
	}
}

// Restart will call stop then start for this process
func Restart(args ...string) {
	logger.Log(fmt.Sprintf("Restarting %v\n", process))
	Stop(args...)
	Start(args...)
}

// Kill will kill the given process
func Kill(args ...string) {
	runInstances("Killing", func(i int, id string) error {
		defer os.Remove(pidFileName(i))
		return run("kill", id)
	})
}

// Console will run an interactive command for the given console command
func Console(args ...string) {
	cfg.StartCmd = "/bin/bash -c"
	cfg.QuotedOpts = "'" + cfg.Console + "'"
	runInteractive("run", settingsToParams(0, false)...)
}

// Bash will execute a bash command against the given container
func Bash(args ...string) {
	cfg.StartCmd = "/bin/bash"
	runInteractive("run", settingsToParams(0, false)...)
}

func Install(args ...string) {
	go func() {
		os.Chdir(root)
		opts := []string{"clone", args[0]}

		logger.Log("Cloning " + args[0])
		cmd := exec.Command("git", opts...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Run()

		if err == nil {
			parts := strings.Split(args[0], "/")
			path := parts[len(parts):][0]
			logger.Log("Building Images here: " + path)
			os.Chdir(path)
			Build(path)
		} else {
			logger.Log(err.Error())
		}
	}()
}

func Update(args ...string) {
	go func() {
		logger.Log("Updaing...")
		cmd := exec.Command("git", "pull")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Run()

		if err == nil {
			Build(root)
		} else {
			logger.Log(err.Error())
		}
	}()

}

func ip(instance int) string {
	return networkSettings(instance).Ip
}

func ports(instance int, settings map[string]interface{}) (public, private Ports) {

	if settings["PortMapping"] != nil {
		s := settings["PortMapping"].(map[string]interface{})

		if s["Tcp"] != nil {
			for private_port, public_port := range s["Tcp"].(map[string]interface{}) {
				private.tcp = private_port
				public.tcp = public_port.(string)
			}

		}

		if s["Udp"] != nil {
			for private_port, public_port := range s["Udp"].(map[string]interface{}) {
				private.tcp = private_port
				public.tcp = public_port.(string)
			}
		}

	}
	return
}

//func Ssh(args ...string) {
//}

func networkSettings(instance int) (net Network) {
	settings, err := inspect(instance)
	settings = settings["NetworkSettings"].(map[string]interface{})

	net.Ip = settings["IPAddress"].(string)

	net.Public, net.Private = ports(instance, settings)
	return
}

func inspect(instance int) (u map[string]interface{}, err error) {
	id, err := pid(instance)
	out, err := exec.Command("docker", "inspect", id).Output()
	if err != nil {
		return
	}

	all := []map[string]interface{}{}
	err = json.Unmarshal(out, &all)
	if len(all) > 0 {
		u = all[0]
	}
	return
}

// Running determines if the given process is running.
func running(args ...string) (found bool) {
	found = false
	cmd := exec.Command("docker", "ps")

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		logger.Log(fmt.Sprintln(err))
	}

	_, err = cmd.StderrPipe()
	if err != nil {
		logger.Log(fmt.Sprintln(err))
	}

	err = cmd.Start()
	if err != nil {
		logger.Log(fmt.Sprintln(err))
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(stdout)
	s := buf.String()

	cmd.Wait()

	for _, id := range pids() {
		if !found {
			found = strings.Contains(s, id)
		}
	}

	return
}

// Logs will print out all of the logs for each of the instances
func Logs(args ...string) {
	runInstances("Logs", func(i int, id string) error {
		return run("log", id)
	})

}

// Status will list out the statuses for the given process
func Status(args ...string) {
	runInstances("Status", func(i int, id string) error {
		return run("ps", id)
	})
}

// run will execute a command against docker with the given
// options as a daemon. run also sets the pid. run will not
// execute if there is an existing pid
func runDaemon(command string, inOpts ...string) error {
	base := []string{"-d"}
	opts := append(base, inOpts...)

	return run(command, opts...)
}

// run will execute a command against docker with the given
// options as a daemon. run also sets the pid. run will not
// execute if there is an existing pid
func run(command string, inOpts ...string) error {
	base := []string{command}
	opts := append(base, inOpts...)
	outOpts(opts)

	cmd := exec.Command("docker", opts...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// runInteractive will give the user the option for input into the
// docker command. examples would be running bash or ssh
func runInteractive(command string, inOpts ...string) error {
	base := []string{command, "-i", "-t"}
	opts := append(base, inOpts...)
	outOpts(opts)

	cmd := exec.Command("docker", opts...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	return cmd.Run()
}

// runner is an interface to a function that will execute
// the run command.
type runner func(instance int, pid string) error

// runInstances wraps the given function and execute
// the number of instances requested by the config file
// for the command given.
func runInstances(message string, fn runner) {
	logger.Log(fmt.Sprintf("%s %v\n", message, process))
	for i := 0; i < cfg.Instances; i++ {
		logger.Log(fmt.Sprintf("...Instance %d of %d %s\n", i, cfg.Instances, process))
		id, err := pid(i)
		if err != nil {
			logger.Log(fmt.Sprintln(err))
		} else {
			fn(i, id)
		}
	}

}

func runExec(cmd string, args ...string) {
	joined := strings.Join(args, " ")
	cfg.StartCmd = "/bin/bash -c"
	cfg.QuotedOpts = fmt.Sprintf("'%s %s'", cmd, joined)

	runInteractive("run", settingsToParams(0, false)...)
}
<|endoftext|>


<commit_before>package ls

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"../../conio"
	"../../dos"
)

const (
	O_STRIP_DIR = 1
	O_LONG      = 2
	O_INDICATOR = 4
	O_COLOR     = 8
	O_ALL       = 16
	O_TIME      = 32
	O_REVERSE   = 64
	O_RECURSIVE = 128
	O_ONE       = 256
	O_HELP      = 512
	O_SIZESORT  = 1024
)

type fileInfoT struct {
	name        string
	os.FileInfo // anonymous
}

const (
	ANSI_EXEC     = "\x1B[1;35m"
	ANSI_DIR      = "\x1B[1;32m"
	ANSI_NORM     = "\x1B[1;37m"
	ANSI_READONLY = "\x1B[1;33m"
	ANSI_HIDDEN   = "\x1B[1;34m"
	ANSI_END      = "\x1B[0m"
)

func (this fileInfoT) Name() string { return this.name }

func lsOneLong(folder string, status os.FileInfo, flag int, out io.Writer) {
	indicator := " "
	prefix := ""
	postfix := ""
	if (flag & O_COLOR) != 0 {
		prefix = ANSI_NORM
		postfix = ANSI_END
	}
	if status.IsDir() {
		io.WriteString(out, "d")
		indicator = "/"
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_DIR
			postfix = ANSI_END
		}
	} else {
		io.WriteString(out, "-")
	}
	mode := status.Mode()
	perm := mode.Perm()
	name := status.Name()
	attr := dos.GetFileAttributesFromFileInfo(status)

	if (attr & dos.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
		indicator = "@"
	}
	if (perm & 4) > 0 {
		io.WriteString(out, "r")
	} else {
		io.WriteString(out, "-")
	}
	if (perm & 2) > 0 {
		io.WriteString(out, "w")
	} else {
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_READONLY
			postfix = ANSI_END
		}
		io.WriteString(out, "-")
	}
	if (perm & 1) > 0 {
		io.WriteString(out, "x")
	} else if dos.IsExecutableSuffix(filepath.Ext(name)) {
		io.WriteString(out, "x")
		indicator = "*"
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_EXEC
			postfix = ANSI_END
		}
	} else {
		io.WriteString(out, "-")
	}
	if (attr&dos.FILE_ATTRIBUTE_HIDDEN) != 0 &&
		(flag&O_COLOR) != 0 {
		prefix = ANSI_HIDDEN
		postfix = ANSI_END
	}
	if (flag & O_STRIP_DIR) > 0 {
		name = filepath.Base(name)
	}
	stamp := status.ModTime()
	fmt.Fprintf(out, " %8d %04d-%02d-%02d %02d:%02d %s%s%s",
		status.Size(),
		stamp.Year(),
		stamp.Month(),
		stamp.Day(),
		stamp.Hour(),
		stamp.Minute(),
		prefix,
		name,
		postfix)
	if (flag & O_INDICATOR) > 0 {
		io.WriteString(out, indicator)
	}
	io.WriteString(out, "\n")
}

func lsBox(folder string, nodes []os.FileInfo, flag int, out io.Writer) {
	nodes_ := make([]string, len(nodes))
	for key, val := range nodes {
		prefix := ""
		postfix := ""
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_NORM
			postfix = ANSI_END
		}
		indicator := ""
		if val.IsDir() {
			if (flag & O_COLOR) != 0 {
				prefix = ANSI_DIR
				postfix = ANSI_END
			}
			if (flag & O_INDICATOR) != 0 {
				indicator = "/"
			}
		}
		if (val.Mode().Perm() & 2) == 0 {
			if (flag & O_COLOR) != 0 {
				prefix = ANSI_READONLY
				postfix = ANSI_END
			}
		}
		if !val.IsDir() && dos.IsExecutableSuffix(filepath.Ext(val.Name())) {
			if (flag & O_COLOR) != 0 {
				prefix = ANSI_EXEC
				postfix = ANSI_END
			}
			if (flag & O_INDICATOR) != 0 {
				indicator = "*"
			}
		}
		attr := dos.GetFileAttributesFromFileInfo(val)
		if (attr&dos.FILE_ATTRIBUTE_HIDDEN) != 0 &&
			(flag&O_COLOR) != 0 {
			prefix = ANSI_HIDDEN
			postfix = ANSI_END
		}
		if (attr&dos.FILE_ATTRIBUTE_REPARSE_POINT) != 0 &&
			(flag&O_INDICATOR) != 0 {
			indicator = "@"
		}
		nodes_[key] = prefix + val.Name() + postfix + indicator
	}
	conio.BoxPrint(nodes_, out)
}

func lsLong(folder string, nodes []os.FileInfo, flag int, out io.Writer) {
	for _, finfo := range nodes {
		lsOneLong(folder, finfo, flag, out)
	}
}

func lsSimple(folder string, nodes []os.FileInfo, flag int, out io.Writer) {
	for _, f := range nodes {
		fmt.Fprintln(out, f.Name())
	}
}

type fileInfoCollection struct {
	flag  int
	nodes []os.FileInfo
}

func (this fileInfoCollection) Len() int {
	return len(this.nodes)
}

func (this fileInfoCollection) Less(i, j int) bool {
	var result bool
	if (this.flag & O_TIME) != 0 {
		result = this.nodes[i].ModTime().After(this.nodes[j].ModTime())
		if !result && !this.nodes[i].ModTime().Before(this.nodes[j].ModTime()) {
			result = (this.nodes[i].Name() < this.nodes[j].Name())
		}
	} else if (this.flag & O_SIZESORT) != 0 {
		diff := this.nodes[i].Size() - this.nodes[j].Size()
		if diff != 0 {
			result = (diff < 0)
		} else {
			result = (this.nodes[i].Name() < this.nodes[j].Name())
		}
	} else {
		result = (this.nodes[i].Name() < this.nodes[j].Name())
	}
	if (this.flag & O_REVERSE) != 0 {
		result = !result
	}
	return result
}
func (this fileInfoCollection) Swap(i, j int) {
	this.nodes[i], this.nodes[j] = this.nodes[j], this.nodes[i]
}

func lsFolder(folder string, flag int, out io.Writer) error {
	var folder_ string
	if rxDriveOnly.MatchString(folder) {
		folder_ = folder + "."
	} else {
		folder_ = folder
	}
	nodesArray := fileInfoCollection{flag: flag}
	var folders []string = nil
	if (flag & O_RECURSIVE) != 0 {
		folders = make([]string, 0)
	}
	tmp := make([]os.FileInfo, 0)

	var wildcard string
	if folder == "" {
		wildcard = "*"
	} else {
		wildcard = dos.Join(folder, "*")
	}
	dos.ForFiles(wildcard, func(f *dos.FileInfo) bool {
		if (flag & O_ALL) == 0 {
			if strings.HasPrefix(f.Name(), ".") {
				return true
			}
			attr := dos.GetFileAttributesFromFileInfo(f)
			if (attr & dos.FILE_ATTRIBUTE_HIDDEN) != 0 {
				return true
			}
		}
		if f.IsDir() && folders != nil && f.Name() != "." && f.Name() != ".." {
			folders = append(folders, f.Name())
		} else {
			tmp = append(tmp, f)
		}
		return true
	})
	nodesArray.nodes = tmp
	sort.Sort(nodesArray)
	if (flag & O_ONE) != 0 {
		lsSimple(folder_, nodesArray.nodes, O_STRIP_DIR|flag, out)
	} else if (flag & O_LONG) != 0 {
		lsLong(folder_, nodesArray.nodes, O_STRIP_DIR|flag, out)
	} else {
		lsBox(folder_, nodesArray.nodes, O_STRIP_DIR|flag, out)
	}
	if folders != nil && len(folders) > 0 {
		for _, f1 := range folders {
			f1fullpath := dos.Join(folder, f1)
			fmt.Fprintf(out, "\n%s:\n", f1fullpath)
			lsFolder(f1fullpath, flag, out)
		}
	}
	return nil
}

var rxDriveOnly = regexp.MustCompile("^[a-zA-Z]:$")

func lsCore(paths []string, flag int, out io.Writer) error {
	if len(paths) <= 0 {
		return lsFolder("", flag, out)
	}
	dirs := make([]string, 0)
	printCount := 0
	files := make([]os.FileInfo, 0)
	for _, name := range paths {
		var nameStat string
		if rxDriveOnly.MatchString(name) {
			nameStat = name + "."
		} else {
			nameStat = name
		}
		status, err := os.Stat(nameStat)
		if err != nil {
			if os.IsNotExist(err) {
				return fmt.Errorf("ls: %s not exist.", nameStat)
			} else {
				return err
			}
		} else if status.IsDir() {
			dirs = append(dirs, name)
		} else if (flag & O_LONG) != 0 {
			lsOneLong(".", &fileInfoT{name, status}, flag, out)
			printCount += 1
		} else {
			files = append(files, &fileInfoT{name, status})
		}
	}
	if len(files) > 0 {
		lsBox(".", files, flag, out)
		printCount = len(files)
	}
	for _, name := range dirs {
		if len(paths) > 1 {
			if printCount > 0 {
				io.WriteString(out, "\n")
			}
			fmt.Fprintf(out, "%s:\n", name)
		}
		err := lsFolder(name, flag, out)
		if err != nil {
			return err
		}
		printCount++
	}
	return nil
}

var option = map[rune](func(*int) error){
	'l': func(flag *int) error {
		*flag |= O_LONG
		return nil
	},
	'F': func(flag *int) error {
		*flag |= O_INDICATOR
		return nil
	},
	'o': func(flag *int) error {
		*flag |= O_COLOR
		return nil
	},
	'a': func(flag *int) error {
		*flag |= O_ALL
		return nil
	},
	't': func(flag *int) error {
		*flag |= O_TIME
		return nil
	},
	'r': func(flag *int) error {
		*flag |= O_REVERSE
		return nil
	},
	'R': func(flag *int) error {
		*flag |= O_RECURSIVE
		return nil
	},
	'1': func(flag *int) error {
		*flag |= O_ONE
		return nil
	},
	'h': func(flag *int) error {
		*flag |= O_HELP
		return nil
	},
	'S': func(flag *int) error {
		*flag |= O_SIZESORT
		return nil
	},
}

// 存在しないオプションに関するエラー
type OptionError struct {
	Option rune
}

func (this OptionError) Error() string {
	return fmt.Sprintf("-%c: No such option", this.Option)
}

// ls 機能のエントリ:引数をオプションとパスに分離する
func Main(args []string, out io.Writer) error {
	flag := 0
	paths := make([]string, 0)
	for _, arg := range args {
		if strings.HasPrefix(arg, "-") {
			for _, o := range arg[1:] {
				setter, ok := option[o]
				if !ok {
					return OptionError{Option: o}
				}
				if err := setter(&flag); err != nil {
					return err
				}
			}
		} else {
			paths = append(paths, arg)
		}
	}
	if (flag & O_HELP) != 0 {
		message := make([]byte, 0, 80)
		message = append(message, "Usage: ls [-"...)
		for optKey, _ := range option {
			message = append(message, byte(optKey))
		}
		message = append(message, "] [PATH(s)]..."...)
		return errors.New(string(message))
	}
	if _, ok := out.(io.Closer); ok {
		// output is a not ansicolor instance.
		flag &^= O_COLOR
	}
	if (flag & O_COLOR) != 0 {
		io.WriteString(out, ANSI_END)
	}
	return lsCore(paths, flag, out)
}

// vim:set fenc=utf8 ts=4 sw=4 noet:
<commit_msg>Change ls -h option : help -> human readable size<commit_after>package ls

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"../../conio"
	"../../dos"
)

const (
	O_STRIP_DIR = 1
	O_LONG      = 2
	O_INDICATOR = 4
	O_COLOR     = 8
	O_ALL       = 16
	O_TIME      = 32
	O_REVERSE   = 64
	O_RECURSIVE = 128
	O_ONE       = 256
	O_HELP      = 512
	O_SIZESORT  = 1024
	O_HUMAN     = 2048
)

type fileInfoT struct {
	name        string
	os.FileInfo // anonymous
}

const (
	ANSI_EXEC     = "\x1B[1;35m"
	ANSI_DIR      = "\x1B[1;32m"
	ANSI_NORM     = "\x1B[1;37m"
	ANSI_READONLY = "\x1B[1;33m"
	ANSI_HIDDEN   = "\x1B[1;34m"
	ANSI_END      = "\x1B[0m"
)

func (this fileInfoT) Name() string { return this.name }

func lsOneLong(folder string, status os.FileInfo, flag int, out io.Writer) {
	indicator := " "
	prefix := ""
	postfix := ""
	if (flag & O_COLOR) != 0 {
		prefix = ANSI_NORM
		postfix = ANSI_END
	}
	if status.IsDir() {
		io.WriteString(out, "d")
		indicator = "/"
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_DIR
			postfix = ANSI_END
		}
	} else {
		io.WriteString(out, "-")
	}
	mode := status.Mode()
	perm := mode.Perm()
	name := status.Name()
	attr := dos.GetFileAttributesFromFileInfo(status)

	if (attr & dos.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
		indicator = "@"
	}
	if (perm & 4) > 0 {
		io.WriteString(out, "r")
	} else {
		io.WriteString(out, "-")
	}
	if (perm & 2) > 0 {
		io.WriteString(out, "w")
	} else {
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_READONLY
			postfix = ANSI_END
		}
		io.WriteString(out, "-")
	}
	if (perm & 1) > 0 {
		io.WriteString(out, "x")
	} else if dos.IsExecutableSuffix(filepath.Ext(name)) {
		io.WriteString(out, "x")
		indicator = "*"
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_EXEC
			postfix = ANSI_END
		}
	} else {
		io.WriteString(out, "-")
	}
	if (attr&dos.FILE_ATTRIBUTE_HIDDEN) != 0 &&
		(flag&O_COLOR) != 0 {
		prefix = ANSI_HIDDEN
		postfix = ANSI_END
	}
	if (flag & O_STRIP_DIR) > 0 {
		name = filepath.Base(name)
	}
	stamp := status.ModTime()
	if (flag & O_HUMAN) != 0 {
		size := status.Size()
		if size >= 1024*1024*1024 {
			MB := size / 1024 / 1024
			GB := MB / 1024
			MB = MB % 1024
			fmt.Fprintf(out, " %5d.%01dG", GB, MB/102)
		} else if size >= 1024*1024 {
			KB := size / 1024
			MB := KB / 1024
			KB = KB % 1024
			fmt.Fprintf(out, " %5d.%01dM", MB, KB/102)
		} else if size > 1024 {
			KB := size / 1024
			B := size % 1024
			fmt.Fprintf(out, " %5d.%01dK", KB, B/102)
		} else {
			fmt.Fprintf(out, " %8d", size)
		}
	} else {
		fmt.Fprintf(out, " %8d", status.Size())
	}
	fmt.Fprintf(out, " %04d-%02d-%02d %02d:%02d %s%s%s",
		stamp.Year(),
		stamp.Month(),
		stamp.Day(),
		stamp.Hour(),
		stamp.Minute(),
		prefix,
		name,
		postfix)
	if (flag & O_INDICATOR) > 0 {
		io.WriteString(out, indicator)
	}
	io.WriteString(out, "\n")
}

func lsBox(folder string, nodes []os.FileInfo, flag int, out io.Writer) {
	nodes_ := make([]string, len(nodes))
	for key, val := range nodes {
		prefix := ""
		postfix := ""
		if (flag & O_COLOR) != 0 {
			prefix = ANSI_NORM
			postfix = ANSI_END
		}
		indicator := ""
		if val.IsDir() {
			if (flag & O_COLOR) != 0 {
				prefix = ANSI_DIR
				postfix = ANSI_END
			}
			if (flag & O_INDICATOR) != 0 {
				indicator = "/"
			}
		}
		if (val.Mode().Perm() & 2) == 0 {
			if (flag & O_COLOR) != 0 {
				prefix = ANSI_READONLY
				postfix = ANSI_END
			}
		}
		if !val.IsDir() && dos.IsExecutableSuffix(filepath.Ext(val.Name())) {
			if (flag & O_COLOR) != 0 {
				prefix = ANSI_EXEC
				postfix = ANSI_END
			}
			if (flag & O_INDICATOR) != 0 {
				indicator = "*"
			}
		}
		attr := dos.GetFileAttributesFromFileInfo(val)
		if (attr&dos.FILE_ATTRIBUTE_HIDDEN) != 0 &&
			(flag&O_COLOR) != 0 {
			prefix = ANSI_HIDDEN
			postfix = ANSI_END
		}
		if (attr&dos.FILE_ATTRIBUTE_REPARSE_POINT) != 0 &&
			(flag&O_INDICATOR) != 0 {
			indicator = "@"
		}
		nodes_[key] = prefix + val.Name() + postfix + indicator
	}
	conio.BoxPrint(nodes_, out)
}

func lsLong(folder string, nodes []os.FileInfo, flag int, out io.Writer) {
	for _, finfo := range nodes {
		lsOneLong(folder, finfo, flag, out)
	}
}

func lsSimple(folder string, nodes []os.FileInfo, flag int, out io.Writer) {
	for _, f := range nodes {
		fmt.Fprintln(out, f.Name())
	}
}

type fileInfoCollection struct {
	flag  int
	nodes []os.FileInfo
}

func (this fileInfoCollection) Len() int {
	return len(this.nodes)
}

func (this fileInfoCollection) Less(i, j int) bool {
	var result bool
	if (this.flag & O_TIME) != 0 {
		result = this.nodes[i].ModTime().After(this.nodes[j].ModTime())
		if !result && !this.nodes[i].ModTime().Before(this.nodes[j].ModTime()) {
			result = (this.nodes[i].Name() < this.nodes[j].Name())
		}
	} else if (this.flag & O_SIZESORT) != 0 {
		diff := this.nodes[i].Size() - this.nodes[j].Size()
		if diff != 0 {
			result = (diff < 0)
		} else {
			result = (this.nodes[i].Name() < this.nodes[j].Name())
		}
	} else {
		result = (this.nodes[i].Name() < this.nodes[j].Name())
	}
	if (this.flag & O_REVERSE) != 0 {
		result = !result
	}
	return result
}
func (this fileInfoCollection) Swap(i, j int) {
	this.nodes[i], this.nodes[j] = this.nodes[j], this.nodes[i]
}

func lsFolder(folder string, flag int, out io.Writer) error {
	var folder_ string
	if rxDriveOnly.MatchString(folder) {
		folder_ = folder + "."
	} else {
		folder_ = folder
	}
	nodesArray := fileInfoCollection{flag: flag}
	var folders []string = nil
	if (flag & O_RECURSIVE) != 0 {
		folders = make([]string, 0)
	}
	tmp := make([]os.FileInfo, 0)

	var wildcard string
	if folder == "" {
		wildcard = "*"
	} else {
		wildcard = dos.Join(folder, "*")
	}
	dos.ForFiles(wildcard, func(f *dos.FileInfo) bool {
		if (flag & O_ALL) == 0 {
			if strings.HasPrefix(f.Name(), ".") {
				return true
			}
			attr := dos.GetFileAttributesFromFileInfo(f)
			if (attr & dos.FILE_ATTRIBUTE_HIDDEN) != 0 {
				return true
			}
		}
		if f.IsDir() && folders != nil && f.Name() != "." && f.Name() != ".." {
			folders = append(folders, f.Name())
		} else {
			tmp = append(tmp, f)
		}
		return true
	})
	nodesArray.nodes = tmp
	sort.Sort(nodesArray)
	if (flag & O_ONE) != 0 {
		lsSimple(folder_, nodesArray.nodes, O_STRIP_DIR|flag, out)
	} else if (flag & O_LONG) != 0 {
		lsLong(folder_, nodesArray.nodes, O_STRIP_DIR|flag, out)
	} else {
		lsBox(folder_, nodesArray.nodes, O_STRIP_DIR|flag, out)
	}
	if folders != nil && len(folders) > 0 {
		for _, f1 := range folders {
			f1fullpath := dos.Join(folder, f1)
			fmt.Fprintf(out, "\n%s:\n", f1fullpath)
			lsFolder(f1fullpath, flag, out)
		}
	}
	return nil
}

var rxDriveOnly = regexp.MustCompile("^[a-zA-Z]:$")

func lsCore(paths []string, flag int, out io.Writer) error {
	if len(paths) <= 0 {
		return lsFolder("", flag, out)
	}
	dirs := make([]string, 0)
	printCount := 0
	files := make([]os.FileInfo, 0)
	for _, name := range paths {
		var nameStat string
		if rxDriveOnly.MatchString(name) {
			nameStat = name + "."
		} else {
			nameStat = name
		}
		status, err := os.Stat(nameStat)
		if err != nil {
			if os.IsNotExist(err) {
				return fmt.Errorf("ls: %s not exist.", nameStat)
			} else {
				return err
			}
		} else if status.IsDir() {
			dirs = append(dirs, name)
		} else if (flag & O_LONG) != 0 {
			lsOneLong(".", &fileInfoT{name, status}, flag, out)
			printCount += 1
		} else {
			files = append(files, &fileInfoT{name, status})
		}
	}
	if len(files) > 0 {
		lsBox(".", files, flag, out)
		printCount = len(files)
	}
	for _, name := range dirs {
		if len(paths) > 1 {
			if printCount > 0 {
				io.WriteString(out, "\n")
			}
			fmt.Fprintf(out, "%s:\n", name)
		}
		err := lsFolder(name, flag, out)
		if err != nil {
			return err
		}
		printCount++
	}
	return nil
}

var option = map[rune](func(*int) error){
	'l': func(flag *int) error {
		*flag |= O_LONG
		return nil
	},
	'F': func(flag *int) error {
		*flag |= O_INDICATOR
		return nil
	},
	'o': func(flag *int) error {
		*flag |= O_COLOR
		return nil
	},
	'a': func(flag *int) error {
		*flag |= O_ALL
		return nil
	},
	't': func(flag *int) error {
		*flag |= O_TIME
		return nil
	},
	'r': func(flag *int) error {
		*flag |= O_REVERSE
		return nil
	},
	'R': func(flag *int) error {
		*flag |= O_RECURSIVE
		return nil
	},
	'1': func(flag *int) error {
		*flag |= O_ONE
		return nil
	},
	'h': func(flag *int) error {
		*flag |= O_HUMAN
		return nil
	},
	'S': func(flag *int) error {
		*flag |= O_SIZESORT
		return nil
	},
}

// 存在しないオプションに関するエラー
type OptionError struct {
	Option rune
}

func (this OptionError) Error() string {
	return fmt.Sprintf("-%c: No such option", this.Option)
}

// ls 機能のエントリ:引数をオプションとパスに分離する
func Main(args []string, out io.Writer) error {
	flag := 0
	paths := make([]string, 0)
	for _, arg := range args {
		if strings.HasPrefix(arg, "-") {
			for _, o := range arg[1:] {
				setter, ok := option[o]
				if !ok {
					return OptionError{Option: o}
				}
				if err := setter(&flag); err != nil {
					return err
				}
			}
		} else {
			paths = append(paths, arg)
		}
	}
	if (flag & O_HELP) != 0 {
		message := make([]byte, 0, 80)
		message = append(message, "Usage: ls [-"...)
		for optKey, _ := range option {
			message = append(message, byte(optKey))
		}
		message = append(message, "] [PATH(s)]..."...)
		return errors.New(string(message))
	}
	if _, ok := out.(io.Closer); ok {
		// output is a not ansicolor instance.
		flag &^= O_COLOR
	}
	if (flag & O_COLOR) != 0 {
		io.WriteString(out, ANSI_END)
	}
	return lsCore(paths, flag, out)
}

// vim:set fenc=utf8 ts=4 sw=4 noet:
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"syscall"

	"../conio"
	"../dos"
	. "../interpreter"
)

func cmd_mkdir(cmd *Interpreter) (ErrorLevel, error) {
	if len(cmd.Args) <= 1 {
		fmt.Println("Usage: mkdir [/s] DIRECTORIES...")
		return NOERROR, nil
	}
	var errorcount ErrorLevel = 0
	for _, arg1 := range cmd.Args[1:] {
		err := syscall.Mkdir(arg1, 0777)
		if err != nil {
			fmt.Fprintf(cmd.Stderr, "%s: %s\n", arg1, err)
			errorcount++
		}
	}
	return errorcount, nil
}

func cmd_rmdir(cmd *Interpreter) (ErrorLevel, error) {
	if len(cmd.Args) <= 1 {
		fmt.Println("Usage: rmdir [/s] DIRECTORIES...")
		return NOERROR, nil
	}
	s_option := false
	message := "%s: Rmdir Are you sure? [Yes/No/Quit] "
	errorcount := ErrorLevel(0)
	for _, arg1 := range cmd.Args[1:] {
		if arg1 == "/s" {
			s_option = true
			message = "%s : Delete Tree. Are you sure? [Yes/No/Quit] "
			continue
		}
		fmt.Fprintf(cmd.Stderr, message, arg1)
		ch := conio.GetCh()
		fmt.Fprintf(cmd.Stderr, "%c ", ch)
		switch ch {
		case 'y', 'Y':

		case 'q', 'Q':
			fmt.Fprintln(cmd.Stderr, "-> canceled all")
			return errorcount, nil
		default:
			fmt.Fprintln(cmd.Stderr, "-> canceled")
			continue
		}
		var err error
		if s_option {
			fmt.Fprintln(cmd.Stdout)
			err = dos.Truncate(arg1, func(path string, err error) bool {
				fmt.Fprintf(cmd.Stderr, "%s -> %s\n", path, err.Error())
				return true
			}, cmd.Stdout)
		} else {
			err = syscall.Rmdir(arg1)
		}
		if err != nil {
			fmt.Fprintf(cmd.Stderr, "-> %s\n", err.Error())
			errorcount++
		} else {
			fmt.Fprintln(cmd.Stderr, "-> done.")
		}
	}
	return errorcount, nil
}
<commit_msg>Fixed #81 No errors are reported on rmdir NOT directory before prompt<commit_after>package commands

import (
	"fmt"
	"os"
	"syscall"

	"../conio"
	"../dos"
	. "../interpreter"
)

func cmd_mkdir(cmd *Interpreter) (ErrorLevel, error) {
	if len(cmd.Args) <= 1 {
		fmt.Println("Usage: mkdir [/s] DIRECTORIES...")
		return NOERROR, nil
	}
	var errorcount ErrorLevel = 0
	for _, arg1 := range cmd.Args[1:] {
		err := syscall.Mkdir(arg1, 0777)
		if err != nil {
			fmt.Fprintf(cmd.Stderr, "%s: %s\n", arg1, err)
			errorcount++
		}
	}
	return errorcount, nil
}

func cmd_rmdir(cmd *Interpreter) (ErrorLevel, error) {
	if len(cmd.Args) <= 1 {
		fmt.Println("Usage: rmdir [/s] DIRECTORIES...")
		return NOERROR, nil
	}
	s_option := false
	message := "%s: Rmdir Are you sure? [Yes/No/Quit] "
	errorcount := ErrorLevel(0)
	for _, arg1 := range cmd.Args[1:] {
		if arg1 == "/s" {
			s_option = true
			message = "%s : Delete Tree. Are you sure? [Yes/No/Quit] "
			continue
		}
		stat, statErr := os.Lstat(arg1)
		if statErr != nil {
			fmt.Fprintf(cmd.Stderr, "%s: %s\n", arg1, statErr)
			errorcount++
			continue
		}
		if !stat.IsDir() {
			fmt.Fprintf(cmd.Stderr, "%s: not directory\n", arg1)
			errorcount++
			continue
		}
		fmt.Fprintf(cmd.Stderr, message, arg1)
		ch := conio.GetCh()
		fmt.Fprintf(cmd.Stderr, "%c ", ch)
		switch ch {
		case 'y', 'Y':

		case 'q', 'Q':
			fmt.Fprintln(cmd.Stderr, "-> canceled all")
			return errorcount, nil
		default:
			fmt.Fprintln(cmd.Stderr, "-> canceled")
			continue
		}
		var err error
		if s_option {
			fmt.Fprintln(cmd.Stdout)
			err = dos.Truncate(arg1, func(path string, err error) bool {
				fmt.Fprintf(cmd.Stderr, "%s -> %s\n", path, err.Error())
				return true
			}, cmd.Stdout)
		} else {
			err = syscall.Rmdir(arg1)
		}
		if err != nil {
			fmt.Fprintf(cmd.Stderr, "-> %s\n", err.Error())
			errorcount++
		} else {
			fmt.Fprintln(cmd.Stderr, "-> done.")
		}
	}
	return errorcount, nil
}
<|endoftext|>


<commit_before>package bsw

const VERSION = "2.0.2"
<commit_msg>Bump version<commit_after>package bsw

const VERSION = "2.0.3"
<|endoftext|>


<commit_before>package gcng

import (
	"net/http"

	"code.cloudfoundry.org/lager"
	"github.com/concourse/atc/dbng"
	"github.com/concourse/atc/worker/transport"
	"github.com/concourse/baggageclaim"
	bclient "github.com/concourse/baggageclaim/client"
)

type volumeCollector struct {
	rootLogger                lager.Logger
	volumeFactory             dbng.VolumeFactory
	baggageclaimClientFactory BaggageclaimClientFactory
}

//go:generate counterfeiter . BaggageclaimClientFactory

type BaggageclaimClientFactory interface {
	NewClient(apiURL string, workerName string) bclient.Client
}

type baggageclaimClientFactory struct {
	dbWorkerFactory dbng.WorkerFactory
}

func NewBaggageclaimClientFactory(dbWorkerFactory dbng.WorkerFactory) BaggageclaimClientFactory {
	return &baggageclaimClientFactory{
		dbWorkerFactory: dbWorkerFactory,
	}
}

func (f *baggageclaimClientFactory) NewClient(apiURL string, workerName string) bclient.Client {
	rountTripper := transport.NewBaggageclaimRoundTripper(
		workerName,
		&apiURL,
		f.dbWorkerFactory,
		&http.Transport{DisableKeepAlives: true},
	)
	return bclient.New(apiURL, rountTripper)
}

func NewVolumeCollector(
	logger lager.Logger,
	volumeFactory dbng.VolumeFactory,
	baggageclaimClientFactory BaggageclaimClientFactory,
) Collector {
	return &volumeCollector{
		rootLogger:                logger,
		volumeFactory:             volumeFactory,
		baggageclaimClientFactory: baggageclaimClientFactory,
	}
}

func (vc *volumeCollector) Run() error {
	logger := vc.rootLogger.Session("run")

	logger.Debug("start")
	defer logger.Debug("done")

	var (
		creatingVolumes   []dbng.CreatingVolume
		createdVolumes    []dbng.CreatedVolume
		destroyingVolumes []dbng.DestroyingVolume
		err               error
	)

	creatingVolumes, createdVolumes, destroyingVolumes, err = vc.volumeFactory.GetDuplicateResourceCacheVolumes()
	if err != nil {
		logger.Error("failed-to-get-duplicate-resource-cache-volumes", err)
		return err
	}

	if len(creatingVolumes) > 0 || len(createdVolumes) > 0 || len(destroyingVolumes) > 0 {
		logger.Debug("found-duplicate-resource-cache-volumes", lager.Data{
			"creating":   len(creatingVolumes),
			"created":    len(createdVolumes),
			"destroying": len(destroyingVolumes),
		})
	}

	orphanedCreatedVolumes, orphanedDestroyingVolumes, err := vc.volumeFactory.GetOrphanedVolumes()
	if err != nil {
		logger.Error("failed-to-get-orphaned-volumes", err)
		return err
	}

	if len(orphanedCreatedVolumes) > 0 || len(orphanedDestroyingVolumes) > 0 {
		logger.Debug("found-orphaned-volumes", lager.Data{
			"created":    len(createdVolumes),
			"destroying": len(destroyingVolumes),
		})
	}

	createdVolumes = append(createdVolumes, orphanedCreatedVolumes...)
	destroyingVolumes = append(destroyingVolumes, orphanedDestroyingVolumes...)

	for _, creatingVolume := range creatingVolumes {
		vLog := logger.Session("mark-creating-as-created", lager.Data{
			"volume": creatingVolume.Handle(),
		})

		createdVolume, err := creatingVolume.Created()
		if err != nil {
			vLog.Error("failed-to-transition-from-creating-to-created", err)
			continue
		}

		createdVolumes = append(createdVolumes, createdVolume)
	}

	for _, createdVolume := range createdVolumes {
		vLog := logger.Session("mark-created-as-destroying", lager.Data{
			"volume": createdVolume.Handle(),
			"worker": createdVolume.Worker().Name,
		})

		destroyingVolume, err := createdVolume.Destroying()
		if err != nil {
			vLog.Error("failed-to-transition", err)
			continue
		}

		destroyingVolumes = append(destroyingVolumes, destroyingVolume)
	}

	for _, destroyingVolume := range destroyingVolumes {
		vLog := logger.Session("destroy", lager.Data{
			"handle": destroyingVolume.Handle(),
			"worker": destroyingVolume.Worker().Name,
		})

		if destroyingVolume.Worker().BaggageclaimURL == nil {
			vLog.Info("baggageclaim-url-is-missing")
			continue
		}

		baggageclaimClient := vc.baggageclaimClientFactory.NewClient(*destroyingVolume.Worker().BaggageclaimURL, destroyingVolume.Worker().Name)

		volume, found, err := baggageclaimClient.LookupVolume(vLog, destroyingVolume.Handle())
		if err != nil {
			vLog.Error("failed-to-lookup-volume-in-baggageclaim", err)
			continue
		}

		if vc.destroyRealVolume(vLog.Session("in-worker"), volume, found) {
			vc.destroyDBVolume(vLog.Session("in-db"), destroyingVolume)
		}
	}

	return nil
}

func (vc *volumeCollector) destroyRealVolume(logger lager.Logger, volume baggageclaim.Volume, found bool) bool {
	if found {
		logger.Debug("destroying")

		err := volume.Destroy()
		if err != nil {
			logger.Error("failed-to-destroy", err)
			return false
		}

		logger.Debug("destroyed")
	} else {
		logger.Debug("already-removed")
	}

	return true
}

func (vc *volumeCollector) destroyDBVolume(logger lager.Logger, dbVolume dbng.DestroyingVolume) {
	logger.Debug("destroying")

	destroyed, err := dbVolume.Destroy()
	if err != nil {
		logger.Error("failed-to-destroy", err)
		return
	}

	if !destroyed {
		logger.Info("could-not-destroy")
		return
	}

	logger.Debug("destroyed")
}
<commit_msg>fix log data<commit_after>package gcng

import (
	"net/http"

	"code.cloudfoundry.org/lager"
	"github.com/concourse/atc/dbng"
	"github.com/concourse/atc/worker/transport"
	"github.com/concourse/baggageclaim"
	bclient "github.com/concourse/baggageclaim/client"
)

type volumeCollector struct {
	rootLogger                lager.Logger
	volumeFactory             dbng.VolumeFactory
	baggageclaimClientFactory BaggageclaimClientFactory
}

//go:generate counterfeiter . BaggageclaimClientFactory

type BaggageclaimClientFactory interface {
	NewClient(apiURL string, workerName string) bclient.Client
}

type baggageclaimClientFactory struct {
	dbWorkerFactory dbng.WorkerFactory
}

func NewBaggageclaimClientFactory(dbWorkerFactory dbng.WorkerFactory) BaggageclaimClientFactory {
	return &baggageclaimClientFactory{
		dbWorkerFactory: dbWorkerFactory,
	}
}

func (f *baggageclaimClientFactory) NewClient(apiURL string, workerName string) bclient.Client {
	rountTripper := transport.NewBaggageclaimRoundTripper(
		workerName,
		&apiURL,
		f.dbWorkerFactory,
		&http.Transport{DisableKeepAlives: true},
	)
	return bclient.New(apiURL, rountTripper)
}

func NewVolumeCollector(
	logger lager.Logger,
	volumeFactory dbng.VolumeFactory,
	baggageclaimClientFactory BaggageclaimClientFactory,
) Collector {
	return &volumeCollector{
		rootLogger:                logger,
		volumeFactory:             volumeFactory,
		baggageclaimClientFactory: baggageclaimClientFactory,
	}
}

func (vc *volumeCollector) Run() error {
	logger := vc.rootLogger.Session("run")

	logger.Debug("start")
	defer logger.Debug("done")

	var (
		creatingVolumes   []dbng.CreatingVolume
		createdVolumes    []dbng.CreatedVolume
		destroyingVolumes []dbng.DestroyingVolume
		err               error
	)

	creatingVolumes, createdVolumes, destroyingVolumes, err = vc.volumeFactory.GetDuplicateResourceCacheVolumes()
	if err != nil {
		logger.Error("failed-to-get-duplicate-resource-cache-volumes", err)
		return err
	}

	if len(creatingVolumes) > 0 || len(createdVolumes) > 0 || len(destroyingVolumes) > 0 {
		logger.Debug("found-duplicate-resource-cache-volumes", lager.Data{
			"creating":   len(creatingVolumes),
			"created":    len(createdVolumes),
			"destroying": len(destroyingVolumes),
		})
	}

	orphanedCreatedVolumes, orphanedDestroyingVolumes, err := vc.volumeFactory.GetOrphanedVolumes()
	if err != nil {
		logger.Error("failed-to-get-orphaned-volumes", err)
		return err
	}

	if len(orphanedCreatedVolumes) > 0 || len(orphanedDestroyingVolumes) > 0 {
		logger.Debug("found-orphaned-volumes", lager.Data{
			"created":    len(orphanedCreatedVolumes),
			"destroying": len(orphanedDestroyingVolumes),
		})
	}

	createdVolumes = append(createdVolumes, orphanedCreatedVolumes...)
	destroyingVolumes = append(destroyingVolumes, orphanedDestroyingVolumes...)

	for _, creatingVolume := range creatingVolumes {
		vLog := logger.Session("mark-creating-as-created", lager.Data{
			"volume": creatingVolume.Handle(),
		})

		createdVolume, err := creatingVolume.Created()
		if err != nil {
			vLog.Error("failed-to-transition-from-creating-to-created", err)
			continue
		}

		createdVolumes = append(createdVolumes, createdVolume)
	}

	for _, createdVolume := range createdVolumes {
		vLog := logger.Session("mark-created-as-destroying", lager.Data{
			"volume": createdVolume.Handle(),
			"worker": createdVolume.Worker().Name,
		})

		destroyingVolume, err := createdVolume.Destroying()
		if err != nil {
			vLog.Error("failed-to-transition", err)
			continue
		}

		destroyingVolumes = append(destroyingVolumes, destroyingVolume)
	}

	for _, destroyingVolume := range destroyingVolumes {
		vLog := logger.Session("destroy", lager.Data{
			"handle": destroyingVolume.Handle(),
			"worker": destroyingVolume.Worker().Name,
		})

		if destroyingVolume.Worker().BaggageclaimURL == nil {
			vLog.Info("baggageclaim-url-is-missing")
			continue
		}

		baggageclaimClient := vc.baggageclaimClientFactory.NewClient(*destroyingVolume.Worker().BaggageclaimURL, destroyingVolume.Worker().Name)

		volume, found, err := baggageclaimClient.LookupVolume(vLog, destroyingVolume.Handle())
		if err != nil {
			vLog.Error("failed-to-lookup-volume-in-baggageclaim", err)
			continue
		}

		if vc.destroyRealVolume(vLog.Session("in-worker"), volume, found) {
			vc.destroyDBVolume(vLog.Session("in-db"), destroyingVolume)
		}
	}

	return nil
}

func (vc *volumeCollector) destroyRealVolume(logger lager.Logger, volume baggageclaim.Volume, found bool) bool {
	if found {
		logger.Debug("destroying")

		err := volume.Destroy()
		if err != nil {
			logger.Error("failed-to-destroy", err)
			return false
		}

		logger.Debug("destroyed")
	} else {
		logger.Debug("already-removed")
	}

	return true
}

func (vc *volumeCollector) destroyDBVolume(logger lager.Logger, dbVolume dbng.DestroyingVolume) {
	logger.Debug("destroying")

	destroyed, err := dbVolume.Destroy()
	if err != nil {
		logger.Error("failed-to-destroy", err)
		return
	}

	if !destroyed {
		logger.Info("could-not-destroy")
		return
	}

	logger.Debug("destroyed")
}
<|endoftext|>