<commit_before>package glock

import (
	"errors"
	"io/ioutil"
	"log"
	"time"
)

// LockManager manages all the locks for a single client
type LockManager struct {
	Logger *log.Logger
	opts   AcquireOptions
	client Client
	locks  map[string]Lock
	hb     map[string]chan error
}

// AcquireOptions allows to set options during lock acquisition.
type AcquireOptions struct {
	// TTL is the ttl of the lock. If <= 0 the manager defaultTTL is used
	TTL time.Duration
	// How long to wait for the lock to be acquired.
	MaxWait time.Duration
	// The Data to set with the lock.
	Data string
}

// NewLockManager returns a new LockManager for the given client.
// By default, logging is sent to /dev/null. You must call SetOutput() on
// the Logger instance if you want logging to be sent somewhere.
func NewLockManager(client Client, opts AcquireOptions) *LockManager {
	return &LockManager{
		log.New(ioutil.Discard, "glock: ", log.LstdFlags|log.LUTC),
		opts,
		client,
		make(map[string]Lock),
		make(map[string]chan error),
	}
}

// SetData updates data for an existing, acquired lock. Data won't be
// saved into the backend database until the lock is refreshed (either manually
// or at the next heartbeat.
func (m *LockManager) SetData(lock, data string) error {
	l, ok := m.locks[lock]
	if !ok {
		return ErrInvalidLock
	}
	l.SetData(data)
	return nil
}

// Client returns the current lock client in use
func (m *LockManager) Client() Client {
	return m.client
}

// Info returns information about a lock with the given name
func (m *LockManager) Info(lockName string) (*LockInfo, error) {
	lock, ok := m.locks[lockName]
	if !ok {
		return nil, ErrInvalidLock
	}
	info, err := lock.Info()
	if err != nil {
		return nil, err
	}
	return info, nil
}

// Refresh refreshes a single lock.
func (m *LockManager) Refresh(lockName string) error {
	lock, ok := m.locks[lockName]
	if !ok {
		return ErrInvalidLock
	}
	return lock.Refresh()
}

func (m *LockManager) acquire(lockName string, opts AcquireOptions) error {
	lock := m.client.NewLock(lockName)
	lock.SetData(opts.Data)
	err := lock.Acquire(opts.TTL)
	if err != nil {
		m.Logger.Printf("client %s: Cannot acquire lock '%s': %s",
			m.client.ID(), lockName, err.Error())
		return err
	}
	m.Logger.Printf("client %s: Acquired lock '%s' for %v", m.client.ID(), lockName, opts.TTL)
	m.locks[lockName] = lock
	return nil
}

// Acquire tries to acquire the lock with the given name using the default TTL
// for the manager. If the lock cannot be acquired, it will wait up to MaxWait
// for the lock to be released by the owner.
// If this manager instance already has acquired this lock, this action is a no-op.
func (m *LockManager) Acquire(lockName string, opts AcquireOptions) error {

	var waited time.Duration
	lock := m.client.NewLock(lockName)

	if opts.TTL <= 0 {
		opts.TTL = m.opts.TTL
	}

	if opts.Data == "" {
		opts.Data = m.opts.Data
	}

	if opts.MaxWait <= 0 {
		opts.MaxWait = m.opts.MaxWait
	}

	if lock, ok := m.locks[lockName]; ok {
		lock.SetData(opts.Data)
		return lock.RefreshTTL(opts.TTL)
	}

	for {
		init := time.Now()
		err := m.acquire(lockName, opts)
		if err == nil {
			return nil
		}
		if err != ErrLockHeldByOtherClient {
			return err
		}
		info, err := lock.Info()
		if err != nil {
			return err
		}

		if waited >= opts.MaxWait {
			m.Logger.Printf("client %s: Cannot acquire lock '%s' after %v",
				m.client.ID(), lockName, waited)
			return ErrLockHeldByOtherClient
		}

		wait := info.TTL - time.Since(init)
		if waited+wait > opts.MaxWait {
			wait = opts.MaxWait - waited
		}

		time.Sleep(wait)
		waited = waited + wait
	}
}

// Release releases a lock with the given name. The lock must be held by the current manager.
// Any eventual heartbeating will be stopped as well.
func (m *LockManager) Release(lockName string) error {
	var err error
	m.Logger.Printf("client %s: Releasing lock '%s'", m.client.ID(), lockName)
	if lock, ok := m.locks[lockName]; ok {
		m.StopHeartbeat(lockName)
		err = lock.Release()
		delete(m.locks, lockName)
	}
	return err
}

// ReleaseAll releases all the locks held by the manager.
func (m *LockManager) ReleaseAll() map[string]error {
	results := make(map[string]error)
	var err error
	for n := range m.locks {
		err = m.Release(n)
		if err != nil {
			results[n] = err
		}
	}
	return results
}

func heartbeat(client Client, logger *log.Logger, lockName string, ttl time.Duration, control chan error) {
	client.Reconnect()
	defer client.Close()
	freq := time.Duration(ttl / 2)
	enlapsed := time.Duration(0)
	sleeptime := 10 * time.Millisecond
	if sleeptime > freq {
		sleeptime = freq
	}

	lock := client.NewLock(lockName)
	for {
		select {
		case <-control:
			return

		default:
			if enlapsed >= freq {
				start := time.Now()
				err := lock.RefreshTTL(ttl)
				if err != nil {
					logger.Printf("client %s: heartbeat -- FATAL cannot refresh lock '%s': %s",
						client.ID(), lockName, err.Error())
					select {
					case control <- err:
						return
					case <-time.After(sleeptime):
						panic(err)
					}
				}
				logger.Printf("client %s: heartbeat -- refreshed lock '%s' for %v",
					client.ID(), lockName, ttl)
				s := sleeptime - (time.Now().Sub(start))
				time.Sleep(s)
				enlapsed = s
			} else {
				time.Sleep(sleeptime)
				enlapsed += sleeptime
			}
		}
	}
}

// StartHeartbeat starts a goroutine in the backgroud that will refresh the
// lock The lock will be refreshed every ttl/2 or whichever is greater.  The
// background goroutine will panic() if the lock cannot be refreshed for any
// reason The background goroutine will run forever until StopHeartbeat is
// called or the lock released.  It will return a channel to signal if the lock
// cannot be refreshed during heartbeats (before panicking)
func (m *LockManager) StartHeartbeat(lockName string) (<-chan error, error) {
	info, err := m.Info(lockName)
	if err != nil {
		return nil, err
	}
	m.Logger.Printf("client %s: Starting heartbeats for lock '%s' every %v", m.client.ID(),
		lockName, info.TTL/2)
	m.hb[lockName] = make(chan error)
	go heartbeat(m.client.Clone(), m.Logger, lockName, info.TTL, m.hb[lockName])
	return m.hb[lockName], nil
}

// StopHeartbeat will stop the background gororoutine, if any, that is heartbeating the given lock
func (m *LockManager) StopHeartbeat(lockName string) {
	if c, ok := m.hb[lockName]; ok {
		m.Logger.Printf("client %s: Stopping heartbeats for lock '%s'", m.client.ID(),
			lockName)
		c <- errors.New("")
		close(c)
		delete(m.hb, lockName)
	}
}
<commit_msg>Do not set data unless set<commit_after>package glock

import (
	"errors"
	"io/ioutil"
	"log"
	"time"
)

// LockManager manages all the locks for a single client
type LockManager struct {
	Logger *log.Logger
	opts   AcquireOptions
	client Client
	locks  map[string]Lock
	hb     map[string]chan error
}

// AcquireOptions allows to set options during lock acquisition.
type AcquireOptions struct {
	// TTL is the ttl of the lock. If <= 0 the manager defaultTTL is used
	TTL time.Duration
	// How long to wait for the lock to be acquired.
	MaxWait time.Duration
	// The Data to set with the lock.
	Data string
}

// NewLockManager returns a new LockManager for the given client.
// By default, logging is sent to /dev/null. You must call SetOutput() on
// the Logger instance if you want logging to be sent somewhere.
func NewLockManager(client Client, opts AcquireOptions) *LockManager {
	return &LockManager{
		log.New(ioutil.Discard, "glock: ", log.LstdFlags|log.LUTC),
		opts,
		client,
		make(map[string]Lock),
		make(map[string]chan error),
	}
}

// SetData updates data for an existing, acquired lock. Data won't be
// saved into the backend database until the lock is refreshed (either manually
// or at the next heartbeat.
func (m *LockManager) SetData(lock, data string) error {
	l, ok := m.locks[lock]
	if !ok {
		return ErrInvalidLock
	}
	l.SetData(data)
	return nil
}

// Client returns the current lock client in use
func (m *LockManager) Client() Client {
	return m.client
}

// Info returns information about a lock with the given name
func (m *LockManager) Info(lockName string) (*LockInfo, error) {
	lock, ok := m.locks[lockName]
	if !ok {
		return nil, ErrInvalidLock
	}
	info, err := lock.Info()
	if err != nil {
		return nil, err
	}
	return info, nil
}

// Refresh refreshes a single lock.
func (m *LockManager) Refresh(lockName string) error {
	lock, ok := m.locks[lockName]
	if !ok {
		return ErrInvalidLock
	}
	return lock.Refresh()
}

func (m *LockManager) acquire(lockName string, opts AcquireOptions) error {
	lock := m.client.NewLock(lockName)
	if opts.Data != "" {
		lock.SetData(opts.Data)
	}
	err := lock.Acquire(opts.TTL)
	if err != nil {
		m.Logger.Printf("client %s: Cannot acquire lock '%s': %s",
			m.client.ID(), lockName, err.Error())
		return err
	}
	m.Logger.Printf("client %s: Acquired lock '%s' for %v", m.client.ID(), lockName, opts.TTL)
	m.locks[lockName] = lock
	return nil
}

// Acquire tries to acquire the lock with the given name using the default TTL
// for the manager. If the lock cannot be acquired, it will wait up to MaxWait
// for the lock to be released by the owner.
// If this manager instance already has acquired this lock, this action is a no-op.
func (m *LockManager) Acquire(lockName string, opts AcquireOptions) error {

	var waited time.Duration
	lock := m.client.NewLock(lockName)

	if opts.TTL <= 0 {
		opts.TTL = m.opts.TTL
	}

	if opts.Data == "" {
		opts.Data = m.opts.Data
	}

	if opts.MaxWait <= 0 {
		opts.MaxWait = m.opts.MaxWait
	}

	if lock, ok := m.locks[lockName]; ok {
		lock.SetData(opts.Data)
		return lock.RefreshTTL(opts.TTL)
	}

	for {
		init := time.Now()
		err := m.acquire(lockName, opts)
		if err == nil {
			return nil
		}
		if err != ErrLockHeldByOtherClient {
			return err
		}
		info, err := lock.Info()
		if err != nil {
			return err
		}

		if waited >= opts.MaxWait {
			m.Logger.Printf("client %s: Cannot acquire lock '%s' after %v",
				m.client.ID(), lockName, waited)
			return ErrLockHeldByOtherClient
		}

		wait := info.TTL - time.Since(init)
		if waited+wait > opts.MaxWait {
			wait = opts.MaxWait - waited
		}

		time.Sleep(wait)
		waited = waited + wait
	}
}

// Release releases a lock with the given name. The lock must be held by the current manager.
// Any eventual heartbeating will be stopped as well.
func (m *LockManager) Release(lockName string) error {
	var err error
	m.Logger.Printf("client %s: Releasing lock '%s'", m.client.ID(), lockName)
	if lock, ok := m.locks[lockName]; ok {
		m.StopHeartbeat(lockName)
		err = lock.Release()
		delete(m.locks, lockName)
	}
	return err
}

// ReleaseAll releases all the locks held by the manager.
func (m *LockManager) ReleaseAll() map[string]error {
	results := make(map[string]error)
	var err error
	for n := range m.locks {
		err = m.Release(n)
		if err != nil {
			results[n] = err
		}
	}
	return results
}

