<commit_before>package siatest

import (
	"errors"
	"math"
	"strconv"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/node"
	"github.com/NebulousLabs/Sia/node/api/client"
	"github.com/NebulousLabs/Sia/types"
	"github.com/NebulousLabs/fastrand"
)

type (
	// GroupParams is a helper struct to make creating TestGroups easier.
	GroupParams struct {
		hosts   int // number of hosts to create
		renters int // number of renters to create
		miners  int // number of miners to create
	}

	// TestGroup is a group of of TestNodes that are funded, synced and ready
	// for upload, download and mining depending on their configuration
	TestGroup struct {
		nodes   map[*TestNode]struct{}
		hosts   map[*TestNode]struct{}
		renters map[*TestNode]struct{}
		miners  map[*TestNode]struct{}

		dir string
	}
)

var (
	// defaultAllowance is the allowance used for the group's renters
	defaultAllowance = modules.Allowance{
		Funds:       types.SiacoinPrecision.Mul64(1e3),
		Hosts:       5,
		Period:      50,
		RenewWindow: 10,
	}
)

// addStorageFolderToHosts adds a single storage folder to each host
func addStorageFolderToHosts(hosts map[*TestNode]struct{}) error {
	for host := range hosts {
		if err := host.HostStorageFoldersAddPost(host.Dir, 1048576); err != nil {
			return err
		}
	}
	return nil
}

// announceHosts adds storage to each host and announces them to the group
func announceHosts(hosts map[*TestNode]struct{}) error {
	for host := range hosts {
		if err := host.HostAcceptingContractsPost(true); err != nil {
			return err
		}
		if err := host.HostAnnouncePost(); err != nil {
			return err
		}
	}
	return nil
}

// Close closes the group and all its nodes
func (tg *TestGroup) Close() (err error) {
	for n := range tg.nodes {
		err = build.ComposeErrors(err, n.Close())
	}
	return
}

// fullyConnectNodes takes a list of nodes and connects all their gateways
func fullyConnectNodes(nodes []*TestNode) error {
	// Fully connect the nodes
	for i, nodeA := range nodes {
		for _, nodeB := range nodes[i+1:] {
			if err := nodeA.GatewayConnectPost(nodeB.GatewayAddress()); err != nil && err != client.ErrPeerExists {
				return build.ExtendErr("failed to connect to peer", err)
			}
		}
	}
	return nil
}

// fundNodes uses the funds of a miner node to fund all the nodes of the group
func fundNodes(miner *TestNode, nodes map[*TestNode]struct{}) error {
	// Fund all the nodes equally
	wg, err := miner.WalletGet()
	if err != nil {
		return err
	}
	// Calculate the funding for each node.
	// Add +1 to avoid rounding errors that might lead to insufficient funds.
	funding := wg.ConfirmedSiacoinBalance.Div64(uint64(len(nodes)) + 1)
	// Prepare the transaction outputs
	scos := make([]types.SiacoinOutput, 0, len(nodes))
	for node := range nodes {
		wag, err := node.WalletAddressGet()
		if err != nil {
			return err
		}
		scos = append(scos, types.SiacoinOutput{
			Value:      funding,
			UnlockHash: wag.Address,
		})
	}
	// Send the coins to the outputs
	_, err = miner.WalletSiacoinsMultiPost(scos)
	if err != nil {
		return err
	}
	// Mine the transaction
	if err := miner.MineBlock(); err != nil {
		return err
	}
	return nil
}

