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

package triage

import (
	"context"
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/provider"

	"github.com/google/triage-party/pkg/hubbub"
	"github.com/google/triage-party/pkg/logu"
	"k8s.io/klog/v2"
)

// Rule is a logical triage group
type Rule struct {
	ID         string
	Resolution string            `yaml:"resolution,omitempty"`
	Name       string            `yaml:"name,omitempty"`
	Repos      []string          `yaml:"repos,omitempty"`
	Type       string            `yaml:"type,omitempty"`
	Filters    []provider.Filter `yaml:"filters"`
}

type RuleResult struct {
	Rule  Rule
	Items []*hubbub.Conversation

	AvgAge             time.Duration
	AvgCurrentHold     time.Duration
	AvgAccumulatedHold time.Duration

	// Avoiding time.Duration because it's easy to int64 overflow
	TotalAgeDays             float64
	TotalCurrentHoldDays     float64
	TotalAccumulatedHoldDays float64

	Duplicates map[string]bool

	// OldestInput is the timestamp of the oldest input data
	OldestInput time.Time

	// When was this rule result created?
	Created time.Time
}

// SummarizeRuleResult adds together statistics about a pool of conversations
func SummarizeRuleResult(t Rule, cs []*hubbub.Conversation, seen map[string]*Rule) *RuleResult {
	r := &RuleResult{
		Rule:       t,
		Duplicates: map[string]bool{},
	}

	if seen == nil {
		r.Items = cs
	} else {
		for i, c := range cs {
			if c == nil {
				klog.Errorf("SummarizeRuleResult conversation #%d is nil: %+v", i, c)
				continue
			}

			dupeRule := seen[c.URL]
			if dupeRule != nil {
				// Find a nefarious bug
				if t.ID == dupeRule.ID {
					klog.Errorf("LOGIC FAILURE: %s was previously seen by rule %q, which is the same as the current rule: %q", c.URL, dupeRule.ID, t.ID)
					continue
				}

				klog.V(2).Infof("dupe: %s (now: %q, previous: %q)", c.URL, t.ID, dupeRule.ID)
				r.Duplicates[c.URL] = true
			}
			r.Items = append(r.Items, c)
			seen[c.URL] = &t
		}
	}

	if len(cs) == 0 {
		return r
	}

	for i, c := range cs {
		if c == nil {
			klog.Errorf("SummarizeRuleResult conversation #%d is nil: %+v", i, c)
			continue
		}

		if c.Created.After(time.Now()) {
			klog.Errorf("#%d claims to have be newer than now: %s", c.ID, c.Created)
			continue
		}
		r.TotalAgeDays += time.Since(c.Created).Hours() / 24
		r.TotalCurrentHoldDays += c.CurrentHoldTime.Hours() / 24
		r.TotalAccumulatedHoldDays += c.AccumulatedHoldTime.Hours() / 24
	}

	count := len(cs)

	r.AvgAge = avgDayDuration(r.TotalAgeDays, count)
	r.AvgCurrentHold = avgDayDuration(r.TotalCurrentHoldDays, count)
	r.AvgAccumulatedHold = avgDayDuration(r.TotalAccumulatedHoldDays, count)
	r.Created = time.Now()
	return r
}

// ExecuteRule executes a rule. seen is optional.
func (p *Party) ExecuteRule(ctx context.Context, sp provider.SearchParams, t Rule, seen map[string]*Rule, s *Collection) (*RuleResult, error) {
	klog.V(1).Infof("executing rule %q for results newer than %s", t.ID, logu.STime(sp.NewerThan))
	rcs := []*hubbub.Conversation{}
	oldest := time.Now()

	if s != nil {
		if len(t.Repos) == 0 && len(s.Repos) > 0 {
			t.Repos = s.Repos
		}
	}

	for _, repoUrl := range t.Repos {
		r, err := parseRepo(repoUrl)
		if err != nil {
			return nil, err
		}

		klog.V(2).Infof("%s -> org=%s project=%s", repoUrl, r.Organization, r.Project)

		var ts time.Time
		var cs []*hubbub.Conversation

		sp.Repo = r
		sp.Filters = t.Filters

		switch t.Type {
		case hubbub.Issue:
			cs, ts, err = p.engine.SearchIssues(ctx, sp)
		case hubbub.PullRequest:
			cs, ts, err = p.engine.SearchPullRequests(ctx, sp)
		default:
			cs, ts, err = p.engine.SearchAny(ctx, sp)
		}

		if err != nil {
			return nil, err
		}

		rcs = append(rcs, cs...)
		if ts.Before(oldest) {
			oldest = ts
		}
	}

	klog.V(1).Infof("rule %q matched %d items", t.ID, len(rcs))
	rr := SummarizeRuleResult(t, rcs, seen)
	rr.OldestInput = oldest
	return rr, nil
}

// Return a fully resolved rule
func (p *Party) LookupRule(id string) (Rule, error) {
	t, ok := p.rules[id]
	if !ok {
		return t, fmt.Errorf("rule %q is undefined - typo?", id)
	}
	t.ID = id
	if len(p.reposOverride) > 0 {
		t.Repos = p.reposOverride
	}

	if len(t.Repos) == 0 {
		t.Repos = p.settings.Repos
	}
	return t, nil
}

// ListRules fully resolved rules
func (p *Party) ListRules() ([]Rule, error) {
	ts := []Rule{}
	for k := range p.rules {
		s, err := p.LookupRule(k)
		if err != nil {
			return ts, err
		}
		ts = append(ts, s)
	}
	return ts, nil
}