func heartbeat(client Client, logger *log.Logger, lockName string, ttl time.Duration, control chan error) {
	client.Reconnect()
	defer client.Close()
	freq := time.Duration(ttl / 2)
	enlapsed := time.Duration(0)
	sleeptime := 10 * time.Millisecond
	if sleeptime > freq {
		sleeptime = freq
	}

	lock := client.NewLock(lockName)
	for {
		select {
		case <-control:
			return

		default:
			if enlapsed >= freq {
				start := time.Now()
				err := lock.RefreshTTL(ttl)
				if err != nil {
					logger.Printf("client %s: heartbeat -- FATAL cannot refresh lock '%s': %s",
						client.ID(), lockName, err.Error())
					select {
					case control <- err:
						return
					case <-time.After(sleeptime):
						panic(err)
					}
				}
				logger.Printf("client %s: heartbeat -- refreshed lock '%s' for %v",
					client.ID(), lockName, ttl)
				s := sleeptime - (time.Now().Sub(start))
				time.Sleep(s)
				enlapsed = s
			} else {
				time.Sleep(sleeptime)
				enlapsed += sleeptime
			}
		}
	}
}

// StartHeartbeat starts a goroutine in the backgroud that will refresh the
// lock The lock will be refreshed every ttl/2 or whichever is greater.  The
// background goroutine will panic() if the lock cannot be refreshed for any
// reason The background goroutine will run forever until StopHeartbeat is
// called or the lock released.  It will return a channel to signal if the lock
// cannot be refreshed during heartbeats (before panicking)
func (m *LockManager) StartHeartbeat(lockName string) (<-chan error, error) {
	info, err := m.Info(lockName)
	if err != nil {
		return nil, err
	}
	m.Logger.Printf("client %s: Starting heartbeats for lock '%s' every %v", m.client.ID(),
		lockName, info.TTL/2)
	m.hb[lockName] = make(chan error)
	go heartbeat(m.client.Clone(), m.Logger, lockName, info.TTL, m.hb[lockName])
	return m.hb[lockName], nil
}

// StopHeartbeat will stop the background gororoutine, if any, that is heartbeating the given lock
func (m *LockManager) StopHeartbeat(lockName string) {
	if c, ok := m.hb[lockName]; ok {
		m.Logger.Printf("client %s: Stopping heartbeats for lock '%s'", m.client.ID(),
			lockName)
		c <- errors.New("")
		close(c)
		delete(m.hb, lockName)
	}
}
<|endoftext|>


<commit_before>package sub

import (
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/objectcache"
	"github.com/Symantec/Dominator/lib/triggers"
	"time"
)

type Configuration struct {
	ScanSpeedPercent    uint
	NetworkSpeedPercent uint
	ScanExclusionList   []string
}

type FetchRequest struct {
	ServerAddress string
	Hashes        []hash.Hash
}

type FetchResponse struct{}

type GetConfigurationRequest struct{}

type GetConfigurationResponse Configuration

// The GetFiles() RPC is fully streamed.
// The client sends a stream of strings (filenames) it wants. An empty string
// signals the end of the stream.
// The server (the sub) sends a stream of GetFileResponse messages. No response
// is sent for the end-of-stream signal.

type GetFileResponse struct {
	Error error
	Size  uint64
} // File data are streamed afterwards.

type PollRequest struct {
	HaveGeneration uint64
	ShortPollOnly  bool // If true, do not send FileSystem or ObjectCache.
}

type PollResponse struct {
	NetworkSpeed                 uint64 // Capacity of the network interface.
	CurrentConfiguration         Configuration
	FetchInProgress              bool // Fetch() and Update() mutually exclusive
	UpdateInProgress             bool
	LastFetchError               string
	LastUpdateError              string
	LastUpdateHadTriggerFailures bool
	LastSuccessfulImageName      string
	StartTime                    time.Time
	PollTime                     time.Time
	ScanCount                    uint64
	GenerationCount              uint64
	FileSystem                   *filesystem.FileSystem // Streamed separately.
	FileSystemFollows            bool
	ObjectCache                  objectcache.ObjectCache // Streamed separately.
} // FileSystem is encoded afterwards, followed by ObjectCache.

type SetConfigurationRequest Configuration

type SetConfigurationResponse struct{}

type FileToCopyToCache struct {
	Name string
	Hash hash.Hash
}

type Hardlink struct {
	NewLink string
	Target  string
}

type Inode struct {
	Name string
	filesystem.GenericInode
}

type UpdateRequest struct {
	ImageName string
	// The ordering here reflects the ordering that the sub is expected to use.
	FilesToCopyToCache  []FileToCopyToCache
	DirectoriesToMake   []Inode
	InodesToMake        []Inode
	HardlinksToMake     []Hardlink
	PathsToDelete       []string
	InodesToChange      []Inode
	MultiplyUsedObjects map[hash.Hash]uint64
	Triggers            *triggers.Triggers
}

type UpdateResponse struct{}

type CleanupRequest struct {
	Hashes []hash.Hash
}

type CleanupResponse struct{}
<commit_msg>Add proto/sub.FetchRequest.Wait field.<commit_after>package sub

import (
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/objectcache"
	"github.com/Symantec/Dominator/lib/triggers"
	"time"
)

type Configuration struct {
	ScanSpeedPercent    uint
	NetworkSpeedPercent uint
	ScanExclusionList   []string
}

type FetchRequest struct {
	ServerAddress string
	Wait          bool
	Hashes        []hash.Hash
}

type FetchResponse struct{}

type GetConfigurationRequest struct{}

type GetConfigurationResponse Configuration

// The GetFiles() RPC is fully streamed.
// The client sends a stream of strings (filenames) it wants. An empty string
// signals the end of the stream.
// The server (the sub) sends a stream of GetFileResponse messages. No response
// is sent for the end-of-stream signal.

type GetFileResponse struct {
	Error error
	Size  uint64
} // File data are streamed afterwards.

type PollRequest struct {
	HaveGeneration uint64
	ShortPollOnly  bool // If true, do not send FileSystem or ObjectCache.
}

type PollResponse struct {
	NetworkSpeed                 uint64 // Capacity of the network interface.
	CurrentConfiguration         Configuration
	FetchInProgress              bool // Fetch() and Update() mutually exclusive
	UpdateInProgress             bool
	LastFetchError               string
	LastUpdateError              string
	LastUpdateHadTriggerFailures bool
	LastSuccessfulImageName      string
	StartTime                    time.Time
	PollTime                     time.Time
	ScanCount                    uint64
	GenerationCount              uint64
	FileSystem                   *filesystem.FileSystem // Streamed separately.
	FileSystemFollows            bool
	ObjectCache                  objectcache.ObjectCache // Streamed separately.
} // FileSystem is encoded afterwards, followed by ObjectCache.

type SetConfigurationRequest Configuration

type SetConfigurationResponse struct{}

type FileToCopyToCache struct {
	Name string
	Hash hash.Hash
}

type Hardlink struct {
	NewLink string
	Target  string
}

type Inode struct {
	Name string
	filesystem.GenericInode
}

type UpdateRequest struct {
	ImageName string
	// The ordering here reflects the ordering that the sub is expected to use.
	FilesToCopyToCache  []FileToCopyToCache
	DirectoriesToMake   []Inode
	InodesToMake        []Inode
	HardlinksToMake     []Hardlink
	PathsToDelete       []string
	InodesToChange      []Inode
	MultiplyUsedObjects map[hash.Hash]uint64
	Triggers            *triggers.Triggers
}

type UpdateResponse struct{}

type CleanupRequest struct {
	Hashes []hash.Hash
}

type CleanupResponse struct{}
<|endoftext|>


<commit_before>package sub

import (
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/objectcache"
	"github.com/Symantec/Dominator/lib/triggers"
	"time"
)

type Configuration struct {
	CpuPercent          uint
	NetworkSpeedPercent uint
	ScanSpeedPercent    uint
	ScanExclusionList   []string
}

type FetchRequest struct {
	ServerAddress string
	Wait          bool
	Hashes        []hash.Hash
}

type FetchResponse struct{}

type GetConfigurationRequest struct{}

type GetConfigurationResponse Configuration

// The GetFiles() RPC is fully streamed.
// The client sends a stream of strings (filenames) it wants. An empty string
// signals the end of the stream.
// The server (the sub) sends a stream of GetFileResponse messages. No response
// is sent for the end-of-stream signal.

type GetFileResponse struct {
	Error error
	Size  uint64
} // File data are streamed afterwards.

type PollRequest struct {
	HaveGeneration uint64
	ShortPollOnly  bool // If true, do not send FileSystem or ObjectCache.
}

type PollResponse struct {
	NetworkSpeed                 uint64 // Capacity of the network interface.
	CurrentConfiguration         Configuration
	FetchInProgress              bool // Fetch() and Update() mutually exclusive
	UpdateInProgress             bool
	LastFetchError               string
	LastUpdateError              string
	LastUpdateHadTriggerFailures bool
	LastSuccessfulImageName      string
	StartTime                    time.Time
	PollTime                     time.Time
	ScanCount                    uint64
	DurationOfLastScan           time.Duration
	GenerationCount              uint64
	FileSystem                   *filesystem.FileSystem // Streamed separately.
	FileSystemFollows            bool
	ObjectCache                  objectcache.ObjectCache // Streamed separately.
} // FileSystem is encoded afterwards, followed by ObjectCache.

type SetConfigurationRequest Configuration

type SetConfigurationResponse struct{}

type FileToCopyToCache struct {
	Name       string
	Hash       hash.Hash
	DoHardlink bool
}

type Hardlink struct {
	NewLink string
	Target  string
}

type Inode struct {
	Name string
	filesystem.GenericInode
}

type UpdateRequest struct {
	ImageName string
	Wait      bool
	// The ordering here reflects the ordering that the sub is expected to use.
	FilesToCopyToCache  []FileToCopyToCache
	DirectoriesToMake   []Inode
	InodesToMake        []Inode
	HardlinksToMake     []Hardlink
	PathsToDelete       []string
	InodesToChange      []Inode
	MultiplyUsedObjects map[hash.Hash]uint64
	Triggers            *triggers.Triggers
}

type UpdateResponse struct{}

type CleanupRequest struct {
	Hashes []hash.Hash
}

type CleanupResponse struct{}
<commit_msg>Add PollResponse.FreeSpace field.<commit_after>package sub

import (
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/objectcache"
	"github.com/Symantec/Dominator/lib/triggers"
	"time"
)

type Configuration struct {
	CpuPercent          uint
	NetworkSpeedPercent uint
	ScanSpeedPercent    uint
	ScanExclusionList   []string
}

type FetchRequest struct {
	ServerAddress string
	Wait          bool
	Hashes        []hash.Hash
}

type FetchResponse struct{}

type GetConfigurationRequest struct{}

type GetConfigurationResponse Configuration

// The GetFiles() RPC is fully streamed.
// The client sends a stream of strings (filenames) it wants. An empty string
// signals the end of the stream.
// The server (the sub) sends a stream of GetFileResponse messages. No response
// is sent for the end-of-stream signal.

type GetFileResponse struct {
	Error error
	Size  uint64
} // File data are streamed afterwards.

type PollRequest struct {
	HaveGeneration uint64
	ShortPollOnly  bool // If true, do not send FileSystem or ObjectCache.
}

type PollResponse struct {
	NetworkSpeed                 uint64 // Capacity of the network interface.
	CurrentConfiguration         Configuration
	FetchInProgress              bool // Fetch() and Update() mutually exclusive
	UpdateInProgress             bool
	LastFetchError               string
	LastUpdateError              string
	LastUpdateHadTriggerFailures bool
	LastSuccessfulImageName      string
	FreeSpace                    *uint64
	StartTime                    time.Time
	PollTime                     time.Time
	ScanCount                    uint64
	DurationOfLastScan           time.Duration
	GenerationCount              uint64
	FileSystemFollows            bool
	FileSystem                   *filesystem.FileSystem  // Streamed separately.
	ObjectCache                  objectcache.ObjectCache // Streamed separately.
} // FileSystem is encoded afterwards, followed by ObjectCache.