// hostsInRenterDBCheck makes sure that all the renters see numHosts hosts in
// their database.
func hostsInRenterDBCheck(miner *TestNode, renters map[*TestNode]struct{}, numHosts int) error {
	for renter := range renters {
		err := Retry(100, 100*time.Millisecond, func() error {
			hdag, err := renter.HostDbActiveGet()
			if err != nil {
				return err
			}
			if len(hdag.Hosts) != numHosts {
				if err := miner.MineBlock(); err != nil {
					return err
				}
				return errors.New("renter doesn't have enough active hosts in hostdb")
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// NewGroup creates a group of TestNodes from node params. All the nodes will
// be connected, synced and funded. Hosts nodes are also announced.
func NewGroup(nodeParams ...node.NodeParams) (*TestGroup, error) {
	// Create and init group
	tg := &TestGroup{
		nodes:   make(map[*TestNode]struct{}),
		hosts:   make(map[*TestNode]struct{}),
		renters: make(map[*TestNode]struct{}),
		miners:  make(map[*TestNode]struct{}),
	}

	// Create node and add it to the correct groups
	nodes := make([]*TestNode, 0, len(nodeParams))
	for _, np := range nodeParams {
		node, err := NewCleanNode(np)
		if err != nil {
			return nil, err
		}
		// Add node to nodes
		tg.nodes[node] = struct{}{}
		nodes = append(nodes, node)
		// Add node to hosts
		if np.Host != nil || np.CreateHost {
			tg.hosts[node] = struct{}{}
		}
		// Add node to renters
		if np.Renter != nil || np.CreateRenter {
			tg.renters[node] = struct{}{}
		}
		// Add node to miners
		if np.Miner != nil || np.CreateMiner {
			tg.miners[node] = struct{}{}
		}
	}

	// Fully connect nodes
	if err := fullyConnectNodes(nodes); err != nil {
		return nil, err
	}

	// Get a miner and mine some blocks to generate coins
	if len(tg.miners) == 0 {
		return nil, errors.New("cannot fund group without miners")
	}
	miner := tg.Miners()[0]
	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
		if err := miner.MineBlock(); err != nil {
			return nil, err
		}
	}

	// Fund nodes
	if err := fundNodes(miner, tg.nodes); err != nil {
		return nil, err
	}
	// Set renter allowances
	if err := setRenterAllowances(tg.renters); err != nil {
		return nil, err
	}
	// Add storage to hosts
	if err := addStorageFolderToHosts(tg.hosts); err != nil {
		return nil, err
	}
	// Announce hosts
	if err := announceHosts(tg.hosts); err != nil {
		return nil, err
	}
	// Mine a block to get the announcements confirmed
	if err := miner.MineBlock(); err != nil {
		return nil, err
	}
	// Block until all hosts show up as active in the renters' hostdbs
	if err := hostsInRenterDBCheck(miner, tg.renters, len(tg.hosts)); err != nil {
		return nil, err
	}
	// Wait for all the renters to form contracts
	if err := waitForContracts(miner, tg.renters, tg.hosts); err != nil {
		return nil, err
	}
	// Make sure all nodes are synced
	if err := synchronizationCheck(miner, tg.nodes); err != nil {
		return nil, err
	}
	return tg, nil
}

// setRenterAllowances sets the allowance of each renter
func setRenterAllowances(renters map[*TestNode]struct{}) error {
	for renter := range renters {
		if err := renter.RenterPost(defaultAllowance); err != nil {
			return err
		}
	}
	return nil
}

// NewGroupFromTemplate will create hosts, renters and miners according to the
// settings in groupParams.
func NewGroupFromTemplate(groupParams GroupParams) (*TestGroup, error) {
	var params []node.NodeParams
	// Create host params
	for i := 0; i < groupParams.hosts; i++ {
		params = append(params, node.Host(randomDir()))
	}
	// Create renter params
	for i := 0; i < groupParams.renters; i++ {
		params = append(params, node.Renter(randomDir()))
	}
	// Create miner params
	for i := 0; i < groupParams.miners; i++ {
		params = append(params, Miner(randomDir()))
	}
	return NewGroup(params...)
}

// randomDir is a helper functions that returns a random directory path
func randomDir() string {
	dir, err := TestDir(strconv.Itoa(fastrand.Intn(math.MaxInt32)))
	if err != nil {
		panic(build.ExtendErr("failed to create testing directory", err))
	}
	return dir
}

// synchronizationCheck makes sure that all the nodes are synced and follow the
func synchronizationCheck(miner *TestNode, nodes map[*TestNode]struct{}) error {
	mcg, err := miner.ConsensusGet()
	if err != nil {
		return err
	}
	for node := range nodes {
		err := Retry(100, 100*time.Millisecond, func() error {
			ncg, err := node.ConsensusGet()
			if err != nil {
				return err
			}
			if mcg.CurrentBlock != ncg.CurrentBlock {
				return errors.New("the node's current block doesn't equal the miner's")
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// mapToSlice converts a map of TestNodes into a slice
func mapToSlice(m map[*TestNode]struct{}) []*TestNode {
	tns := make([]*TestNode, 0, len(m))
	for tn := range m {
		tns = append(tns, tn)
	}
	return tns
}

// waitForContracts waits until the renters have formed contracts with the
// hosts in the group.
func waitForContracts(miner *TestNode, renters map[*TestNode]struct{}, hosts map[*TestNode]struct{}) error {
	expectedContracts := defaultAllowance.Hosts
	if uint64(len(hosts)) < expectedContracts {
		expectedContracts = uint64(len(hosts))
	}
	for renter := range renters {
		numRetries := 0
		err := build.Retry(1000, 100*time.Millisecond, func() error {
			if numRetries%10 == 0 {
				if err := miner.MineBlock(); err != nil {
					return err
				}
			}
			rc, err := renter.RenterContractsGet()
			if err != nil {
				return err
			}
			if uint64(len(rc.Contracts)) < expectedContracts {
				return errors.New("Renter hasn't formed enough contracts")
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// Nodes returns all the nodes of the group
func (tg *TestGroup) Nodes() []*TestNode {
	return mapToSlice(tg.nodes)
}

// Hosts returns all the hosts of the group
func (tg *TestGroup) Hosts() []*TestNode {
	return mapToSlice(tg.hosts)
}

// Renters returns all the renters of the group
func (tg *TestGroup) Renters() []*TestNode {
	return mapToSlice(tg.renters)
}

// Miners returns all the miners of the group
func (tg *TestGroup) Miners() []*TestNode {
	return mapToSlice(tg.miners)
}
<commit_msg>add debug outputs and speed up adding a storage folder<commit_after>package siatest

import (
	"errors"
	"log"
	"math"
	"strconv"
	"sync"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/node"
	"github.com/NebulousLabs/Sia/node/api/client"
	"github.com/NebulousLabs/Sia/types"
	"github.com/NebulousLabs/fastrand"
)

type (
	// GroupParams is a helper struct to make creating TestGroups easier.
	GroupParams struct {
		hosts   int // number of hosts to create
		renters int // number of renters to create
		miners  int // number of miners to create
	}

	// TestGroup is a group of of TestNodes that are funded, synced and ready
	// for upload, download and mining depending on their configuration
	TestGroup struct {
		nodes   map[*TestNode]struct{}
		hosts   map[*TestNode]struct{}
		renters map[*TestNode]struct{}
		miners  map[*TestNode]struct{}

		dir string
	}
)

var (
	// defaultAllowance is the allowance used for the group's renters
	defaultAllowance = modules.Allowance{
		Funds:       types.SiacoinPrecision.Mul64(1e3),
		Hosts:       5,
		Period:      50,
		RenewWindow: 10,
	}
)

// addStorageFolderToHosts adds a single storage folder to each host.
func addStorageFolderToHosts(hosts map[*TestNode]struct{}) error {
	errors := make([]error, len(hosts))
	wg := new(sync.WaitGroup)
	i := 0
	// The following api call is very slow. Using multiple threads speeds that
	// process up a lot.
	for host := range hosts {
		wg.Add(1)
		go func(i int, host *TestNode) {
			errors[i] = host.HostStorageFoldersAddPost(host.Dir, 1048576)
			wg.Done()
		}(i, host)
		i++
	}
	wg.Wait()
	return build.ComposeErrors(errors...)
}

// announceHosts adds storage to each host and announces them to the group
func announceHosts(hosts map[*TestNode]struct{}) error {
	for host := range hosts {
		if err := host.HostAcceptingContractsPost(true); err != nil {
			return err
		}
		if err := host.HostAnnouncePost(); err != nil {
			return err
		}
	}
	return nil
}

// Close closes the group and all its nodes
func (tg *TestGroup) Close() (err error) {
	for n := range tg.nodes {
		err = build.ComposeErrors(err, n.Close())
	}
	return
}

// fullyConnectNodes takes a list of nodes and connects all their gateways
func fullyConnectNodes(nodes []*TestNode) error {
	// Fully connect the nodes
	for i, nodeA := range nodes {
		for _, nodeB := range nodes[i+1:] {
			if err := nodeA.GatewayConnectPost(nodeB.GatewayAddress()); err != nil && err != client.ErrPeerExists {
				return build.ExtendErr("failed to connect to peer", err)
			}
		}
	}
	return nil
}

// fundNodes uses the funds of a miner node to fund all the nodes of the group
func fundNodes(miner *TestNode, nodes map[*TestNode]struct{}) error {
	// Fund all the nodes equally
	wg, err := miner.WalletGet()
	if err != nil {
		return err
	}
	// Calculate the funding for each node.
	// Add +1 to avoid rounding errors that might lead to insufficient funds.
	funding := wg.ConfirmedSiacoinBalance.Div64(uint64(len(nodes)) + 1)
	// Prepare the transaction outputs
	scos := make([]types.SiacoinOutput, 0, len(nodes))
	for node := range nodes {
		wag, err := node.WalletAddressGet()
		if err != nil {
			return err
		}
		scos = append(scos, types.SiacoinOutput{
			Value:      funding,
			UnlockHash: wag.Address,
		})
	}
	// Send the coins to the outputs
	_, err = miner.WalletSiacoinsMultiPost(scos)
	if err != nil {
		return err
	}
	// Mine the transaction
	if err := miner.MineBlock(); err != nil {
		return err
	}
	return nil
}

// hostsInRenterDBCheck makes sure that all the renters see numHosts hosts in
// their database.
func hostsInRenterDBCheck(miner *TestNode, renters map[*TestNode]struct{}, numHosts int) error {
	for renter := range renters {
		err := Retry(100, 100*time.Millisecond, func() error {
			hdag, err := renter.HostDbActiveGet()
			if err != nil {
				return err
			}
			if len(hdag.Hosts) != numHosts {
				if err := miner.MineBlock(); err != nil {
					return err
				}
				return errors.New("renter doesn't have enough active hosts in hostdb")
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// NewGroup creates a group of TestNodes from node params. All the nodes will
// be connected, synced and funded. Hosts nodes are also announced.
func NewGroup(nodeParams ...node.NodeParams) (*TestGroup, error) {
	// Create and init group
	tg := &TestGroup{
		nodes:   make(map[*TestNode]struct{}),
		hosts:   make(map[*TestNode]struct{}),
		renters: make(map[*TestNode]struct{}),
		miners:  make(map[*TestNode]struct{}),
	}

	log.Println("creating")
	// Create node and add it to the correct groups
	nodes := make([]*TestNode, 0, len(nodeParams))
	for _, np := range nodeParams {
		node, err := NewCleanNode(np)
		if err != nil {
			return nil, err
		}
		// Add node to nodes
		tg.nodes[node] = struct{}{}
		nodes = append(nodes, node)
		// Add node to hosts
		if np.Host != nil || np.CreateHost {
			tg.hosts[node] = struct{}{}
		}
		// Add node to renters
		if np.Renter != nil || np.CreateRenter {
			tg.renters[node] = struct{}{}
		}
		// Add node to miners
		if np.Miner != nil || np.CreateMiner {
			tg.miners[node] = struct{}{}
		}
	}

	log.Println("fully connect")
	// Fully connect nodes
	if err := fullyConnectNodes(nodes); err != nil {
		return nil, err
	}

	log.Println("mine blocks")
	// Get a miner and mine some blocks to generate coins
	if len(tg.miners) == 0 {
		return nil, errors.New("cannot fund group without miners")
	}
	miner := tg.Miners()[0]
	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
		if err := miner.MineBlock(); err != nil {
			return nil, err
		}
	}
	log.Println("fund nodes")
	// Fund nodes
	if err := fundNodes(miner, tg.nodes); err != nil {
		return nil, err
	}
	log.Println("set allowances")
	// Set renter allowances
	if err := setRenterAllowances(tg.renters); err != nil {
		return nil, err
	}
	log.Println("add storage")
	// Add storage to hosts
	if err := addStorageFolderToHosts(tg.hosts); err != nil {
		return nil, err
	}
	log.Println("announce")
	// Announce hosts
	if err := announceHosts(tg.hosts); err != nil {
		return nil, err
	}
	// Mine a block to get the announcements confirmed
	if err := miner.MineBlock(); err != nil {
		return nil, err
	}
	log.Println("renterdb check")
	// Block until all hosts show up as active in the renters' hostdbs
	if err := hostsInRenterDBCheck(miner, tg.renters, len(tg.hosts)); err != nil {
		return nil, err
	}
	log.Println("wait contracts")
	// Wait for all the renters to form contracts
	if err := waitForContracts(miner, tg.renters, tg.hosts); err != nil {
		return nil, err
	}
	log.Println("wait sync")
	// Make sure all nodes are synced
	if err := synchronizationCheck(miner, tg.nodes); err != nil {
		return nil, err
	}
	log.Println("done")
	return tg, nil
}

// setRenterAllowances sets the allowance of each renter
func setRenterAllowances(renters map[*TestNode]struct{}) error {
	for renter := range renters {
		if err := renter.RenterPost(defaultAllowance); err != nil {
			return err
		}
	}
	return nil
}

// NewGroupFromTemplate will create hosts, renters and miners according to the
// settings in groupParams.
func NewGroupFromTemplate(groupParams GroupParams) (*TestGroup, error) {
	var params []node.NodeParams
	// Create host params
	for i := 0; i < groupParams.hosts; i++ {
		params = append(params, node.Host(randomDir()))
	}
	// Create renter params
	for i := 0; i < groupParams.renters; i++ {
		params = append(params, node.Renter(randomDir()))
	}
	// Create miner params
	for i := 0; i < groupParams.miners; i++ {
		params = append(params, Miner(randomDir()))
	}
	return NewGroup(params...)
}

// randomDir is a helper functions that returns a random directory path
func randomDir() string {
	dir, err := TestDir(strconv.Itoa(fastrand.Intn(math.MaxInt32)))
	if err != nil {
		panic(build.ExtendErr("failed to create testing directory", err))
	}
	return dir
}

// synchronizationCheck makes sure that all the nodes are synced and follow the
func synchronizationCheck(miner *TestNode, nodes map[*TestNode]struct{}) error {
	mcg, err := miner.ConsensusGet()
	if err != nil {
		return err
	}
	for node := range nodes {
		err := Retry(100, 100*time.Millisecond, func() error {
			ncg, err := node.ConsensusGet()
			if err != nil {
				return err
			}
			if mcg.CurrentBlock != ncg.CurrentBlock {
				return errors.New("the node's current block doesn't equal the miner's")
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// mapToSlice converts a map of TestNodes into a slice
func mapToSlice(m map[*TestNode]struct{}) []*TestNode {
	tns := make([]*TestNode, 0, len(m))
	for tn := range m {
		tns = append(tns, tn)
	}
	return tns
}

// waitForContracts waits until the renters have formed contracts with the
// hosts in the group.
func waitForContracts(miner *TestNode, renters map[*TestNode]struct{}, hosts map[*TestNode]struct{}) error {
	expectedContracts := defaultAllowance.Hosts
	if uint64(len(hosts)) < expectedContracts {
		expectedContracts = uint64(len(hosts))
	}
	for renter := range renters {
		numRetries := 0
		err := build.Retry(1000, 100*time.Millisecond, func() error {
			if numRetries%10 == 0 {
				if err := miner.MineBlock(); err != nil {
					return err
				}
			}
			rc, err := renter.RenterContractsGet()
			if err != nil {
				return err
			}
			if uint64(len(rc.Contracts)) < expectedContracts {
				return errors.New("Renter hasn't formed enough contracts")
			}
			return nil
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// Nodes returns all the nodes of the group
func (tg *TestGroup) Nodes() []*TestNode {
	return mapToSlice(tg.nodes)
}

// Hosts returns all the hosts of the group
func (tg *TestGroup) Hosts() []*TestNode {
	return mapToSlice(tg.hosts)
}

// Renters returns all the renters of the group
func (tg *TestGroup) Renters() []*TestNode {
	return mapToSlice(tg.renters)
}

// Miners returns all the miners of the group
func (tg *TestGroup) Miners() []*TestNode {
	return mapToSlice(tg.miners)
}
<|endoftext|>


<commit_before>package chroot

import (
	"bytes"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"os"
	"os/exec"
)

// StepMountExtra mounts the attached device.
//
// Produces:
//   mount_path string - The location where the volume was mounted.
type StepMountExtra struct {
	mounts []string
}

func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction {
	config := state["config"].(*Config)
	mountPath := state["mount_path"].(string)
	ui := state["ui"].(packer.Ui)

	s.mounts = make([]string, 0, len(config.ChrootMounts))

	ui.Say("Mounting additional paths within the chroot...")
	for _, mountInfo := range config.ChrootMounts {
		innerPath := mountPath + mountInfo[2]

		if err := os.MkdirAll(innerPath, 0755); err != nil {
			err := fmt.Errorf("Error creating mount directory: %s", err)
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		flags := "-t " + mountInfo[0]
		if mountInfo[0] == "bind" {
			flags = "--bind"
		}

		ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
		stderr := new(bytes.Buffer)
		mountCommand := fmt.Sprintf(
			"%s %s %s %s",
			config.MountCommand,
			flags,
			mountInfo[1],
			innerPath)
		cmd := exec.Command("/bin/sh", "-c", mountCommand)
		cmd.Stderr = stderr
		if err := cmd.Run(); err != nil {
			err := fmt.Errorf(
				"Error mounting: %s\nStderr: %s", err, stderr.String())
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		s.mounts = append(s.mounts, innerPath)
	}

	return multistep.ActionContinue
}

func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
	if s.mounts == nil {
		return
	}

	config := state["config"].(*Config)
	ui := state["ui"].(packer.Ui)

	for _, path := range s.mounts {
		unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path)
		cmd := exec.Command("/bin/sh", "-c", unmountCommand)
		if err := cmd.Run(); err != nil {
			ui.Error(fmt.Sprintf(
				"Error unmounting root device: %s", err))
			return
		}
	}
}
<commit_msg>builder/amazon/chroot: unmount in reverse<commit_after>package chroot

import (
	"bytes"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"os"
	"os/exec"
)

// StepMountExtra mounts the attached device.
//
// Produces:
//   mount_path string - The location where the volume was mounted.
type StepMountExtra struct {
	mounts []string
}

func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction {
	config := state["config"].(*Config)
	mountPath := state["mount_path"].(string)
	ui := state["ui"].(packer.Ui)

	s.mounts = make([]string, 0, len(config.ChrootMounts))

	ui.Say("Mounting additional paths within the chroot...")
	for _, mountInfo := range config.ChrootMounts {
		innerPath := mountPath + mountInfo[2]

		if err := os.MkdirAll(innerPath, 0755); err != nil {
			err := fmt.Errorf("Error creating mount directory: %s", err)
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		flags := "-t " + mountInfo[0]
		if mountInfo[0] == "bind" {
			flags = "--bind"
		}

		ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
		stderr := new(bytes.Buffer)
		mountCommand := fmt.Sprintf(
			"%s %s %s %s",
			config.MountCommand,
			flags,
			mountInfo[1],
			innerPath)
		cmd := exec.Command("/bin/sh", "-c", mountCommand)
		cmd.Stderr = stderr
		if err := cmd.Run(); err != nil {
			err := fmt.Errorf(
				"Error mounting: %s\nStderr: %s", err, stderr.String())
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		s.mounts = append(s.mounts, innerPath)
	}

	return multistep.ActionContinue
}

func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
	if s.mounts == nil {
		return
	}

	config := state["config"].(*Config)
	ui := state["ui"].(packer.Ui)

	for i := len(s.mounts) - 1; i >= 0; i-- {
		path := s.mounts[i]
		unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path)

		stderr := new(bytes.Buffer)
		cmd := exec.Command("/bin/sh", "-c", unmountCommand)
		cmd.Stderr = stderr
		if err := cmd.Run(); err != nil {
			ui.Error(fmt.Sprintf(
				"Error unmounting device: %s\nStderr: %s", err, stderr.String()))
			return
		}
	}
}
<|endoftext|>


<commit_before>package kms

import (
	"context"
	"crypto/sha512"
	"fmt"

	apiv1 "cloud.google.com/go/kms/apiv1"
	"github.com/grafeas/voucher/signer"
	kms_pb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

const (
	AlgoSHA512 = "SHA512"
	APIPath    = "//cloudkms.googleapis.com/v1"
)

type Key struct {
	Path string
	Algo string
}

// Signer is an AttestationSigner that uses Google's Cloud KMS to sign attestations
// Only supports SHA512 digests.
type Signer struct {
	keys   map[string]Key
	client *apiv1.KeyManagementClient
}

func NewSigner(keys map[string]Key) (*Signer, error) {
	client, err := apiv1.NewKeyManagementClient(context.Background())
	if err != nil {
		return nil, err
	}

	return &Signer{
		keys:   keys,
		client: client,
	}, nil
}

func (s *Signer) Sign(checkName, body string) (string, string, error) {
	key, ok := s.keys[checkName]
	if !ok {
		return "", "", signer.ErrNoKeyForCheck
	}

	if key.Algo != AlgoSHA512 {
		return "", "", fmt.Errorf("unable to hash algorithm %q, must be SHA512", key.Algo)
	}

	resp, err := s.client.AsymmetricSign(context.Background(), &kms_pb.AsymmetricSignRequest{
		Name: key.Path,
		Digest: &kms_pb.Digest{
			Digest: &kms_pb.Digest_Sha512{Sha512: sha512Digest([]byte(body))},
		},
	})

	if err != nil {
		return "", "", err
	}

	return string(resp.Signature), fmt.Sprintf(APIPath+"/%v", key.Path), nil
}

// Close closes the KMS signer's connections.
func (s *Signer) Close() error {
	return s.client.Close()
}

func sha512Digest(data []byte) []byte {
	output := make([]byte, sha512.Size)
	sha := sha512.Sum512(data)
	copy(output, sha[:])
	return output
}
<commit_msg>Add sha256 and sha384 support for kms.<commit_after>package kms

import (
	"context"
	"crypto/sha256"
	"crypto/sha512"
	"fmt"
	"hash"

	apiv1 "cloud.google.com/go/kms/apiv1"
	"github.com/grafeas/voucher/signer"
	kms_pb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

const (
	AlgoSHA256 = "SHA256"
	AlgoSHA384 = "SHA384"
	AlgoSHA512 = "SHA512"
	APIPath    = "//cloudkms.googleapis.com/v1"
)

type Key struct {
	Path string
	Algo string
}

// Signer is an AttestationSigner that uses Google's Cloud KMS to sign attestations
// Only supports SHA512 digests.
type Signer struct {
	keys   map[string]Key
	client *apiv1.KeyManagementClient
}

func NewSigner(keys map[string]Key) (*Signer, error) {
	client, err := apiv1.NewKeyManagementClient(context.Background())
	if err != nil {
		return nil, err
	}

	return &Signer{
		keys:   keys,
		client: client,
	}, nil
}

func (s *Signer) Sign(checkName, body string) (string, string, error) {
	key, ok := s.keys[checkName]
	if !ok {
		return "", "", signer.ErrNoKeyForCheck
	}

	var digest hash.Hash
	switch key.Algo {
	case AlgoSHA256:
		digest = sha256.New()
	case AlgoSHA384:
		digest = sha512.New384()
	case AlgoSHA512:
		digest = sha512.New()
	default:
		return "", "", fmt.Errorf("Unsupported digest algorithm %v", key.Algo)
	}

	if _, err := digest.Write([]byte(body)); err != nil {
		return "", "", err
	}

	var d kms_pb.Digest
	switch key.Algo {
	case AlgoSHA256:
		d.Digest = &kms_pb.Digest_Sha256{
			Sha256: digest.Sum(nil),
		}
	case AlgoSHA384:
		d.Digest = &kms_pb.Digest_Sha384{
			Sha384: digest.Sum(nil),
		}
	case AlgoSHA512:
		d.Digest = &kms_pb.Digest_Sha512{
			Sha512: digest.Sum(nil),
		}
	default:
		return "", "", fmt.Errorf("Unsupported digest algorithm %v", key.Algo)
	}

	resp, err := s.client.AsymmetricSign(context.Background(), &kms_pb.AsymmetricSignRequest{
		Name: key.Path,
		Digest: &d,
	})

	if err != nil {
		return "", "", err
	}

	return string(resp.Signature), fmt.Sprintf(APIPath+"/%v", key.Path), nil
}

// Close closes the KMS signer's connections.
func (s *Signer) Close() error {
	return s.client.Close()
}
<|endoftext|>


<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libfs

import (
	"context"
	"os"
	"path"
	"strings"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
	billy "github.com/src-d/go-billy"
)

// FS is a wrapper around a KBFS subdirectory that implements the
// billy.Filesystem interface.  It uses forward-slash separated paths.
// It may return errors wrapped with the `github.com/pkg/errors`
// package.
type FS struct {
	// Yes, storing ctx in a struct is a mortal sin, but the
	// billy.Filesystem interface doesn't give us a way to accept ctxs
	// any other way.
	ctx      context.Context
	config   libkbfs.Config
	root     libkbfs.Node
	h        *libkbfs.TlfHandle
	log      logger.Logger
	deferLog logger.Logger
}

var _ billy.Filesystem = (*FS)(nil)

// NewFS returns a new FS instance, chroot'd to the given TLF and
// subdir within that TLF.  `subdir` must exist, and point to a
// directory, before this function is called.
func NewFS(ctx context.Context, config libkbfs.Config,
	tlfHandle *libkbfs.TlfHandle, subdir string) (*FS, error) {
	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return nil, err
	}

	// Look up the subdir's root.
	n := rootNode
	var parts []string
	if len(subdir) > 0 {
		parts = strings.Split(subdir, "/")
	}
	for i, p := range parts {
		var ei libkbfs.EntryInfo
		n, ei, err = config.KBFSOps().Lookup(ctx, n, p)
		if err != nil {
			return nil, err
		}
		if ei.Type != libkbfs.Dir {
			return nil, errors.Errorf("%s is not a directory",
				path.Join(parts[:i]...))
		}
	}

	log := config.MakeLogger("")
	log.CDebugf(ctx, "Made new FS for TLF=%s, subdir=%s",
		tlfHandle.GetCanonicalName(), subdir)

	return &FS{
		ctx:      ctx,
		config:   config,
		root:     n,
		h:        tlfHandle,
		log:      log,
		deferLog: log.CloneWithAddedDepth(1),
	}, nil
}

func (fs *FS) lookupOrCreateFile(
	dir libkbfs.Node, filename string, flag int, perm os.FileMode) (
	libkbfs.Node, libkbfs.EntryInfo, error) {
	n, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, dir, filename)
	switch errors.Cause(err).(type) {
	case libkbfs.NoSuchNameError:
		// The file doesn't exist yet; create if requested
		if flag&os.O_CREATE == 0 {
			return nil, libkbfs.EntryInfo{}, err
		}
		fs.log.CDebugf(
			fs.ctx, "Creating %s since it doesn't exist yet", filename)
		excl := libkbfs.NoExcl
		if flag&os.O_EXCL != 0 {
			excl = libkbfs.WithExcl
		}
		isExec := (perm & 0100) != 0
		n, ei, err = fs.config.KBFSOps().CreateFile(
			fs.ctx, dir, filename, isExec, excl)
		switch errors.Cause(err).(type) {
		case libkbfs.NameExistsError:
			// Someone made it already; recurse to try the lookup again.
			fs.log.CDebugf(
				fs.ctx, "Attempting lookup again after failed create")
			return fs.lookupOrCreateFile(dir, filename, flag, perm)
		case nil:
			return n, ei, nil
		default:
			return nil, libkbfs.EntryInfo{}, err
		}
	case nil:
		// If we were supposed to have exclusively-created this file,
		// we must fail.
		if flag&os.O_CREATE != 0 && flag&os.O_EXCL != 0 {
			return nil, libkbfs.EntryInfo{},
				errors.New("Exclusive create failed because the file exists")
		}

		// Make sure this is a file.
		if !ei.Type.IsFile() {
			return nil, libkbfs.EntryInfo{},
				errors.Errorf("%s is not a file", filename)
		}
		return n, ei, nil
	default:
		return nil, libkbfs.EntryInfo{}, err
	}
}

func (fs *FS) lookupParent(filename string) (
	parent libkbfs.Node, base string, err error) {
	parts := strings.Split(filename, "/")
	n := fs.root
	// Iterate through each of the parent directories of the file, but
	// not the file itself.
	for i := 0; i < len(parts)-1; i++ {
		p := parts[i]
		var ei libkbfs.EntryInfo
		n, ei, err = fs.config.KBFSOps().Lookup(fs.ctx, n, p)
		if err != nil {
			return nil, "", err
		}
		if ei.Type != libkbfs.Dir {
			return nil, "", errors.Errorf("%s is not a directory",
				path.Join(parts[:i]...))
		}
	}

	return n, parts[len(parts)-1], nil
}

// OpenFile implements the billy.Filesystem interface for FS.
func (fs *FS) OpenFile(filename string, flag int, perm os.FileMode) (
	f billy.File, err error) {
	fs.log.CDebugf(
		fs.ctx, "OpenFile %s, flag=%d, perm=%o", filename, flag, perm)
	defer func() {
		fs.deferLog.CDebugf(fs.ctx, "OpenFile done: %+v", err)
	}()

	n, fName, err := fs.lookupParent(filename)
	if err != nil {
		return nil, err
	}

	n, ei, err := fs.lookupOrCreateFile(n, fName, flag, perm)
	if err != nil {
		return nil, err
	}

	if flag&os.O_TRUNC != 0 {
		err := fs.config.KBFSOps().Truncate(fs.ctx, n, 0)
		if err != nil {
			return nil, err
		}
	}

	offset := int64(0)
	if flag&os.O_APPEND != 0 {
		if ei.Size >= uint64(1<<63) {
			return nil, errors.New("offset too large")
		}
		offset = int64(ei.Size)
	}

	return &File{
		fs:       fs,
		filename: filename,
		node:     n,
		readOnly: flag == os.O_RDONLY,
		offset:   offset,
	}, nil
}

// Create implements the billy.Filesystem interface for FS.
func (fs *FS) Create(filename string) (billy.File, error) {
	return fs.OpenFile(filename, os.O_CREATE, 0600)
}

// Open implements the billy.Filesystem interface for FS.
func (fs *FS) Open(filename string) (billy.File, error) {
	return fs.OpenFile(filename, os.O_RDONLY, 0600)
}

// Stat implements the billy.Filesystem interface for FS.
func (fs *FS) Stat(filename string) (fi os.FileInfo, err error) {
	fs.log.CDebugf(fs.ctx, "Stat %s", filename)
	defer func() {
		fs.deferLog.CDebugf(fs.ctx, "Stat done: %+v", err)
	}()

	n, base, err := fs.lookupParent(filename)
	if err != nil {
		return nil, err
	}

	_, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, n, base)
	if err != nil {
		return nil, err
	}

	return &FileInfo{
		fs:       fs,
		ei:       ei,
		fullpath: filename,
	}, nil
}

// Rename implements the billy.Filesystem interface for FS.
func (fs *FS) Rename(oldpath, newpath string) error {
	return nil
}

// Remove implements the billy.Filesystem interface for FS.
func (fs *FS) Remove(filename string) error {
	return nil
}

// Join implements the billy.Filesystem interface for FS.
func (fs *FS) Join(elem ...string) string {
	return path.Clean(path.Join(elem...))
}

// TempFile implements the billy.Filesystem interface for FS.
func (fs *FS) TempFile(dir, prefix string) (billy.File, error) {
	return nil, nil
}

// ReadDir implements the billy.Filesystem interface for FS.
func (fs *FS) ReadDir(path string) (fis []os.FileInfo, err error) {
	fs.log.CDebugf(fs.ctx, "ReadDir %s", path)
	defer func() {
		fs.deferLog.CDebugf(fs.ctx, "ReadDir done: %+v", err)
	}()

	n, base, err := fs.lookupParent(path)
	if err != nil {
		return nil, err
	}

	n, _, err = fs.config.KBFSOps().Lookup(fs.ctx, n, base)
	if err != nil {
		return nil, err
	}

	children, err := fs.config.KBFSOps().GetDirChildren(fs.ctx, n)
	if err != nil {
		return nil, err
	}

	fis = make([]os.FileInfo, 0, len(children))
	for name, ei := range children {
		fis = append(fis, &FileInfo{
			fs:       fs,
			ei:       ei,
			fullpath: path + "/" + name,
		})
	}
	return fis, nil
}

// MkdirAll implements the billy.Filesystem interface for FS.
func (fs *FS) MkdirAll(filename string, perm os.FileMode) error {
	return nil
}

// Lstat implements the billy.Filesystem interface for FS.
func (fs *FS) Lstat(filename string) (os.FileInfo, error) {
	return nil, nil
}

// Symlink implements the billy.Filesystem interface for FS.
func (fs *FS) Symlink(target, link string) error {
	return nil
}

// Readlink implements the billy.Filesystem interface for FS.
func (fs *FS) Readlink(link string) (string, error) {
	return "", nil
}

// Chmod implements the billy.Filesystem interface for FS.
func (fs *FS) Chmod(name string, mode os.FileMode) error {
	return nil
}

// Lchown implements the billy.Filesystem interface for FS.
func (fs *FS) Lchown(name string, uid, gid int) error {
	return nil
}

// Chown implements the billy.Filesystem interface for FS.
func (fs *FS) Chown(name string, uid, gid int) error {
	return nil
}

// Chtimes implements the billy.Filesystem interface for FS.
func (fs *FS) Chtimes(name string, atime time.Time, mtime time.Time) error {
	return nil
}

// Chroot implements the billy.Filesystem interface for FS.
func (fs *FS) Chroot(path string) (billy.Filesystem, error) {
	return nil, nil
}

// Root implements the billy.Filesystem interface for FS.
func (fs *FS) Root() string {
	return ""
}

// SyncAll syncs any outstanding buffered writes to the KBFS journal.
func (fs *FS) SyncAll() error {
	return fs.config.KBFSOps().SyncAll(fs.ctx, fs.root.GetFolderBranch())
}
<commit_msg>libfs: use path join<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libfs

import (
	"context"
	"os"
	"path"
	"strings"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
	billy "github.com/src-d/go-billy"
)

// FS is a wrapper around a KBFS subdirectory that implements the
// billy.Filesystem interface.  It uses forward-slash separated paths.
// It may return errors wrapped with the `github.com/pkg/errors`
// package.
type FS struct {
	// Yes, storing ctx in a struct is a mortal sin, but the
	// billy.Filesystem interface doesn't give us a way to accept ctxs
	// any other way.
	ctx      context.Context
	config   libkbfs.Config
	root     libkbfs.Node
	h        *libkbfs.TlfHandle
	log      logger.Logger
	deferLog logger.Logger
}

var _ billy.Filesystem = (*FS)(nil)

// NewFS returns a new FS instance, chroot'd to the given TLF and
// subdir within that TLF.  `subdir` must exist, and point to a
// directory, before this function is called.
func NewFS(ctx context.Context, config libkbfs.Config,
	tlfHandle *libkbfs.TlfHandle, subdir string) (*FS, error) {
	rootNode, _, err := config.KBFSOps().GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return nil, err
	}

	// Look up the subdir's root.
	n := rootNode
	var parts []string
	if len(subdir) > 0 {
		parts = strings.Split(subdir, "/")
	}
	for i, p := range parts {
		var ei libkbfs.EntryInfo
		n, ei, err = config.KBFSOps().Lookup(ctx, n, p)
		if err != nil {
			return nil, err
		}
		if ei.Type != libkbfs.Dir {
			return nil, errors.Errorf("%s is not a directory",
				path.Join(parts[:i]...))
		}
	}

	log := config.MakeLogger("")
	log.CDebugf(ctx, "Made new FS for TLF=%s, subdir=%s",
		tlfHandle.GetCanonicalName(), subdir)

	return &FS{
		ctx:      ctx,
		config:   config,
		root:     n,
		h:        tlfHandle,
		log:      log,
		deferLog: log.CloneWithAddedDepth(1),
	}, nil
}

func (fs *FS) lookupOrCreateFile(
	dir libkbfs.Node, filename string, flag int, perm os.FileMode) (
	libkbfs.Node, libkbfs.EntryInfo, error) {
	n, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, dir, filename)
	switch errors.Cause(err).(type) {
	case libkbfs.NoSuchNameError:
		// The file doesn't exist yet; create if requested
		if flag&os.O_CREATE == 0 {
			return nil, libkbfs.EntryInfo{}, err
		}
		fs.log.CDebugf(
			fs.ctx, "Creating %s since it doesn't exist yet", filename)
		excl := libkbfs.NoExcl
		if flag&os.O_EXCL != 0 {
			excl = libkbfs.WithExcl
		}
		isExec := (perm & 0100) != 0
		n, ei, err = fs.config.KBFSOps().CreateFile(
			fs.ctx, dir, filename, isExec, excl)
		switch errors.Cause(err).(type) {
		case libkbfs.NameExistsError:
			// Someone made it already; recurse to try the lookup again.
			fs.log.CDebugf(
				fs.ctx, "Attempting lookup again after failed create")
			return fs.lookupOrCreateFile(dir, filename, flag, perm)
		case nil:
			return n, ei, nil
		default:
			return nil, libkbfs.EntryInfo{}, err
		}
	case nil:
		// If we were supposed to have exclusively-created this file,
		// we must fail.
		if flag&os.O_CREATE != 0 && flag&os.O_EXCL != 0 {
			return nil, libkbfs.EntryInfo{},
				errors.New("Exclusive create failed because the file exists")
		}

		// Make sure this is a file.
		if !ei.Type.IsFile() {
			return nil, libkbfs.EntryInfo{},
				errors.Errorf("%s is not a file", filename)
		}
		return n, ei, nil
	default:
		return nil, libkbfs.EntryInfo{}, err
	}
}

func (fs *FS) lookupParent(filename string) (
	parent libkbfs.Node, base string, err error) {
	parts := strings.Split(filename, "/")
	n := fs.root
	// Iterate through each of the parent directories of the file, but
	// not the file itself.
	for i := 0; i < len(parts)-1; i++ {
		p := parts[i]
		var ei libkbfs.EntryInfo
		n, ei, err = fs.config.KBFSOps().Lookup(fs.ctx, n, p)
		if err != nil {
			return nil, "", err
		}
		if ei.Type != libkbfs.Dir {
			return nil, "", errors.Errorf("%s is not a directory",
				path.Join(parts[:i]...))
		}
	}

	return n, parts[len(parts)-1], nil
}

// OpenFile implements the billy.Filesystem interface for FS.
func (fs *FS) OpenFile(filename string, flag int, perm os.FileMode) (
	f billy.File, err error) {
	fs.log.CDebugf(
		fs.ctx, "OpenFile %s, flag=%d, perm=%o", filename, flag, perm)
	defer func() {
		fs.deferLog.CDebugf(fs.ctx, "OpenFile done: %+v", err)
	}()

	n, fName, err := fs.lookupParent(filename)
	if err != nil {
		return nil, err
	}

	n, ei, err := fs.lookupOrCreateFile(n, fName, flag, perm)
	if err != nil {
		return nil, err
	}

	if flag&os.O_TRUNC != 0 {
		err := fs.config.KBFSOps().Truncate(fs.ctx, n, 0)
		if err != nil {
			return nil, err
		}
	}

	offset := int64(0)
	if flag&os.O_APPEND != 0 {
		if ei.Size >= uint64(1<<63) {
			return nil, errors.New("offset too large")
		}
		offset = int64(ei.Size)
	}

	return &File{
		fs:       fs,
		filename: filename,
		node:     n,
		readOnly: flag == os.O_RDONLY,
		offset:   offset,
	}, nil
}

// Create implements the billy.Filesystem interface for FS.
func (fs *FS) Create(filename string) (billy.File, error) {
	return fs.OpenFile(filename, os.O_CREATE, 0600)
}

// Open implements the billy.Filesystem interface for FS.
func (fs *FS) Open(filename string) (billy.File, error) {
	return fs.OpenFile(filename, os.O_RDONLY, 0600)
}

// Stat implements the billy.Filesystem interface for FS.
func (fs *FS) Stat(filename string) (fi os.FileInfo, err error) {
	fs.log.CDebugf(fs.ctx, "Stat %s", filename)
	defer func() {
		fs.deferLog.CDebugf(fs.ctx, "Stat done: %+v", err)
	}()

	n, base, err := fs.lookupParent(filename)
	if err != nil {
		return nil, err
	}

	_, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, n, base)
	if err != nil {
		return nil, err
	}

	return &FileInfo{
		fs:       fs,
		ei:       ei,
		fullpath: filename,
	}, nil
}

// Rename implements the billy.Filesystem interface for FS.
func (fs *FS) Rename(oldpath, newpath string) error {
	return nil
}

// Remove implements the billy.Filesystem interface for FS.
func (fs *FS) Remove(filename string) error {
	return nil
}

// Join implements the billy.Filesystem interface for FS.
func (fs *FS) Join(elem ...string) string {
	return path.Clean(path.Join(elem...))
}

// TempFile implements the billy.Filesystem interface for FS.
func (fs *FS) TempFile(dir, prefix string) (billy.File, error) {
	return nil, nil
}

// ReadDir implements the billy.Filesystem interface for FS.
func (fs *FS) ReadDir(p string) (fis []os.FileInfo, err error) {
	fs.log.CDebugf(fs.ctx, "ReadDir %s", p)
	defer func() {
		fs.deferLog.CDebugf(fs.ctx, "ReadDir done: %+v", err)
	}()

	n, base, err := fs.lookupParent(p)
	if err != nil {
		return nil, err
	}

	n, _, err = fs.config.KBFSOps().Lookup(fs.ctx, n, base)
	if err != nil {
		return nil, err
	}

	children, err := fs.config.KBFSOps().GetDirChildren(fs.ctx, n)
	if err != nil {
		return nil, err
	}

	fis = make([]os.FileInfo, 0, len(children))
	for name, ei := range children {
		fis = append(fis, &FileInfo{
			fs:       fs,
			ei:       ei,
			fullpath: path.Join(p, name),
		})
	}
	return fis, nil
}

// MkdirAll implements the billy.Filesystem interface for FS.
func (fs *FS) MkdirAll(filename string, perm os.FileMode) error {
	return nil
}

// Lstat implements the billy.Filesystem interface for FS.
func (fs *FS) Lstat(filename string) (os.FileInfo, error) {
	return nil, nil
}

// Symlink implements the billy.Filesystem interface for FS.
func (fs *FS) Symlink(target, link string) error {
	return nil
}

// Readlink implements the billy.Filesystem interface for FS.
func (fs *FS) Readlink(link string) (string, error) {
	return "", nil
}

// Chmod implements the billy.Filesystem interface for FS.
func (fs *FS) Chmod(name string, mode os.FileMode) error {
	return nil
}

// Lchown implements the billy.Filesystem interface for FS.
func (fs *FS) Lchown(name string, uid, gid int) error {
	return nil
}

// Chown implements the billy.Filesystem interface for FS.
func (fs *FS) Chown(name string, uid, gid int) error {
	return nil
}

// Chtimes implements the billy.Filesystem interface for FS.
func (fs *FS) Chtimes(name string, atime time.Time, mtime time.Time) error {
	return nil
}

// Chroot implements the billy.Filesystem interface for FS.
func (fs *FS) Chroot(path string) (billy.Filesystem, error) {
	return nil, nil
}

// Root implements the billy.Filesystem interface for FS.
func (fs *FS) Root() string {
	return ""
}

// SyncAll syncs any outstanding buffered writes to the KBFS journal.
func (fs *FS) SyncAll() error {
	return fs.config.KBFSOps().SyncAll(fs.ctx, fs.root.GetFolderBranch())
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

var input = flag.String("input", "sites.json", "json file to read")
var parsed = flag.String("parsed", "../templates/js/sites_parsed.js", "GIGO.sites_parsed= js file to write for falling-sky")
var raw = flag.String("raw", "../templates/js/sites_parsed_raw.js", "js file to write for other automation")
var validator = flag.String("validator", "http://validator.test-ipv6.com/urlvalidate.cgi", "use this service to validate a url, instead of fetching directly")
var slow = flag.Duration("slow",time.Second * 8,"time to consider a lookup 'slow'")
var minimumCount = flag.Int("minimum",0,"Minimum number of sites that must answer, else fail")

// SiteRecord describes a single mirror or "Other Sites" record
type SiteRecord struct {
	Site     string `json:"site"`     // What site name to publicly attribute
	Mirror   bool   `json:"mirror"`   // if true, site is a full mirror; if false, just "OtherSites"
	Hide     bool   `json:"hide"`     // If true, stop sending traffic here
	V4       string `json:"v4"`       // IPv4 test URL  (http or https)
	V6       string `json:"v6"`       // IPv6 test URL (http or https)
	Loc      string `json:"loc"`      // Country where the site is located
	Provider string `json:"provider"` // What provider to publicly attribute
	Monitor  string `json:"monitor"`  // Who to notify on error (automation)
	Contact  string `json:"contact"`  // Who to notify on error (human)
	Reason   string `json:"reason"`   // Reason disabled
	Transparent bool `json:"transparent"` // Whether or not the site is a transparent mirror
}

// SitesMap is a series of site records, keyed by site name
type SitesMap map[string]*SiteRecord

// SitesFile is a container, containing a SitesMap
// This works around the problems with Go unmarshalling to a top level map
type SitesFile struct {
	Sites SitesMap `json:"sites"`
}

// ReadSitesFile reads from disk
func ReadSitesFile(fn string) (*SitesFile, error) {
	var sf SitesFile

	b, err := ioutil.ReadFile(fn)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(b, &sf)
	if err != nil {
		return nil, err
	}

	return &sf, nil
}

func (sm *SitesMap) WriteJS(fn string, prefix string, suffix string) error {
	if fn == "" {
		return nil
	}
	s := prefix + sm.String() + suffix
	b := []byte(s)
	err := ioutil.WriteFile(fn+".new", b, 0755)
	if err != nil {
		return err
	}
	err = os.Rename(fn+".new", fn)
	return err
}

// Bytes returns formatted data
func (sf *SitesFile) Bytes() ([]byte, error) {
	b, err := json.MarshalIndent(sf, "", " ")
	if err != nil {
		log.Fatal(err)
	}
	return b, err
}

// String returns formatted data
func (sf *SitesFile) String() string {
	b, err := sf.Bytes()
	if err != nil {
		log.Fatal(err)
	}
	return string(b)
}

// Bytes returns formatted data
func (sm *SitesMap) Bytes() ([]byte, error) {
	b, err := json.MarshalIndent(sm, "", " ")
	if err != nil {
		log.Fatal(err)
	}
	return b, err
}

// String returns formatted data
func (sm *SitesMap) String() string {
	b, err := sm.Bytes()
	if err != nil {
		log.Fatal(err)
	}
	return string(b)
}

// Print the sites file to stdout
func (sf *SitesFile) Print() {
	fmt.Println(sf.String())
}

// FixDefaults Scans the records and fixes any missing info,
// canonicalizes 1/0 values.
func (sf *SitesFile) FixDefaults() error {
	for key, sr := range sf.Sites {

		// Make sure the "site" is filled out (same as the key)
		if sr.Site == "" {
			sr.Site = key
		}
		// Canonicalize true/false/empty/etc as "1" and "0" strings
	}

	return nil
}

func (sf *SitesFile) DeleteHidden() error {
	for key, sr := range sf.Sites {
		if sr.Hide {
			delete(sf.Sites, key)
		}
	}
	return nil
}
func (sf *SitesFile) CountRemaining() error {
	if *minimumCount < 1 {
		return nil
	}
	c := len(sf.Sites)
	if c < *minimumCount {
		return fmt.Errorf("Too few sites (%v) validated, wanted %v.  Fix errors or lower --minimum.",c,*minimumCount)
	}
	return nil
}

func CheckHTTP(urlString string) error {

	// Complain about slow urlStrings
	t0 := time.Now()
	defer func() {
		t1 := time.Now()
		td := t1.Sub(t0)
		if td > (*slow) {
			log.Printf("Slow! %s %s\n", td, urlString)
		}
	}()

	client := &http.Client{
		Timeout: time.Duration(15) * time.Second,
	}

	var err error
	var resp *http.Response

	if *validator == "" {
		resp, err = client.Get(urlString)
		if err != nil {
			return err
		}
	} else {
		form := url.Values{}
		form.Add("url", urlString)
		req, err := http.NewRequest("POST", *validator, strings.NewReader(form.Encode()))
		resp, err = client.Do(req)
		if err != nil {
			return err
		}
	}

	// Start the fetch
	defer resp.Body.Close()

	// Finish the fetch
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300  {
	   return fmt.Errorf("%s: %s", urlString, resp.Status);
	}

	// Check the content type
	ct := resp.Header.Get("Content-Type")
	if ct == "" {
		return fmt.Errorf("%s: No Content-Type in response", urlString)
	}
	ctl := strings.ToLower(ct)

	// If we like the content type, return success
	if ctl == "image/gif" ||
		ctl == "image/jpg" ||
		ctl == "image/jpeg" ||
		ctl == "image/png" {
		return nil
	}

	// Otherwise complain
	return fmt.Errorf("%s: Bad Content-Type: %s", urlString, ct)

}

// CheckHTTP checks if a given URL is good or not;
// and if not good, marks  SiteRecord.Hide=1
func (sr *SiteRecord) CheckHTTP(wg *sync.WaitGroup) {
	// Mark the goroutine as done on function exit
	defer wg.Done()

	// Try IPv4 then IPv6.
	// Don't do these in parallel with each other;
	// we don't want to deal with lock issues
	// on sr.Reason .  So, do them in serial.
	if err4 := CheckHTTP(sr.V4); err4 != nil {
		sr.Reason = err4.Error()
		sr.Hide = true
		log.Println(sr.V4, sr.Reason)
		return
	}
	if err6 := CheckHTTP(sr.V6); err6 != nil {
		sr.Reason = err6.Error()
		sr.Hide = true
		log.Println(sr.V6, sr.Reason)
		return
	}
}

// CheckHTTP against the entire SitesFile
func (sf *SitesFile) CheckHTTP() {
	// Each site, we will spin off as a goroutine.
	// When we do so, we need to track their finish.
	wg := &sync.WaitGroup{}

	for _, sr := range sf.Sites {
		if sr.Hide == false {
			wg.Add(1)
			go sr.CheckHTTP(wg)
			time.Sleep(20 * time.Millisecond)
		}
	}
	wg.Wait()
}

func main() {
	flag.Parse()
	sf, err := ReadSitesFile(*input)
	if err != nil {
		log.Fatal(err)
	}
	sf.FixDefaults() // Fix the v4 and v6 test urls if blank
	sf.DeleteHidden() // First pass cleanup, stuff humans marked hide

	log.Printf("Checking %v sites\n",len(sf.Sites))

	sf.CheckHTTP() // Do active checks against the v4 and v6 test urls
	sf.DeleteHidden() // Second pass cleanup, stuff CheckHTTP marked hide

	log.Printf("After checking, %v sites remain\n",len(sf.Sites))

	if err= sf.CountRemaining(); err!=nil {
		log.Fatal(err)
	}

	err = sf.Sites.WriteJS(*parsed, "GIGO.sites_parsed=", ";")
	if err != nil {
		log.Fatal(err)
	}
	err = sf.Sites.WriteJS(*raw, "", "")
	if err != nil {
		log.Fatal(err)
	}

}
<commit_msg>more checks for official mirrors; clean up outputs; stop warning on slow targets if the target also has an error.<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

var input = flag.String("input", "sites.json", "json file to read")
var parsed = flag.String("parsed", "../templates/js/sites_parsed.js", "GIGO.sites_parsed= js file to write for falling-sky")
var raw = flag.String("raw", "../templates/js/sites_parsed_raw.js", "js file to write for other automation")
var validator = flag.String("validator", "http://validator.test-ipv6.com/urlvalidate.cgi", "use this service to validate a url, instead of fetching directly")
var slow = flag.Duration("slow", time.Second*8, "time to consider a lookup 'slow'")
var minimumCount = flag.Int("minimum", 0, "Minimum number of sites that must answer, else fail")

// SiteRecord describes a single mirror or "Other Sites" record
type SiteRecord struct {
	Site        string `json:"site"`        // What site name to publicly attribute
	Mirror      bool   `json:"mirror"`      // if true, site is a full mirror; if false, just "OtherSites"
	Hide        bool   `json:"hide"`        // If true, stop sending traffic here
	V4          string `json:"v4"`          // IPv4 test URL  (http or https)
	V6          string `json:"v6"`          // IPv6 test URL (http or https)
	Loc         string `json:"loc"`         // Country where the site is located
	Provider    string `json:"provider"`    // What provider to publicly attribute
	Monitor     string `json:"monitor"`     // Who to notify on error (automation)
	Contact     string `json:"contact"`     // Who to notify on error (human)
	Reason      string `json:"reason"`      // Reason disabled
	Transparent bool   `json:"transparent"` // Whether or not the site is a transparent mirror
}

// SitesMap is a series of site records, keyed by site name
type SitesMap map[string]*SiteRecord

// SitesFile is a container, containing a SitesMap
// This works around the problems with Go unmarshalling to a top level map
type SitesFile struct {
	Sites SitesMap `json:"sites"`
}

// ReadSitesFile reads from disk
func ReadSitesFile(fn string) (*SitesFile, error) {
	var sf SitesFile

	b, err := ioutil.ReadFile(fn)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(b, &sf)
	if err != nil {
		return nil, err
	}

	return &sf, nil
}

func (sm *SitesMap) WriteJS(fn string, prefix string, suffix string) error {
	if fn == "" {
		return nil
	}
	s := prefix + sm.String() + suffix
	b := []byte(s)
	err := ioutil.WriteFile(fn+".new", b, 0755)
	if err != nil {
		return err
	}
	err = os.Rename(fn+".new", fn)
	return err
}

// Bytes returns formatted data
func (sf *SitesFile) Bytes() ([]byte, error) {
	b, err := json.MarshalIndent(sf, "", " ")
	if err != nil {
		log.Fatal(err)
	}
	return b, err
}

// String returns formatted data
func (sf *SitesFile) String() string {
	b, err := sf.Bytes()
	if err != nil {
		log.Fatal(err)
	}
	return string(b)
}

// Bytes returns formatted data
func (sm *SitesMap) Bytes() ([]byte, error) {
	b, err := json.MarshalIndent(sm, "", " ")
	if err != nil {
		log.Fatal(err)
	}
	return b, err
}

// String returns formatted data
func (sm *SitesMap) String() string {
	b, err := sm.Bytes()
	if err != nil {
		log.Fatal(err)
	}
	return string(b)
}

// Print the sites file to stdout
func (sf *SitesFile) Print() {
	fmt.Println(sf.String())
}

// FixDefaults Scans the records and fixes any missing info,
// canonicalizes 1/0 values.
func (sf *SitesFile) FixDefaults() error {
	for key, sr := range sf.Sites {

		// Make sure the "site" is filled out (same as the key)
		if sr.Site == "" {
			sr.Site = key
		}
		// Canonicalize true/false/empty/etc as "1" and "0" strings
	}

	return nil
}

func (sf *SitesFile) DeleteHidden() error {
	for key, sr := range sf.Sites {
		if sr.Hide {
			delete(sf.Sites, key)
		}
	}
	return nil
}
func (sf *SitesFile) CountRemaining() error {
	if *minimumCount < 1 {
		return nil
	}
	c := len(sf.Sites)
	if c < *minimumCount {
		return fmt.Errorf("Too few sites (%v) validated, wanted %v.  Fix errors or lower --minimum.", c, *minimumCount)
	}
	return nil
}

func CheckHTTP(urlString string) (E error) {

	// Complain about slow urlStrings
	t0 := time.Now()
	defer func() {
		if E == nil {
			t1 := time.Now()
			td := t1.Sub(t0)
			if td > (*slow) {
				log.Printf("Slow! %s %s\n", td, urlString)
			}
		}
	}()

	client := &http.Client{
		Timeout: time.Duration(15) * time.Second,
	}

	var err error
	var resp *http.Response

	if *validator == "" {
		resp, err = client.Get(urlString)
		if err != nil {
			return err
		}
	} else {
		form := url.Values{}
		form.Add("url", urlString)
		req, err := http.NewRequest("POST", *validator, strings.NewReader(form.Encode()))
		resp, err = client.Do(req)
		if err != nil {
			return err
		}
	}

	// Start the fetch
	defer resp.Body.Close()

	// Finish the fetch
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("%s: %s", urlString, resp.Status)
	}

	// Check the content type
	ct := resp.Header.Get("Content-Type")
	if ct == "" {
		return fmt.Errorf("%s: No Content-Type in response", urlString)
	}
	ctl := strings.ToLower(ct)

	// If we like the content type, return success
	if ctl == "image/gif" ||
		ctl == "image/jpg" ||
		ctl == "image/jpeg" ||
		ctl == "image/png" {
		return nil
	}

	// Otherwise complain
	return fmt.Errorf("%s: Bad Content-Type: %s", urlString, ct)

}

// CheckHTTP checks if a given URL is good or not;
// and if not good, marks  SiteRecord.Hide=1
func (sr *SiteRecord) CheckHTTP(wg *sync.WaitGroup) {
	// Mark the goroutine as done on function exit
	defer wg.Done()

	// Try IPv4 then IPv6.
	// Don't do these in parallel with each other;
	// we don't want to deal with lock issues
	// on sr.Reason .  So, do them in serial.
	if err4 := CheckHTTP(sr.V4); err4 != nil {
		sr.Reason = err4.Error()
		sr.Hide = true
		log.Println(sr.Reason)
		return
	}
	if err6 := CheckHTTP(sr.V6); err6 != nil {
		sr.Reason = err6.Error()
		sr.Hide = true
		log.Println(sr.Reason)
		return
	}

}

// CheckMIRROR checks if a mirror's urls are good - a subset of
// a full verification.  TODO: Make a lib that does full site verification.
func (sr *SiteRecord) CheckMIRROR(domain string, wg *sync.WaitGroup) {
	// Mark the goroutine as done on function exit
	defer wg.Done()

	// Try IPv4 then IPv6.
	// Don't do these in parallel with each other;
	// we don't want to deal with lock issues
	// on sr.Reason .  So, do them in serial.
	patterns := []string{"http://ipv4.%s/images-nc/knob_green.png", "http://ipv6.%s/images-nc/knob_green.png", "http://ds.v6ns.%s/images-nc/knob_green.png", "http://mtu1280.%s/images-nc/knob_green.png"}

	for _, pattern := range patterns {
		try := fmt.Sprintf(pattern, domain)
		if err := CheckHTTP(try); err != nil {
			sr.Reason = err.Error()
			sr.Hide = true
			log.Println(sr.Reason)
			return
		}
	}
}

// CheckHTTP against the entire SitesFile
func (sf *SitesFile) CheckHTTP() {
	// Each site, we will spin off as a goroutine.
	// When we do so, we need to track their finish.
	wg := &sync.WaitGroup{}

	for domain, sr := range sf.Sites {
		if sr.Hide == false {
			wg.Add(1)
			if sr.Mirror {
				go sr.CheckMIRROR(domain, wg)
			} else {
				go sr.CheckHTTP(wg)
			}
			time.Sleep(20 * time.Millisecond)
		}
	}
	wg.Wait()
}

func main() {
	flag.Parse()
	sf, err := ReadSitesFile(*input)
	if err != nil {
		log.Fatal(err)
	}
	sf.FixDefaults()  // Fix the v4 and v6 test urls if blank
	sf.DeleteHidden() // First pass cleanup, stuff humans marked hide

	log.Printf("Checking %v sites\n", len(sf.Sites))

	sf.CheckHTTP()    // Do active checks against the v4 and v6 test urls
	sf.DeleteHidden() // Second pass cleanup, stuff CheckHTTP marked hide

	log.Printf("After checking, %v sites remain\n", len(sf.Sites))

	if err = sf.CountRemaining(); err != nil {
		log.Fatal(err)
	}

	err = sf.Sites.WriteJS(*parsed, "GIGO.sites_parsed=", ";")
	if err != nil {
		log.Fatal(err)
	}
	err = sf.Sites.WriteJS(*raw, "", "")
	if err != nil {
		log.Fatal(err)
	}

}
<|endoftext|>


<commit_before>package plugin_test

import (
	"io/ioutil"
	"net/rpc"
	"os"
	"path/filepath"
	"runtime"

	"github.com/cloudfoundry/cli/cf/command"
	testCommand "github.com/cloudfoundry/cli/cf/command/fakes"
	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
	testconfig "github.com/cloudfoundry/cli/cf/configuration/plugin_config/fakes"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/plugin"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Install", func() {
	var (
		ui                  *testterm.FakeUI
		requirementsFactory *testreq.FakeReqFactory
		config              *testconfig.FakePluginConfiguration

		coreCmds   map[string]command.Command
		pluginFile *os.File
		homeDir    string
		pluginDir  string
		curDir     string

		test_1         string
		test_2         string
		test_with_help string
		test_with_push string
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		requirementsFactory = &testreq.FakeReqFactory{}
		config = &testconfig.FakePluginConfiguration{}
		coreCmds = make(map[string]command.Command)

		dir, err := os.Getwd()
		if err != nil {
			panic(err)
		}
		test_1 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_1.exe")
		test_2 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_2.exe")
		test_with_help = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_help.exe")
		test_with_push = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_push.exe")

		rpc.DefaultServer = rpc.NewServer()

		homeDir, err = ioutil.TempDir(os.TempDir(), "plugins")
		Expect(err).ToNot(HaveOccurred())

		pluginDir = filepath.Join(homeDir, ".cf", "plugins")
		config.GetPluginPathReturns(pluginDir)

		curDir, err = os.Getwd()
		Expect(err).ToNot(HaveOccurred())
		pluginFile, err = ioutil.TempFile("./", "test_plugin")
		Expect(err).ToNot(HaveOccurred())

		if runtime.GOOS != "windows" {
			err = os.Chmod(test_1, 0700)
			Expect(err).ToNot(HaveOccurred())
		}
	})

	AfterEach(func() {
		os.Remove(filepath.Join(curDir, pluginFile.Name()))
		os.Remove(homeDir)
	})

	runCommand := func(args ...string) bool {
		cmd := NewPluginInstall(ui, config, coreCmds)
		return testcmd.RunCommand(cmd, args, requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails with usage when not provided a path to the plugin executable", func() {
			Expect(runCommand()).ToNot(HavePassedRequirements())
		})
	})

	Describe("failures", func() {
		Context("when the plugin contains a 'help' command", func() {
			It("fails", func() {
				runCommand(test_with_help)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithHelp' cannot be installed from", test_with_help, "at this time because the command 'cf help' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		Context("when the plugin contains a core command", func() {
			It("fails", func() {
				coreCmds["push"] = &testCommand.FakeCommand{}
				runCommand(test_with_push)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithPush' cannot be installed from", test_with_push, "at this time because the command 'cf push' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		It("plugin binary argument is a bad file path", func() {
			runCommand("path/to/not/a/thing.exe")

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Binary file 'path/to/not/a/thing.exe' not found"},
				[]string{"FAILED"},
			))
		})

		It("if plugin name is already taken", func() {
			config.PluginsReturns(map[string]plugin_config.PluginMetadata{"Test1": plugin_config.PluginMetadata{}})
			runCommand(test_1)

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Plugin name", "Test1", "is already taken"},
				[]string{"FAILED"},
			))
		})

		Context("io", func() {
			BeforeEach(func() {
				err := os.MkdirAll(pluginDir, 0700)
				Expect(err).NotTo(HaveOccurred())
			})

			It("if a file with the plugin name already exists under ~/.cf/plugin/", func() {
				config.PluginsReturns(map[string]plugin_config.PluginMetadata{"useless": plugin_config.PluginMetadata{}})
				config.GetPluginPathReturns(curDir)

				runCommand(filepath.Join(curDir, pluginFile.Name()))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Installing plugin"},
					[]string{"The file", pluginFile.Name(), "already exists"},
					[]string{"FAILED"},
				))
			})
		})
	})

	Describe("success", func() {
		var (
			sourceBinaryPath string
		)

		BeforeEach(func() {
			err := os.MkdirAll(pluginDir, 0700)
			Expect(err).ToNot(HaveOccurred())

			sourceBinaryPath = filepath.Join("..", "..", "..", "fixtures", "plugins", "test_1.exe")
			config.GetPluginPathReturns(pluginDir)
			runCommand(sourceBinaryPath)
		})

		It("copies the plugin into directory <FAKE_HOME_DIR>/.cf/plugins/PLUGIN_FILE_NAME", func() {
			_, err := os.Stat(test_1)
			Expect(err).ToNot(HaveOccurred())
			_, err = os.Stat(filepath.Join(pluginDir, "test_1.exe"))
			Expect(err).ToNot(HaveOccurred())
		})

		if runtime.GOOS != "windows" {
			It("Chmods the plugin so it is executable", func() {
				fileInfo, err := os.Stat(filepath.Join(pluginDir, "test_1.exe"))
				Expect(err).ToNot(HaveOccurred())
				Expect(int(fileInfo.Mode())).To(Equal(0700))
			})
		}

		It("populate the configuration with plugin metadata", func() {
			pluginName, pluginMetadata := config.SetPluginArgsForCall(0)

			Expect(pluginName).To(Equal("Test1"))
			Expect(pluginMetadata.Location).To(Equal(filepath.Join(pluginDir, "test_1.exe")))
			Expect(pluginMetadata.Commands[0].Name).To(Equal("test_1_cmd1"))
			Expect(pluginMetadata.Commands[0].HelpText).To(Equal("help text for test_1_cmd1"))
			Expect(pluginMetadata.Commands[1].Name).To(Equal("test_1_cmd2"))
			Expect(pluginMetadata.Commands[1].HelpText).To(Equal("help text for test_1_cmd2"))
			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Installing plugin", sourceBinaryPath},
				[]string{"OK"},
				[]string{"Plugin", "Test1", "successfully installed"},
			))
		})
	})
})
<commit_msg>Remove duplicated plugin_binary path variable in test<commit_after>package plugin_test

import (
	"io/ioutil"
	"net/rpc"
	"os"
	"path/filepath"
	"runtime"

	"github.com/cloudfoundry/cli/cf/command"
	testCommand "github.com/cloudfoundry/cli/cf/command/fakes"
	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
	testconfig "github.com/cloudfoundry/cli/cf/configuration/plugin_config/fakes"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/plugin"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Install", func() {
	var (
		ui                  *testterm.FakeUI
		requirementsFactory *testreq.FakeReqFactory
		config              *testconfig.FakePluginConfiguration

		coreCmds   map[string]command.Command
		pluginFile *os.File
		homeDir    string
		pluginDir  string
		curDir     string

		test_1         string
		test_2         string
		test_with_help string
		test_with_push string
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		requirementsFactory = &testreq.FakeReqFactory{}
		config = &testconfig.FakePluginConfiguration{}
		coreCmds = make(map[string]command.Command)

		dir, err := os.Getwd()
		if err != nil {
			panic(err)
		}
		test_1 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_1.exe")
		test_2 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_2.exe")
		test_with_help = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_help.exe")
		test_with_push = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_push.exe")

		rpc.DefaultServer = rpc.NewServer()

		homeDir, err = ioutil.TempDir(os.TempDir(), "plugins")
		Expect(err).ToNot(HaveOccurred())

		pluginDir = filepath.Join(homeDir, ".cf", "plugins")
		config.GetPluginPathReturns(pluginDir)

		curDir, err = os.Getwd()
		Expect(err).ToNot(HaveOccurred())
		pluginFile, err = ioutil.TempFile("./", "test_plugin")
		Expect(err).ToNot(HaveOccurred())

		if runtime.GOOS != "windows" {
			err = os.Chmod(test_1, 0700)
			Expect(err).ToNot(HaveOccurred())
		}
	})

	AfterEach(func() {
		os.Remove(filepath.Join(curDir, pluginFile.Name()))
		os.Remove(homeDir)
	})

	runCommand := func(args ...string) bool {
		cmd := NewPluginInstall(ui, config, coreCmds)
		return testcmd.RunCommand(cmd, args, requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails with usage when not provided a path to the plugin executable", func() {
			Expect(runCommand()).ToNot(HavePassedRequirements())
		})
	})

	Describe("failures", func() {
		Context("when the plugin contains a 'help' command", func() {
			It("fails", func() {
				runCommand(test_with_help)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithHelp' cannot be installed from", test_with_help, "at this time because the command 'cf help' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		Context("when the plugin contains a core command", func() {
			It("fails", func() {
				coreCmds["push"] = &testCommand.FakeCommand{}
				runCommand(test_with_push)

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Plugin 'TestWithPush' cannot be installed from", test_with_push, "at this time because the command 'cf push' already exists."},
					[]string{"FAILED"},
				))
			})
		})

		It("plugin binary argument is a bad file path", func() {
			runCommand("path/to/not/a/thing.exe")

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Binary file 'path/to/not/a/thing.exe' not found"},
				[]string{"FAILED"},
			))
		})

		It("if plugin name is already taken", func() {
			config.PluginsReturns(map[string]plugin_config.PluginMetadata{"Test1": plugin_config.PluginMetadata{}})
			runCommand(test_1)

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Plugin name", "Test1", "is already taken"},
				[]string{"FAILED"},
			))
		})

		Context("io", func() {
			BeforeEach(func() {
				err := os.MkdirAll(pluginDir, 0700)
				Expect(err).NotTo(HaveOccurred())
			})

			It("if a file with the plugin name already exists under ~/.cf/plugin/", func() {
				config.PluginsReturns(map[string]plugin_config.PluginMetadata{"useless": plugin_config.PluginMetadata{}})
				config.GetPluginPathReturns(curDir)

				runCommand(filepath.Join(curDir, pluginFile.Name()))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Installing plugin"},
					[]string{"The file", pluginFile.Name(), "already exists"},
					[]string{"FAILED"},
				))
			})
		})
	})

	Describe("success", func() {
		BeforeEach(func() {
			err := os.MkdirAll(pluginDir, 0700)
			Expect(err).ToNot(HaveOccurred())
			config.GetPluginPathReturns(pluginDir)
			runCommand(test_1)
		})

		It("copies the plugin into directory <FAKE_HOME_DIR>/.cf/plugins/PLUGIN_FILE_NAME", func() {
			_, err := os.Stat(test_1)
			Expect(err).ToNot(HaveOccurred())
			_, err = os.Stat(filepath.Join(pluginDir, "test_1.exe"))
			Expect(err).ToNot(HaveOccurred())
		})

		if runtime.GOOS != "windows" {
			It("Chmods the plugin so it is executable", func() {
				fileInfo, err := os.Stat(filepath.Join(pluginDir, "test_1.exe"))
				Expect(err).ToNot(HaveOccurred())
				Expect(int(fileInfo.Mode())).To(Equal(0700))
			})
		}

		It("populate the configuration with plugin metadata", func() {
			pluginName, pluginMetadata := config.SetPluginArgsForCall(0)

			Expect(pluginName).To(Equal("Test1"))
			Expect(pluginMetadata.Location).To(Equal(filepath.Join(pluginDir, "test_1.exe")))
			Expect(pluginMetadata.Commands[0].Name).To(Equal("test_1_cmd1"))
			Expect(pluginMetadata.Commands[0].HelpText).To(Equal("help text for test_1_cmd1"))
			Expect(pluginMetadata.Commands[1].Name).To(Equal("test_1_cmd2"))
			Expect(pluginMetadata.Commands[1].HelpText).To(Equal("help text for test_1_cmd2"))
			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Installing plugin", test_1},
				[]string{"OK"},
				[]string{"Plugin", "Test1", "successfully installed"},
			))
		})
	})
})
<|endoftext|>


<commit_before>package apigee

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/gofrs/uuid"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/zambien/go-apigee-edge"
)

func resourceApiProxyDeployment() *schema.Resource {
	return &schema.Resource{
		Create: resourceApiProxyDeploymentCreate,
		Read:   resourceApiProxyDeploymentRead,
		Update: resourceApiProxyDeploymentUpdate,
		Delete: resourceApiProxyDeploymentDelete,
		Importer: &schema.ResourceImporter{
			State: resourceApiProxyDeploymentImport,
		},

		Schema: map[string]*schema.Schema{
			"proxy_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"org": {
				Type:       schema.TypeString,
				Optional:   true,
				Deprecated: "org is not required, the value from the provider is used.",
			},
			"env": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"revision": {
				Type:     schema.TypeString,
				Required: true,
			},
			"delay": {
				Type:     schema.TypeInt,
				Optional: true,
				Default:  0,
			},
			"override": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
		},
	}
}

func resourceApiProxyDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	log.Print("[DEBUG] resourceApiProxyDeploymentImport START")
	client := meta.(*apigee.EdgeClient)

	splits := strings.Split(d.Id(), "_")
	if len(splits) < 2 {
		return []*schema.ResourceData{}, fmt.Errorf("[ERR] Wrong format of resource: %s. Please follow '{name}_{env}_deployment'", d.Id())
	}
	nameOffset := len(splits[len(splits)-1]) + len(splits[len(splits)-2])
	envOffset := len(splits[len(splits)-1])
	name := d.Id()[:(len(d.Id())-nameOffset)-2]
	IDEnv := d.Id()[len(name)+1 : (len(d.Id())-envOffset)-1]
	deployment, _, err := client.Proxies.GetDeployments(name)
	if err != nil {
		log.Printf("[DEBUG] resourceApiProxyDeploymentImport. Error getting deployment api: %v", err)
		return nil, nil
	}
	d.Set("org", deployment.Organization)
	d.Set("proxy_name", deployment.Name)
	d.Set("env", IDEnv)

	return []*schema.ResourceData{d}, nil
}

func resourceApiProxyDeploymentRead(d *schema.ResourceData, meta interface{}) (e error) {
	log.Print("[DEBUG] resourceApiProxyDeploymentRead START")
	log.Printf("[DEBUG] resourceApiProxyDeploymentRead proxy_name: %#v", d.Get("proxy_name").(string))

	client := meta.(*apigee.EdgeClient)

	found := false
	matchedRevision := "0"

	if deployments, _, err := client.Proxies.GetDeployments(d.Get("proxy_name").(string)); err != nil {
		log.Printf("[ERROR] resourceApiProxyDeploymentRead error getting deployments: %s", err.Error())
		if strings.Contains(err.Error(), "404 ") {
			log.Printf("[DEBUG] resourceApiProxyDeploymentRead 404 encountered.  Removing state for deployment proxy_name: %#v", d.Get("proxy_name").(string))
			d.SetId("")
			return nil
		} else {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentRead error reading deployments: %s", err.Error())
		}
	} else {
		log.Printf("[DEBUG] resourceApiProxyDeploymentRead deployments call fired for proxy_name: %#v", d.Get("proxy_name").(string))
		for _, environment := range deployments.Environments {
			log.Printf("[DEBUG] resourceApiProxyDeploymentRead checking revisions in deployed environment: %#v for expected environment: %#v\n", environment.Name, d.Get("env").(string))
			if environment.Name == d.Get("env").(string) {
				//We don't break.  Always get the last one if there are multiple deployments.
				for _, revision := range environment.Revision {
					found = true
					log.Printf("[DEBUG] resourceApiProxyDeploymentRead checking deployed revision: %#v for expected revision: %#v\n", revision.Number.String(), d.Get("revision").(string))
					if d.Get("revision").(string) != "latest" && d.Get("revision").(string) == revision.Number.String() {
						matchedRevision = revision.Number.String()
						break
					} else {
						matchedRevision = revision.Number.String()
					}
				}
			}
		}
	}

	if found {
		if d.Get("revision").(string) == "latest" {
			d.SetId(matchedRevision)
		} else {
			d.Set("revision", matchedRevision)
		}
		log.Printf("[DEBUG] resourceApiProxyDeploymentRead - deployment found. Revision is: %#v", d.Get("revision").(string))
	} else {
		log.Print("[DEBUG] resourceApiProxyDeploymentRead - no deployment found")
		d.SetId("")
	}
	return nil
}

func resourceApiProxyDeploymentCreate(d *schema.ResourceData, meta interface{}) error {

	log.Print("[DEBUG] resourceApiProxyDeploymentCreate START")

	client := meta.(*apigee.EdgeClient)

	proxy_name := d.Get("proxy_name").(string)
	env := d.Get("env").(string)
	rev_int, _ := strconv.Atoi(d.Get("revision").(string))
	rev := apigee.Revision(rev_int)
	delay := int(d.Get("delay").(int))
	override := bool(d.Get("override").(bool))

	if d.Get("revision").(string) == "latest" {
		// deploy latest
		rev_int, err := getLatestRevision(client, proxy_name)
		rev = apigee.Revision(rev_int)
		if err != nil {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error getting latest revision: %v", err)
		}
	}

	proxyDep, _, err := client.Proxies.Deploy(proxy_name, env, rev, delay, override)

	if err != nil {

		if strings.Contains(err.Error(), "conflicts with existing deployment path") {
			//create, fail, update
			log.Printf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error())
			log.Print("[DEBUG] resourceApiProxyDeploymentCreate something got out of sync... maybe someone messing around in apigee directly.  Terraform OVERRIDE!!!")
			resourceApiProxyDeploymentUpdate(d, meta)
		} else {
			log.Printf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error())
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error())
		}
	}

	id, _ := uuid.NewV4()
	d.SetId(id.String())
	d.Set("revision", proxyDep.Revision.String())

	log.Printf("[DEBUG] resourceApiProxyDeploymentUpdate Deployed revision %d of %s", rev, proxy_name)
	return resourceApiProxyDeploymentRead(d, meta)
}