// parseRepo returns provider, organization and project for a URL
// rawURL should be a valid url with host like https://github.com/org/repo
// or https://gitlab.com/org/repo
// or https://gitlab.com/org/group/repo
func parseRepo(rawURL string) (r provider.Repo, err error) {
	u, err := url.Parse(rawURL)
	if err != nil {
		return
	}
	if u.Host == "" {
		err = fmt.Errorf("Provided string %s is not a valid URL", rawURL)
		return
	}
	parts := strings.Split(u.Path, "/")
	if len(parts) != 3 && len(parts) != 4 {
		// gitlab may have https://gitlab.com/organization/group/repo
		err = fmt.Errorf("expected 2/3 repository parts, got %d: %v", len(parts), parts)
		return
	}
	if len(parts) == 3 {
		r = provider.Repo{
			Host:         u.Host,
			Organization: parts[1],
			Project:      parts[2],
		}
	} else {
		r = provider.Repo{
			Host:         u.Host,
			Organization: parts[1],
			Group:        parts[2],
			Project:      parts[3],
		}
	}

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

package triage

import (
	"context"
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/provider"

	"github.com/google/triage-party/pkg/hubbub"
	"github.com/google/triage-party/pkg/logu"
	"k8s.io/klog/v2"
)

// Rule is a logical triage group
type Rule struct {
	ID         string
	Resolution string            `yaml:"resolution,omitempty"`
	Name       string            `yaml:"name,omitempty"`
	Repos      []string          `yaml:"repos,omitempty"`
	Type       string            `yaml:"type,omitempty"`
	Filters    []provider.Filter `yaml:"filters"`
}

type RuleResult struct {
	Rule  Rule
	Items []*hubbub.Conversation

	AvgAge             time.Duration
	AvgCurrentHold     time.Duration
	AvgAccumulatedHold time.Duration

	// Avoiding time.Duration because it's easy to int64 overflow
	TotalAgeDays             float64
	TotalCurrentHoldDays     float64
	TotalAccumulatedHoldDays float64

	Duplicates map[string]bool

	// OldestInput is the timestamp of the oldest input data
	OldestInput time.Time

	// When was this rule result created?
	Created time.Time
}

// SummarizeRuleResult adds together statistics about a pool of conversations
func SummarizeRuleResult(t Rule, cs []*hubbub.Conversation, seen map[string]*Rule) *RuleResult {
	r := &RuleResult{
		Rule:       t,
		Duplicates: map[string]bool{},
	}

	if seen == nil {
		r.Items = cs
	} else {
		for i, c := range cs {
			if c == nil {
				klog.Errorf("SummarizeRuleResult conversation #%d is nil: %+v", i, c)
				continue
			}

			dupeRule := seen[c.URL]
			if dupeRule != nil {
				// Find a nefarious bug
				if t.ID == dupeRule.ID {
					klog.Errorf("LOGIC FAILURE: %s was previously seen by rule %q, which is the same as the current rule: %q", c.URL, dupeRule.ID, t.ID)
					continue
				}

				klog.V(2).Infof("dupe: %s (now: %q, previous: %q)", c.URL, t.ID, dupeRule.ID)
				r.Duplicates[c.URL] = true
			}
			r.Items = append(r.Items, c)
			seen[c.URL] = &t
		}
	}

	if len(cs) == 0 {
		return r
	}

	for i, c := range cs {
		if c == nil {
			klog.Errorf("SummarizeRuleResult conversation #%d is nil: %+v", i, c)
			continue
		}

		if c.Created.After(time.Now()) {
			klog.Errorf("#%d claims to have be newer than now: %s", c.ID, c.Created)
			continue
		}
		r.TotalAgeDays += time.Since(c.Created).Hours() / 24
		r.TotalCurrentHoldDays += c.CurrentHoldTime.Hours() / 24
		r.TotalAccumulatedHoldDays += c.AccumulatedHoldTime.Hours() / 24
	}

	count := len(cs)

	r.AvgAge = avgDayDuration(r.TotalAgeDays, count)
	r.AvgCurrentHold = avgDayDuration(r.TotalCurrentHoldDays, count)
	r.AvgAccumulatedHold = avgDayDuration(r.TotalAccumulatedHoldDays, count)
	r.Created = time.Now()
	return r
}

// ExecuteRule executes a rule. seen is optional.
func (p *Party) ExecuteRule(ctx context.Context, sp provider.SearchParams, t Rule, seen map[string]*Rule, s *Collection) (*RuleResult, error) {
	klog.V(1).Infof("executing rule %q for results newer than %s", t.ID, logu.STime(sp.NewerThan))
	rcs := []*hubbub.Conversation{}
	oldest := time.Now()

	if s != nil {
		if len(s.Repos) > 0 {
			t.Repos = s.Repos
		}
	}

	for _, repoUrl := range t.Repos {
		r, err := parseRepo(repoUrl)
		if err != nil {
			return nil, err
		}

		klog.V(2).Infof("%s -> org=%s project=%s", repoUrl, r.Organization, r.Project)

		var ts time.Time
		var cs []*hubbub.Conversation

		sp.Repo = r
		sp.Filters = t.Filters

		switch t.Type {
		case hubbub.Issue:
			cs, ts, err = p.engine.SearchIssues(ctx, sp)
		case hubbub.PullRequest:
			cs, ts, err = p.engine.SearchPullRequests(ctx, sp)
		default:
			cs, ts, err = p.engine.SearchAny(ctx, sp)
		}

		if err != nil {
			return nil, err
		}

		rcs = append(rcs, cs...)
		if ts.Before(oldest) {
			oldest = ts
		}
	}

	klog.V(1).Infof("rule %q matched %d items", t.ID, len(rcs))
	rr := SummarizeRuleResult(t, rcs, seen)
	rr.OldestInput = oldest
	return rr, nil
}

// Return a fully resolved rule
func (p *Party) LookupRule(id string) (Rule, error) {
	t, ok := p.rules[id]
	if !ok {
		return t, fmt.Errorf("rule %q is undefined - typo?", id)
	}
	t.ID = id
	if len(p.reposOverride) > 0 {
		t.Repos = p.reposOverride
	}

	if len(t.Repos) == 0 {
		t.Repos = p.settings.Repos
	}
	return t, nil
}

// ListRules fully resolved rules
func (p *Party) ListRules() ([]Rule, error) {
	ts := []Rule{}
	for k := range p.rules {
		s, err := p.LookupRule(k)
		if err != nil {
			return ts, err
		}
		ts = append(ts, s)
	}
	return ts, nil
}

// parseRepo returns provider, organization and project for a URL
// rawURL should be a valid url with host like https://github.com/org/repo
// or https://gitlab.com/org/repo
// or https://gitlab.com/org/group/repo
func parseRepo(rawURL string) (r provider.Repo, err error) {
	u, err := url.Parse(rawURL)
	if err != nil {
		return
	}
	if u.Host == "" {
		err = fmt.Errorf("Provided string %s is not a valid URL", rawURL)
		return
	}
	parts := strings.Split(u.Path, "/")
	if len(parts) != 3 && len(parts) != 4 {
		// gitlab may have https://gitlab.com/organization/group/repo
		err = fmt.Errorf("expected 2/3 repository parts, got %d: %v", len(parts), parts)
		return
	}
	if len(parts) == 3 {
		r = provider.Repo{
			Host:         u.Host,
			Organization: parts[1],
			Project:      parts[2],
		}
	} else {
		r = provider.Repo{
			Host:         u.Host,
			Organization: parts[1],
			Group:        parts[2],
			Project:      parts[3],
		}
	}

	return
}
<|endoftext|>


<commit_before>package ds18b20

import (
	"bufio"
	"bytes"
	"fmt"
	"kgerator/thermo"
	"os"
	"strconv"
)

func Open(path string) (thermo.Meter, error) {
	d := ds18b20(path)
	return &d, nil
}

type ds18b20 string

func (d *ds18b20) String() string {
	temp, err := d.Sample()
	if err != nil {
		return fmt.Sprint(string(*d), "error reading:", err)
	}

	return fmt.Sprint(string(*d), " ", temp)
}

func (d *ds18b20) Sample() (thermo.F, error) {
	file, err := os.Open(string(*d))
	if err != nil {
		return 0, fmt.Errorf("Opening ds18b20 %s: %s", string(*d), err)
	}

	read := bufio.NewReader(file)
	line, err := read.ReadBytes('\n')
	if err != nil {
		return 0, fmt.Errorf("Reading ds18b20 %s: %s", string(*d), err)
	}

	if !bytes.HasSuffix(line, []byte("YES\n")) {
		return 0, fmt.Errorf("Reading ds18b20 %s: bad crc", string(*d))
	}

	line, err = read.ReadBytes('\n')
	if err != nil {
		return 0, fmt.Errorf("Reading temperature ds18b20 %s: %s", string(*d), err)
	}

	cidx := bytes.IndexByte(line, '=')
	if cidx == -1 {
		return 0, fmt.Errorf("Reading ds18b20 %s: bad format", string(*d))
	}

	n, err := strconv.ParseUint(string(line[cidx+1:]), 10, 32)
	if err != nil {
		return 0, fmt.Errorf("Error parsing temperature ds18b20 %s: %s", string(*d), err)
	}

	c := thermo.C(float64(n) / 1000)
	return thermo.F(thermo.CtoF(c)), nil
}
<commit_msg>Fix parse issue ds18b20<commit_after>package ds18b20

import (
	"bufio"
	"bytes"
	"fmt"
	"kgerator/thermo"
	"os"
	"strconv"
)

func Open(path string) (thermo.Meter, error) {
	d := ds18b20(path)
	return &d, nil
}

type ds18b20 string

func (d *ds18b20) String() string {
	temp, err := d.Sample()
	if err != nil {
		return fmt.Sprint(string(*d), "error reading:", err)
	}

	return fmt.Sprint(string(*d), " ", temp)
}

func (d *ds18b20) Sample() (thermo.F, error) {
	file, err := os.Open(string(*d))
	if err != nil {
		return 0, fmt.Errorf("Opening ds18b20 %s: %s", string(*d), err)
	}

	read := bufio.NewReader(file)
	line, err := read.ReadBytes('\n')
	if err != nil {
		return 0, fmt.Errorf("Reading ds18b20 %s: %s", string(*d), err)
	}

	if !bytes.HasSuffix(line, []byte("YES\n")) {
		return 0, fmt.Errorf("Reading ds18b20 %s: bad crc", string(*d))
	}

	line, err = read.ReadBytes('\n')
	if err != nil {
		return 0, fmt.Errorf("Reading temperature ds18b20 %s: %s", string(*d), err)
	}

	cidx := bytes.IndexByte(line, '=')
	if cidx == -1 {
		return 0, fmt.Errorf("Reading ds18b20 %s: bad format", string(*d))
	}

	n, err := strconv.ParseUint(string(bytes.TrimSpace(line[cidx+1:])), 10, 32)
	if err != nil {
		return 0, fmt.Errorf("Error parsing temperature ds18b20 %s: %s", string(*d), err)
	}

	c := thermo.C(float64(n) / 1000)
	return thermo.F(thermo.CtoF(c)), nil
}
<|endoftext|>


<commit_before>package drive

import (
    "fmt"
    "os"
    "path/filepath"
    "github.com/soniakeys/graph"
    "github.com/sabhiram/go-git-ignore"
    "golang.org/x/net/context"
    "google.golang.org/api/drive/v3"
    "google.golang.org/api/googleapi"
)

const DefaultIgnoreFile = ".gdriveignore"

func (self *Drive) prepareSyncFiles(localPath string, root *drive.File) (*syncFiles, error) {
    localCh := make(chan struct{files []*localFile; err error})
    remoteCh := make(chan struct{files []*remoteFile; err error})

    go func() {
        files, err := prepareLocalFiles(localPath)
        localCh <- struct{files []*localFile; err error}{files, err}
    }()

    go func() {
        files, err := self.prepareRemoteFiles(root)
        remoteCh <- struct{files []*remoteFile; err error}{files, err}
    }()

    local := <-localCh
    if local.err != nil {
        return nil, local.err
    }

    remote := <-remoteCh
    if remote.err != nil {
        return nil, remote.err
    }

    return &syncFiles{
        root: &remoteFile{file: root},
        local: local.files,
        remote: remote.files,
    }, nil
}

func prepareLocalFiles(root string) ([]*localFile, error) {
    var files []*localFile

    // Get absolute root path
    absRootPath, err := filepath.Abs(root)
    if err != nil {
        return nil, err
    }

    // Prepare ignorer
    shouldIgnore, err := prepareIgnorer(filepath.Join(absRootPath, DefaultIgnoreFile))
    if err != nil {
        return nil, err
    }

    err = filepath.Walk(absRootPath, func(absPath string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        // Skip root directory
        if absPath == absRootPath {
            return nil
        }

        // Get relative path from root
        relPath, err := filepath.Rel(absRootPath, absPath)
        if err != nil {
            return err
        }

        // Skip file if it is ignored by ignore file
        if shouldIgnore(relPath) {
            return nil
        }

        files = append(files, &localFile{
            absPath: absPath,
            relPath: relPath,
            info: info,
        })

        return nil
    })

    if err != nil {
        return nil, fmt.Errorf("Failed to prepare local files: %s", err)
    }

    return files, err
}

func (self *Drive) listAllFiles(q string, fields []googleapi.Field) ([]*drive.File, error) {
    var files []*drive.File

    err := self.service.Files.List().Q(q).Fields(fields...).PageSize(1000).Pages(context.TODO(), func(fl *drive.FileList) error {
        files = append(files, fl.Files...)
        return nil
    })

    return files, err
}

func (self *Drive) prepareRemoteFiles(rootDir *drive.File) ([]*remoteFile, error) {
    // Find all files which has rootDir as root
    query := fmt.Sprintf("appProperties has {key='syncRootId' and value='%s'}", rootDir.Id)
    fields := []googleapi.Field{"nextPageToken", "files(id,name,parents,md5Checksum,mimeType)"}
    files, err := self.listAllFiles(query, fields)
    if err != nil {
        return nil, fmt.Errorf("Failed listing files: %s", err)
    }

    if err := checkFiles(files); err != nil {
        return nil, err
    }

    relPaths, err := prepareRemoteRelPaths(rootDir, files)
    if err != nil {
        return nil, err
    }

    var remoteFiles []*remoteFile
    for _, f := range files {
        relPath, ok := relPaths[f.Id]
        if !ok {
            return nil, fmt.Errorf("File %s does not have a valid parent", f.Id)
        }
        remoteFiles = append(remoteFiles, &remoteFile{
            relPath: relPath,
            file: f,
        })
    }

    return remoteFiles, nil
}

func prepareRemoteRelPaths(root *drive.File, files []*drive.File) (map[string]string, error) {
    // The graph will only hold integer values so we use
    // maps to lookup file by index and index by file id
    indexLookup := map[string]graph.NI{}
    fileLookup := map[graph.NI]*drive.File{}

    // All files includes root dir
    allFiles := append([]*drive.File{root}, files...)

    // Prepare lookup maps
    for i, f := range allFiles {
        indexLookup[f.Id] = graph.NI(i)
        fileLookup[graph.NI(i)] = f
    }

    // Graph will hold relationship between parent and file
    g := &graph.AdjacencyList{}

    // Add relationship between parent and file for all files to graph
    for i, f := range allFiles {
        if f == root {
            continue
        }

        // Lookup index of parent
        parentIdx, found := indexLookup[f.Parents[0]]
        if !found {
            return nil, fmt.Errorf("Could not find parent of %s (%s)", f.Id, f.Name)
        }

        g.AddEdge(graph.NI(parentIdx), graph.NI(i))
    }

    // This will hold a map of file id => relative path
    paths := map[string]string{}

    // Find relative path from root for all files
    for _, f := range allFiles {
        if f == root {
            continue
        }

        // Find nodes between root and file
        nodes := g.BreadthFirstPath(0, indexLookup[f.Id])

        // This will hold the name of all paths between root and
        // file (exluding root and including file itself)
        pathNames := []string{}

        // Lookup file for each node and grab name
        for _, n := range nodes {
            file := fileLookup[n]
            if file == root {
                continue
            }
            pathNames = append(pathNames, file.Name)
        }

        // Join path names to form relative path and add to map
        paths[f.Id] = filepath.Join(pathNames...)
    }

    return paths, nil
}

func checkFiles(files []*drive.File) error {
    uniq := map[string]string{}

    for _, f := range files {
        // Ensure all files have exactly one parent
        if len(f.Parents) != 1 {
            return fmt.Errorf("File %s does not have exacly one parent", f.Id)
        }

        // Ensure that there are no duplicate files
        uniqKey := f.Name + f.Parents[0]
        if dupeId, isDupe := uniq[uniqKey]; isDupe {
            return fmt.Errorf("Found name collision between %s and %s", f.Id, dupeId)
        }
        uniq[uniqKey] = f.Id
    }

    return nil
}

type localFile struct {
    absPath string
    relPath string
    info os.FileInfo
}

type remoteFile struct {
    relPath string
    file *drive.File
}

type changedFile struct {
    local *localFile
    remote *remoteFile
}

type syncFiles struct {
    root *remoteFile
    local []*localFile
    remote []*remoteFile
}

func (self *syncFiles) filterMissingRemoteDirs() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if lf.info.IsDir() && !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalDirs() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingRemoteFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !lf.info.IsDir() && !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterChangedLocalFiles() []*changedFile {
    var files []*changedFile

    for _, lf := range self.local {
        // Skip directories
        if lf.info.IsDir() {
            continue
        }

        // Skip files that don't exist on drive
        rf, found := self.findRemoteByPath(lf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterChangedRemoteFiles() []*changedFile {
    var files []*changedFile

    for _, rf := range self.remote {
        // Skip directories
        if isDir(rf.file) {
            continue
        }

        // Skip local files that don't exist
        lf, found := self.findLocalByPath(rf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousRemoteFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousLocalFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) existsRemote(lf *localFile) bool {
    _, found := self.findRemoteByPath(lf.relPath)
    return found
}

func (self *syncFiles) existsLocal(rf *remoteFile) bool {
    _, found := self.findLocalByPath(rf.relPath)
    return found
}

func (self *syncFiles) findRemoteByPath(relPath string) (*remoteFile, bool) {
    if relPath == "." {
        return self.root, true
    }

    for _, rf := range self.remote {
        if relPath == rf.relPath {
            return rf, true
        }
    }

    return nil, false
}

func (self *syncFiles) findLocalByPath(relPath string) (*localFile, bool) {
    for _, lf := range self.local {
        if relPath == lf.relPath {
            return lf, true
        }
    }

    return nil, false
}

type byLocalPathLength []*localFile

func (self byLocalPathLength) Len() int {
    return len(self)
}

func (self byLocalPathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byLocalPathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}

type byRemotePathLength []*remoteFile

func (self byRemotePathLength) Len() int {
    return len(self)
}

func (self byRemotePathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byRemotePathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}

type ignoreFunc func(string) bool

func prepareIgnorer(path string) (ignoreFunc, error) {
    acceptAll := func(string) bool {
        return false
    }

    if !fileExists(path) {
        return acceptAll, nil
    }

    ignorer, err := ignore.CompileIgnoreFile(path)
    if err != nil {
        return acceptAll, fmt.Errorf("Failed to prepare ignorer: %s", err)
    }

    return ignorer.MatchesPath, nil
}
<commit_msg>Skip files that are not a directory or regular file<commit_after>package drive

import (
    "fmt"
    "os"
    "path/filepath"
    "github.com/soniakeys/graph"
    "github.com/sabhiram/go-git-ignore"
    "golang.org/x/net/context"
    "google.golang.org/api/drive/v3"
    "google.golang.org/api/googleapi"
)

const DefaultIgnoreFile = ".gdriveignore"

func (self *Drive) prepareSyncFiles(localPath string, root *drive.File) (*syncFiles, error) {
    localCh := make(chan struct{files []*localFile; err error})
    remoteCh := make(chan struct{files []*remoteFile; err error})

    go func() {
        files, err := prepareLocalFiles(localPath)
        localCh <- struct{files []*localFile; err error}{files, err}
    }()

    go func() {
        files, err := self.prepareRemoteFiles(root)
        remoteCh <- struct{files []*remoteFile; err error}{files, err}
    }()

    local := <-localCh
    if local.err != nil {
        return nil, local.err
    }

    remote := <-remoteCh
    if remote.err != nil {
        return nil, remote.err
    }

    return &syncFiles{
        root: &remoteFile{file: root},
        local: local.files,
        remote: remote.files,
    }, nil
}

func prepareLocalFiles(root string) ([]*localFile, error) {
    var files []*localFile

    // Get absolute root path
    absRootPath, err := filepath.Abs(root)
    if err != nil {
        return nil, err
    }

    // Prepare ignorer
    shouldIgnore, err := prepareIgnorer(filepath.Join(absRootPath, DefaultIgnoreFile))
    if err != nil {
        return nil, err
    }

    err = filepath.Walk(absRootPath, func(absPath string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        // Skip root directory
        if absPath == absRootPath {
            return nil
        }

        // Skip files that are not a directory or regular file
        if !info.IsDir() && !info.Mode().IsRegular() {
            return nil
        }

        // Get relative path from root
        relPath, err := filepath.Rel(absRootPath, absPath)
        if err != nil {
            return err
        }

        // Skip file if it is ignored by ignore file
        if shouldIgnore(relPath) {
            return nil
        }

        files = append(files, &localFile{
            absPath: absPath,
            relPath: relPath,
            info: info,
        })

        return nil
    })

    if err != nil {
        return nil, fmt.Errorf("Failed to prepare local files: %s", err)
    }

    return files, err
}

func (self *Drive) listAllFiles(q string, fields []googleapi.Field) ([]*drive.File, error) {
    var files []*drive.File

    err := self.service.Files.List().Q(q).Fields(fields...).PageSize(1000).Pages(context.TODO(), func(fl *drive.FileList) error {
        files = append(files, fl.Files...)
        return nil
    })

    return files, err
}

func (self *Drive) prepareRemoteFiles(rootDir *drive.File) ([]*remoteFile, error) {
    // Find all files which has rootDir as root
    query := fmt.Sprintf("appProperties has {key='syncRootId' and value='%s'}", rootDir.Id)
    fields := []googleapi.Field{"nextPageToken", "files(id,name,parents,md5Checksum,mimeType)"}
    files, err := self.listAllFiles(query, fields)
    if err != nil {
        return nil, fmt.Errorf("Failed listing files: %s", err)
    }

    if err := checkFiles(files); err != nil {
        return nil, err
    }

    relPaths, err := prepareRemoteRelPaths(rootDir, files)
    if err != nil {
        return nil, err
    }

    var remoteFiles []*remoteFile
    for _, f := range files {
        relPath, ok := relPaths[f.Id]
        if !ok {
            return nil, fmt.Errorf("File %s does not have a valid parent", f.Id)
        }
        remoteFiles = append(remoteFiles, &remoteFile{
            relPath: relPath,
            file: f,
        })
    }

    return remoteFiles, nil
}

func prepareRemoteRelPaths(root *drive.File, files []*drive.File) (map[string]string, error) {
    // The graph will only hold integer values so we use
    // maps to lookup file by index and index by file id
    indexLookup := map[string]graph.NI{}
    fileLookup := map[graph.NI]*drive.File{}

    // All files includes root dir
    allFiles := append([]*drive.File{root}, files...)

    // Prepare lookup maps
    for i, f := range allFiles {
        indexLookup[f.Id] = graph.NI(i)
        fileLookup[graph.NI(i)] = f
    }

    // Graph will hold relationship between parent and file
    g := &graph.AdjacencyList{}

    // Add relationship between parent and file for all files to graph
    for i, f := range allFiles {
        if f == root {
            continue
        }

        // Lookup index of parent
        parentIdx, found := indexLookup[f.Parents[0]]
        if !found {
            return nil, fmt.Errorf("Could not find parent of %s (%s)", f.Id, f.Name)
        }

        g.AddEdge(graph.NI(parentIdx), graph.NI(i))
    }

    // This will hold a map of file id => relative path
    paths := map[string]string{}

    // Find relative path from root for all files
    for _, f := range allFiles {
        if f == root {
            continue
        }

        // Find nodes between root and file
        nodes := g.BreadthFirstPath(0, indexLookup[f.Id])

        // This will hold the name of all paths between root and
        // file (exluding root and including file itself)
        pathNames := []string{}

        // Lookup file for each node and grab name
        for _, n := range nodes {
            file := fileLookup[n]
            if file == root {
                continue
            }
            pathNames = append(pathNames, file.Name)
        }

        // Join path names to form relative path and add to map
        paths[f.Id] = filepath.Join(pathNames...)
    }

    return paths, nil
}

func checkFiles(files []*drive.File) error {
    uniq := map[string]string{}

    for _, f := range files {
        // Ensure all files have exactly one parent
        if len(f.Parents) != 1 {
            return fmt.Errorf("File %s does not have exacly one parent", f.Id)
        }

        // Ensure that there are no duplicate files
        uniqKey := f.Name + f.Parents[0]
        if dupeId, isDupe := uniq[uniqKey]; isDupe {
            return fmt.Errorf("Found name collision between %s and %s", f.Id, dupeId)
        }
        uniq[uniqKey] = f.Id
    }

    return nil
}

type localFile struct {
    absPath string
    relPath string
    info os.FileInfo
}

type remoteFile struct {
    relPath string
    file *drive.File
}

type changedFile struct {
    local *localFile
    remote *remoteFile
}

type syncFiles struct {
    root *remoteFile
    local []*localFile
    remote []*remoteFile
}

func (self *syncFiles) filterMissingRemoteDirs() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if lf.info.IsDir() && !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalDirs() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingRemoteFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !lf.info.IsDir() && !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) filterMissingLocalFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !isDir(rf.file) && !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterChangedLocalFiles() []*changedFile {
    var files []*changedFile

    for _, lf := range self.local {
        // Skip directories
        if lf.info.IsDir() {
            continue
        }

        // Skip files that don't exist on drive
        rf, found := self.findRemoteByPath(lf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterChangedRemoteFiles() []*changedFile {
    var files []*changedFile

    for _, rf := range self.remote {
        // Skip directories
        if isDir(rf.file) {
            continue
        }

        // Skip local files that don't exist
        lf, found := self.findLocalByPath(rf.relPath)
        if !found {
            continue
        }

        // Add files where remote md5 sum does not match local
        if rf.file.Md5Checksum != md5sum(lf.absPath) {
            files = append(files, &changedFile{
                local: lf,
                remote: rf,
            })
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousRemoteFiles() []*remoteFile {
    var files []*remoteFile

    for _, rf := range self.remote {
        if !self.existsLocal(rf) {
            files = append(files, rf)
        }
    }

    return files
}

func (self *syncFiles) filterExtraneousLocalFiles() []*localFile {
    var files []*localFile

    for _, lf := range self.local {
        if !self.existsRemote(lf) {
            files = append(files, lf)
        }
    }

    return files
}

func (self *syncFiles) existsRemote(lf *localFile) bool {
    _, found := self.findRemoteByPath(lf.relPath)
    return found
}

func (self *syncFiles) existsLocal(rf *remoteFile) bool {
    _, found := self.findLocalByPath(rf.relPath)
    return found
}

func (self *syncFiles) findRemoteByPath(relPath string) (*remoteFile, bool) {
    if relPath == "." {
        return self.root, true
    }

    for _, rf := range self.remote {
        if relPath == rf.relPath {
            return rf, true
        }
    }

    return nil, false
}

func (self *syncFiles) findLocalByPath(relPath string) (*localFile, bool) {
    for _, lf := range self.local {
        if relPath == lf.relPath {
            return lf, true
        }
    }

    return nil, false
}

type byLocalPathLength []*localFile

func (self byLocalPathLength) Len() int {
    return len(self)
}

func (self byLocalPathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byLocalPathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}

type byRemotePathLength []*remoteFile

func (self byRemotePathLength) Len() int {
    return len(self)
}

func (self byRemotePathLength) Swap(i, j int) {
    self[i], self[j] = self[j], self[i]
}

func (self byRemotePathLength) Less(i, j int) bool {
    return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}

type ignoreFunc func(string) bool

func prepareIgnorer(path string) (ignoreFunc, error) {
    acceptAll := func(string) bool {
        return false
    }

    if !fileExists(path) {
        return acceptAll, nil
    }

    ignorer, err := ignore.CompileIgnoreFile(path)
    if err != nil {
        return acceptAll, fmt.Errorf("Failed to prepare ignorer: %s", err)
    }

    return ignorer.MatchesPath, nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/codegangsta/cli"
	"github.com/drone/drone-exec/docker"
	"github.com/drone/drone-go/drone"
	"github.com/drone/drone/yaml/matrix"
	"github.com/fatih/color"
	"github.com/samalba/dockerclient"
)

var ExecCmd = cli.Command{
	Name:  "exec",
	Usage: "executes a local build",
	Action: func(c *cli.Context) {
		if err := execCmd(c); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	},
	Flags: []cli.Flag{
		cli.StringFlag{
			EnvVar: "DOCKER_HOST",
			Name:   "docker-host",
			Usage:  "docker deamon address",
			Value:  "unix:///var/run/docker.sock",
		},
		cli.BoolFlag{
			EnvVar: "DOCKER_TLS_VERIFY",
			Name:   "docker-tls-verify",
			Usage:  "docker daemon supports tlsverify",
		},
		cli.StringFlag{
			EnvVar: "DOCKER_CERT_PATH",
			Name:   "docker-cert-path",
			Usage:  "docker certificate directory",
			Value:  "",
		},
		cli.StringFlag{
			Name:  "i",
			Value: "",
			Usage: "identify file injected in the container",
		},
		cli.BoolFlag{
			Name:  "trusted",
			Usage: "enable elevated privilege",
		},
		cli.BoolFlag{
			Name:  "cache",
			Usage: "execute cache steps",
		},
		cli.BoolFlag{
			Name:  "deploy",
			Usage: "execute publish and deployment steps",
		},
		cli.BoolFlag{
			Name:  "notify",
			Usage: "execute notification steps",
		},
		cli.BoolFlag{
			Name:  "pull",
			Usage: "always pull the latest docker image",
		},
		cli.BoolTFlag{
			Name:  "debug",
			Usage: "execute the build in debug mode",
		},
	},
}

func execCmd(c *cli.Context) error {

	cert, _ := ioutil.ReadFile(filepath.Join(
		c.String("docker-cert-path"),
		"cert.pem",
	))

	key, _ := ioutil.ReadFile(filepath.Join(
		c.String("docker-cert-path"),
		"key.pem",
	))

	ca, _ := ioutil.ReadFile(filepath.Join(
		c.String("docker-cert-path"),
		"ca.pem",
	))
	if len(cert) == 0 || len(key) == 0 || len(ca) == 0 {
		println("")
	}

	yml, err := ioutil.ReadFile(".drone.yml")
	if err != nil {
		return err
	}

	axes, err := matrix.Parse(string(yml))
	if err != nil {
		return err
	}
	if len(axes) == 0 {
		axes = append(axes, matrix.Axis{})
	}

	cli, err := newDockerClient(c.String("docker-host"), cert, key, ca)
	if err != nil {
		return err
	}

	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	execArgs := []string{"--build", "--debug", "--mount", pwd}
	for _, arg := range []string{"cache", "deploy", "notify", "pull"} {
		if c.Bool(arg) {
			execArgs = append(execArgs, "--"+arg)
		}
	}

	proj := resolvePath(pwd)

	var exits []int

	for i, axis := range axes {
		color.Magenta("[DRONE] starting job #%d", i+1)
		if len(axis) != 0 {
			color.Magenta("[DRONE] export %s", axis)
		}

		payload := struct {
			System    drone.System    `json:"system"`
			Workspace drone.Workspace `json:"workspace"`
			Repo      drone.Repo      `json:"repo"`
			Build     drone.Build     `json:"build"`
			Job       drone.Job       `json:"job"`
			Config    string          `json:"config"`
		}{
			Repo: drone.Repo{
				IsTrusted: c.Bool("trusted"),
				IsPrivate: true,
			},
			Job: drone.Job{
				Status:      drone.StatusRunning,
				Environment: axis,
			},
			Config: string(yml),
			Build: drone.Build{
				Status: drone.StatusRunning,
				Commit: "0000000000", // hack
			},
			System: drone.System{
				Globals: []string{},
				Plugins: []string{"plugins/*", "*/*"},
			},
		}

		// gets the ssh key if provided
		if len(c.String("i")) != 0 {
			key, err = ioutil.ReadFile(c.String("i"))
			if err != nil {
				return err
			}
			payload.Workspace.Keys = &drone.Key{
				Private: string(key),
			}
		}

		if len(proj) != 0 {
			payload.Repo.Link = fmt.Sprintf("https://%s", proj)
		}
		out, _ := json.Marshal(payload)

		exit, err := run(cli, execArgs, string(out))
		if err != nil {
			return err
		}
		exits = append(exits, exit)

		color.Magenta("[DRONE] finished job #%d", i+1)
		color.Magenta("[DRONE] exit code %d", exit)
	}

	var passed = true
	for i, _ := range axes {
		exit := exits[i]
		if exit == 0 {
			color.Green("[DRONE] job #%d passed", i+1)
		} else {
			color.Red("[DRONE] job #%d failed", i+1)
			passed = false
		}
	}
	if passed {
		color.Green("[DRONE] build passed")
	} else {
		color.Red("[DRONE] build failed")
		os.Exit(1)
	}

	return nil
}

func run(client dockerclient.Client, args []string, input string) (int, error) {

	image := "drone/drone-exec:latest"
	entrypoint := []string{"/bin/drone-exec"}
	args = append(args, "--", input)

	conf := &dockerclient.ContainerConfig{
		Image:      image,
		Entrypoint: entrypoint,
		Cmd:        args,
		HostConfig: dockerclient.HostConfig{
			Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
		},
		Volumes: map[string]struct{}{
			"/var/run/docker.sock": struct{}{},
		},
	}

	info, err := docker.Run(client, conf, false)

	client.StopContainer(info.Id, 15)
	client.RemoveContainer(info.Id, true, true)
	return info.State.ExitCode, err
}

func newDockerClient(addr string, cert, key, ca []byte) (dockerclient.Client, error) {
	var tlc *tls.Config

	if len(cert) != 0 {
		pem, err := tls.X509KeyPair(cert, key)
		if err != nil {
			return dockerclient.NewDockerClient(addr, nil)
		}
		tlc = &tls.Config{}
		tlc.Certificates = []tls.Certificate{pem}

		if len(ca) != 0 {
			pool := x509.NewCertPool()
			pool.AppendCertsFromPEM(ca)
			tlc.RootCAs = pool

		} else {
			tlc.InsecureSkipVerify = true
		}
	}

	return dockerclient.NewDockerClient(addr, tlc)
}
<commit_msg>ability to specify the event<commit_after>package main

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/codegangsta/cli"
	"github.com/drone/drone-exec/docker"
	"github.com/drone/drone-go/drone"
	"github.com/drone/drone/yaml/matrix"
	"github.com/fatih/color"
	"github.com/samalba/dockerclient"
)

var ExecCmd = cli.Command{
	Name:  "exec",
	Usage: "executes a local build",
	Action: func(c *cli.Context) {
		if err := execCmd(c); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	},
	Flags: []cli.Flag{
		cli.StringFlag{
			EnvVar: "DOCKER_HOST",
			Name:   "docker-host",
			Usage:  "docker deamon address",
			Value:  "unix:///var/run/docker.sock",
		},
		cli.BoolFlag{
			EnvVar: "DOCKER_TLS_VERIFY",
			Name:   "docker-tls-verify",
			Usage:  "docker daemon supports tlsverify",
		},
		cli.StringFlag{
			EnvVar: "DOCKER_CERT_PATH",
			Name:   "docker-cert-path",
			Usage:  "docker certificate directory",
			Value:  "",
		},
		cli.StringFlag{
			Name:  "i",
			Value: "",
			Usage: "identify file injected in the container",
		},
		cli.BoolFlag{
			Name:  "trusted",
			Usage: "enable elevated privilege",
		},
		cli.BoolFlag{
			Name:  "cache",
			Usage: "execute cache steps",
		},
		cli.BoolFlag{
			Name:  "deploy",
			Usage: "execute publish and deployment steps",
		},
		cli.BoolFlag{
			Name:  "notify",
			Usage: "execute notification steps",
		},
		cli.BoolFlag{
			Name:  "pull",
			Usage: "always pull the latest docker image",
		},
                cli.StringFlag{
                        Name:   "event",
                        Usage:  "hook event type",
                        Value:  "push",
                },
		cli.BoolTFlag{
			Name:  "debug",
			Usage: "execute the build in debug mode",
		},
	},
}

func execCmd(c *cli.Context) error {

	cert, _ := ioutil.ReadFile(filepath.Join(
		c.String("docker-cert-path"),
		"cert.pem",
	))

	key, _ := ioutil.ReadFile(filepath.Join(
		c.String("docker-cert-path"),
		"key.pem",
	))

	ca, _ := ioutil.ReadFile(filepath.Join(
		c.String("docker-cert-path"),
		"ca.pem",
	))
	if len(cert) == 0 || len(key) == 0 || len(ca) == 0 {
		println("")
	}

	yml, err := ioutil.ReadFile(".drone.yml")
	if err != nil {
		return err
	}

	axes, err := matrix.Parse(string(yml))
	if err != nil {
		return err
	}
	if len(axes) == 0 {
		axes = append(axes, matrix.Axis{})
	}

	cli, err := newDockerClient(c.String("docker-host"), cert, key, ca)
	if err != nil {
		return err
	}

	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	execArgs := []string{"--build", "--debug", "--mount", pwd}
	for _, arg := range []string{"cache", "deploy", "notify", "pull"} {
		if c.Bool(arg) {
			execArgs = append(execArgs, "--"+arg)
		}
	}

	proj := resolvePath(pwd)

	var exits []int

	for i, axis := range axes {
		color.Magenta("[DRONE] starting job #%d", i+1)
		if len(axis) != 0 {
			color.Magenta("[DRONE] export %s", axis)
		}

		payload := struct {
			System    drone.System    `json:"system"`
			Workspace drone.Workspace `json:"workspace"`
			Repo      drone.Repo      `json:"repo"`
			Build     drone.Build     `json:"build"`
			Job       drone.Job       `json:"job"`
			Config    string          `json:"config"`
		}{
			Repo: drone.Repo{
				IsTrusted: c.Bool("trusted"),
				IsPrivate: true,
			},
			Job: drone.Job{
				Status:      drone.StatusRunning,
				Environment: axis,
			},
			Config: string(yml),
			Build: drone.Build{
				Status: drone.StatusRunning,
				Commit: "0000000000", // hack
				Event: c.String("event"),
			},
			System: drone.System{
				Globals: []string{},
				Plugins: []string{"plugins/*", "*/*"},
			},
		}

		// gets the ssh key if provided
		if len(c.String("i")) != 0 {
			key, err = ioutil.ReadFile(c.String("i"))
			if err != nil {
				return err
			}
			payload.Workspace.Keys = &drone.Key{
				Private: string(key),
			}
		}

		if len(proj) != 0 {
			payload.Repo.Link = fmt.Sprintf("https://%s", proj)
		}
		out, _ := json.Marshal(payload)

		exit, err := run(cli, execArgs, string(out))
		if err != nil {
			return err
		}
		exits = append(exits, exit)

		color.Magenta("[DRONE] finished job #%d", i+1)
		color.Magenta("[DRONE] exit code %d", exit)
	}

	var passed = true
	for i, _ := range axes {
		exit := exits[i]
		if exit == 0 {
			color.Green("[DRONE] job #%d passed", i+1)
		} else {
			color.Red("[DRONE] job #%d failed", i+1)
			passed = false
		}
	}
	if passed {
		color.Green("[DRONE] build passed")
	} else {
		color.Red("[DRONE] build failed")
		os.Exit(1)
	}

	return nil
}

func run(client dockerclient.Client, args []string, input string) (int, error) {

	image := "drone/drone-exec:latest"
	entrypoint := []string{"/bin/drone-exec"}
	args = append(args, "--", input)

	conf := &dockerclient.ContainerConfig{
		Image:      image,
		Entrypoint: entrypoint,
		Cmd:        args,
		HostConfig: dockerclient.HostConfig{
			Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
		},
		Volumes: map[string]struct{}{
			"/var/run/docker.sock": struct{}{},
		},
	}

	info, err := docker.Run(client, conf, false)

	client.StopContainer(info.Id, 15)
	client.RemoveContainer(info.Id, true, true)
	return info.State.ExitCode, err
}

func newDockerClient(addr string, cert, key, ca []byte) (dockerclient.Client, error) {
	var tlc *tls.Config

	if len(cert) != 0 {
		pem, err := tls.X509KeyPair(cert, key)
		if err != nil {
			return dockerclient.NewDockerClient(addr, nil)
		}
		tlc = &tls.Config{}
		tlc.Certificates = []tls.Certificate{pem}

		if len(ca) != 0 {
			pool := x509.NewCertPool()
			pool.AppendCertsFromPEM(ca)
			tlc.RootCAs = pool

		} else {
			tlc.InsecureSkipVerify = true
		}
	}

	return dockerclient.NewDockerClient(addr, tlc)
}
<|endoftext|>


<commit_before>package machine

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"koding/klient/machine"
	"koding/klient/machine/machinegroup"
	"koding/klient/machine/mount"
	"koding/klient/machine/mount/prefetch"
	"koding/klientctl/helper"

	humanize "github.com/dustin/go-humanize"
	"github.com/koding/kite/dnode"
	"github.com/koding/logging"
)

// MountOptions stores options for `machine mount` call.
type MountOptions struct {
	Identifier string // Machine identifier.
	Path       string // Machine local path - absolute and cleaned.
	RemotePath string // Remote machine path - raw format.
	Log        logging.Logger
}

func (c *Client) mountPoint(ident string) (string, error) {
	m, err := c.machine(ident)
	if err != nil {
		return "", err
	}

	return filepath.Join(c.konfig().Mount.Home, m.Label), nil
}

// Mount synchronizes directories between remote and local machines.
func (c *Client) Mount(options *MountOptions) (err error) {
	c.init()

	if options == nil {
		return errors.New("invalid nil options")
	}

	if options.Path == "" {
		if options.Path, err = c.mountPoint(options.Identifier); err != nil {
			return err
		}
	}

	// Create and check mount point directory.
	clean, err := mountPointDirectory(options.Path)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			clean()
		}
	}()

	// Translate identifier to machine ID.
	idReq := &machinegroup.IDRequest{
		Identifier: options.Identifier,
	}
	var idRes machinegroup.IDResponse

	if err := c.klient().Call("machine.id", idReq, &idRes); err != nil {
		return err
	}

	fmt.Fprintf(os.Stdout, "Mounting to %s directory.\nChecking remote path...\n", options.Path)

	m := mount.Mount{
		Path:       options.Path,
		RemotePath: options.RemotePath,
	}

	// First head the remote machine directory in order to get basic mount info.
	headMountReq := &machinegroup.HeadMountRequest{
		MountRequest: machinegroup.MountRequest{
			ID:    idRes.ID,
			Mount: m,
		},
	}
	var headMountRes machinegroup.HeadMountResponse

	if err := c.klient().Call("machine.mount.head", headMountReq, &headMountRes); err != nil {
		return err
	}

	// Remote directory is already mounted to this machine.
	//
	// TODO: ask user if she wants to create another mount or stop the process.
	if headMountRes.ExistMountID != "" {
		fmt.Fprintf(os.Stdout, "Remote directory %s is already mounted by: %s\n",
			headMountRes.AbsRemotePath, headMountRes.ExistMountID)

		clean()
		return nil
	}

	fmt.Fprintf(os.Stdout, "Mounted remote directory %s has %d file(s) of total size %s\n",
		headMountRes.AbsRemotePath, headMountRes.AllCount, humanize.IBytes(uint64(headMountRes.AllDiskSize)))

	// TODO: ask user if she wants to continue.

	m.RemotePath = headMountRes.AbsRemotePath
	fmt.Fprintf(os.Stdout, "Initializing mount %s...\n", m)

	// Create mount.
	addMountReq := &machinegroup.AddMountRequest{
		MountRequest: machinegroup.MountRequest{
			ID:    idRes.ID,
			Mount: m,
		},
		Strategies: prefetch.DefaultStrategy.Available(),
	}
	var addMountRes machinegroup.AddMountResponse
	if err := c.klient().Call("machine.mount.add", addMountReq, &addMountRes); err != nil {
		return err
	}

	// Prefetch files.
	_, _, privPath, err := sshGetKeyPath()
	if err == nil {
		err = addMountRes.Prefetch.Run(os.Stdout, prefetch.DefaultStrategy, privPath)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "Cannot prefetch mount files: %s\n", err)
		options.Log.Error("Prefetching failed: %s\n", err)
		if exitErr, ok := err.(*exec.ExitError); ok {
			options.Log.Error("Output: %q", exitErr.Stderr)
		}
	}

	// Rescan cache folder in order to update index.
	upIdxMountReq := &machinegroup.UpdateIndexRequest{
		MountID: addMountRes.MountID,
	}
	var upIdxMountRes machinegroup.UpdateIndexResponse
	if err := c.klient().Call("machine.mount.updateIndex", upIdxMountReq, &upIdxMountRes); err != nil {
		return err
	}

	fmt.Fprintf(os.Stdout, "Created mount with ID: %s\n", addMountRes.MountID)
	return nil
}

// ListMountOptions stores options for `machine mount list` call.
type ListMountOptions struct {
	ID      string // Machine ID - optional.
	MountID string // Mount ID - optional.
	Log     logging.Logger
}

// ListMount lists local mounts that are known to a klient.
func (c *Client) ListMount(options *ListMountOptions) (map[string][]mount.Info, error) {
	if options == nil {
		return nil, errors.New("invalid nil options")
	}

	// List mounts.
	listMountReq := &machinegroup.ListMountRequest{
		ID:      machine.ID(options.ID),
		MountID: mount.ID(options.MountID),
	}
	var listMountRes machinegroup.ListMountResponse

	if err := c.klient().Call("machine.mount.list", listMountReq, &listMountRes); err != nil {
		return nil, err
	}

	return listMountRes.Mounts, nil
}

// InspectMountOptions stores options for `machine mount inspect` call.
type InspectMountOptions struct {
	Identifier string // Mount identifier.
	Sync       bool   // Get syncing history.
	Tree       bool   // Show index tree.
	Log        logging.Logger
}

// InspectMount inspects provided mount.
func (c *Client) InspectMount(options *InspectMountOptions) (machinegroup.InspectMountResponse, error) {
	var inspectMountRes machinegroup.InspectMountResponse
	if options == nil {
		return inspectMountRes, errors.New("invalid nil options")
	}

	// Inspect mount.
	inspectMountReq := &machinegroup.InspectMountRequest{
		Identifier: options.Identifier,
		Sync:       options.Sync,
		Tree:       options.Tree,
	}

	err := c.klient().Call("machine.mount.inspect", inspectMountReq, &inspectMountRes)
	return inspectMountRes, err
}

// UmountOptions stores options for `machine umount` call.
type UmountOptions struct {
	Identifiers []string // Mount identifiers.
	Force       bool     // Disable interactive mode.
	All         bool     // Unmount all.
	Log         logging.Logger
}

// Umount removes existing mount.
func (c *Client) Umount(options *UmountOptions) (err error) {
	if options == nil {
		return errors.New("invalid nil options")
	}

	// Get all mounts if all option is set.
	identifiers := options.Identifiers
	if options.All {
		mids, err := c.allMounts()
		if err != nil {
			return err
		}
		identifiers = mids
	}

	// If there are no mounts we return nil.
	if len(identifiers) == 0 {
		fmt.Fprintf(os.Stdout, "There is nothing to unmount\n")
		return nil
	}

	// Do not be too verbose.
	promptSuffix := strings.Join(identifiers, ", ")
	if n := len(identifiers); n > 2 {
		promptSuffix = strconv.Itoa(n) + " mounts"
	}

	fmt.Fprintf(os.Stdout, "Unmounting %s...\n", promptSuffix)

	var yn = "yes"
	// TODO(ppknap): remove second condition when atom package implements "force" flag.
	if !options.Force && len(identifiers) > 1 {
		yn, err = helper.Ask("This operation will remove all cached data. Do you want to continue [y/N]: ")
		if err != nil {
			return err
		}
	}

	if yn = strings.ToLower(yn); yn != "yes" && yn != "y" {
		return errors.New("aborted by user")
	}

	// Remove mounts.
	for _, identifier := range identifiers {
		umountReq := &machinegroup.UmountRequest{
			Identifier: identifier,
		}
		var umountRes machinegroup.UmountResponse

		err := c.klient().Call("machine.umount", umountReq, &umountRes)
		if err != nil {
			fmt.Fprintf(os.Stdout, "Cannot unmount %s (ID: %s): %s\n", umountRes.Mount, umountRes.MountID, err)
		} else {
			fmt.Fprintf(os.Stdout, "Successfully unmounted %s (ID: %s)\n", umountRes.Mount, umountRes.MountID)
		}
	}

	return nil
}

// WaitIdleOptions describes options for "machine.mount.waitIdle" method's request.
type WaitIdleOptions struct {
	Identifier string
	Path       string
	Timeout    time.Duration
}

// WaitIdle waits for all the sync operations for finish
// for the given mount.
func (c *Client) WaitIdle(opts *WaitIdleOptions) error {
	ch := make(chan bool)

	req := &machinegroup.WaitIdleRequest{
		Identifier: opts.Identifier,
		Path:       opts.Path,
		Timeout:    opts.Timeout,
		Done: dnode.Callback(func(r *dnode.Partial) {
			ch <- r.MustBool()
		}),
	}

	if err := c.klient().Call("machine.mount.waitIdle", req, nil); err != nil {
		return err
	}

	if !<-ch {
		if req.Timeout != 0 {
			return fmt.Errorf("waiting for mount to synchronize has timed out after %s", req.Timeout)
		}
		return fmt.Errorf("waiting for mount to synchronize has timed out")
	}

	return nil
}

func (c *Client) allMounts() (mids []string, err error) {
	var (
		listMountReq machinegroup.ListMountRequest
		listMountRes machinegroup.ListMountResponse
	)

	if err := c.klient().Call("machine.mount.list", listMountReq, &listMountRes); err != nil {
		return nil, err
	}

	for _, infos := range listMountRes.Mounts {
		for _, info := range infos {
			mids = append(mids, string(info.ID))
		}
	}

	return mids, nil
}

// mountPointDirectory checks and prepares local directory for mounting.
// Returned clean function can be used to remove resources in case of other
// mounting errors.
//
// NOTE: This logic will be moved to klient.
func mountPointDirectory(path string) (clean func(), err error) {
	switch info, err := os.Stat(path); {
	case os.IsNotExist(err):
		// Create a new directory.
		if err := os.MkdirAll(path, 0755); err != nil {
			return nil, fmt.Errorf("cannot create destination directory: %s", err)
		}
		return func() {
			os.RemoveAll(path)
		}, nil
	case err != nil:
		return nil, fmt.Errorf("cannot stat destination directory: %s", err)
	case !info.IsDir():
		return nil, fmt.Errorf("file %q is not a directory", path)

	}

	// Provided directory already exists. Check if it's empty.
	f, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("cannot open destination directory: %s", err)
	}
	defer f.Close()

	switch _, err = f.Readdirnames(1); err {
	case nil:
		return nil, errors.New("destination directory is not empty")
	case io.EOF:
	default:
		return nil, fmt.Errorf("destination directory error: %s", err)
	}
	clean = func() {
		removeContent(path)
	}

	return clean, nil

}

// removeContent removes all files inside provided path but not the path itself.
func removeContent(path string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	for {
		names, err := f.Readdirnames(100)
		if err != nil {
			return err
		}

		for _, name := range names {
			os.RemoveAll(filepath.Join(path, name)) // Ignore errors.
		}
	}
}

// Mount synchronizes directories between remote and local machines
// using DefaultClient.
func Mount(opts *MountOptions) error { return DefaultClient.Mount(opts) }

// ListMount lists local mounts that are known to a klient using DefaultClient.
func ListMount(opts *ListMountOptions) (map[string][]mount.Info, error) {
	return DefaultClient.ListMount(opts)
}

// WaitIdle waits for mount's synchronization to complate using DefaultClient.
func WaitIdle(opts *WaitIdleOptions) error { return DefaultClient.WaitIdle(opts) }

// InspectMount inspects existing mount using DefaultClient.
func InspectMount(opts *InspectMountOptions) (machinegroup.InspectMountResponse, error) {
	return DefaultClient.InspectMount(opts)
}

// Umount removes existing mount using DefaultClient.
func Umount(opts *UmountOptions) error { return DefaultClient.Umount(opts) }
<commit_msg>endpoint/machine: fix callback args unmarshaling<commit_after>package machine

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"koding/klient/machine"
	"koding/klient/machine/machinegroup"
	"koding/klient/machine/mount"
	"koding/klient/machine/mount/prefetch"
	"koding/klientctl/helper"

	humanize "github.com/dustin/go-humanize"
	"github.com/koding/kite/dnode"
	"github.com/koding/logging"
)

// MountOptions stores options for `machine mount` call.
type MountOptions struct {
	Identifier string // Machine identifier.
	Path       string // Machine local path - absolute and cleaned.
	RemotePath string // Remote machine path - raw format.
	Log        logging.Logger
}

func (c *Client) mountPoint(ident string) (string, error) {
	m, err := c.machine(ident)
	if err != nil {
		return "", err
	}

	return filepath.Join(c.konfig().Mount.Home, m.Label), nil
}

// Mount synchronizes directories between remote and local machines.
func (c *Client) Mount(options *MountOptions) (err error) {
	c.init()

	if options == nil {
		return errors.New("invalid nil options")
	}

	if options.Path == "" {
		if options.Path, err = c.mountPoint(options.Identifier); err != nil {
			return err
		}
	}

	// Create and check mount point directory.
	clean, err := mountPointDirectory(options.Path)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			clean()
		}
	}()

	// Translate identifier to machine ID.
	idReq := &machinegroup.IDRequest{
		Identifier: options.Identifier,
	}
	var idRes machinegroup.IDResponse

	if err := c.klient().Call("machine.id", idReq, &idRes); err != nil {
		return err
	}

	fmt.Fprintf(os.Stdout, "Mounting to %s directory.\nChecking remote path...\n", options.Path)

	m := mount.Mount{
		Path:       options.Path,
		RemotePath: options.RemotePath,
	}

	// First head the remote machine directory in order to get basic mount info.
	headMountReq := &machinegroup.HeadMountRequest{
		MountRequest: machinegroup.MountRequest{
			ID:    idRes.ID,
			Mount: m,
		},
	}
	var headMountRes machinegroup.HeadMountResponse

	if err := c.klient().Call("machine.mount.head", headMountReq, &headMountRes); err != nil {
		return err
	}

	// Remote directory is already mounted to this machine.
	//
	// TODO: ask user if she wants to create another mount or stop the process.
	if headMountRes.ExistMountID != "" {
		fmt.Fprintf(os.Stdout, "Remote directory %s is already mounted by: %s\n",
			headMountRes.AbsRemotePath, headMountRes.ExistMountID)

		clean()
		return nil
	}

	fmt.Fprintf(os.Stdout, "Mounted remote directory %s has %d file(s) of total size %s\n",
		headMountRes.AbsRemotePath, headMountRes.AllCount, humanize.IBytes(uint64(headMountRes.AllDiskSize)))

	// TODO: ask user if she wants to continue.

	m.RemotePath = headMountRes.AbsRemotePath
	fmt.Fprintf(os.Stdout, "Initializing mount %s...\n", m)

	// Create mount.
	addMountReq := &machinegroup.AddMountRequest{
		MountRequest: machinegroup.MountRequest{
			ID:    idRes.ID,
			Mount: m,
		},
		Strategies: prefetch.DefaultStrategy.Available(),
	}
	var addMountRes machinegroup.AddMountResponse
	if err := c.klient().Call("machine.mount.add", addMountReq, &addMountRes); err != nil {
		return err
	}

	// Prefetch files.
	_, _, privPath, err := sshGetKeyPath()
	if err == nil {
		err = addMountRes.Prefetch.Run(os.Stdout, prefetch.DefaultStrategy, privPath)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "Cannot prefetch mount files: %s\n", err)
		options.Log.Error("Prefetching failed: %s\n", err)
		if exitErr, ok := err.(*exec.ExitError); ok {
			options.Log.Error("Output: %q", exitErr.Stderr)
		}
	}

	// Rescan cache folder in order to update index.
	upIdxMountReq := &machinegroup.UpdateIndexRequest{
		MountID: addMountRes.MountID,
	}
	var upIdxMountRes machinegroup.UpdateIndexResponse
	if err := c.klient().Call("machine.mount.updateIndex", upIdxMountReq, &upIdxMountRes); err != nil {
		return err
	}

	fmt.Fprintf(os.Stdout, "Created mount with ID: %s\n", addMountRes.MountID)
	return nil
}

// ListMountOptions stores options for `machine mount list` call.
type ListMountOptions struct {
	ID      string // Machine ID - optional.
	MountID string // Mount ID - optional.
	Log     logging.Logger
}

// ListMount lists local mounts that are known to a klient.
func (c *Client) ListMount(options *ListMountOptions) (map[string][]mount.Info, error) {
	if options == nil {
		return nil, errors.New("invalid nil options")
	}

	// List mounts.
	listMountReq := &machinegroup.ListMountRequest{
		ID:      machine.ID(options.ID),
		MountID: mount.ID(options.MountID),
	}
	var listMountRes machinegroup.ListMountResponse

	if err := c.klient().Call("machine.mount.list", listMountReq, &listMountRes); err != nil {
		return nil, err
	}

	return listMountRes.Mounts, nil
}

// InspectMountOptions stores options for `machine mount inspect` call.
type InspectMountOptions struct {
	Identifier string // Mount identifier.
	Sync       bool   // Get syncing history.
	Tree       bool   // Show index tree.
	Log        logging.Logger
}

// InspectMount inspects provided mount.
func (c *Client) InspectMount(options *InspectMountOptions) (machinegroup.InspectMountResponse, error) {
	var inspectMountRes machinegroup.InspectMountResponse
	if options == nil {
		return inspectMountRes, errors.New("invalid nil options")
	}

	// Inspect mount.
	inspectMountReq := &machinegroup.InspectMountRequest{
		Identifier: options.Identifier,
		Sync:       options.Sync,
		Tree:       options.Tree,
	}

	err := c.klient().Call("machine.mount.inspect", inspectMountReq, &inspectMountRes)
	return inspectMountRes, err
}

// UmountOptions stores options for `machine umount` call.
type UmountOptions struct {
	Identifiers []string // Mount identifiers.
	Force       bool     // Disable interactive mode.
	All         bool     // Unmount all.
	Log         logging.Logger
}

// Umount removes existing mount.
func (c *Client) Umount(options *UmountOptions) (err error) {
	if options == nil {
		return errors.New("invalid nil options")
	}

	// Get all mounts if all option is set.
	identifiers := options.Identifiers
	if options.All {
		mids, err := c.allMounts()
		if err != nil {
			return err
		}
		identifiers = mids
	}

	// If there are no mounts we return nil.
	if len(identifiers) == 0 {
		fmt.Fprintf(os.Stdout, "There is nothing to unmount\n")
		return nil
	}

	// Do not be too verbose.
	promptSuffix := strings.Join(identifiers, ", ")
	if n := len(identifiers); n > 2 {
		promptSuffix = strconv.Itoa(n) + " mounts"
	}

	fmt.Fprintf(os.Stdout, "Unmounting %s...\n", promptSuffix)

	var yn = "yes"
	// TODO(ppknap): remove second condition when atom package implements "force" flag.
	if !options.Force && len(identifiers) > 1 {
		yn, err = helper.Ask("This operation will remove all cached data. Do you want to continue [y/N]: ")
		if err != nil {
			return err
		}
	}

	if yn = strings.ToLower(yn); yn != "yes" && yn != "y" {
		return errors.New("aborted by user")
	}

	// Remove mounts.
	for _, identifier := range identifiers {
		umountReq := &machinegroup.UmountRequest{
			Identifier: identifier,
		}
		var umountRes machinegroup.UmountResponse

		err := c.klient().Call("machine.umount", umountReq, &umountRes)
		if err != nil {
			fmt.Fprintf(os.Stdout, "Cannot unmount %s (ID: %s): %s\n", umountRes.Mount, umountRes.MountID, err)
		} else {
			fmt.Fprintf(os.Stdout, "Successfully unmounted %s (ID: %s)\n", umountRes.Mount, umountRes.MountID)
		}
	}

	return nil
}

// WaitIdleOptions describes options for "machine.mount.waitIdle" method's request.
type WaitIdleOptions struct {
	Identifier string
	Path       string
	Timeout    time.Duration
}

// WaitIdle waits for all the sync operations for finish
// for the given mount.
func (c *Client) WaitIdle(opts *WaitIdleOptions) error {
	ch := make(chan bool)

	req := &machinegroup.WaitIdleRequest{
		Identifier: opts.Identifier,
		Path:       opts.Path,
		Timeout:    opts.Timeout,
		Done: dnode.Callback(func(r *dnode.Partial) {
			ch <- r.One().MustBool()
		}),
	}

	if err := c.klient().Call("machine.mount.waitIdle", req, nil); err != nil {
		return err
	}

	if !<-ch {
		if req.Timeout != 0 {
			return fmt.Errorf("waiting for mount to synchronize has timed out after %s", req.Timeout)
		}
		return fmt.Errorf("waiting for mount to synchronize has timed out")
	}

	return nil
}

func (c *Client) allMounts() (mids []string, err error) {
	var (
		listMountReq machinegroup.ListMountRequest
		listMountRes machinegroup.ListMountResponse
	)

	if err := c.klient().Call("machine.mount.list", listMountReq, &listMountRes); err != nil {
		return nil, err
	}

	for _, infos := range listMountRes.Mounts {
		for _, info := range infos {
			mids = append(mids, string(info.ID))
		}
	}

	return mids, nil
}

// mountPointDirectory checks and prepares local directory for mounting.
// Returned clean function can be used to remove resources in case of other
// mounting errors.
//
// NOTE: This logic will be moved to klient.
func mountPointDirectory(path string) (clean func(), err error) {
	switch info, err := os.Stat(path); {
	case os.IsNotExist(err):
		// Create a new directory.
		if err := os.MkdirAll(path, 0755); err != nil {
			return nil, fmt.Errorf("cannot create destination directory: %s", err)
		}
		return func() {
			os.RemoveAll(path)
		}, nil
	case err != nil:
		return nil, fmt.Errorf("cannot stat destination directory: %s", err)
	case !info.IsDir():
		return nil, fmt.Errorf("file %q is not a directory", path)

	}

	// Provided directory already exists. Check if it's empty.
	f, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("cannot open destination directory: %s", err)
	}
	defer f.Close()

	switch _, err = f.Readdirnames(1); err {
	case nil:
		return nil, errors.New("destination directory is not empty")
	case io.EOF:
	default:
		return nil, fmt.Errorf("destination directory error: %s", err)
	}
	clean = func() {
		removeContent(path)
	}

	return clean, nil

}