type SetConfigurationRequest Configuration

type SetConfigurationResponse struct{}

type FileToCopyToCache struct {
	Name       string
	Hash       hash.Hash
	DoHardlink bool
}

type Hardlink struct {
	NewLink string
	Target  string
}

type Inode struct {
	Name string
	filesystem.GenericInode
}

type UpdateRequest struct {
	ImageName string
	Wait      bool
	// The ordering here reflects the ordering that the sub is expected to use.
	FilesToCopyToCache  []FileToCopyToCache
	DirectoriesToMake   []Inode
	InodesToMake        []Inode
	HardlinksToMake     []Hardlink
	PathsToDelete       []string
	InodesToChange      []Inode
	MultiplyUsedObjects map[hash.Hash]uint64
	Triggers            *triggers.Triggers
}

type UpdateResponse struct{}

type CleanupRequest struct {
	Hashes []hash.Hash
}

type CleanupResponse struct{}
<|endoftext|>


<commit_before>package handler

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sync"

	"github.com/rackspace/rack/auth"
	"github.com/rackspace/rack/internal/github.com/rackspace/gophercloud"
	"github.com/rackspace/rack/output"
)

// StreamPipeHandler is an interface that commands implement if they can stream input
// from STDIN.
type StreamPipeHandler interface {
	// PipeHandler is an interface that commands implement if they can accept input
	// from STDIN.
	PipeHandler
	// HandlePipe is a method that commands implement for processing piped input.
	HandleStreamPipe(*Resource) error
}

// PipeHandler is an interface that commands implement if they can accept input
// from STDIN.
type PipeHandler interface {
	// Commander is an interface that all commands will implement.
	Commander
	// HandleSingle contains logic for processing a single resource. This method
	// will be used if input isn't sent to STDIN, so it will contain, for example,
	// logic for handling flags that would be mandatory if otherwise not piped in.
	HandleSingle(*Resource) error
	// HandlePipe is a method that commands implement for processing piped input.
	HandlePipe(*Resource, string) error
	// StdinField is the field that the command accepts on STDIN.
	StdinField() string
}

// PreJSONer is an interface that commands will satisfy if they have a `PreJSON` method.
type PreJSONer interface {
	PreJSON(*Resource) error
}

// PreCSVer is an interface that commands will satisfy if they have a `PreCSV` method.
type PreCSVer interface {
	PreCSV(*Resource) error
}

// PreTabler is an interface that commands will satisfy if they have a `PreTable` method.
type PreTabler interface {
	PreTable(*Resource) error
}

// Commander is an interface that all commands implement.
type Commander interface {
	// See `Context`.
	Context() *Context
	// Keys returns the keys available for the command output.
	Keys() []string
	// ServiceClientType returns the type of the service client to use.
	ServiceClientType() string
	// HandleFlags processes flags for the command that are relevant for both piped
	// and non-piped commands.
	HandleFlags(*Resource) error
	// Execute executes the command's HTTP request.
	Execute(*Resource)
}

// Handle is the function that handles all commands. It accepts a Commander as
// a parameter, which all commands implement.
func Handle(command Commander) {
	ctx := command.Context()
	ctx.ServiceClientType = command.ServiceClientType()
	ctx.Results = make(chan *Resource)

	resource := &Resource{
		Keys: command.Keys(),
	}

	err := ctx.CheckArgNum(0)
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}

	err = ctx.handleGlobalOptions()
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}

	client, err := auth.NewClient(ctx.CLIContext, ctx.ServiceClientType, ctx.logger, ctx.GlobalOptions.noCache, ctx.GlobalOptions.useServiceNet)
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}
	client.HTTPClient.Transport.(*auth.LogRoundTripper).Logger = ctx.logger
	ctx.ServiceClient = client

	err = command.HandleFlags(resource)
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}

	go handleExecute(command, resource)

	for resource := range ctx.Results {
		processResult(command, resource)
		printResult(command, resource)
	}

	ctx.storeCredentials()
}

func handleExecute(command Commander, resource *Resource) {
	ctx := command.Context()
	// can the command accept input on STDIN?
	if pipeableCommand, ok := command.(PipeHandler); ok {
		// should we expect something on STDIN?
		if ctx.CLIContext.IsSet("stdin") {
			stdinField := ctx.CLIContext.String("stdin")
			// if so, does the given field accept pipeable input?
			if stdinField == pipeableCommand.StdinField() {
				// if so, does the given command and field accept streaming input?
				if streamPipeableCommand, ok := pipeableCommand.(StreamPipeHandler); ok {
					go func() {
						err := streamPipeableCommand.HandleStreamPipe(resource)
						if err != nil {
							resource.Err = fmt.Errorf("Error handling streamable, pipeable command: %s\n", err)
						} else {
							streamPipeableCommand.Execute(resource)
						}
						ctx.Results <- resource
						close(ctx.Results)
					}()
				} else {
					wg := sync.WaitGroup{}
					scanner := bufio.NewScanner(os.Stdin)
					for scanner.Scan() {
						item := scanner.Text()
						wg.Add(1)
						go func() {
							err := pipeableCommand.HandlePipe(resource, item)
							if err != nil {
								resource.Err = fmt.Errorf("Error handling pipeable command on %s: %s\n", item, err)
								ctx.Results <- resource
							} else {
								pipeableCommand.Execute(resource)
								ctx.Results <- resource
							}
							wg.Done()
						}()
					}
					if scanner.Err() != nil {
						resource.Err = scanner.Err()
						errExit1(command, resource)
					}
					wg.Wait()
					close(ctx.Results)
				}
			} else {
				resource.Err = fmt.Errorf("Unknown STDIN field: %s\n", stdinField)
				errExit1(command, resource)
			}
		} else {
			go func() {
				err := pipeableCommand.HandleSingle(resource)
				if err != nil {
					resource.Err = err
					errExit1(command, resource)
				}
				command.Execute(resource)
				ctx.Results <- resource
				close(ctx.Results)
			}()
		}
	} else {
		go func() {
			command.Execute(resource)
			ctx.Results <- resource
			close(ctx.Results)
		}()
	}
}

func processResult(command Commander, resource *Resource) {
	ctx := command.Context()

	// if an error was encountered during `handleExecution`, return it instead of
	// the `resource.Result`.
	if resource.Err != nil {
		ctx.CLIContext.App.Writer = os.Stderr
		resource.Keys = []string{"error"}
		var errorBody string

		switch resource.Err.(type) {
		case *gophercloud.UnexpectedResponseCodeError:
			errBodyRaw := resource.Err.(*gophercloud.UnexpectedResponseCodeError).Body
			errMap := make(map[string]map[string]interface{})
			err := json.Unmarshal(errBodyRaw, &errMap)
			if err != nil {
				errorBody = string(errBodyRaw)
				break
			}
			for _, v := range errMap {
				errorBody = v["message"].(string)
				break
			}
		default:
			errorBody = resource.Err.Error()
		}

		resource.Result = map[string]interface{}{"error": errorBody}
	} else if resource.Result == nil {
		switch resource.Result.(type) {
		case map[string]interface{}:
			resource.Result = fmt.Sprintf("No results found\n")
		default:
			resource.Result = fmt.Sprintf("No result found.\n")
		}
	} else {
		// limit the returned fields if any were given in the `fields` flag
		ctx.limitFields(resource)

		var err error
		// apply any output-specific transformations on the result
		switch ctx.GlobalOptions.output {
		case "json":
			if jsoner, ok := command.(PreJSONer); ok {
				err = jsoner.PreJSON(resource)
			}
		case "csv":
			if csver, ok := command.(PreCSVer); ok {
				err = csver.PreCSV(resource)
			}
		default:
			if tabler, ok := command.(PreTabler); ok {
				err = tabler.PreTable(resource)
			}
		}
		if err != nil {
			resource.Result = map[string]interface{}{"error": err.Error()}
		}
	}
}

func printResult(command Commander, resource *Resource) {
	ctx := command.Context()
	w := ctx.CLIContext.App.Writer
	keys := resource.Keys
	noHeader := false
	if ctx.GlobalOptions.noHeader {
		noHeader = true
	}
	switch resource.Result.(type) {
	case map[string]interface{}:
		m := resource.Result.(map[string]interface{})
		m = onlyNonNil(m)
		switch ctx.GlobalOptions.output {
		case "json":
			output.MetadataJSON(w, m, keys)
		case "csv":
			output.MetadataCSV(w, m, keys, noHeader)
		default:
			output.MetadataTable(w, m, keys)
		}
	case []map[string]interface{}:
		ms := resource.Result.([]map[string]interface{})
		for i, m := range ms {
			ms[i] = onlyNonNil(m)
		}
		switch ctx.GlobalOptions.output {
		case "json":
			output.ListJSON(w, ms, keys)
		case "csv":
			output.ListCSV(w, ms, keys, noHeader)
		default:
			output.ListTable(w, ms, keys, noHeader)
		}
	case io.Reader:
		if _, ok := resource.Result.(io.ReadCloser); ok {
			defer resource.Result.(io.ReadCloser).Close()
		}
		_, err := io.Copy(w, resource.Result.(io.Reader))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error copying (io.Reader) result: %s\n", err)
		}
	default:
		switch ctx.GlobalOptions.output {
		case "json":
			output.DefaultJSON(w, resource.Result)
		default:
			fmt.Fprintf(w, "%v\n", resource.Result)
		}
	}
}

// errExit1 tells `rack` to print the error and exit.
func errExit1(command Commander, resource *Resource) {
	processResult(command, resource)
	printResult(command, resource)
	os.Exit(1)
}
<commit_msg>update keys on error<commit_after>package handler

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sync"

	"github.com/rackspace/rack/auth"
	"github.com/rackspace/rack/internal/github.com/rackspace/gophercloud"
	"github.com/rackspace/rack/output"
)

// StreamPipeHandler is an interface that commands implement if they can stream input
// from STDIN.
type StreamPipeHandler interface {
	// PipeHandler is an interface that commands implement if they can accept input
	// from STDIN.
	PipeHandler
	// HandlePipe is a method that commands implement for processing piped input.
	HandleStreamPipe(*Resource) error
}

// PipeHandler is an interface that commands implement if they can accept input
// from STDIN.
type PipeHandler interface {
	// Commander is an interface that all commands will implement.
	Commander
	// HandleSingle contains logic for processing a single resource. This method
	// will be used if input isn't sent to STDIN, so it will contain, for example,
	// logic for handling flags that would be mandatory if otherwise not piped in.
	HandleSingle(*Resource) error
	// HandlePipe is a method that commands implement for processing piped input.
	HandlePipe(*Resource, string) error
	// StdinField is the field that the command accepts on STDIN.
	StdinField() string
}

// PreJSONer is an interface that commands will satisfy if they have a `PreJSON` method.
type PreJSONer interface {
	PreJSON(*Resource) error
}

// PreCSVer is an interface that commands will satisfy if they have a `PreCSV` method.
type PreCSVer interface {
	PreCSV(*Resource) error
}

// PreTabler is an interface that commands will satisfy if they have a `PreTable` method.
type PreTabler interface {
	PreTable(*Resource) error
}

// Commander is an interface that all commands implement.
type Commander interface {
	// See `Context`.
	Context() *Context
	// Keys returns the keys available for the command output.
	Keys() []string
	// ServiceClientType returns the type of the service client to use.
	ServiceClientType() string
	// HandleFlags processes flags for the command that are relevant for both piped
	// and non-piped commands.
	HandleFlags(*Resource) error
	// Execute executes the command's HTTP request.
	Execute(*Resource)
}