func resourceApiProxyDeploymentUpdate(d *schema.ResourceData, meta interface{}) error {

	log.Print("[DEBUG] resourceApiProxyDeploymentUpdate START")

	client := meta.(*apigee.EdgeClient)

	proxy_name := d.Get("proxy_name").(string)
	env := d.Get("env").(string)
	delay := int(d.Get("delay").(int))
	override := bool(d.Get("override").(bool))

	//We must set delay and override here if not set.
	if delay == 0 {
		delay = 15 //seconds
	}
	if override == false {
		override = true
	}

	rev_int, _ := strconv.Atoi(d.Get("revision").(string))
	rev := apigee.Revision(rev_int)
	if d.Get("revision").(string) == "latest" {
		// deploy latest
		rev_int, err := getLatestRevision(client, proxy_name)
		rev = apigee.Revision(rev_int)
		if err != nil {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error getting latest revision: %v", err)
		}
	}

	_, _, err := client.Proxies.ReDeploy(proxy_name, env, rev, delay, override)

	if err != nil {
		log.Printf("[ERROR] resourceApiProxyDeploymentUpdate error redeploying: %s", err.Error())
		if strings.Contains(err.Error(), " is already deployed into environment ") {
			return resourceApiProxyDeploymentRead(d, meta)
		}
		return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error redeploying: %s", err.Error())
	}

	log.Printf("[DEBUG] resourceApiProxyDeploymentUpdate Deployed revision %d of %s", rev, proxy_name)
	return resourceApiProxyDeploymentRead(d, meta)
}