// removeContent removes all files inside provided path but not the path itself.
func removeContent(path string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	for {
		names, err := f.Readdirnames(100)
		if err != nil {
			return err
		}

		for _, name := range names {
			os.RemoveAll(filepath.Join(path, name)) // Ignore errors.
		}
	}
}

// Mount synchronizes directories between remote and local machines
// using DefaultClient.
func Mount(opts *MountOptions) error { return DefaultClient.Mount(opts) }

// ListMount lists local mounts that are known to a klient using DefaultClient.
func ListMount(opts *ListMountOptions) (map[string][]mount.Info, error) {
	return DefaultClient.ListMount(opts)
}

// WaitIdle waits for mount's synchronization to complete using DefaultClient.
func WaitIdle(opts *WaitIdleOptions) error { return DefaultClient.WaitIdle(opts) }

// InspectMount inspects existing mount using DefaultClient.
func InspectMount(opts *InspectMountOptions) (machinegroup.InspectMountResponse, error) {
	return DefaultClient.InspectMount(opts)
}

// Umount removes existing mount using DefaultClient.
func Umount(opts *UmountOptions) error { return DefaultClient.Umount(opts) }
<|endoftext|>


<commit_before>package paypal

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/workers/payment/paymentmodels"
	"strings"

	"github.com/koding/logging"
	"github.com/koding/paypal"
)