// Handle is the function that handles all commands. It accepts a Commander as
// a parameter, which all commands implement.
func Handle(command Commander) {
	ctx := command.Context()
	ctx.ServiceClientType = command.ServiceClientType()
	ctx.Results = make(chan *Resource)

	resource := &Resource{
		Keys: command.Keys(),
	}

	err := ctx.CheckArgNum(0)
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}

	err = ctx.handleGlobalOptions()
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}

	client, err := auth.NewClient(ctx.CLIContext, ctx.ServiceClientType, ctx.logger, ctx.GlobalOptions.noCache, ctx.GlobalOptions.useServiceNet)
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}
	client.HTTPClient.Transport.(*auth.LogRoundTripper).Logger = ctx.logger
	ctx.ServiceClient = client

	err = command.HandleFlags(resource)
	if err != nil {
		resource.Err = err
		errExit1(command, resource)
	}

	go handleExecute(command, resource)

	for resource := range ctx.Results {
		processResult(command, resource)
		printResult(command, resource)
	}

	ctx.storeCredentials()
}

func handleExecute(command Commander, resource *Resource) {
	ctx := command.Context()
	// can the command accept input on STDIN?
	if pipeableCommand, ok := command.(PipeHandler); ok {
		// should we expect something on STDIN?
		if ctx.CLIContext.IsSet("stdin") {
			stdinField := ctx.CLIContext.String("stdin")
			// if so, does the given field accept pipeable input?
			if stdinField == pipeableCommand.StdinField() {
				// if so, does the given command and field accept streaming input?
				if streamPipeableCommand, ok := pipeableCommand.(StreamPipeHandler); ok {
					go func() {
						err := streamPipeableCommand.HandleStreamPipe(resource)
						if err != nil {
							resource.Err = fmt.Errorf("Error handling streamable, pipeable command: %s\n", err)
						} else {
							streamPipeableCommand.Execute(resource)
						}
						ctx.Results <- resource
						close(ctx.Results)
					}()
				} else {
					wg := sync.WaitGroup{}
					scanner := bufio.NewScanner(os.Stdin)
					for scanner.Scan() {
						item := scanner.Text()
						wg.Add(1)
						go func() {
							err := pipeableCommand.HandlePipe(resource, item)
							if err != nil {
								resource.Err = fmt.Errorf("Error handling pipeable command on %s: %s\n", item, err)
								ctx.Results <- resource
							} else {
								pipeableCommand.Execute(resource)
								ctx.Results <- resource
							}
							wg.Done()
						}()
					}
					if scanner.Err() != nil {
						resource.Err = scanner.Err()
						errExit1(command, resource)
					}
					wg.Wait()
					close(ctx.Results)
				}
			} else {
				resource.Err = fmt.Errorf("Unknown STDIN field: %s\n", stdinField)
				errExit1(command, resource)
			}
		} else {
			go func() {
				err := pipeableCommand.HandleSingle(resource)
				if err != nil {
					resource.Err = err
					errExit1(command, resource)
				}
				command.Execute(resource)
				ctx.Results <- resource
				close(ctx.Results)
			}()
		}
	} else {
		go func() {
			command.Execute(resource)
			ctx.Results <- resource
			close(ctx.Results)
		}()
	}
}

func processResult(command Commander, resource *Resource) {
	ctx := command.Context()

	// if an error was encountered during `handleExecution`, return it instead of
	// the `resource.Result`.
	if resource.Err != nil {
		ctx.CLIContext.App.Writer = os.Stderr
		resource.Keys = []string{"error"}
		var errorBody string

		switch resource.Err.(type) {
		case *gophercloud.UnexpectedResponseCodeError:
			errBodyRaw := resource.Err.(*gophercloud.UnexpectedResponseCodeError).Body
			errMap := make(map[string]map[string]interface{})
			err := json.Unmarshal(errBodyRaw, &errMap)
			if err != nil {
				errorBody = string(errBodyRaw)
				break
			}
			for _, v := range errMap {
				errorBody = v["message"].(string)
				break
			}
		default:
			errorBody = resource.Err.Error()
		}

		resource.Result = map[string]interface{}{"error": errorBody}
	} else if resource.Result == nil {
		switch resource.Result.(type) {
		case map[string]interface{}:
			resource.Result = fmt.Sprintf("No results found\n")
		default:
			resource.Result = fmt.Sprintf("No result found.\n")
		}
	} else {
		// limit the returned fields if any were given in the `fields` flag
		ctx.limitFields(resource)

		var err error
		// apply any output-specific transformations on the result
		switch ctx.GlobalOptions.output {
		case "json":
			if jsoner, ok := command.(PreJSONer); ok {
				err = jsoner.PreJSON(resource)
			}
		case "csv":
			if csver, ok := command.(PreCSVer); ok {
				err = csver.PreCSV(resource)
			}
		default:
			if tabler, ok := command.(PreTabler); ok {
				err = tabler.PreTable(resource)
			}
		}
		if err != nil {
			resource.Keys = []string{"error"}
			resource.Result = map[string]interface{}{"error": err.Error()}
		}
	}
}

func printResult(command Commander, resource *Resource) {
	ctx := command.Context()
	w := ctx.CLIContext.App.Writer
	keys := resource.Keys
	noHeader := false
	if ctx.GlobalOptions.noHeader {
		noHeader = true
	}
	switch resource.Result.(type) {
	case map[string]interface{}:
		m := resource.Result.(map[string]interface{})
		m = onlyNonNil(m)
		switch ctx.GlobalOptions.output {
		case "json":
			output.MetadataJSON(w, m, keys)
		case "csv":
			output.MetadataCSV(w, m, keys, noHeader)
		default:
			output.MetadataTable(w, m, keys)
		}
	case []map[string]interface{}:
		ms := resource.Result.([]map[string]interface{})
		for i, m := range ms {
			ms[i] = onlyNonNil(m)
		}
		switch ctx.GlobalOptions.output {
		case "json":
			output.ListJSON(w, ms, keys)
		case "csv":
			output.ListCSV(w, ms, keys, noHeader)
		default:
			output.ListTable(w, ms, keys, noHeader)
		}
	case io.Reader:
		if _, ok := resource.Result.(io.ReadCloser); ok {
			defer resource.Result.(io.ReadCloser).Close()
		}
		_, err := io.Copy(w, resource.Result.(io.Reader))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error copying (io.Reader) result: %s\n", err)
		}
	default:
		switch ctx.GlobalOptions.output {
		case "json":
			output.DefaultJSON(w, resource.Result)
		default:
			fmt.Fprintf(w, "%v\n", resource.Result)
		}
	}
}

// errExit1 tells `rack` to print the error and exit.
func errExit1(command Commander, resource *Resource) {
	processResult(command, resource)
	printResult(command, resource)
	os.Exit(1)
}
<|endoftext|>


<commit_before>package handlers

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"golang.org/x/oauth2"

	"github.com/google/go-github/github"
	"github.com/gorilla/mux"
	fb "github.com/huandu/facebook"
	"github.com/play-with-docker/play-with-docker/config"
	"github.com/play-with-docker/play-with-docker/pwd/types"
	"github.com/satori/go.uuid"
)

func LoggedInUser(rw http.ResponseWriter, req *http.Request) {
	cookie, err := ReadCookie(req)
	if err != nil {
		log.Println("Cannot read cookie")
		rw.WriteHeader(http.StatusUnauthorized)
		return
	}
	user, err := core.UserGet(cookie.Id)
	if err != nil {
		log.Printf("Couldn't get user with id %s. Got: %v\n", cookie.Id, err)
		rw.WriteHeader(http.StatusUnauthorized)
		return
	}
	json.NewEncoder(rw).Encode(user)
}

func ListProviders(rw http.ResponseWriter, req *http.Request) {
	playground := core.PlaygroundFindByDomain(req.Host)
	if playground == nil {
		log.Printf("Playground for domain %s was not found!", req.Host)
		rw.WriteHeader(http.StatusBadRequest)
		return
	}

	providers := []string{}
	for name, _ := range config.Providers[playground.Id] {
		providers = append(providers, name)
	}
	json.NewEncoder(rw).Encode(providers)
}