func resourceApiProxyDeploymentDelete(d *schema.ResourceData, meta interface{}) error {

	log.Print("[DEBUG] resourceApiProxyDeploymentDelete START")

	client := meta.(*apigee.EdgeClient)

	proxy_name := d.Get("proxy_name").(string)
	env := d.Get("env").(string)

	rev_int, _ := strconv.Atoi(d.Get("revision").(string))
	rev := apigee.Revision(rev_int)
	if d.Get("revision").(string) == "latest" {
		// deploy latest
		rev_int, err := getLatestRevision(client, proxy_name)
		rev = apigee.Revision(rev_int)
		if err != nil {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentDelete error getting latest revision: %v", err)
		}
	}

	_, _, err := client.Proxies.Undeploy(proxy_name, env, rev)
	if err != nil {
		log.Printf("[ERROR] resourceApiProxyDeploymentDelete error undeploying: %s", err.Error())
		return fmt.Errorf("[ERROR] resourceApiProxyDeploymentDelete error undeploying: %s", err.Error())
	}
	log.Printf("[DEBUG] resourceApiProxyDeploymentDelete Deleted revision %d of %s", rev, proxy_name)

	return resourceApiProxyDeploymentRead(d, meta)
}

func getLatestRevision(client *apigee.EdgeClient, proxyName string) (int, error) {
	proxy, _, err := client.Proxies.Get(proxyName)
	if err != nil {
		return -1, fmt.Errorf("[ERROR] resourceApiProxyRead error reading proxies: %s", err.Error())
	}
	return len(proxy.Revisions), nil
}
<commit_msg>resolve #68<commit_after>package apigee

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/gofrs/uuid"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/zambien/go-apigee-edge"
)