const (
	CurrencyCode = "USD"
	ProviderName = "paypal"
	Month        = "Month"
	Year         = "Year"
)

var (
	Log                  = logging.NewLogger("payment")
	client               *paypal.PayPalClient
	returnURL, cancelURL string
)

func InitializeClientKey(creds config.Paypal) {
	returnURL = creds.ReturnUrl
	cancelURL = creds.CancelUrl

	client = paypal.NewDefaultClient(
		creds.Username, creds.Password, creds.Signature, creds.IsSandbox,
	)
}

func Client() (*paypal.PayPalClient, error) {
	err := isClientInitialized()
	if err != nil {
		return nil, err
	}

	return client, nil
}

func isClientInitialized() error {
	if client == nil {
		return errors.New("paypal client unitialized")
	}

	if returnURL == "" {
		return errors.New("return url is empty")
	}

	if cancelURL == "" {
		return errors.New("cancel url is empty")
	}

	return nil
}

func amount(cents uint64) float64 {
	return float64(cents)
}

func normalizeAmount(amount uint64) string {
	return fmt.Sprintf("%G", float64(amount)/100)
}

func handlePaypalErr(response *paypal.PayPalResponse, err error) error {
	if err != nil {
		return err
	}

	if response.Ack != "Success" {
		return errors.New("paypal request failed")
	}

	return nil
}