func Login(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	providerName := vars["provider"]
	playground := core.PlaygroundFindByDomain(req.Host)
	if playground == nil {
		log.Printf("Playground for domain %s was not found!", req.Host)
		rw.WriteHeader(http.StatusBadRequest)
		return
	}

	provider, found := config.Providers[playground.Id][providerName]
	if !found {
		log.Printf("Could not find provider %s\n", providerName)
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	loginRequest, err := core.UserNewLoginRequest(providerName)
	if err != nil {
		log.Printf("Could not start a new user login request for provider %s. Got: %v\n", providerName, err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	scheme := "http"
	if req.TLS != nil {
		scheme = "https"
	}
	host := "localhost"
	if req.Host != "" {
		host = req.Host
	}
	provider.RedirectURL = fmt.Sprintf("%s://%s/oauth/providers/%s/callback", scheme, host, providerName)
	url := provider.AuthCodeURL(loginRequest.Id, oauth2.SetAuthURLParam("nonce", uuid.NewV4().String()))

	http.Redirect(rw, req, url, http.StatusFound)
}

func LoginCallback(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	providerName := vars["provider"]
	playground := core.PlaygroundFindByDomain(req.Host)
	if playground == nil {
		log.Printf("Playground for domain %s was not found!", req.Host)
		rw.WriteHeader(http.StatusBadRequest)
		return
	}

	provider, found := config.Providers[playground.Id][providerName]
	if !found {
		log.Printf("Could not find provider %s\n", providerName)
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	query := req.URL.Query()

	code := query.Get("code")
	loginRequestId := query.Get("state")

	loginRequest, err := core.UserGetLoginRequest(loginRequestId)
	if err != nil {
		log.Printf("Could not get login request %s for provider %s. Got: %v\n", loginRequestId, providerName, err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	ctx := req.Context()
	tok, err := provider.Exchange(ctx, code)
	if err != nil {
		log.Printf("Could not exchage code for access token for provider %s. Got: %v\n", providerName, err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	user := &types.User{Provider: providerName}
	if providerName == "github" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tok.AccessToken},
		)
		tc := oauth2.NewClient(ctx, ts)
		client := github.NewClient(tc)
		u, _, err := client.Users.Get(ctx, "")
		if err != nil {
			log.Printf("Could not get user from github. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}
		user.ProviderUserId = strconv.Itoa(u.GetID())
		user.Name = u.GetName()
		user.Avatar = u.GetAvatarURL()
		user.Email = u.GetEmail()
	} else if providerName == "facebook" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tok.AccessToken},
		)
		tc := oauth2.NewClient(ctx, ts)
		session := &fb.Session{
			Version:    "v2.10",
			HttpClient: tc,
		}
		p := fb.Params{}
		p["fields"] = "email,name,picture"
		res, err := session.Get("/me", p)
		if err != nil {
			log.Printf("Could not get user from facebook. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}
		user.ProviderUserId = res.Get("id").(string)
		user.Name = res.Get("name").(string)
		user.Avatar = res.Get("picture.data.url").(string)
		user.Email = res.Get("email").(string)
	} else if providerName == "docker" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tok.AccessToken},
		)
		tc := oauth2.NewClient(ctx, ts)

		endpoint := getDockerEndpoint(playground)
		resp, err := tc.Get(fmt.Sprintf("https://%s/api/id/v1/openid/userinfo", endpoint))
		if err != nil {
			log.Printf("Could not get user from docker. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}

		userInfo := map[string]string{}
		if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
			log.Printf("Could not decode user info. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}

		user.ProviderUserId = userInfo["sub"]
		user.Name = userInfo["preferred_username"]
		user.Email = userInfo["email"]
		// Since DockerID doesn't return a user avatar, we try with twitter through avatars.io
		// Worst case we get a generic avatar
		user.Avatar = fmt.Sprintf("https://avatars.io/twitter/%s", user.Name)
	}

	user, err = core.UserLogin(loginRequest, user)
	if err != nil {
		log.Printf("Could not login user. Got: %v\n", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	cookieData := CookieID{Id: user.Id, UserName: user.Name, UserAvatar: user.Avatar}

	host := "localhost"
	if req.Host != "" {
		host = req.Host
	}

	if err := cookieData.SetCookie(rw, host); err != nil {
		log.Printf("Could not encode cookie. Got: %v\n", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	r, _ := playground.Extras.GetString("LoginRedirect")

	fmt.Fprintf(rw, `
<html>
    <head>
	<script>
	if (window.opener && !window.opener.closed) {
	    try {
	      window.opener.postMessage('done','*');
	    }
	    catch(e) {  }
	    window.close();
	} else {
	    window.location = '%s';
	}
	</script>
    </head>
    <body>
    </body>
</html>`, r)
}

func getDockerEndpoint(p *types.Playground) string {
	if len(p.DockerHost) > 0 {
		return p.DockerHost
	}
	return "id.docker.com"
}
<commit_msg>Set cookie to parent domain to allow subdomains to access<commit_after>package handlers

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"golang.org/x/oauth2"

	"github.com/google/go-github/github"
	"github.com/gorilla/mux"
	fb "github.com/huandu/facebook"
	"github.com/play-with-docker/play-with-docker/config"
	"github.com/play-with-docker/play-with-docker/pwd/types"
	uuid "github.com/satori/go.uuid"
)

func LoggedInUser(rw http.ResponseWriter, req *http.Request) {
	cookie, err := ReadCookie(req)
	if err != nil {
		log.Println("Cannot read cookie")
		rw.WriteHeader(http.StatusUnauthorized)
		return
	}
	user, err := core.UserGet(cookie.Id)
	if err != nil {
		log.Printf("Couldn't get user with id %s. Got: %v\n", cookie.Id, err)
		rw.WriteHeader(http.StatusUnauthorized)
		return
	}
	json.NewEncoder(rw).Encode(user)
}

func ListProviders(rw http.ResponseWriter, req *http.Request) {
	playground := core.PlaygroundFindByDomain(req.Host)
	if playground == nil {
		log.Printf("Playground for domain %s was not found!", req.Host)
		rw.WriteHeader(http.StatusBadRequest)
		return
	}

	providers := []string{}
	for name, _ := range config.Providers[playground.Id] {
		providers = append(providers, name)
	}
	json.NewEncoder(rw).Encode(providers)
}

func Login(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	providerName := vars["provider"]
	playground := core.PlaygroundFindByDomain(req.Host)
	if playground == nil {
		log.Printf("Playground for domain %s was not found!", req.Host)
		rw.WriteHeader(http.StatusBadRequest)
		return
	}

	provider, found := config.Providers[playground.Id][providerName]
	if !found {
		log.Printf("Could not find provider %s\n", providerName)
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	loginRequest, err := core.UserNewLoginRequest(providerName)
	if err != nil {
		log.Printf("Could not start a new user login request for provider %s. Got: %v\n", providerName, err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	scheme := "http"
	if req.TLS != nil {
		scheme = "https"
	}
	host := "localhost"
	if req.Host != "" {
		host = req.Host
	}
	provider.RedirectURL = fmt.Sprintf("%s://%s/oauth/providers/%s/callback", scheme, host, providerName)
	url := provider.AuthCodeURL(loginRequest.Id, oauth2.SetAuthURLParam("nonce", uuid.NewV4().String()))

	http.Redirect(rw, req, url, http.StatusFound)
}

func LoginCallback(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	providerName := vars["provider"]
	playground := core.PlaygroundFindByDomain(req.Host)
	if playground == nil {
		log.Printf("Playground for domain %s was not found!", req.Host)
		rw.WriteHeader(http.StatusBadRequest)
		return
	}

	provider, found := config.Providers[playground.Id][providerName]
	if !found {
		log.Printf("Could not find provider %s\n", providerName)
		rw.WriteHeader(http.StatusNotFound)
		return
	}

	query := req.URL.Query()

	code := query.Get("code")
	loginRequestId := query.Get("state")

	loginRequest, err := core.UserGetLoginRequest(loginRequestId)
	if err != nil {
		log.Printf("Could not get login request %s for provider %s. Got: %v\n", loginRequestId, providerName, err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	ctx := req.Context()
	tok, err := provider.Exchange(ctx, code)
	if err != nil {
		log.Printf("Could not exchage code for access token for provider %s. Got: %v\n", providerName, err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	user := &types.User{Provider: providerName}
	if providerName == "github" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tok.AccessToken},
		)
		tc := oauth2.NewClient(ctx, ts)
		client := github.NewClient(tc)
		u, _, err := client.Users.Get(ctx, "")
		if err != nil {
			log.Printf("Could not get user from github. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}
		user.ProviderUserId = strconv.Itoa(u.GetID())
		user.Name = u.GetName()
		user.Avatar = u.GetAvatarURL()
		user.Email = u.GetEmail()
	} else if providerName == "facebook" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tok.AccessToken},
		)
		tc := oauth2.NewClient(ctx, ts)
		session := &fb.Session{
			Version:    "v2.10",
			HttpClient: tc,
		}
		p := fb.Params{}
		p["fields"] = "email,name,picture"
		res, err := session.Get("/me", p)
		if err != nil {
			log.Printf("Could not get user from facebook. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}
		user.ProviderUserId = res.Get("id").(string)
		user.Name = res.Get("name").(string)
		user.Avatar = res.Get("picture.data.url").(string)
		user.Email = res.Get("email").(string)
	} else if providerName == "docker" {
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tok.AccessToken},
		)
		tc := oauth2.NewClient(ctx, ts)

		endpoint := getDockerEndpoint(playground)
		resp, err := tc.Get(fmt.Sprintf("https://%s/api/id/v1/openid/userinfo", endpoint))
		if err != nil {
			log.Printf("Could not get user from docker. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}

		userInfo := map[string]string{}
		if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
			log.Printf("Could not decode user info. Got: %v\n", err)
			rw.WriteHeader(http.StatusInternalServerError)
			return
		}

		user.ProviderUserId = userInfo["sub"]
		user.Name = userInfo["preferred_username"]
		user.Email = userInfo["email"]
		// Since DockerID doesn't return a user avatar, we try with twitter through avatars.io
		// Worst case we get a generic avatar
		user.Avatar = fmt.Sprintf("https://avatars.io/twitter/%s", user.Name)
	}

	user, err = core.UserLogin(loginRequest, user)
	if err != nil {
		log.Printf("Could not login user. Got: %v\n", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	cookieData := CookieID{Id: user.Id, UserName: user.Name, UserAvatar: user.Avatar}

	host := "localhost"
	if req.Host != "" {
		// we get the parent domain so cookie is set
		// in all subdomain and siblings
		host = getParentDomain(req.Host)
	}

	if err := cookieData.SetCookie(rw, host); err != nil {
		log.Printf("Could not encode cookie. Got: %v\n", err)
		rw.WriteHeader(http.StatusInternalServerError)
		return
	}

	r, _ := playground.Extras.GetString("LoginRedirect")

	fmt.Fprintf(rw, `
<html>
    <head>
	<script>
	if (window.opener && !window.opener.closed) {
	    try {
	      window.opener.postMessage('done','*');
	    }
	    catch(e) {  }
	    window.close();
	} else {
	    window.location = '%s';
	}
	</script>
    </head>
    <body>
    </body>
</html>`, r)
}

// getParentDomain returns the parent domain (if available)
// of the currend domain
func getParentDomain(host string) string {
	levels := strings.Split(host, ".")
	if len(levels) > 2 {
		return strings.Join(levels[1:], ".")
	}
	return host
}

func getDockerEndpoint(p *types.Playground) string {
	if len(p.DockerHost) > 0 {
		return p.DockerHost
	}
	return "id.docker.com"
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced Authors.
// 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.

// Package agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

package node

import (
	"github.com/control-center/serviced/coordinator/client"
	"github.com/control-center/serviced/utils"
	"github.com/zenoss/glog"

	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

// validOwnerSpec returns true if the owner is specified in owner:group format and the
// identifiers are valid POSIX.1-2008 username and group strings, respectively.
func validOwnerSpec(owner string) bool {
	var pattern = regexp.MustCompile(`^[a-zA-Z]+[a-zA-Z0-9.-]*:[a-zA-Z]+[a-zA-Z0-9.-]*$`)
	return pattern.MatchString(owner)
}

// GetInterfaceIPAddress attempts to find the IP address based on interface name
func GetInterfaceIPAddress(_interface string) (string, error) {
	output, err := exec.Command("/sbin/ip", "-4", "-o", "addr").Output()
	if err != nil {
		return "", err
	}

	for _, line := range strings.Split(string(output), "\n") {
		fields := strings.Fields(line)
		if len(fields) < 4 {
			continue
		}

		if strings.HasPrefix(fields[1], _interface) {
			return strings.Split(fields[3], "/")[0], nil
		}
	}

	return "", fmt.Errorf("Unable to find ip for interface: %s", _interface)
}

// getIPAddrFromOutGoingConnection get the IP bound to the interface which
// handles the default route traffic.
func getIPAddrFromOutGoingConnection() (ip string, err error) {
	addr, err := net.ResolveUDPAddr("udp4", "8.8.8.8:53")
	if err != nil {
		return "", err
	}

	conn, err := net.DialUDP("udp4", nil, addr)
	if err != nil {
		return "", err
	}

	localAddr := conn.LocalAddr()
	parts := strings.Split(localAddr.String(), ":")
	return parts[0], nil
}

// ExecPath returns the path to the currently running executable.
func ExecPath() (string, string, error) {
	path, err := os.Readlink("/proc/self/exe")
	if err != nil {
		return "", "", err
	}
	return filepath.Dir(path), filepath.Base(path), nil
}

// DockerVersion contains the tuples that describe the version of docker.
type DockerVersion struct {
	Client []int
	Server []int
}

// equals compares two DockerVersion structs and returns true if they are equal.
func (a *DockerVersion) equals(b *DockerVersion) bool {
	if len(a.Client) != len(b.Client) {
		return false
	}
	for i, aI := range a.Client {
		if aI != b.Client[i] {
			return false
		}
	}
	if len(a.Server) != len(b.Server) {
		return false
	}
	for i, aI := range a.Server {
		if aI != b.Server[i] {
			return false
		}
	}
	return true
}

// GetDockerVersion returns docker version number.
func GetDockerVersion() (DockerVersion, error) {
	cmd := exec.Command("docker", "version")
	output, err := cmd.Output()
	if err != nil {
		return DockerVersion{}, err
	}
	return parseDockerVersion(string(output))
}

// parseDockerVersion parses the output of the 'docker version' commmand and
// returns a DockerVersion object.
func parseDockerVersion(output string) (version DockerVersion, err error) {

	for _, line := range strings.Split(output, "\n") {
		parts := strings.SplitN(line, ":", 2)
		if len(parts) < 2 {
			continue
		}
		if strings.HasPrefix(parts[0], "Client version") {
			a := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2)
			b := strings.Split(a[0], ".")
			version.Client = make([]int, len(b))
			for i, v := range b {
				x, err := strconv.Atoi(v)
				if err != nil {
					return version, err
				}
				version.Client[i] = x
			}
		}
		if strings.HasPrefix(parts[0], "Server version") {
			a := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2)
			b := strings.Split(a[0], ".")
			version.Server = make([]int, len(b))
			for i, v := range b {
				x, err := strconv.Atoi(v)
				if err != nil {
					return version, err
				}
				version.Server[i] = x
			}
		}
	}
	if len(version.Client) == 0 {
		return version, fmt.Errorf("no client version found")
	}
	if len(version.Server) == 0 {
		return version, fmt.Errorf("no server version found")
	}
	return version, nil
}

// CreateDirectory creates a directory using the given username as the owner and the
// given perm as the directory permission.
func CreateDirectory(path, username string, perm os.FileMode) error {
	user, err := user.Lookup(username)
	if err == nil {
		err = os.MkdirAll(path, perm)
		if err == nil || err == os.ErrExist {
			uid, _ := strconv.Atoi(user.Uid)
			gid, _ := strconv.Atoi(user.Gid)
			err = os.Chown(path, uid, gid)
		}
	}
	return err
}

// singleJoiningSlash joins a and b ensuring there is only a single /
// character between them.
func singleJoiningSlash(a, b string) string {
	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")
	switch {
	case aslash && bslash:
		return a + b[1:]
	case !aslash && !bslash:
		return a + "/" + b
	}
	return a + b
}

// NewReverseProxy differs from httputil.NewSingleHostReverseProxy in that it rewrites
// the path so that it does /not/ include the incoming path. e.g. request for
// "/mysvc/thing" when proxy is served from "/mysvc" means target is
// targeturl.Path + "/thing"; vs. httputil.NewSingleHostReverseProxy, in which
// it would be targeturl.Path + "/mysvc/thing".
func NewReverseProxy(path string, targeturl *url.URL) *httputil.ReverseProxy {
	targetQuery := targeturl.RawQuery
	director := func(r *http.Request) {
		r.URL.Scheme = targeturl.Scheme
		r.URL.Host = targeturl.Host
		newpath := strings.TrimPrefix(r.URL.Path, path)
		r.URL.Path = singleJoiningSlash(targeturl.Path, newpath)
		if targetQuery == "" || r.URL.RawQuery == "" {
			r.URL.RawQuery = targetQuery + r.URL.RawQuery
		} else {
			r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery
		}
	}
	return &httputil.ReverseProxy{Director: director}
}

// Assumes that the local docker image (imageSpec) exists and has been sync'd
// with the registry.
var dockerRun = func(imageSpec string, args ...string) (output string, err error) {
	targs := []string{"run", imageSpec}
	for _, s := range args {
		targs = append(targs, s)
	}
	docker := exec.Command("docker", targs...)
	var outputBytes []byte
	outputBytes, err = docker.Output()
	if err != nil {
		return
	}
	output = string(outputBytes)
	return
}

type uidgid struct {
	uid int
	gid int
}

var userSpecCache struct {
	lookup map[string]uidgid
	sync.Mutex
}

func init() {
	userSpecCache.lookup = make(map[string]uidgid)
}

// Assumes that the local docker image (imageSpec) exists and has been sync'd
// with the registry.
func getInternalImageIDs(userSpec, imageSpec string) (uid, gid int, err error) {

	userSpecCache.Lock()
	defer userSpecCache.Unlock()

	key := userSpec + "!" + imageSpec
	if val, found := userSpecCache.lookup[key]; found {
		return val.uid, val.gid, nil
	}

	var output string
	// explicitly ignoring errors because of -rm under load
	output, _ = dockerRun(imageSpec, "/bin/sh", "-c",
		fmt.Sprintf(`touch test.txt && chown %s test.txt && ls -ln test.txt | awk '{ print $3, $4 }'`,
			userSpec))

	s := strings.TrimSpace(string(output))
	pattern := regexp.MustCompile(`^\d+ \d+$`)

	if !pattern.MatchString(s) {
		err = fmt.Errorf("unexpected output from getInternalImageIDs: %s", s)
		return
	}
	fields := strings.Fields(s)
	if len(fields) != 2 {
		err = fmt.Errorf("unexpected number of fields from container spec: %s", fields)
		return
	}
	uid, err = strconv.Atoi(fields[0])
	if err != nil {
		return
	}
	gid, err = strconv.Atoi(fields[1])
	if err != nil {
		return
	}
	// cache the results
	userSpecCache.lookup[key] = uidgid{uid: uid, gid: gid}
	time.Sleep(time.Second)
	return
}

// createVolumeDir() creates a directory on the running host using the user ids
// found within the specified image. For example, it can create a directory owned
// by the mysql user (as seen by the container) despite there being no mysql user
// on the host system.
// Assumes that the local docker image (imageSpec) exists and has been sync'd
// with the registry.
func createVolumeDir(conn client.Connection, hostPath, containerSpec, imageSpec, userSpec, permissionSpec string) error {

	if false {
		// use hostpath based filelock
		lockfile := ".serviced.initializing.lck"
		lockfileHostPath := path.Join(hostPath, lockfile)
		if lck, err := utils.LockFile(lockfileHostPath); err != nil {
			glog.Errorf("DFS volume init unable to lock %s", lockfileHostPath)
			return err
		} else {
			lck.Close()
			defer os.Remove(lockfileHostPath)
			glog.V(0).Infof("DFS volume init locked %s", lockfileHostPath)
		}
	} else {
		// use zookeeper lock of basename of hostPath (volume name)
		zkVolumeInitLock := path.Join("/locks/volumeinit", filepath.Base(hostPath))
		lock := conn.NewLock(zkVolumeInitLock)
		lock.Lock()
		defer lock.Unlock()
	}

	// return if service volume has been initialized
	dotfileCompatibility := path.Join(hostPath, ".serviced.initialized") // for compatibility with previous versions of serviced
	dotfileHostPath := path.Join(filepath.Dir(hostPath), fmt.Sprintf(".%s.serviced.initialized", filepath.Base(hostPath)))
	dotfiles := []string{dotfileCompatibility, dotfileHostPath}
	for _, dotfileHostPath := range dotfiles {
		_, err := os.Stat(dotfileHostPath)
		if err == nil {
			glog.V(2).Infof("DFS volume initialized earlier for src:%s dst:%s image:%s user:%s perm:%s", hostPath, containerSpec, imageSpec, userSpec, permissionSpec)
			return nil
		}
	}

	// start initializing dfs volume dir with dir in image
	starttime := time.Now()

	var err error
	var output []byte
	command := [...]string{
		"docker", "run",
		"--rm", "--user=root", "--workdir=/",
		"-v", hostPath + ":/mnt/dfs",
		imageSpec,
		"/bin/bash", "-c",
		fmt.Sprintf(`
set -e
if [ ! -d "%s" ]; then
	echo "WARNING: DFS mount %s does not exist in image %s"
else
	cp -rp %s/. /mnt/dfs/
fi
chown %s /mnt/dfs
chmod %s /mnt/dfs
sync
`, containerSpec, containerSpec, imageSpec, containerSpec, userSpec, permissionSpec),
	}

	for i := 0; i < 2; i++ {
		docker := exec.Command(command[0], command[1:]...)
		output, err = docker.CombinedOutput()
		if err == nil {
			duration := time.Now().Sub(starttime)
			if strings.Contains(string(output), "WARNING:") {
				glog.Warning(string(output))
			} else {
				glog.Info(string(output))
			}
			glog.Infof("DFS volume init #%d took %s for src:%s dst:%s image:%s user:%s perm:%s", i, duration, hostPath, containerSpec, imageSpec, userSpec, permissionSpec)

			if e := ioutil.WriteFile(dotfileHostPath, []byte(""), 0664); e != nil {
				glog.Errorf("unable to create DFS volume initialized dotfile %s: %s", dotfileHostPath, e)
				return e
			}
			return nil
		}
		time.Sleep(time.Second)
		glog.Warningf("retrying due to error creating DFS volume %+v: %s", hostPath, string(output))
	}

	glog.Errorf("could not create DFS volume %+v: %s", hostPath, string(output))
	return err
}

// In the container
func AddToEtcHosts(host, ip string) error {
	// First make sure /etc/hosts is writeable
	command := []string{
		"/bin/bash", "-c", fmt.Sprintf(`
if [ -n "$(mount | grep /etc/hosts)" ]; then \
	cat /etc/hosts > /tmp/etchosts; \
	umount /etc/hosts; \
	mv /tmp/etchosts /etc/hosts; \
fi; \
echo "%s %s" >> /etc/hosts`, ip, host)}
	return exec.Command(command[0], command[1:]...).Run()
}
<commit_msg>Added some error logging around lock and unlock<commit_after>// Copyright 2014 The Serviced Authors.
// 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.

// Package agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

package node

import (
	"github.com/control-center/serviced/coordinator/client"
	"github.com/zenoss/glog"

	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

// validOwnerSpec returns true if the owner is specified in owner:group format and the
// identifiers are valid POSIX.1-2008 username and group strings, respectively.
func validOwnerSpec(owner string) bool {
	var pattern = regexp.MustCompile(`^[a-zA-Z]+[a-zA-Z0-9.-]*:[a-zA-Z]+[a-zA-Z0-9.-]*$`)
	return pattern.MatchString(owner)
}

// GetInterfaceIPAddress attempts to find the IP address based on interface name
func GetInterfaceIPAddress(_interface string) (string, error) {
	output, err := exec.Command("/sbin/ip", "-4", "-o", "addr").Output()
	if err != nil {
		return "", err
	}

	for _, line := range strings.Split(string(output), "\n") {
		fields := strings.Fields(line)
		if len(fields) < 4 {
			continue
		}

		if strings.HasPrefix(fields[1], _interface) {
			return strings.Split(fields[3], "/")[0], nil
		}
	}

	return "", fmt.Errorf("Unable to find ip for interface: %s", _interface)
}

// getIPAddrFromOutGoingConnection get the IP bound to the interface which
// handles the default route traffic.
func getIPAddrFromOutGoingConnection() (ip string, err error) {
	addr, err := net.ResolveUDPAddr("udp4", "8.8.8.8:53")
	if err != nil {
		return "", err
	}

	conn, err := net.DialUDP("udp4", nil, addr)
	if err != nil {
		return "", err
	}

	localAddr := conn.LocalAddr()
	parts := strings.Split(localAddr.String(), ":")
	return parts[0], nil
}

// ExecPath returns the path to the currently running executable.
func ExecPath() (string, string, error) {
	path, err := os.Readlink("/proc/self/exe")
	if err != nil {
		return "", "", err
	}
	return filepath.Dir(path), filepath.Base(path), nil
}

// DockerVersion contains the tuples that describe the version of docker.
type DockerVersion struct {
	Client []int
	Server []int
}

// equals compares two DockerVersion structs and returns true if they are equal.
func (a *DockerVersion) equals(b *DockerVersion) bool {
	if len(a.Client) != len(b.Client) {
		return false
	}
	for i, aI := range a.Client {
		if aI != b.Client[i] {
			return false
		}
	}
	if len(a.Server) != len(b.Server) {
		return false
	}
	for i, aI := range a.Server {
		if aI != b.Server[i] {
			return false
		}
	}
	return true
}

// GetDockerVersion returns docker version number.
func GetDockerVersion() (DockerVersion, error) {
	cmd := exec.Command("docker", "version")
	output, err := cmd.Output()
	if err != nil {
		return DockerVersion{}, err
	}
	return parseDockerVersion(string(output))
}

// parseDockerVersion parses the output of the 'docker version' commmand and
// returns a DockerVersion object.
func parseDockerVersion(output string) (version DockerVersion, err error) {

	for _, line := range strings.Split(output, "\n") {
		parts := strings.SplitN(line, ":", 2)
		if len(parts) < 2 {
			continue
		}
		if strings.HasPrefix(parts[0], "Client version") {
			a := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2)
			b := strings.Split(a[0], ".")
			version.Client = make([]int, len(b))
			for i, v := range b {
				x, err := strconv.Atoi(v)
				if err != nil {
					return version, err
				}
				version.Client[i] = x
			}
		}
		if strings.HasPrefix(parts[0], "Server version") {
			a := strings.SplitN(strings.TrimSpace(parts[1]), "-", 2)
			b := strings.Split(a[0], ".")
			version.Server = make([]int, len(b))
			for i, v := range b {
				x, err := strconv.Atoi(v)
				if err != nil {
					return version, err
				}
				version.Server[i] = x
			}
		}
	}
	if len(version.Client) == 0 {
		return version, fmt.Errorf("no client version found")
	}
	if len(version.Server) == 0 {
		return version, fmt.Errorf("no server version found")
	}
	return version, nil
}

// CreateDirectory creates a directory using the given username as the owner and the
// given perm as the directory permission.
func CreateDirectory(path, username string, perm os.FileMode) error {
	user, err := user.Lookup(username)
	if err == nil {
		err = os.MkdirAll(path, perm)
		if err == nil || err == os.ErrExist {
			uid, _ := strconv.Atoi(user.Uid)
			gid, _ := strconv.Atoi(user.Gid)
			err = os.Chown(path, uid, gid)
		}
	}
	return err
}

// singleJoiningSlash joins a and b ensuring there is only a single /
// character between them.
func singleJoiningSlash(a, b string) string {
	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")
	switch {
	case aslash && bslash:
		return a + b[1:]
	case !aslash && !bslash:
		return a + "/" + b
	}
	return a + b
}

// NewReverseProxy differs from httputil.NewSingleHostReverseProxy in that it rewrites
// the path so that it does /not/ include the incoming path. e.g. request for
// "/mysvc/thing" when proxy is served from "/mysvc" means target is
// targeturl.Path + "/thing"; vs. httputil.NewSingleHostReverseProxy, in which
// it would be targeturl.Path + "/mysvc/thing".
func NewReverseProxy(path string, targeturl *url.URL) *httputil.ReverseProxy {
	targetQuery := targeturl.RawQuery
	director := func(r *http.Request) {
		r.URL.Scheme = targeturl.Scheme
		r.URL.Host = targeturl.Host
		newpath := strings.TrimPrefix(r.URL.Path, path)
		r.URL.Path = singleJoiningSlash(targeturl.Path, newpath)
		if targetQuery == "" || r.URL.RawQuery == "" {
			r.URL.RawQuery = targetQuery + r.URL.RawQuery
		} else {
			r.URL.RawQuery = targetQuery + "&" + r.URL.RawQuery
		}
	}
	return &httputil.ReverseProxy{Director: director}
}

// Assumes that the local docker image (imageSpec) exists and has been sync'd
// with the registry.
var dockerRun = func(imageSpec string, args ...string) (output string, err error) {
	targs := []string{"run", imageSpec}
	for _, s := range args {
		targs = append(targs, s)
	}
	docker := exec.Command("docker", targs...)
	var outputBytes []byte
	outputBytes, err = docker.Output()
	if err != nil {
		return
	}
	output = string(outputBytes)
	return
}

type uidgid struct {
	uid int
	gid int
}

var userSpecCache struct {
	lookup map[string]uidgid
	sync.Mutex
}

func init() {
	userSpecCache.lookup = make(map[string]uidgid)
}

// Assumes that the local docker image (imageSpec) exists and has been sync'd
// with the registry.
func getInternalImageIDs(userSpec, imageSpec string) (uid, gid int, err error) {

	userSpecCache.Lock()
	defer userSpecCache.Unlock()

	key := userSpec + "!" + imageSpec
	if val, found := userSpecCache.lookup[key]; found {
		return val.uid, val.gid, nil
	}

	var output string
	// explicitly ignoring errors because of -rm under load
	output, _ = dockerRun(imageSpec, "/bin/sh", "-c",
		fmt.Sprintf(`touch test.txt && chown %s test.txt && ls -ln test.txt | awk '{ print $3, $4 }'`,
			userSpec))

	s := strings.TrimSpace(string(output))
	pattern := regexp.MustCompile(`^\d+ \d+$`)

	if !pattern.MatchString(s) {
		err = fmt.Errorf("unexpected output from getInternalImageIDs: %s", s)
		return
	}
	fields := strings.Fields(s)
	if len(fields) != 2 {
		err = fmt.Errorf("unexpected number of fields from container spec: %s", fields)
		return
	}
	uid, err = strconv.Atoi(fields[0])
	if err != nil {
		return
	}
	gid, err = strconv.Atoi(fields[1])
	if err != nil {
		return
	}
	// cache the results
	userSpecCache.lookup[key] = uidgid{uid: uid, gid: gid}
	time.Sleep(time.Second)
	return
}

// createVolumeDir() creates a directory on the running host using the user ids
// found within the specified image. For example, it can create a directory owned
// by the mysql user (as seen by the container) despite there being no mysql user
// on the host system.
// Assumes that the local docker image (imageSpec) exists and has been sync'd
// with the registry.
func createVolumeDir(conn client.Connection, hostPath, containerSpec, imageSpec, userSpec, permissionSpec string) error {

	// use zookeeper lock of basename of hostPath (volume name)
	zkVolumeInitLock := path.Join("/locks/volumeinit", filepath.Base(hostPath))
	lock := conn.NewLock(zkVolumeInitLock)
	if err := lock.Lock(); err != nil {
		glog.Errorf("Could not acquire lock for %s: %s", zkVolumeInitLock, err)
		return err
	}
	defer func() {
		if err := lock.Unlock(); err != nil {
			glog.Errorf("Could not unlock %s: %s", zkVolumeInitLock, err)
		}
	}()

	// return if service volume has been initialized
	dotfileCompatibility := path.Join(hostPath, ".serviced.initialized") // for compatibility with previous versions of serviced
	dotfileHostPath := path.Join(filepath.Dir(hostPath), fmt.Sprintf(".%s.serviced.initialized", filepath.Base(hostPath)))
	dotfiles := []string{dotfileCompatibility, dotfileHostPath}
	for _, dotfileHostPath := range dotfiles {
		_, err := os.Stat(dotfileHostPath)
		if err == nil {
			glog.V(2).Infof("DFS volume initialized earlier for src:%s dst:%s image:%s user:%s perm:%s", hostPath, containerSpec, imageSpec, userSpec, permissionSpec)
			return nil
		}
	}

	// start initializing dfs volume dir with dir in image
	starttime := time.Now()

	var err error
	var output []byte
	command := [...]string{
		"docker", "run",
		"--rm", "--user=root", "--workdir=/",
		"-v", hostPath + ":/mnt/dfs",
		imageSpec,
		"/bin/bash", "-c",
		fmt.Sprintf(`
set -e
if [ ! -d "%s" ]; then
	echo "WARNING: DFS mount %s does not exist in image %s"
else
	cp -rp %s/. /mnt/dfs/
fi
chown %s /mnt/dfs
chmod %s /mnt/dfs
sync
`, containerSpec, containerSpec, imageSpec, containerSpec, userSpec, permissionSpec),
	}

	for i := 0; i < 2; i++ {
		docker := exec.Command(command[0], command[1:]...)
		output, err = docker.CombinedOutput()
		if err == nil {
			duration := time.Now().Sub(starttime)
			if strings.Contains(string(output), "WARNING:") {
				glog.Warning(string(output))
			} else {
				glog.Info(string(output))
			}
			glog.Infof("DFS volume init #%d took %s for src:%s dst:%s image:%s user:%s perm:%s", i, duration, hostPath, containerSpec, imageSpec, userSpec, permissionSpec)

			if e := ioutil.WriteFile(dotfileHostPath, []byte(""), 0664); e != nil {
				glog.Errorf("unable to create DFS volume initialized dotfile %s: %s", dotfileHostPath, e)
				return e
			}
			return nil
		}
		time.Sleep(time.Second)
		glog.Warningf("retrying due to error creating DFS volume %+v: %s", hostPath, string(output))
	}

	glog.Errorf("could not create DFS volume %+v: %s", hostPath, string(output))
	return err
}

// In the container
func AddToEtcHosts(host, ip string) error {
	// First make sure /etc/hosts is writeable
	command := []string{
		"/bin/bash", "-c", fmt.Sprintf(`
if [ -n "$(mount | grep /etc/hosts)" ]; then \
	cat /etc/hosts > /tmp/etchosts; \
	umount /etc/hosts; \
	mv /tmp/etchosts /etc/hosts; \
fi; \
echo "%s %s" >> /etc/hosts`, ip, host)}
	return exec.Command(command[0], command[1:]...).Run()
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

// A helper that allows using gcsfuse with mount(8).
//
// Can be invoked using a command-line of the form expected for mount helpers.
// Calls the gcsfuse binary, which it finds from one of a list of expected
// locations, and waits for it to complete. The device and mount point are
// passed on as positional arguments, and other known options are converted to
// appropriate flags.
//
// This binary returns with exit code zero only after gcsfuse has reported that
// it has successfuly mounted the file system. Further output from gcsfuse is
// suppressed.
package main

// Example invocation on OS X:
//
//     mount -t porp -o foo=bar\ baz -o ro,blah bucket ~/tmp/mp
//
// becomes the following arguments:
//
//     Arg 0: "/sbin/mount_gcsfuse "
//     Arg 1: "-o"
//     Arg 2: "foo=bar baz"
//     Arg 3: "-o"
//     Arg 4: "ro"
//     Arg 5: "-o"
//     Arg 6: "blah"
//     Arg 7: "bucket"
//     Arg 8: "/path/to/mp"
//
// On Linux, the fstab entry
//
//     bucket /path/to/mp porp user,foo=bar\040baz
//
// becomes
//
//     Arg 0: "/sbin/mount.gcsfuse"
//     Arg 1: "bucket"
//     Arg 2: "/path/to/mp"
//     Arg 3: "-o"
//     Arg 4: "rw,noexec,nosuid,nodev,user,foo=bar baz"
//

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/googlecloudplatform/gcsfuse/internal/mount"
)

// Turn mount-style options into gcsfuse arguments. Skip known detritus that
// the mount command gives us.
//
// The result of this function should be appended to exec.Command.Args.
func makeGcsfuseArgs(
	device string,
	mountPoint string,
	opts map[string]string) (args []string, err error) {
	// Deal with options.
	for name, value := range opts {
		switch name {
		// Don't pass through options that are relevant to mount(8) but not to
		// gcsfuse, and that fusermount chokes on with "Invalid argument" on Linux.
		case "user", "nouser", "auto", "noauto", "_netdev", "no_netdev":

		// Special case: support mount-like formatting for gcsfuse bool flags.
		case "implicit_dirs":
			args = append(
				args,
				"--"+strings.Replace(name, "_", "-", -1),
			)

		// Special case: support mount-like formatting for gcsfuse string flags.
		case "key_file":
			args = append(
				args,
				"--"+strings.Replace(name, "_", "-", -1),
				value,
			)

		// Special case: support mount-like formatting for gcsfuse string flags.
		case "dir_mode", "file_mode":
			args = append(
				args,
				"--"+strings.Replace(name, "_", "-", -1),
				value,
			)

		// Pass through everything else.
		default:
			var formatted string
			if value == "" {
				formatted = name
			} else {
				formatted = fmt.Sprintf("%s=%s", name, value)
			}

			args = append(args, "-o", formatted)
		}
	}

	// Set the bucket and mount point.
	args = append(args, device, mountPoint)

	return
}

// Parse the supplied command-line arguments from a mount(8) invocation on OS X
// or Linux.
func parseArgs(
	args []string) (
	device string,
	mountPoint string,
	opts map[string]string,
	err error) {
	opts = make(map[string]string)

	// Process each argument in turn.
	positionalCount := 0
	for i, s := range args {
		switch {
		// Skip the program name.
		case i == 0:
			continue

		// "-o" is illegal only when at the end. We handle its argument in the case
		// below.
		case s == "-o":
			if i == len(args)-1 {
				err = fmt.Errorf("Unexpected -o at end of args.")
				return
			}

		// Is this an options string following a "-o"?
		case i > 0 && args[i-1] == "-o":
			mount.ParseOptions(opts, s)

		// Is this the device?
		case positionalCount == 0:
			device = s
			positionalCount++

		// Is this the mount point?
		case positionalCount == 1:
			mountPoint = s
			positionalCount++

		default:
			err = fmt.Errorf("Unexpected arg %d: %q", i, s)
			return
		}
	}

	if positionalCount != 2 {
		err = fmt.Errorf("Expected two positional arguments; got %d.", positionalCount)
		return
	}

	return
}

func run(args []string) (err error) {
	// If invoked with a single "--help" argument, print a usage message and exit
	// successfully.
	if len(args) == 2 && args[1] == "--help" {
		fmt.Fprintf(
			os.Stderr,
			"Usage: %s [-o options] bucket_name mount_point\n",
			args[0])

		return
	}

	// Find the path to gcsfuse.
	gcsfusePath, err := findGcsfuse()
	if err != nil {
		err = fmt.Errorf("findGcsfuse: %v", err)
		return
	}

	// Find the path to fusermount.
	fusermountPath, err := findFusermount()
	if err != nil {
		err = fmt.Errorf("findFusermount: %v", err)
		return
	}

	// Attempt to parse arguments.
	device, mountPoint, opts, err := parseArgs(args)
	if err != nil {
		err = fmt.Errorf("parseArgs: %v", err)
		return
	}

	// Choose gcsfuse args.
	gcsfuseArgs, err := makeGcsfuseArgs(device, mountPoint, opts)
	if err != nil {
		err = fmt.Errorf("makeGcsfuseArgs: %v", err)
		return
	}

	fmt.Fprintf(
		os.Stderr,
		"Calling gcsfuse with arguments: %s\n",
		strings.Join(gcsfuseArgs, " "))

	// Run gcsfuse.
	cmd := exec.Command(gcsfusePath, gcsfuseArgs...)
	cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", path.Dir(fusermountPath)))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		err = fmt.Errorf("running gcsfuse: %v", err)
		return
	}

	return
}

func main() {
	err := run(os.Args)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<commit_msg>Patch for gcsfuse systemd integration<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

// A helper that allows using gcsfuse with mount(8).
//
// Can be invoked using a command-line of the form expected for mount helpers.
// Calls the gcsfuse binary, which it finds from one of a list of expected
// locations, and waits for it to complete. The device and mount point are
// passed on as positional arguments, and other known options are converted to
// appropriate flags.
//
// This binary returns with exit code zero only after gcsfuse has reported that
// it has successfuly mounted the file system. Further output from gcsfuse is
// suppressed.
package main

// Example invocation on OS X:
//
//     mount -t porp -o foo=bar\ baz -o ro,blah bucket ~/tmp/mp
//
// becomes the following arguments:
//
//     Arg 0: "/sbin/mount_gcsfuse "
//     Arg 1: "-o"
//     Arg 2: "foo=bar baz"
//     Arg 3: "-o"
//     Arg 4: "ro"
//     Arg 5: "-o"
//     Arg 6: "blah"
//     Arg 7: "bucket"
//     Arg 8: "/path/to/mp"
//
// On Linux, the fstab entry
//
//     bucket /path/to/mp porp user,foo=bar\040baz
//
// becomes
//
//     Arg 0: "/sbin/mount.gcsfuse"
//     Arg 1: "bucket"
//     Arg 2: "/path/to/mp"
//     Arg 3: "-o"
//     Arg 4: "rw,noexec,nosuid,nodev,user,foo=bar baz"
//

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/googlecloudplatform/gcsfuse/internal/mount"
)

// Turn mount-style options into gcsfuse arguments. Skip known detritus that
// the mount command gives us.
//
// The result of this function should be appended to exec.Command.Args.
func makeGcsfuseArgs(
	device string,
	mountPoint string,
	opts map[string]string) (args []string, err error) {
	// Deal with options.
	for name, value := range opts {
		switch name {
		// Don't pass through options that are relevant to mount(8) but not to
		// gcsfuse, and that fusermount chokes on with "Invalid argument" on Linux.
		case "user", "nouser", "auto", "noauto", "_netdev", "no_netdev":

		// Special case: support mount-like formatting for gcsfuse bool flags.
		case "implicit_dirs":
			args = append(
				args,
				"--"+strings.Replace(name, "_", "-", -1),
			)

		// Special case: support mount-like formatting for gcsfuse string flags.
		case "key_file":
			args = append(
				args,
				"--"+strings.Replace(name, "_", "-", -1),
				value,
			)

		// Special case: support mount-like formatting for gcsfuse string flags.
		case "dir_mode", "file_mode":
			args = append(
				args,
				"--"+strings.Replace(name, "_", "-", -1),
				value,
			)

		// Pass through everything else.
		default:
			var formatted string
			if value == "" {
				formatted = name
			} else {
				formatted = fmt.Sprintf("%s=%s", name, value)
			}

			args = append(args, "-o", formatted)
		}
	}

	// Set the bucket and mount point.
	args = append(args, device, mountPoint)

	return
}

// Parse the supplied command-line arguments from a mount(8) invocation on OS X
// or Linux.
func parseArgs(
	args []string) (
	device string,
	mountPoint string,
	opts map[string]string,
	err error) {
	opts = make(map[string]string)

	// Process each argument in turn.
	positionalCount := 0
	for i, s := range args {
		switch {
		// Skip the program name.
		case i == 0:
			continue

		// "-o" is illegal only when at the end. We handle its argument in the case
		// below.
		case s == "-o":
			if i == len(args)-1 {
				err = fmt.Errorf("Unexpected -o at end of args.")
				return
			}

		// Work around for systemd .mount file integration with gcsfuse
		case s == "-n":
			continue

		// Is this an options string following a "-o"?
		case i > 0 && args[i-1] == "-o":
			mount.ParseOptions(opts, s)

		// Is this the device?
		case positionalCount == 0:
			device = s
			positionalCount++

		// Is this the mount point?
		case positionalCount == 1:
			mountPoint = s
			positionalCount++

		default:
			err = fmt.Errorf("Unexpected arg %d: %q", i, s)
			return
		}
	}

	if positionalCount != 2 {
		err = fmt.Errorf("Expected two positional arguments; got %d.", positionalCount)
		return
	}

	return
}

func run(args []string) (err error) {
	// If invoked with a single "--help" argument, print a usage message and exit
	// successfully.
	if len(args) == 2 && args[1] == "--help" {
		fmt.Fprintf(
			os.Stderr,
			"Usage: %s [-o options] bucket_name mount_point\n",
			args[0])

		return
	}

	// Find the path to gcsfuse.
	gcsfusePath, err := findGcsfuse()
	if err != nil {
		err = fmt.Errorf("findGcsfuse: %v", err)
		return
	}

	// Find the path to fusermount.
	fusermountPath, err := findFusermount()
	if err != nil {
		err = fmt.Errorf("findFusermount: %v", err)
		return
	}

	// Attempt to parse arguments.
	device, mountPoint, opts, err := parseArgs(args)
	if err != nil {
		err = fmt.Errorf("parseArgs: %v", err)
		return
	}

	// Choose gcsfuse args.
	gcsfuseArgs, err := makeGcsfuseArgs(device, mountPoint, opts)
	if err != nil {
		err = fmt.Errorf("makeGcsfuseArgs: %v", err)
		return
	}

	fmt.Fprintf(
		os.Stderr,
		"Calling gcsfuse with arguments: %s\n",
		strings.Join(gcsfuseArgs, " "))

	// Run gcsfuse.
	cmd := exec.Command(gcsfusePath, gcsfuseArgs...)
	cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", path.Dir(fusermountPath)))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		err = fmt.Errorf("running gcsfuse: %v", err)
		return
	}

	return
}

func main() {
	err := run(os.Args)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>// +build V7

package main

import (
	"fmt"

	plugin "code.cloudfoundry.org/cli/plugin/v7"
)

type Test1 struct {
}

func (c *Test1) Run(cliConnection plugin.CliConnection, args []string) {
	switch args[0] {
	case "ApiEndpoint":
		result, _ := cliConnection.ApiEndpoint()
		fmt.Println("Done ApiEndpoint:", result)
	case "GetApp":
		result, _ := cliConnection.GetApp(args[1])
		fmt.Println("Done GetApp:", result)
	case "GetCurrentSpace":
		result, _ := cliConnection.GetCurrentSpace()
		fmt.Printf("Done GetCurrentSpace:, result:%v, name: %s, guid: %s\n", result, result.Name, result.GUID)
	case "TestPluginCommandWithAliasV7", "Cool-V7":
		fmt.Println("You called Test Plugin Command V7 With Alias!")
	case "AccessToken":
		result, _ := cliConnection.AccessToken()
		fmt.Println("Done AccessToken:", result)
	case "CoolTest":
		fmt.Println("I am a test plugin")
	}
}
func (c *Test1) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "CF-CLI-Integration-Test-Plugin",
		Version: plugin.VersionType{
			Major: 6,
			Minor: 0,
			Build: 0,
		},
		MinCliVersion: plugin.VersionType{
			Major: 6,
			Minor: 0,
			Build: 0,
		},
		Commands: []plugin.Command{
			{Name: "ApiEndpoint"},
			{Name: "GetApp"},
			{Name: "GetCurrentSpace"},
			{
				Name:     "TestPluginCommandWithAliasV7",
				Alias:    "Cool-V7",
				HelpText: "This is my plugin help test. Banana.",
				UsageDetails: plugin.Usage{
					Usage: "I R Usage",
					Options: map[string]string{
						"--dis-flag": "is a flag",
					},
				},
			},
			{Name: "CoolTest"},
			{Name: "AccessToken"},
		},
	}
}

// func uninstalling() {
//  os.Remove(filepath.Join(os.TempDir(), "uninstall-test-file-for-test_1.exe"))
// }

func main() {
	plugin.Start(new(Test1))
}
<commit_msg>Display potential error from GetCurrentSpace<commit_after>// +build V7

package main

import (
	"fmt"

	plugin "code.cloudfoundry.org/cli/plugin/v7"
)

type Test1 struct {
}

func (c *Test1) Run(cliConnection plugin.CliConnection, args []string) {
	switch args[0] {
	case "ApiEndpoint":
		result, _ := cliConnection.ApiEndpoint()
		fmt.Println("Done ApiEndpoint:", result)
	case "GetApp":
		result, _ := cliConnection.GetApp(args[1])
		fmt.Println("Done GetApp:", result)
	case "GetCurrentSpace":
		result, err := cliConnection.GetCurrentSpace()
		if err != nil {
			fmt.Printf("Error: %s", err)
		} else {
			fmt.Printf("Done GetCurrentSpace:, result:%v, name: %s, guid: %s\n", result, result.Name, result.GUID)
		}
	case "TestPluginCommandWithAliasV7", "Cool-V7":
		fmt.Println("You called Test Plugin Command V7 With Alias!")
	case "AccessToken":
		result, _ := cliConnection.AccessToken()
		fmt.Println("Done AccessToken:", result)
	case "CoolTest":
		fmt.Println("I am a test plugin")
	}
}
func (c *Test1) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "CF-CLI-Integration-Test-Plugin",
		Version: plugin.VersionType{
			Major: 6,
			Minor: 0,
			Build: 0,
		},
		MinCliVersion: plugin.VersionType{
			Major: 6,
			Minor: 0,
			Build: 0,
		},
		Commands: []plugin.Command{
			{Name: "ApiEndpoint"},
			{Name: "GetApp"},
			{Name: "GetCurrentSpace"},
			{
				Name:     "TestPluginCommandWithAliasV7",
				Alias:    "Cool-V7",
				HelpText: "This is my plugin help test. Banana.",
				UsageDetails: plugin.Usage{
					Usage: "I R Usage",
					Options: map[string]string{
						"--dis-flag": "is a flag",
					},
				},
			},
			{Name: "CoolTest"},
			{Name: "AccessToken"},
		},
	}
}

// func uninstalling() {
//  os.Remove(filepath.Join(os.TempDir(), "uninstall-test-file-for-test_1.exe"))
// }

func main() {
	plugin.Start(new(Test1))
}
<|endoftext|>