func resourceApiProxyDeployment() *schema.Resource {
	return &schema.Resource{
		Create: resourceApiProxyDeploymentCreate,
		Read:   resourceApiProxyDeploymentRead,
		Update: resourceApiProxyDeploymentUpdate,
		Delete: resourceApiProxyDeploymentDelete,
		Importer: &schema.ResourceImporter{
			State: resourceApiProxyDeploymentImport,
		},

		Schema: map[string]*schema.Schema{
			"proxy_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"org": {
				Type:       schema.TypeString,
				Optional:   true,
				Deprecated: "org is not required, the value from the provider is used.",
			},
			"env": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"revision": {
				Type:     schema.TypeString,
				Required: true,
			},
			"delay": {
				Type:     schema.TypeInt,
				Optional: true,
				Default:  0,
			},
			"override": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
		},
	}
}

func resourceApiProxyDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	log.Print("[DEBUG] resourceApiProxyDeploymentImport START")
	client := meta.(*apigee.EdgeClient)

	splits := strings.Split(d.Id(), "_")
	if len(splits) < 2 {
		return []*schema.ResourceData{}, fmt.Errorf("[ERR] Wrong format of resource: %s. Please follow '{name}_{env}_deployment'", d.Id())
	}
	nameOffset := len(splits[len(splits)-1]) + len(splits[len(splits)-2])
	envOffset := len(splits[len(splits)-1])
	name := d.Id()[:(len(d.Id())-nameOffset)-2]
	IDEnv := d.Id()[len(name)+1 : (len(d.Id())-envOffset)-1]
	deployment, _, err := client.Proxies.GetDeployments(name)
	if err != nil {
		log.Printf("[DEBUG] resourceApiProxyDeploymentImport. Error getting deployment api: %v", err)
		return nil, nil
	}
	d.Set("org", deployment.Organization)
	d.Set("proxy_name", deployment.Name)
	d.Set("env", IDEnv)

	return []*schema.ResourceData{d}, nil
}