func getInterval(interval string) string {
	switch interval {
	case "monthly":
		return Month
	case "yearly":
		return Year
	default:
		return Month
	}
}

func goodName(plan *paymentmodels.Plan) string {
	return fmt.Sprintf("%s %s",
		strings.ToTitle(plan.Title), strings.ToTitle(plan.Interval),
	)
}
<commit_msg>payments: send correct interval to paypal<commit_after>package paypal

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/workers/payment/paymentmodels"
	"strings"

	"github.com/koding/logging"
	"github.com/koding/paypal"
)

const (
	CurrencyCode = "USD"
	ProviderName = "paypal"
	Month        = "Month"
	Year         = "Year"
)

var (
	Log                  = logging.NewLogger("payment")
	client               *paypal.PayPalClient
	returnURL, cancelURL string
)

func InitializeClientKey(creds config.Paypal) {
	returnURL = creds.ReturnUrl
	cancelURL = creds.CancelUrl

	client = paypal.NewDefaultClient(
		creds.Username, creds.Password, creds.Signature, creds.IsSandbox,
	)
}

func Client() (*paypal.PayPalClient, error) {
	err := isClientInitialized()
	if err != nil {
		return nil, err
	}

	return client, nil
}

func isClientInitialized() error {
	if client == nil {
		return errors.New("paypal client unitialized")
	}

	if returnURL == "" {
		return errors.New("return url is empty")
	}

	if cancelURL == "" {
		return errors.New("cancel url is empty")
	}

	return nil
}

func amount(cents uint64) float64 {
	return float64(cents)
}

func normalizeAmount(amount uint64) string {
	return fmt.Sprintf("%G", float64(amount)/100)
}

func handlePaypalErr(response *paypal.PayPalResponse, err error) error {
	if err != nil {
		return err
	}

	if response.Ack != "Success" {
		return errors.New("paypal request failed")
	}

	return nil
}

func getInterval(interval string) string {
	switch interval {
	case "month":
		return Month
	case "year":
		return Year
	default:
		return Month
	}
}

func goodName(plan *paymentmodels.Plan) string {
	return fmt.Sprintf("%s %s",
		strings.ToTitle(plan.Title), strings.ToTitle(plan.Interval),
	)
}
<|endoftext|>


<commit_before>package main

import "errors"
import "fmt"
import "io"
import "net/http"
import "os"
import "os/exec"
import "path/filepath"
import "regexp"
import "runtime"
import "sort"
import "strings"
import "github.com/google/go-github/github"
import "github.com/codegangsta/cli"
import "github.com/kardianos/osext"

var shell string
var dvmDir string
var debug bool
var silent bool

func main() {
  app := cli.NewApp()
  app.Name = "Docker Version Manager"
  app.Usage = "Manage multiple versions of the Docker client"
  app.EnableBashCompletion = true
  app.Flags = []cli.Flag{
    cli.StringFlag { Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to the current directory." },
    cli.StringFlag { Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
    cli.BoolFlag { Name: "debug", Usage: "Print additional debug information." },
    cli.BoolFlag{ Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
  }
  app.Commands = []cli.Command{
    {
      Name: "install",
      Aliases: []string{"i"},
      Usage: "dvm install <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        install(c.Args().First())
      },
    },
    {
      Name: "use",
      Usage: "dvm use <version>. dvm use system reverts to the system installation of Docker. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        use(c.Args().First())
      },
    },
    {
      Name: "list",
      Aliases: []string{"ls"},
      Usage: "dvm list [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        list(c.Args().First())
      },
    },
    {
      Name: "list-remote",
      Aliases: []string{"ls-remote"},
      Usage: "dvm list-remote [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        listRemote(c.Args().First())
      },
    },
    {
      Name: "deactivate",
      Usage: "dvm deactivate. Undo effects of `dvm` on current shell",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        deactivate()
      },
    },
  }

  app.Run(os.Args)
}