func resourceApiProxyDeploymentRead(d *schema.ResourceData, meta interface{}) (e error) {
	log.Print("[DEBUG] resourceApiProxyDeploymentRead START")
	log.Printf("[DEBUG] resourceApiProxyDeploymentRead proxy_name: %#v", d.Get("proxy_name").(string))

	client := meta.(*apigee.EdgeClient)

	found := false
	matchedRevision := "0"

	if deployments, _, err := client.Proxies.GetDeployments(d.Get("proxy_name").(string)); err != nil {
		log.Printf("[ERROR] resourceApiProxyDeploymentRead error getting deployments: %s", err.Error())
		if strings.Contains(err.Error(), "404 ") {
			log.Printf("[DEBUG] resourceApiProxyDeploymentRead 404 encountered.  Removing state for deployment proxy_name: %#v", d.Get("proxy_name").(string))
			d.SetId("")
			return nil
		} else {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentRead error reading deployments: %s", err.Error())
		}
	} else {
		log.Printf("[DEBUG] resourceApiProxyDeploymentRead deployments call fired for proxy_name: %#v", d.Get("proxy_name").(string))
		for _, environment := range deployments.Environments {
			log.Printf("[DEBUG] resourceApiProxyDeploymentRead checking revisions in deployed environment: %#v for expected environment: %#v\n", environment.Name, d.Get("env").(string))
			if environment.Name == d.Get("env").(string) {
				//We don't break.  Always get the last one if there are multiple deployments.
				for _, revision := range environment.Revision {
					found = true
					log.Printf("[DEBUG] resourceApiProxyDeploymentRead checking deployed revision: %#v for expected revision: %#v\n", revision.Number.String(), d.Get("revision").(string))
					if d.Get("revision").(string) != "latest" && d.Get("revision").(string) == revision.Number.String() {
						matchedRevision = revision.Number.String()
						break
					} else {
						matchedRevision = revision.Number.String()
					}
				}
			}
		}
	}

	if found {
		if d.Get("revision").(string) == "latest" {
			d.SetId(matchedRevision)
		} else {
			d.Set("revision", matchedRevision)
		}
		log.Printf("[DEBUG] resourceApiProxyDeploymentRead - deployment found. Revision is: %#v", d.Get("revision").(string))
	} else {
		log.Print("[DEBUG] resourceApiProxyDeploymentRead - no deployment found")
		d.SetId("")
	}
	return nil
}