func setGlobalVars(c *cli.Context) {
  debug = c.GlobalBool("debug")
  shell = c.GlobalString("shell")
  silent = c.GlobalBool("silent")
  dvmDir = c.GlobalString("dvm-dir")
  if dvmDir == "" {
    var err error
    dvmDir, err = osext.ExecutableFolder()
    if err != nil {
      fmt.Fprintln(os.Stderr, "Unable to determine DVM home directory")
      os.Exit(1)
    }
  }
}

func list(pattern string) {
  versions := getInstalledVersions(pattern)

  for _, version := range versions {
    fmt.Println(version)
  }
}

func install(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The install command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  versionDir := getVersionDir(version)

  // TODO: Support experimental

  if _, err := os.Stat(versionDir); err == nil {
    if !silent { fmt.Fprintf(os.Stderr, "%s is already installed\n", version) }
    use(version)
    return
  }

  if !silent { fmt.Printf("Installing %s\n", version) }

  url := fmt.Sprintf("https://get.docker.com/builds/%s/%s/%s", getDockerOS(), getDockerArch(), getDockerBinaryName(version))
  tmpPath := filepath.Join(getDvmDir(), ".tmp/docker", version, getBinaryName())
  downloadFile(url, tmpPath)
  binaryPath := filepath.Join(getDvmDir(), "bin/docker", version, getBinaryName())
  ensureParentDirectoryExists(binaryPath)
  err := os.Rename(tmpPath, binaryPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to copy %s to %s.\n%s\n", tmpPath, binaryPath, err)
  }

  if debug {
    fmt.Printf("Installed Docker %s to %s", version, binaryPath)
  }
}

func use(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The use command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  // dvm use system undoes changes to the PATH and uses installed version of DOcker
  if version == "system" {
    systemDockerVersion, err := useSystemDocker()
    if err != nil {
      fmt.Fprintln(os.Stderr, "System version of Docker not found.")
      os.Exit(3)
    }

    if !silent { fmt.Printf("Now using system version of Docker: %s\n", systemDockerVersion) }
    writeShellScript()
    return
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  ensureVersionIsInstalled(version)
  removePreviousDvmVersionFromPath()
  prependDvmVersionToPath(version)
  writeShellScript()

  if !silent {
    fmt.Printf("Now using Docker %s\n", version)
  }
}

func getDvmDir() string {
  return dvmDir
}

func getDockerBinaryName(version string) string {
    if runtime.GOOS == "windows" {
      return fmt.Sprintf("docker-%s.exe", version)
    }
    return fmt.Sprintf("docker-%s", version)
}

func getBinaryName() string {
    if runtime.GOOS == "windows" {
      return "docker.exe"
    }
    return "docker"
}

func deactivate() {
  removePreviousDvmVersionFromPath()
  writeShellScript()
}

func prependDvmVersionToPath(version string) {
  var pathSep string
  if runtime.GOOS == "windows" { pathSep = ";" } else { pathSep = ":" }
  versionDir := getVersionDir(version)
  path := fmt.Sprintf("%s%s%s", versionDir, pathSep, os.Getenv("PATH"))
  os.Setenv("PATH", path)
}

func writeShellScript() {
  if runtime.GOOS == "windows" && shell == "" {
    fmt.Fprintf(os.Stderr, "The --shell flag or SHELL environment variable must be set when running on Windows. Available values are sh, powershell and cmd.")
    os.Exit(127)
  }

  path := os.Getenv("PATH")

  var contents string
  var fileExtension string
  if shell == "powershell" {
    contents = fmt.Sprintf(`$env:PATH="%s"`, path)
    fileExtension = "ps1"
  } else if shell == "cmd" {
    contents = fmt.Sprintf("PATH=%s", path)
    fileExtension = "cmd"
  } else { // default to bash
    contents = fmt.Sprintf("export PATH=%s", path)
    fileExtension = "sh"
  }

  // Write to a shell script for the calling wrapper to execute
  scriptPath := filepath.Join(dvmDir, ".tmp", ("dvm-output." + fileExtension))

  if debug {
    fmt.Printf("Writing wrapper shell script to %s\n%s\n", scriptPath, contents)
  }

  ensureParentDirectoryExists(scriptPath)
  scriptFile, err := os.Create(scriptPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create %s\n%s\n", scriptPath, err)
    os.Exit(1)
  }

  _, err = io.WriteString(scriptFile, contents)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s\n%s\n", scriptPath, err)
    os.Exit(1)
  }

  scriptFile.Close()
}

func removePreviousDvmVersionFromPath() {
  versionDir := getVersionDir("")

  var pathRegex string
  if runtime.GOOS == "windows" {
    escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
    pathRegex = escapedVersionDir + `\\\d+\.\d+\.\d+;`
  } else {
    pathRegex = versionDir + `/\d+\.\d+\.\d+:`
  }

  regex, _ := regexp.Compile(pathRegex)
  path := regex.ReplaceAllString(os.Getenv("PATH"), "")
  os.Setenv("PATH", path)
}

func ensureVersionIsInstalled(version string) {
    versionDir := getVersionDir(version)
    if _, err := os.Stat(versionDir); err == nil {
      return
    }

    if !silent {
      fmt.Printf("%s is not installed. Installing now...\n", version)
      install(version)
    }
}

func downloadFile(url string, destPath string) {
  ensureParentDirectoryExists(destPath)

  destFile, err := os.Create(destPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
  defer destFile.Close()
  os.Chmod(destPath, 0755)

  if debug { fmt.Printf("Downloading %s\n", url) }

  response, err := http.Get(url)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\n%s\n", url, err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\nStatus Code: %d\n", url, response.StatusCode)
    os.Exit(1)
  }
  defer response.Body.Close()

  _, err = io.Copy(destFile, response.Body)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
}

func versionExists(version string) bool {
  availableVersions := getAvailableVersions(version)

  for _,availableVersion := range availableVersions {
      if version == availableVersion {
          return true
      }
  }
  return false
}

func ensureParentDirectoryExists(filePath string) {
  dir := filepath.Dir(filePath)

  err := os.MkdirAll(dir, 0777)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create directory %s\n", dir)
    os.Exit(1)
  }
}

func useSystemDocker() (string, error) {
  removePreviousDvmVersionFromPath()
  systemDockerPath, _ := exec.LookPath("docker")
  rawVersion, _ := exec.Command(systemDockerPath, "-v").Output()

  if debug { fmt.Printf("docker -v output: %s\n", rawVersion)}

  versionRegex := regexp.MustCompile(`^Docker version (.*),`)
  match := versionRegex.FindSubmatch(rawVersion)
  if len(match) < 2 {
    return "", errors.New("No system installation of Docker detected.")
  }

  return string(match[1][:]), nil
}

func listRemote(pattern string) {
    versions := getAvailableVersions(pattern)
    for _, version := range versions {
      fmt.Println(version)
    }
}

func getInstalledVersions(pattern string) []string {
  versions, _ := filepath.Glob(getVersionDir(pattern + "*"))

  var results []string
  for _, versionDir := range versions {
    results = append(results, filepath.Base(versionDir))
  }

  return results
}

func getAvailableVersions(pattern string) []string {
  gh := github.NewClient(nil)
	tags, response, err := gh.Repositories.ListTags("docker", "docker", nil)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\n%s\n", err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\nStatus Code:%d\n", response.StatusCode)
    os.Exit(1)
  }

  versionRegex := regexp.MustCompile(`^v([1-9]+\.\d+\.\d+)$`)
  patternRegex, err := regexp.Compile(pattern)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Invalid pattern.\n%s\n", err)
    os.Exit(3)
  }

  var results []string
  for _, tag := range tags {
    version := *tag.Name
    match := versionRegex.FindStringSubmatch(version)
    if len(match) > 1 && patternRegex.MatchString(version) {
      results = append(results, match[1])
    }
  }

  sort.Strings(results)
  return results
}

func getVersionDir(version string) string {
  return filepath.Join(dvmDir, "bin", "docker", version)
}

func getDockerOS() string {
  switch runtime.GOOS {
    case "windows":
      return "Windows"
    case "darwin":
      return "Darwin"
    case "linux":
      return "Linux"
    }

  fmt.Fprintf(os.Stderr, "Unsupported OS: %s\n", runtime.GOOS)
  os.Exit(1)
  return ""
}

func getDockerArch() string {
  switch runtime.GOARCH {
    case "amd64":
      return "x86_64"
    case "386":
      return "i386"
    }

  fmt.Fprintf(os.Stderr, "Unsupported ARCH: %s\n", runtime.GOARCH)
  os.Exit(1)
  return ""
}
/*
func help() {
  echo
  echo "Docker Version Manager"
  echo
  echo "Usage:"
  echo "  dvm help                              Show this message"
  echo "  dvm --version                         Print out the latest released version of dvm"
  echo "  dvm install <version>                 Download and install a <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm uninstall <version>               Uninstall a version"
  echo "  dvm use <version>                     Modify PATH to use <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm current                           Display currently activated version"
  echo "  dvm ls                                List installed versions"
  echo "  dvm ls <version>                      List versions matching a given description"
  echo "  dvm ls-remote                         List remote versions available for install"
  echo "  dvm deactivate                        Undo effects of \`dvm\` on current shell"
  echo "  dvm alias [<pattern>]                 Show all aliases beginning with <pattern>"
  echo "  dvm alias <name> <version>            Set an alias named <name> pointing to <version>"
  echo "  dvm unalias <name>                    Deletes the alias named <name>"
  echo "  dvm unload                            Unload \`dvm\` from shell"
  echo "  dvm which [<version>]                 Display path to installed docker version."
  echo
  echo "Example:"
  echo "  dvm install 1.8.1                     Install a specific version number"
  echo "  dvm use 1.6                           Use the latest available 1.6.x release"
  echo "  dvm alias default 1.8.1               Set default Docker version on a shell"
  echo
  echo "Note:"
  echo "  to remove, delete, or uninstall dvm - remove ~/.dvm"
  echo
}*/
<commit_msg>pretty colors!<commit_after>package main