func resourceApiProxyDeploymentCreate(d *schema.ResourceData, meta interface{}) error {

	log.Print("[DEBUG] resourceApiProxyDeploymentCreate START")

	client := meta.(*apigee.EdgeClient)

	proxy_name := d.Get("proxy_name").(string)
	env := d.Get("env").(string)
	rev_int, _ := strconv.Atoi(d.Get("revision").(string))
	rev := apigee.Revision(rev_int)
	delay := int(d.Get("delay").(int))
	override := bool(d.Get("override").(bool))

	if d.Get("revision").(string) == "latest" {
		// deploy latest
		rev_int, err := getLatestRevision(client, proxy_name)
		rev = apigee.Revision(rev_int)
		if err != nil {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentCreate error getting latest revision: %v", err)
		}
	}

	proxyDep, _, err := client.Proxies.Deploy(proxy_name, env, rev, delay, override)

	if err != nil {

		if strings.Contains(err.Error(), "conflicts with existing deployment path") {
			//create, fail, update
			log.Printf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error())
			log.Print("[DEBUG] resourceApiProxyDeploymentCreate something got out of sync... maybe someone messing around in apigee directly.  Terraform OVERRIDE!!!")
			resourceApiProxyDeploymentUpdate(d, meta)
		} else {
			log.Printf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error())
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentCreate error deploying: %s", err.Error())
		}
	}

	id, _ := uuid.NewV4()
	d.SetId(id.String())
	d.Set("revision", proxyDep.Revision.String())

	log.Printf("[DEBUG] resourceApiProxyDeploymentUpdate Deployed revision %d of %s", rev, proxy_name)
	return resourceApiProxyDeploymentRead(d, meta)
}

func resourceApiProxyDeploymentUpdate(d *schema.ResourceData, meta interface{}) error {

	log.Print("[DEBUG] resourceApiProxyDeploymentUpdate START")

	client := meta.(*apigee.EdgeClient)

	proxy_name := d.Get("proxy_name").(string)
	env := d.Get("env").(string)
	delay := int(d.Get("delay").(int))
	override := bool(d.Get("override").(bool))

	//We must set delay and override here if not set.
	if delay == 0 {
		delay = 15 //seconds
	}
	if override == false {
		override = true
	}

	rev_int, _ := strconv.Atoi(d.Get("revision").(string))
	rev := apigee.Revision(rev_int)
	if d.Get("revision").(string) == "latest" {
		// deploy latest
		rev_int, err := getLatestRevision(client, proxy_name)
		rev = apigee.Revision(rev_int)
		if err != nil {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error getting latest revision: %v", err)
		}
	}

	_, _, err := client.Proxies.ReDeploy(proxy_name, env, rev, delay, override)

	if err != nil {
		log.Printf("[ERROR] resourceApiProxyDeploymentUpdate error redeploying: %s", err.Error())
		if strings.Contains(err.Error(), " is already deployed ") {
			return resourceApiProxyDeploymentRead(d, meta)
		}
		return fmt.Errorf("[ERROR] resourceApiProxyDeploymentUpdate error redeploying: %s", err.Error())
	}

	log.Printf("[DEBUG] resourceApiProxyDeploymentUpdate Deployed revision %d of %s", rev, proxy_name)
	return resourceApiProxyDeploymentRead(d, meta)
}

func resourceApiProxyDeploymentDelete(d *schema.ResourceData, meta interface{}) error {

	log.Print("[DEBUG] resourceApiProxyDeploymentDelete START")

	client := meta.(*apigee.EdgeClient)

	proxy_name := d.Get("proxy_name").(string)
	env := d.Get("env").(string)

	rev_int, _ := strconv.Atoi(d.Get("revision").(string))
	rev := apigee.Revision(rev_int)
	if d.Get("revision").(string) == "latest" {
		// deploy latest
		rev_int, err := getLatestRevision(client, proxy_name)
		rev = apigee.Revision(rev_int)
		if err != nil {
			return fmt.Errorf("[ERROR] resourceApiProxyDeploymentDelete error getting latest revision: %v", err)
		}
	}

	_, _, err := client.Proxies.Undeploy(proxy_name, env, rev)
	if err != nil {
		log.Printf("[ERROR] resourceApiProxyDeploymentDelete error undeploying: %s", err.Error())
		return fmt.Errorf("[ERROR] resourceApiProxyDeploymentDelete error undeploying: %s", err.Error())
	}
	log.Printf("[DEBUG] resourceApiProxyDeploymentDelete Deleted revision %d of %s", rev, proxy_name)

	return resourceApiProxyDeploymentRead(d, meta)
}

func getLatestRevision(client *apigee.EdgeClient, proxyName string) (int, error) {
	proxy, _, err := client.Proxies.Get(proxyName)
	if err != nil {
		return -1, fmt.Errorf("[ERROR] resourceApiProxyRead error reading proxies: %s", err.Error())
	}
	return len(proxy.Revisions), nil
}
<|endoftext|>