import "errors"
import "fmt"
import "io"
import "net/http"
import "os"
import "os/exec"
import "path/filepath"
import "regexp"
import "runtime"
import "sort"
import "strings"
import "github.com/fatih/color"
import "github.com/google/go-github/github"
import "github.com/codegangsta/cli"
import "github.com/kardianos/osext"

var shell string
var dvmDir string
var debug bool
var silent bool

const (
    INVALID_ARGUMENT = 127
    INVALID_OPERATION = 3
    RUNTIME_ERROR = 1
)

func main() {
  app := cli.NewApp()
  app.Name = "Docker Version Manager"
  app.Usage = "Manage multiple versions of the Docker client"
  app.EnableBashCompletion = true
  app.Flags = []cli.Flag{
    cli.StringFlag { Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to the current directory." },
    cli.StringFlag { Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
    cli.BoolFlag { Name: "debug", Usage: "Print additional debug information." },
    cli.BoolFlag{ Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
  }
  app.Commands = []cli.Command{
    {
      Name: "install",
      Aliases: []string{"i"},
      Usage: "dvm install <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        install(c.Args().First())
      },
    },
    {
      Name: "use",
      Usage: "dvm use <version>. dvm use system reverts to the system installation of Docker. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        use(c.Args().First())
      },
    },
    {
      Name: "list",
      Aliases: []string{"ls"},
      Usage: "dvm list [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        list(c.Args().First())
      },
    },
    {
      Name: "list-remote",
      Aliases: []string{"ls-remote"},
      Usage: "dvm list-remote [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        listRemote(c.Args().First())
      },
    },
    {
      Name: "deactivate",
      Usage: "dvm deactivate. Undo effects of `dvm` on current shell",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        deactivate()
      },
    },
  }

  app.Run(os.Args)
}

func writeDebug(format string, a ...interface{}) {
  if !debug { return }

  color.Cyan(format, a...)
}

func writeInfo(format string, a ...interface{}) {
  if silent { return }

  color.White(format, a...)
}

func writeWarning(format string, a ...interface{}) {
  if silent { return }

  color.Yellow(format, a...)
}

func writeError(format string, err error, a ...interface{}) {
  color.Set(color.FgRed)
  fmt.Fprintf(os.Stderr, format + "\n", a...)
  if err != nil {
    fmt.Fprintln(os.Stderr, err)
  }
  color.Unset()
}

func die(format string, err error, exitCode int, a ...interface{}) {
  writeError(format, err, a...)
  os.Exit(exitCode)
}

func setGlobalVars(c *cli.Context) {
  debug = c.GlobalBool("debug")
  shell = c.GlobalString("shell")
  silent = c.GlobalBool("silent")
  dvmDir = c.GlobalString("dvm-dir")
  if dvmDir == "" {
    var err error
    dvmDir, err = osext.ExecutableFolder()
    if err != nil {
      die("Unable to determine DVM home directory", nil, 1)
    }
  }
}

func list(pattern string) {
  versions := getInstalledVersions(pattern)

  for _, version := range versions {
    writeInfo(version)
  }
}

func install(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    die("The install command requires that a version is specified or the DOCKER_VERSION environment variable is set.", nil, INVALID_ARGUMENT)
  }

  if !versionExists(version) {
    die("Version %s not found - try `dvm ls-remote` to browse available versions.", nil, INVALID_OPERATION, version)
  }

  versionDir := getVersionDir(version)

  // TODO: Support experimental

  if _, err := os.Stat(versionDir); err == nil {
    writeWarning("%s is already installed", version)
    use(version)
    return
  }

  writeInfo("Installing %s...", version)

  url := fmt.Sprintf("https://get.docker.com/builds/%s/%s/%s", getDockerOS(), getDockerArch(), getDockerBinaryName(version))
  tmpPath := filepath.Join(getDvmDir(), ".tmp/docker", version, getBinaryName())
  downloadFile(url, tmpPath)
  binaryPath := filepath.Join(getDvmDir(), "bin/docker", version, getBinaryName())
  ensureParentDirectoryExists(binaryPath)
  err := os.Rename(tmpPath, binaryPath)
  if err != nil {
    die("Unable to copy %s to %s.", err, RUNTIME_ERROR, tmpPath, binaryPath)
  }

  writeDebug("Installed Docker %s to %s", version, binaryPath)
}

func use(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    die("The use command requires that a version is specified or the DOCKER_VERSION environment variable is set.", nil, INVALID_OPERATION)
  }

  // dvm use system undoes changes to the PATH and uses installed version of DOcker
  if version == "system" {
    systemDockerVersion, err := useSystemDocker()
    if err != nil {
      die("System version of Docker not found.", nil, INVALID_OPERATION)
    }

    writeInfo("Now using system version of Docker: %s", systemDockerVersion)
    writeShellScript()
    return
  }

  if !versionExists(version) {
    die("Version %s not found - try `dvm ls-remote` to browse available versions.", nil, INVALID_OPERATION, version)
  }

  ensureVersionIsInstalled(version)
  removePreviousDvmVersionFromPath()
  prependDvmVersionToPath(version)
  writeShellScript()

  writeInfo("Now using Docker %s", version)
}

func getDvmDir() string {
  return dvmDir
}

func getDockerBinaryName(version string) string {
    if runtime.GOOS == "windows" {
      return fmt.Sprintf("docker-%s.exe", version)
    }
    return fmt.Sprintf("docker-%s", version)
}

func getBinaryName() string {
    if runtime.GOOS == "windows" {
      return "docker.exe"
    }
    return "docker"
}

func deactivate() {
  removePreviousDvmVersionFromPath()
  writeShellScript()
}

func prependDvmVersionToPath(version string) {
  var pathSep string
  if runtime.GOOS == "windows" { pathSep = ";" } else { pathSep = ":" }
  versionDir := getVersionDir(version)
  path := fmt.Sprintf("%s%s%s", versionDir, pathSep, os.Getenv("PATH"))
  os.Setenv("PATH", path)
}

func writeShellScript() {
  if runtime.GOOS == "windows" && shell == "" {
    die("The --shell flag or SHELL environment variable must be set when running on Windows. Available values are sh, powershell and cmd.", nil, INVALID_ARGUMENT)
  }

  path := os.Getenv("PATH")

  var contents string
  var fileExtension string
  if shell == "powershell" {
    contents = fmt.Sprintf(`$env:PATH="%s"`, path)
    fileExtension = "ps1"
  } else if shell == "cmd" {
    contents = fmt.Sprintf("PATH=%s", path)
    fileExtension = "cmd"
  } else { // default to bash
    contents = fmt.Sprintf("export PATH=%s", path)
    fileExtension = "sh"
  }

  // Write to a shell script for the calling wrapper to execute
  scriptPath := filepath.Join(dvmDir, ".tmp", ("dvm-output." + fileExtension))

  writeDebug("Writing wrapper shell script to %s", scriptPath, contents)
  writeDebug(contents)

  ensureParentDirectoryExists(scriptPath)
  scriptFile, err := os.Create(scriptPath)
  if err != nil {
    die("Unable to create %s", err, RUNTIME_ERROR, scriptPath)
  }

  _, err = io.WriteString(scriptFile, contents)
  if err != nil {
    die("Unable to write to %s", err, RUNTIME_ERROR, scriptPath)
  }

  scriptFile.Close()
}

func removePreviousDvmVersionFromPath() {
  versionDir := getVersionDir("")

  var pathRegex string
  if runtime.GOOS == "windows" {
    escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
    pathRegex = escapedVersionDir + `\\\d+\.\d+\.\d+;`
  } else {
    pathRegex = versionDir + `/\d+\.\d+\.\d+:`
  }

  regex, _ := regexp.Compile(pathRegex)
  path := regex.ReplaceAllString(os.Getenv("PATH"), "")
  os.Setenv("PATH", path)
}

func ensureVersionIsInstalled(version string) {
    versionDir := getVersionDir(version)
    if _, err := os.Stat(versionDir); err == nil {
      return
    }

    writeInfo("%s is not installed. Installing now...", version)
    install(version)
}

func downloadFile(url string, destPath string) {
  ensureParentDirectoryExists(destPath)

  destFile, err := os.Create(destPath)
  if err != nil {
    die("Unable to create to %s.", err, RUNTIME_ERROR, destPath)
  }
  defer destFile.Close()
  os.Chmod(destPath, 0755)

  writeDebug("Downloading %s", url)

  response, err := http.Get(url)
  if err != nil {
    die("Unable to download %s.", err, RUNTIME_ERROR, url)
  }

  if response.StatusCode != 200 {
    writeError("Unable to download %s.", nil)
    die("Status Code: %d", nil, RUNTIME_ERROR, response.StatusCode)
  }
  defer response.Body.Close()

  _, err = io.Copy(destFile, response.Body)
  if err != nil {
    die("Unable to write to %s.", err, RUNTIME_ERROR, destPath)
  }
}

func versionExists(version string) bool {
  availableVersions := getAvailableVersions(version)

  for _,availableVersion := range availableVersions {
      if version == availableVersion {
          return true
      }
  }
  return false
}

func ensureParentDirectoryExists(filePath string) {
  dir := filepath.Dir(filePath)

  err := os.MkdirAll(dir, 0777)
  if err != nil {
    die("Unable to create directory %s.", err, RUNTIME_ERROR, dir)
  }
}

func useSystemDocker() (string, error) {
  removePreviousDvmVersionFromPath()
  systemDockerPath, _ := exec.LookPath("docker")
  rawVersion, _ := exec.Command(systemDockerPath, "-v").Output()

  writeDebug("docker -v output: %s", rawVersion)

  versionRegex := regexp.MustCompile(`^Docker version (.*),`)
  match := versionRegex.FindSubmatch(rawVersion)
  if len(match) < 2 {
    return "", errors.New("No system installation of Docker detected.")
  }

  return string(match[1][:]), nil
}

func listRemote(pattern string) {
    versions := getAvailableVersions(pattern)
    for _, version := range versions {
      writeInfo(version)
    }
}

func getInstalledVersions(pattern string) []string {
  versions, _ := filepath.Glob(getVersionDir(pattern + "*"))

  var results []string
  for _, versionDir := range versions {
    results = append(results, filepath.Base(versionDir))
  }

  return results
}

func getAvailableVersions(pattern string) []string {
  gh := github.NewClient(nil)
	tags, response, err := gh.Repositories.ListTags("docker", "docker", nil)
  if err != nil {
    die("Unable to retrieve list of Docker tags from GitHub", err, RUNTIME_ERROR)
  }
  if response.StatusCode != 200 {
    die("Unable to retrieve list of Docker tags from GitHub (Status %s).", nil, RUNTIME_ERROR, response.StatusCode)
  }

  versionRegex := regexp.MustCompile(`^v([1-9]+\.\d+\.\d+)$`)
  patternRegex, err := regexp.Compile(pattern)
  if err != nil {
    die("Invalid pattern.", err, INVALID_OPERATION)
  }

  var results []string
  for _, tag := range tags {
    version := *tag.Name
    match := versionRegex.FindStringSubmatch(version)
    if len(match) > 1 && patternRegex.MatchString(version) {
      results = append(results, match[1])
    }
  }

  sort.Strings(results)
  return results
}

func getVersionDir(version string) string {
  return filepath.Join(dvmDir, "bin", "docker", version)
}

func getDockerOS() string {
  switch runtime.GOOS {
    case "windows":
      return "Windows"
    case "darwin":
      return "Darwin"
    case "linux":
      return "Linux"
    }

  die("Unsupported OS: %s", nil, RUNTIME_ERROR, runtime.GOOS)
  return ""
}

func getDockerArch() string {
  switch runtime.GOARCH {
    case "amd64":
      return "x86_64"
    case "386":
      return "i386"
    }

  die("Unsupported ARCH: %s", nil, RUNTIME_ERROR, runtime.GOARCH)
  return ""
}
<|endoftext|>