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

package internal

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"text/tabwriter"
	"text/template"
	"time"

	"github.com/codegangsta/cli"
)

var flagCategories map[string]string

// Set up custom help text for goofys; in particular the usage section.
func filterCategory(flags []cli.Flag, category string) (ret []cli.Flag) {
	for _, f := range flags {
		if flagCategories[f.GetName()] == category {
			ret = append(ret, f)
		}
	}
	return
}

func init() {
	cli.AppHelpTemplate = `NAME:
   {{.Name}} - {{.Usage}}

USAGE:
   {{.Name}} {{if .Flags}}[global options]{{end}} bucket[:prefix] mountpoint
   {{if .Version}}
VERSION:
   {{.Version}}
   {{end}}{{if len .Authors}}
AUTHOR(S):
   {{range .Authors}}{{ . }}{{end}}
   {{end}}{{if .Commands}}
COMMANDS:
   {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
   {{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
   {{range category .Flags ""}}{{.}}
   {{end}}
TUNING OPTIONS:
   {{range category .Flags "tuning"}}{{.}}
   {{end}}
AWS S3 OPTIONS:
   {{range category .Flags "aws"}}{{.}}
   {{end}}
MISC OPTIONS:
   {{range category .Flags "misc"}}{{.}}
   {{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
   {{.Copyright}}
   {{end}}
`
}

var VersionHash string

func NewApp() (app *cli.App) {
	uid, gid := MyUserAndGroup()

	app = &cli.App{
		Name:     "goofys",
		Version:  "0.0.15-" + VersionHash,
		Usage:    "Mount an S3 bucket locally",
		HideHelp: true,
		Writer:   os.Stderr,
		Flags: []cli.Flag{

			cli.BoolFlag{
				Name:  "help, h",
				Usage: "Print this help text and exit successfully.",
			},

			/////////////////////////
			// File system
			/////////////////////////

			cli.StringSliceFlag{
				Name:  "o",
				Usage: "Additional system-specific mount options. Be careful!",
			},

			cli.StringFlag{
				Name: "cache",
				Usage: "Directory to use for data cache. " +
					"Requires catfs and `-o allow_other'. " +
					"Can also pass in other catfs options " +
					"(ex: --cache \"--free:10%:$HOME/cache\") (default: off)",
			},

			cli.IntFlag{
				Name:  "dir-mode",
				Value: 0755,
				Usage: "Permission bits for directories. (default: 0755)",
			},

			cli.IntFlag{
				Name:  "file-mode",
				Value: 0644,
				Usage: "Permission bits for files. (default: 0644)",
			},

			cli.IntFlag{
				Name:  "uid",
				Value: uid,
				Usage: "UID owner of all inodes.",
			},

			cli.IntFlag{
				Name:  "gid",
				Value: gid,
				Usage: "GID owner of all inodes.",
			},

			/////////////////////////
			// S3
			/////////////////////////

			cli.StringFlag{
				Name:  "endpoint",
				Value: "",
				Usage: "The non-AWS endpoint to connect to." +
					" Possible values: http://127.0.0.1:8081/",
			},

			cli.StringFlag{
				Name:  "region",
				Value: "us-east-1",
				Usage: "The region to connect to. Usually this is auto-detected." +
					" Possible values: us-east-1, us-west-1, us-west-2, eu-west-1, " +
					"eu-central-1, ap-southeast-1, ap-southeast-2, ap-northeast-1, " +
					"sa-east-1, cn-north-1",
			},

			cli.StringFlag{
				Name:  "storage-class",
				Value: "STANDARD",
				Usage: "The type of storage to use when writing objects." +
					" Possible values: REDUCED_REDUNDANCY, STANDARD, STANDARD_IA.",
			},

			cli.StringFlag{
				Name:  "profile",
				Usage: "Use a named profile from $HOME/.aws/credentials instead of \"default\"",
			},

			cli.BoolFlag{
				Name:  "use-content-type",
				Usage: "Set Content-Type according to file extension and /etc/mime.types (default: off)",
			},

			/// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
			/// See http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html
			cli.BoolFlag{
				Name:  "sse",
				Usage: "Enable basic server-side encryption at rest (SSE-S3) in S3 for all writes (default: off)",
			},

			cli.StringFlag{
				Name:  "sse-kms",
				Usage: "Enable KMS encryption (SSE-KMS) for all writes using this particular KMS `key-id`. Leave blank to Use the account's CMK - customer master key (default: off)",
				Value: "",
			},

			/// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
			cli.StringFlag{
				Name:  "acl",
				Usage: "The canned ACL to apply to the object. Possible values: private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control (default: off)",
				Value: "",
			},

			/////////////////////////
			// Tuning
			/////////////////////////

			cli.BoolFlag{
				Name:  "cheap",
				Usage: "Reduce S3 operation costs at the expense of some performance (default: off)",
			},

			cli.BoolFlag{
				Name:  "no-implicit-dir",
				Usage: "Assume all directory objects (\"dir/\") exist (default: off)",
			},

			cli.DurationFlag{
				Name:  "stat-cache-ttl",
				Value: time.Minute,
				Usage: "How long to cache StatObject results and inode attributes.",
			},

			cli.DurationFlag{
				Name:  "type-cache-ttl",
				Value: time.Minute,
				Usage: "How long to cache name -> file/dir mappings in directory " +
					"inodes.",
			},

			/////////////////////////
			// Debugging
			/////////////////////////

			cli.BoolFlag{
				Name:  "debug_fuse",
				Usage: "Enable fuse-related debugging output.",
			},

			cli.BoolFlag{
				Name:  "debug_s3",
				Usage: "Enable S3-related debugging output.",
			},

			cli.BoolFlag{
				Name:  "f",
				Usage: "Run goofys in foreground.",
			},
		},
	}

	var funcMap = template.FuncMap{
		"category": filterCategory,
		"join":     strings.Join,
	}

	flagCategories = map[string]string{}

	for _, f := range []string{"region", "sse", "sse-kms", "storage-class", "acl"} {
		flagCategories[f] = "aws"
	}

	for _, f := range []string{"cheap", "no-implicit-dir", "stat-cache-ttl", "type-cache-ttl"} {
		flagCategories[f] = "tuning"
	}

	for _, f := range []string{"help, h", "debug_fuse", "debug_s3", "version, v", "f"} {
		flagCategories[f] = "misc"
	}

	cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
		w = tabwriter.NewWriter(w, 1, 8, 2, ' ', 0)
		var tmplGet = template.Must(template.New("help").Funcs(funcMap).Parse(templ))
		tmplGet.Execute(w, app)
	}

	return
}

type FlagStorage struct {
	// File system
	MountOptions      map[string]string
	MountPoint        string
	MountPointArg     string
	MountPointCreated string

	Cache    []string
	DirMode  os.FileMode
	FileMode os.FileMode
	Uid      uint32
	Gid      uint32

	// S3
	Endpoint       string
	Region         string
	RegionSet      bool
	StorageClass   string
	Profile        string
	UseContentType bool
	UseSSE         bool
	UseKMS         bool
	KMSKeyID       string
	ACL            string

	// Tuning
	Cheap        bool
	ExplicitDir  bool
	StatCacheTTL time.Duration
	TypeCacheTTL time.Duration

	// Debugging
	DebugFuse  bool
	DebugS3    bool
	Foreground bool
}

func parseOptions(m map[string]string, s string) {
	// NOTE(jacobsa): The man pages don't define how escaping works, and as far
	// as I can tell there is no way to properly escape or quote a comma in the
	// options list for an fstab entry. So put our fingers in our ears and hope
	// that nobody needs a comma.
	for _, p := range strings.Split(s, ",") {
		var name string
		var value string

		// Split on the first equals sign.
		if equalsIndex := strings.IndexByte(p, '='); equalsIndex != -1 {
			name = p[:equalsIndex]
			value = p[equalsIndex+1:]
		} else {
			name = p
		}

		m[name] = value
	}

	return
}

func (flags *FlagStorage) Cleanup() {
	if flags.MountPointCreated != flags.MountPointArg {
		err := os.Remove(flags.MountPointCreated)
		if err != nil {
			log.Errorf("rmdir %v = %v", flags.MountPointCreated, err)
		}
	}
}

// Add the flags accepted by run to the supplied flag set, returning the
// variables into which the flags will parse.
func PopulateFlags(c *cli.Context) (ret *FlagStorage) {
	flags := &FlagStorage{
		// File system
		MountOptions: make(map[string]string),
		DirMode:      os.FileMode(c.Int("dir-mode")),
		FileMode:     os.FileMode(c.Int("file-mode")),
		Uid:          uint32(c.Int("uid")),
		Gid:          uint32(c.Int("gid")),

		// Tuning,
		Cheap:        c.Bool("cheap"),
		ExplicitDir:  c.Bool("no-implicit-dir"),
		StatCacheTTL: c.Duration("stat-cache-ttl"),
		TypeCacheTTL: c.Duration("type-cache-ttl"),

		// S3
		Endpoint:       c.String("endpoint"),
		Region:         c.String("region"),
		RegionSet:      c.IsSet("region"),
		StorageClass:   c.String("storage-class"),
		Profile:        c.String("profile"),
		UseContentType: c.Bool("use-content-type"),
		UseSSE:         c.Bool("sse"),
		UseKMS:         c.IsSet("sse-kms"),
		KMSKeyID:       c.String("sse-kms"),
		ACL:            c.String("acl"),

		// Debugging,
		DebugFuse:  c.Bool("debug_fuse"),
		DebugS3:    c.Bool("debug_s3"),
		Foreground: c.Bool("f"),
	}

	// Handle the repeated "-o" flag.
	for _, o := range c.StringSlice("o") {
		parseOptions(flags.MountOptions, o)
	}

	flags.MountPointArg = c.Args()[1]
	flags.MountPoint = flags.MountPointArg
	var err error

	defer func() {
		if err != nil {
			flags.Cleanup()
		}
	}()

	if c.IsSet("cache") {
		cache := c.String("cache")
		cacheArgs := strings.Split(c.String("cache"), ":")
		cacheDir := cacheArgs[len(cacheArgs)-1]

		fi, err := os.Stat(cacheDir)
		if err != nil || !fi.IsDir() {
			io.WriteString(cli.ErrWriter,
				fmt.Sprintf("Invalid value \"%v\" for --cache: not a directory\n\n",
					cacheDir))
			return nil
		}

		if _, ok := flags.MountOptions["allow_other"]; !ok {
			flags.MountPointCreated, err = ioutil.TempDir("", ".goofys-mnt")
			if err != nil {
				io.WriteString(cli.ErrWriter,
					fmt.Sprintf("Unable to create temp dir: %v", err))
				return nil
			}
			flags.MountPoint = flags.MountPointCreated
		}

		cacheArgs = append(cacheArgs, "")
		cacheArgs[len(cacheArgs)-1] = cacheDir
		cacheArgs[len(cacheArgs)-2] = flags.MountPoint
		cacheArgs = append(cacheArgs, flags.MountPointArg)

		cacheArgs = append(cacheArgs, "")
		copy(cacheArgs[1:], cacheArgs[0:])
		cacheArgs[0] = "--test"

		if flags.MountPointArg == flags.MountPoint {
			cacheArgs = append(cacheArgs, "", "")
			copy(cacheArgs[3:], cacheArgs[1:])
			cacheArgs[1] = "-o"
			cacheArgs[2] = "nonempty"
		}

		catfs := exec.Command("catfs", cacheArgs...)
		_, err = catfs.Output()
		if err != nil {
			if ee, ok := err.(*exec.Error); ok {
				io.WriteString(cli.ErrWriter,
					fmt.Sprintf("--cache requires catfs (%v) but %v\n\n",
						"http://github.com/kahing/catfs",
						ee.Error()))
			} else if ee, ok := err.(*exec.ExitError); ok {
				io.WriteString(cli.ErrWriter,
					fmt.Sprintf("Invalid value \"%v\" for --cache: %v\n\n",
						cache, string(ee.Stderr)))
			}
			return nil
		}

		flags.Cache = cacheArgs[1:]
	}

	// KMS implies SSE
	if flags.UseKMS {
		flags.UseSSE = true
	}

	return flags
}

func MassageMountFlags(args []string) (ret []string) {
	if len(args) == 5 && args[3] == "-o" {
		// looks like it's coming from fstab!
		mountOptions := ""
		ret = append(ret, args[0])

		for _, p := range strings.Split(args[4], ",") {
			if strings.HasPrefix(p, "-") {
				ret = append(ret, p)
			} else {
				mountOptions += p
				mountOptions += ","
			}
		}

		if len(mountOptions) != 0 {
			// remove trailing ,
			mountOptions = mountOptions[:len(mountOptions)-1]
			ret = append(ret, "-o")
			ret = append(ret, mountOptions)
		}

		ret = append(ret, args[1])
		ret = append(ret, args[2])
	} else {
		return args
	}

	return
}
<commit_msg>don't remove dir if we didn't create it<commit_after>// Copyright 2015 - 2017 Ka-Hing Cheung
// Copyright 2015 - 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package internal

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"text/tabwriter"
	"text/template"
	"time"

	"github.com/codegangsta/cli"
)

var flagCategories map[string]string

// Set up custom help text for goofys; in particular the usage section.
func filterCategory(flags []cli.Flag, category string) (ret []cli.Flag) {
	for _, f := range flags {
		if flagCategories[f.GetName()] == category {
			ret = append(ret, f)
		}
	}
	return
}

func init() {
	cli.AppHelpTemplate = `NAME:
   {{.Name}} - {{.Usage}}

USAGE:
   {{.Name}} {{if .Flags}}[global options]{{end}} bucket[:prefix] mountpoint
   {{if .Version}}
VERSION:
   {{.Version}}
   {{end}}{{if len .Authors}}
AUTHOR(S):
   {{range .Authors}}{{ . }}{{end}}
   {{end}}{{if .Commands}}
COMMANDS:
   {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
   {{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
   {{range category .Flags ""}}{{.}}
   {{end}}
TUNING OPTIONS:
   {{range category .Flags "tuning"}}{{.}}
   {{end}}
AWS S3 OPTIONS:
   {{range category .Flags "aws"}}{{.}}
   {{end}}
MISC OPTIONS:
   {{range category .Flags "misc"}}{{.}}
   {{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
   {{.Copyright}}
   {{end}}
`
}

var VersionHash string

func NewApp() (app *cli.App) {
	uid, gid := MyUserAndGroup()

	app = &cli.App{
		Name:     "goofys",
		Version:  "0.0.15-" + VersionHash,
		Usage:    "Mount an S3 bucket locally",
		HideHelp: true,
		Writer:   os.Stderr,
		Flags: []cli.Flag{

			cli.BoolFlag{
				Name:  "help, h",
				Usage: "Print this help text and exit successfully.",
			},

			/////////////////////////
			// File system
			/////////////////////////

			cli.StringSliceFlag{
				Name:  "o",
				Usage: "Additional system-specific mount options. Be careful!",
			},

			cli.StringFlag{
				Name: "cache",
				Usage: "Directory to use for data cache. " +
					"Requires catfs and `-o allow_other'. " +
					"Can also pass in other catfs options " +
					"(ex: --cache \"--free:10%:$HOME/cache\") (default: off)",
			},

			cli.IntFlag{
				Name:  "dir-mode",
				Value: 0755,
				Usage: "Permission bits for directories. (default: 0755)",
			},

			cli.IntFlag{
				Name:  "file-mode",
				Value: 0644,
				Usage: "Permission bits for files. (default: 0644)",
			},

			cli.IntFlag{
				Name:  "uid",
				Value: uid,
				Usage: "UID owner of all inodes.",
			},

			cli.IntFlag{
				Name:  "gid",
				Value: gid,
				Usage: "GID owner of all inodes.",
			},

			/////////////////////////
			// S3
			/////////////////////////

			cli.StringFlag{
				Name:  "endpoint",
				Value: "",
				Usage: "The non-AWS endpoint to connect to." +
					" Possible values: http://127.0.0.1:8081/",
			},

			cli.StringFlag{
				Name:  "region",
				Value: "us-east-1",
				Usage: "The region to connect to. Usually this is auto-detected." +
					" Possible values: us-east-1, us-west-1, us-west-2, eu-west-1, " +
					"eu-central-1, ap-southeast-1, ap-southeast-2, ap-northeast-1, " +
					"sa-east-1, cn-north-1",
			},

			cli.StringFlag{
				Name:  "storage-class",
				Value: "STANDARD",
				Usage: "The type of storage to use when writing objects." +
					" Possible values: REDUCED_REDUNDANCY, STANDARD, STANDARD_IA.",
			},

			cli.StringFlag{
				Name:  "profile",
				Usage: "Use a named profile from $HOME/.aws/credentials instead of \"default\"",
			},

			cli.BoolFlag{
				Name:  "use-content-type",
				Usage: "Set Content-Type according to file extension and /etc/mime.types (default: off)",
			},

			/// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
			/// See http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html
			cli.BoolFlag{
				Name:  "sse",
				Usage: "Enable basic server-side encryption at rest (SSE-S3) in S3 for all writes (default: off)",
			},

			cli.StringFlag{
				Name:  "sse-kms",
				Usage: "Enable KMS encryption (SSE-KMS) for all writes using this particular KMS `key-id`. Leave blank to Use the account's CMK - customer master key (default: off)",
				Value: "",
			},

			/// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
			cli.StringFlag{
				Name:  "acl",
				Usage: "The canned ACL to apply to the object. Possible values: private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control (default: off)",
				Value: "",
			},

			/////////////////////////
			// Tuning
			/////////////////////////

			cli.BoolFlag{
				Name:  "cheap",
				Usage: "Reduce S3 operation costs at the expense of some performance (default: off)",
			},

			cli.BoolFlag{
				Name:  "no-implicit-dir",
				Usage: "Assume all directory objects (\"dir/\") exist (default: off)",
			},

			cli.DurationFlag{
				Name:  "stat-cache-ttl",
				Value: time.Minute,
				Usage: "How long to cache StatObject results and inode attributes.",
			},

			cli.DurationFlag{
				Name:  "type-cache-ttl",
				Value: time.Minute,
				Usage: "How long to cache name -> file/dir mappings in directory " +
					"inodes.",
			},

			/////////////////////////
			// Debugging
			/////////////////////////

			cli.BoolFlag{
				Name:  "debug_fuse",
				Usage: "Enable fuse-related debugging output.",
			},

			cli.BoolFlag{
				Name:  "debug_s3",
				Usage: "Enable S3-related debugging output.",
			},

			cli.BoolFlag{
				Name:  "f",
				Usage: "Run goofys in foreground.",
			},
		},
	}

	var funcMap = template.FuncMap{
		"category": filterCategory,
		"join":     strings.Join,
	}

	flagCategories = map[string]string{}

	for _, f := range []string{"region", "sse", "sse-kms", "storage-class", "acl"} {
		flagCategories[f] = "aws"
	}

	for _, f := range []string{"cheap", "no-implicit-dir", "stat-cache-ttl", "type-cache-ttl"} {
		flagCategories[f] = "tuning"
	}

	for _, f := range []string{"help, h", "debug_fuse", "debug_s3", "version, v", "f"} {
		flagCategories[f] = "misc"
	}

	cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
		w = tabwriter.NewWriter(w, 1, 8, 2, ' ', 0)
		var tmplGet = template.Must(template.New("help").Funcs(funcMap).Parse(templ))
		tmplGet.Execute(w, app)
	}

	return
}

type FlagStorage struct {
	// File system
	MountOptions      map[string]string
	MountPoint        string
	MountPointArg     string
	MountPointCreated string

	Cache    []string
	DirMode  os.FileMode
	FileMode os.FileMode
	Uid      uint32
	Gid      uint32

	// S3
	Endpoint       string
	Region         string
	RegionSet      bool
	StorageClass   string
	Profile        string
	UseContentType bool
	UseSSE         bool
	UseKMS         bool
	KMSKeyID       string
	ACL            string

	// Tuning
	Cheap        bool
	ExplicitDir  bool
	StatCacheTTL time.Duration
	TypeCacheTTL time.Duration

	// Debugging
	DebugFuse  bool
	DebugS3    bool
	Foreground bool
}

func parseOptions(m map[string]string, s string) {
	// NOTE(jacobsa): The man pages don't define how escaping works, and as far
	// as I can tell there is no way to properly escape or quote a comma in the
	// options list for an fstab entry. So put our fingers in our ears and hope
	// that nobody needs a comma.
	for _, p := range strings.Split(s, ",") {
		var name string
		var value string

		// Split on the first equals sign.
		if equalsIndex := strings.IndexByte(p, '='); equalsIndex != -1 {
			name = p[:equalsIndex]
			value = p[equalsIndex+1:]
		} else {
			name = p
		}

		m[name] = value
	}

	return
}

func (flags *FlagStorage) Cleanup() {
	if flags.MountPointCreated != "" && flags.MountPointCreated != flags.MountPointArg {
		err := os.Remove(flags.MountPointCreated)
		if err != nil {
			log.Errorf("rmdir %v = %v", flags.MountPointCreated, err)
		}
	}
}

// Add the flags accepted by run to the supplied flag set, returning the
// variables into which the flags will parse.
func PopulateFlags(c *cli.Context) (ret *FlagStorage) {
	flags := &FlagStorage{
		// File system
		MountOptions: make(map[string]string),
		DirMode:      os.FileMode(c.Int("dir-mode")),
		FileMode:     os.FileMode(c.Int("file-mode")),
		Uid:          uint32(c.Int("uid")),
		Gid:          uint32(c.Int("gid")),

		// Tuning,
		Cheap:        c.Bool("cheap"),
		ExplicitDir:  c.Bool("no-implicit-dir"),
		StatCacheTTL: c.Duration("stat-cache-ttl"),
		TypeCacheTTL: c.Duration("type-cache-ttl"),

		// S3
		Endpoint:       c.String("endpoint"),
		Region:         c.String("region"),
		RegionSet:      c.IsSet("region"),
		StorageClass:   c.String("storage-class"),
		Profile:        c.String("profile"),
		UseContentType: c.Bool("use-content-type"),
		UseSSE:         c.Bool("sse"),
		UseKMS:         c.IsSet("sse-kms"),
		KMSKeyID:       c.String("sse-kms"),
		ACL:            c.String("acl"),

		// Debugging,
		DebugFuse:  c.Bool("debug_fuse"),
		DebugS3:    c.Bool("debug_s3"),
		Foreground: c.Bool("f"),
	}

	// Handle the repeated "-o" flag.
	for _, o := range c.StringSlice("o") {
		parseOptions(flags.MountOptions, o)
	}

	flags.MountPointArg = c.Args()[1]
	flags.MountPoint = flags.MountPointArg
	var err error

	defer func() {
		if err != nil {
			flags.Cleanup()
		}
	}()

	if c.IsSet("cache") {
		cache := c.String("cache")
		cacheArgs := strings.Split(c.String("cache"), ":")
		cacheDir := cacheArgs[len(cacheArgs)-1]

		fi, err := os.Stat(cacheDir)
		if err != nil || !fi.IsDir() {
			io.WriteString(cli.ErrWriter,
				fmt.Sprintf("Invalid value \"%v\" for --cache: not a directory\n\n",
					cacheDir))
			return nil
		}

		if _, ok := flags.MountOptions["allow_other"]; !ok {
			flags.MountPointCreated, err = ioutil.TempDir("", ".goofys-mnt")
			if err != nil {
				io.WriteString(cli.ErrWriter,
					fmt.Sprintf("Unable to create temp dir: %v", err))
				return nil
			}
			flags.MountPoint = flags.MountPointCreated
		}

		cacheArgs = append(cacheArgs, "")
		cacheArgs[len(cacheArgs)-1] = cacheDir
		cacheArgs[len(cacheArgs)-2] = flags.MountPoint
		cacheArgs = append(cacheArgs, flags.MountPointArg)

		cacheArgs = append(cacheArgs, "")
		copy(cacheArgs[1:], cacheArgs[0:])
		cacheArgs[0] = "--test"

		if flags.MountPointArg == flags.MountPoint {
			cacheArgs = append(cacheArgs, "", "")
			copy(cacheArgs[3:], cacheArgs[1:])
			cacheArgs[1] = "-o"
			cacheArgs[2] = "nonempty"
		}

		catfs := exec.Command("catfs", cacheArgs...)
		_, err = catfs.Output()
		if err != nil {
			if ee, ok := err.(*exec.Error); ok {
				io.WriteString(cli.ErrWriter,
					fmt.Sprintf("--cache requires catfs (%v) but %v\n\n",
						"http://github.com/kahing/catfs",
						ee.Error()))
			} else if ee, ok := err.(*exec.ExitError); ok {
				io.WriteString(cli.ErrWriter,
					fmt.Sprintf("Invalid value \"%v\" for --cache: %v\n\n",
						cache, string(ee.Stderr)))
			}
			return nil
		}

		flags.Cache = cacheArgs[1:]
	}

	// KMS implies SSE
	if flags.UseKMS {
		flags.UseSSE = true
	}

	return flags
}

func MassageMountFlags(args []string) (ret []string) {
	if len(args) == 5 && args[3] == "-o" {
		// looks like it's coming from fstab!
		mountOptions := ""
		ret = append(ret, args[0])

		for _, p := range strings.Split(args[4], ",") {
			if strings.HasPrefix(p, "-") {
				ret = append(ret, p)
			} else {
				mountOptions += p
				mountOptions += ","
			}
		}

		if len(mountOptions) != 0 {
			// remove trailing ,
			mountOptions = mountOptions[:len(mountOptions)-1]
			ret = append(ret, "-o")
			ret = append(ret, mountOptions)
		}

		ret = append(ret, args[1])
		ret = append(ret, args[2])
	} else {
		return args
	}

	return
}
<|endoftext|>


<commit_before>package constants

// THIS FILE IS GENERATED AUTOMATICALLY, NO TOUCHING!!!!!

const (
	BOSHURL         = "https://s3.amazonaws.com/bbl-precompiled-bosh-releases/release-bosh-261.2-on-ubuntu-trusty-stemcell-3363.9.tgz"
	BOSHSHA1        = "84afe35981fd880b812d42a83e1f123d80fc28a6"
	BOSHAWSCPIURL   = "https://bosh.io/d/github.com/cloudfoundry-incubator/bosh-aws-cpi-release?v=62"
	BOSHAWSCPISHA1  = "f36967927ceae09e5663a41fdda199edfe649dc6"
	AWSStemcellURL  = "https://s3.amazonaws.com/bosh-aws-light-stemcells/light-bosh-stemcell-3363.9-aws-xen-hvm-ubuntu-trusty-go_agent.tgz"
	AWSStemcellSHA1 = "c5a61384178efaade80210de7ebf994d70e6ca25"
	BOSHGCPCPIURL   = "https://bosh.io/d/github.com/cloudfoundry-incubator/bosh-google-cpi-release?v=25.6.2"
	BOSHGCPCPISHA1  = "b4865397d867655fdcc112bc5a7f9a5025cdf311"
	GCPStemcellURL  = "https://s3.amazonaws.com/bosh-gce-light-stemcells/light-bosh-stemcell-3363.9-google-kvm-ubuntu-trusty-go_agent.tgz"
	GCPStemcellSHA1 = "dc0b2d32d004e999acf7f4805c34f7d31b67203c"
)
<commit_msg>Update constants<commit_after>package constants

// THIS FILE IS GENERATED AUTOMATICALLY, NO TOUCHING!!!!!

const (
	BOSHURL         = "https://s3.amazonaws.com/bbl-precompiled-bosh-releases/release-bosh-261.2-on-ubuntu-trusty-stemcell-3363.9.tgz"
	BOSHSHA1        = "84afe35981fd880b812d42a83e1f123d80fc28a6"
	BOSHAWSCPIURL   = "https://bosh.io/d/github.com/cloudfoundry-incubator/bosh-aws-cpi-release?v=62"
	BOSHAWSCPISHA1  = "f36967927ceae09e5663a41fdda199edfe649dc6"
	AWSStemcellURL  = "https://s3.amazonaws.com/bosh-aws-light-stemcells/light-bosh-stemcell-3363.9-aws-xen-hvm-ubuntu-trusty-go_agent.tgz"
	AWSStemcellSHA1 = "c5a61384178efaade80210de7ebf994d70e6ca25"
	BOSHGCPCPIURL   = "https://bosh.io/d/github.com/cloudfoundry-incubator/bosh-google-cpi-release?v=25.7.0"
	BOSHGCPCPISHA1  = "2a12243600f8f54cbc1edaaf29293bfb5682038e"
	GCPStemcellURL  = "https://s3.amazonaws.com/bosh-gce-light-stemcells/light-bosh-stemcell-3363.9-google-kvm-ubuntu-trusty-go_agent.tgz"
	GCPStemcellSHA1 = "dc0b2d32d004e999acf7f4805c34f7d31b67203c"
)
<|endoftext|>


<commit_before>// Package intqueue implements a queue for ints.
//
// It is an express design decision to hard-code
// this queue just for the int type rather than for
// the empty interface.
//
// The internal representation is a slice of ints
// that gets used as a circular buffer.
// This is instead of a more traditional approach
// that would use a linked list of nodes.
// The assumption is that contiguous slabs of RAM
// will generally provide more performance over pointers
// to nodes potentially scattered about the heap.
//
// There is a downside: whereas enqueueing to a
// linked list is always O(1), enqueueing here will
// be O(1) except for when the internal slice of ints
// has to be resized; then, enqueueing will be O(n)
// where n is the size of the queue before being resized.
//
// Therefore, when asking for a new instance of the
// queue, use NewWithCapacity() to pick a capacity that you
// think won't need to grow.
//
// When the queue does need to grow, it always uses a capacity
// that is twice the current capacity. Enqueue() will do this
// doubling for you automatically.
//
// However, if you would like to grow the backing slice
// yourself, to have control over 1) when the size is increased,
// and 2) how much larger the backing slice grows, you can use
// Resize() directly. If your code needs to ask the current
// capacity and length of the queue, Capacity() and Length()
// will provide those numbers.
package intqueue

import "github.com/pkg/errors"

// IntQueue holds the data and state of the queue.
type IntQueue struct {
	data     []int
	head     int
	tail     int
	capacity int
	length   int
}

// DefauiltCapacity is the default capacity of the IntQueue
// when constructed using New() instead of NewWithCapacity().
const DefaultCapacity = 32

// New returns a new empty queue for ints of the default capacity.
func New() (q *IntQueue) {
	return NewWithCapacity(DefaultCapacity)
}

// NewWithCapacity returns a new empty queue for ints with the requested capacity.
func NewWithCapacity(capacity int) (q *IntQueue) {
	q = new(IntQueue)
	q.data = make([]int, capacity, capacity)
	q.head = -1
	q.tail = -1
	q.capacity = capacity
	q.length = 0
	return q
}

// Enqueue enqueues an int. Returns an error if the size
// of the queue cannot be grown any more to accommodate
// the added int.
func (q *IntQueue) Enqueue(i int) error {
	if q.length+1 > q.capacity {
		newCapacity := q.capacity * 2
		// if new_cap became negative, we have exceeded
		// our capacity by doing one bit-shift too far
		if newCapacity < 0 {
			return errors.New("Capacity exceeded")
		}
		// NOTE: Purposefully not concerning ourselves
		// with the error returned from Resize here, because
		// we know our newCapacity is larger than q.capacity.
		q.Resize(newCapacity)
	}
	q.length++
	q.head++
	if q.head == q.capacity {
		q.head = 0
	}
	q.data[q.head] = i
	return nil
}

// Length tells you the current length
// of the queue. It also tells you how many
// slots are being used in the slice that
// backs the queue.
func (q *IntQueue) Length() int {
	return q.length
}

// Capacity tells you the current capacity
// of the slice that backs the queue.
func (q *IntQueue) Capacity() int {
	return q.capacity
}

// Resize resizes the underlying slice that backs
// the queue. The Enqueue method calls this automatically
// when the backing slice is full, but feel free to use
// this method preemptively if your calling code has a
// good time to do this resizing. Also, the Enqueue method
// uses a new backing slice that is twice the size of the
// old one; but if you call Resize yourself, you can pick
// whatever new size you want.
func (q *IntQueue) Resize(newCapacity int) error {
	if newCapacity <= q.capacity {
		return errors.Errorf("New capacity %d is not larger than current capacity %d", newCapacity, q.capacity)
	}
	new_data := make([]int, newCapacity, newCapacity)
	var err error
	var i int
	// Because we are using the slice as a ring buffer,
	// head can be earlier in array than tail, so
	// it would be strange to just copy the old (possibly
	// partially wrapped) slice into the new slice.
	// Instead, we may as well copy the queue in order
	// into the new slice. The Dequeue() method gives us
	// every element in the correct order already, so we
	// just leverage that.
	for err = nil; err == nil; i, err = q.Dequeue() {
		new_data = append(new_data, i)
	}
	q.head = q.length - 1
	q.tail = 0
	q.capacity = newCapacity
	q.data = new_data
	return nil
}

// Dequeue dequeues an int. It returns the dequeued int
// or an error of the queue is empty.
func (q *IntQueue) Dequeue() (int, error) {
	if q.length-1 < 0 {
		return 0, errors.New("Queue empty")
	}
	q.length--
	q.tail++
	if q.tail == q.capacity {
		q.tail = 0
	}
	return q.data[q.tail], nil
}
<commit_msg>Renames IntQueue to Queue<commit_after>// Package intqueue implements a queue for ints.
//
// It is an express design decision to hard-code
// this queue just for the int type rather than for
// the empty interface.
//
// The internal representation is a slice of ints
// that gets used as a circular buffer.
// This is instead of a more traditional approach
// that would use a linked list of nodes.
// The assumption is that contiguous slabs of RAM
// will generally provide more performance over pointers
// to nodes potentially scattered about the heap.
//
// There is a downside: whereas enqueueing to a
// linked list is always O(1), enqueueing here will
// be O(1) except for when the internal slice of ints
// has to be resized; then, enqueueing will be O(n)
// where n is the size of the queue before being resized.
//
// Therefore, when asking for a new instance of the
// queue, use NewWithCapacity() to pick a capacity that you
// think won't need to grow.
//
// When the queue does need to grow, it always uses a capacity
// that is twice the current capacity. Enqueue() will do this
// doubling for you automatically.
//
// However, if you would like to grow the backing slice
// yourself, to have control over 1) when the size is increased,
// and 2) how much larger the backing slice grows, you can use
// Resize() directly. If your code needs to ask the current
// capacity and length of the queue, Capacity() and Length()
// will provide those numbers.
package intqueue

import "github.com/pkg/errors"

// Queue holds the data and state of the queue.
type Queue struct {
	data     []int
	head     int
	tail     int
	capacity int
	length   int
}

// DefaultCapacity is the default capacity of the queue
// when constructed using New() instead of NewWithCapacity().
const DefaultCapacity = 32

// New returns a new empty queue for ints of the default capacity.
func New() (q *Queue) {
	return NewWithCapacity(DefaultCapacity)
}

// NewWithCapacity returns a new empty queue for ints with the requested capacity.
func NewWithCapacity(capacity int) (q *Queue) {
	q = new(Queue)
	q.data = make([]int, capacity, capacity)
	q.head = -1
	q.tail = -1
	q.capacity = capacity
	q.length = 0
	return q
}

// Enqueue enqueues an int. Returns an error if the size
// of the queue cannot be grown any more to accommodate
// the added int.
func (q *Queue) Enqueue(i int) error {
	if q.length+1 > q.capacity {
		newCapacity := q.capacity * 2
		// if new_cap became negative, we have exceeded
		// our capacity by doing one bit-shift too far
		if newCapacity < 0 {
			return errors.New("Capacity exceeded")
		}
		// NOTE: Purposefully not concerning ourselves
		// with the error returned from Resize here, because
		// we know our newCapacity is larger than q.capacity.
		q.Resize(newCapacity)
	}
	q.length++
	q.head++
	if q.head == q.capacity {
		q.head = 0
	}
	q.data[q.head] = i
	return nil
}

// Length tells you the current length
// of the queue. It also tells you how many
// slots are being used in the slice that
// backs the queue.
func (q *Queue) Length() int {
	return q.length
}

// Capacity tells you the current capacity
// of the slice that backs the queue.
func (q *Queue) Capacity() int {
	return q.capacity
}

// Resize resizes the underlying slice that backs
// the queue. The Enqueue method calls this automatically
// when the backing slice is full, but feel free to use
// this method preemptively if your calling code has a
// good time to do this resizing. Also, the Enqueue method
// uses a new backing slice that is twice the size of the
// old one; but if you call Resize yourself, you can pick
// whatever new size you want.
func (q *Queue) Resize(newCapacity int) error {
	if newCapacity <= q.capacity {
		return errors.Errorf("New capacity %d is not larger than current capacity %d", newCapacity, q.capacity)
	}
	new_data := make([]int, newCapacity, newCapacity)
	var err error
	var i int
	// Because we are using the slice as a ring buffer,
	// head can be earlier in array than tail, so
	// it would be strange to just copy the old (possibly
	// partially wrapped) slice into the new slice.
	// Instead, we may as well copy the queue in order
	// into the new slice. The Dequeue() method gives us
	// every element in the correct order already, so we
	// just leverage that.
	for err = nil; err == nil; i, err = q.Dequeue() {
		new_data = append(new_data, i)
	}
	q.head = q.length - 1
	q.tail = 0
	q.capacity = newCapacity
	q.data = new_data
	return nil
}

// Dequeue dequeues an int. It returns the dequeued int
// or an error of the queue is empty.
func (q *Queue) Dequeue() (int, error) {
	if q.length-1 < 0 {
		return 0, errors.New("Queue empty")
	}
	q.length--
	q.tail++
	if q.tail == q.capacity {
		q.tail = 0
	}
	return q.data[q.tail], nil
}
<|endoftext|>


<commit_before>/**
 * (C) Copyright 2014, Deft Labs
 *
 * 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 dlshared

import (
	"fmt"
	"errors"
	"testing"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type structPointerTest struct {
	name string
}

func TestNilErr(t *testing.T) {
	if nil == mgo.ErrNotFound { t.Errorf("TestNilErr is broken - mgo not found matches nil") }
	if mgo.IsDup(nil) { t.Errorf("TestNilErr is broken - is dup matches nil") }
}

// Confirm the way structs/pointers works.
func TestStructs(t *testing.T) {

	id1 := structPointerTest{ name: "test" }
	id2 := structPointerTest{ name: "test" }

	if id1 != id2 { t.Errorf("TestStructs is broken - no match") }

	p1 := &id1
	p2 := &id2

	if p1 == p2 { t.Errorf("TestStructs is broken - pointer match") }
	if *p1 != *p2 { t.Errorf("TestStructs is broken - deferenced pointer - no match") }

	id1 = structPointerTest{ name: "test0" }
	id2 = structPointerTest{ name: "test1" }

	if id1 == id2 { t.Errorf("TestStructs is broken - match") }

	p1 = &id1
	p2 = &id2

	if p1 == p2 { t.Errorf("TestStructs is broken - pointer match") }
	if *p1 == *p2 { t.Errorf("TestStructs is broken - deferenced pointer match") }
}

func TestObjectId(t *testing.T) {

	id1 := bson.ObjectIdHex("532b19b784a8f7f139f3e338")
	id2 := bson.ObjectIdHex("532b19b784a8f7f139f3e338")

	if id1 != id2 { t.Errorf("TestObjectId is broken - no match") }

	p1 := &id1
	p2 := &id2

	if p1 == p2 { t.Errorf("TestObjectId is broken - pointer match") }
	if *p1 != *p2 { t.Errorf("TestObjectId is broken - deferenced pointer - no match") }

	id1 = bson.ObjectIdHex("532b19b784a8f7f139f3e338")
	id2 = bson.ObjectIdHex("532b19b884a8f7f139f3e339")

	if id1 == id2 { t.Errorf("TestStructs is broken - match") }

	p1 = &id1
	p2 = &id2

	if p1 == p2 { t.Errorf("TestStructs is broken - pointer match") }
	if *p1 == *p2 { t.Errorf("TestStructs is broken - deferenced pointer match") }
}

// Confirm the way errors behave.
func TestErrors(t *testing.T) {

	err := errors.New("test")

	if nil == err {
		t.Errorf("TestErrors is broken - nil == error")
	}

	if err != err {
		t.Errorf("TestErrors is broken - error == error")
	}
}

// Confirm the way slices behave.
func TestSlices(t *testing.T) {

	var slice []byte

	if slice != nil {
		t.Errorf("TestSlice is broken - slice is not nil")
	}

	if len(slice) != 0 {
		t.Errorf("TestSlice is broken - slice length is not zero")
	}
}

// Confirm the way data types behave.
func TestDataTypes(t *testing.T) {

	val := fmt.Sprintf("%t", true)
	if val != "true" {
		t.Errorf("TestDataTypes is broken - true != true")
	}

	val = fmt.Sprintf("%t", false)
	if val != "false" {
		t.Errorf("TestDataTypes is broken - false != false")
	}
}

// Confirm the way range behaves.
func TestRange(t *testing.T) {

	var test map[string]string

	// This should not panic
	for _, _ = range test { }

	// Just to be clear again
	test = nil

	// This should not panic
	for _, _ = range test { }
}

// Confirm the way maps behave.
func TestMaps(t *testing.T) {

	mapTest := make(map[string]string)
	mapTest["one"] = "one"
	mapTest["two"] = "two"
	mapTest["three"] = "three"

	if _, found := mapTest["four"]; found {
		t.Errorf("TestMaps is broken - something found that does not exist")
	}

	if _, found := mapTest["one"]; !found {
		t.Errorf("TestMaps is broken - something not found that should be found")
	}

	// A missing key, should not panic.
	emptyStr := mapTest["four"]

	if len(emptyStr) != 0 {
		t.Errorf("TestMaps is broken - the empty value has something")
	}

	var testMap map[string]string

	// Make sure a var map is nil
	if testMap != nil {
		t.Errorf("TestMaps is broken - uninitiated map should be nil")
	}
}

<commit_msg>confirmed map behaviors.<commit_after>/**
 * (C) Copyright 2014, Deft Labs
 *
 * 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 dlshared

import (
	"fmt"
	"errors"
	"testing"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type structPointerTest struct {
	name string
}

func TestDeleteMissingKeyInMap(t *testing.T) {

	delete(make(map[string]string, 0), "missing")

}

func TestDeletingKeysInMapWhileInRange(t *testing.T) {

	test := map[string]string { "test0": "value", "test1": "value", "test2": "value" }

	count := 0

	for key, _ := range test { delete(test, key); count++ }

	if count != 3 { t.Errorf("TestDeletingKeysInMapWhileInRange is broken - count off") }

	if len(test) != 0 { t.Errorf("TestDeletingKeysInMapWhileInRange is broken - length not zero") }

	for _, _ = range test {  }
}

func TestNilErr(t *testing.T) {
	if nil == mgo.ErrNotFound { t.Errorf("TestNilErr is broken - mgo not found matches nil") }
	if mgo.IsDup(nil) { t.Errorf("TestNilErr is broken - is dup matches nil") }
}

// Confirm the way structs/pointers works.
func TestStructs(t *testing.T) {

	id1 := structPointerTest{ name: "test" }
	id2 := structPointerTest{ name: "test" }

	if id1 != id2 { t.Errorf("TestStructs is broken - no match") }

	p1 := &id1
	p2 := &id2

	if p1 == p2 { t.Errorf("TestStructs is broken - pointer match") }
	if *p1 != *p2 { t.Errorf("TestStructs is broken - deferenced pointer - no match") }

	id1 = structPointerTest{ name: "test0" }
	id2 = structPointerTest{ name: "test1" }

	if id1 == id2 { t.Errorf("TestStructs is broken - match") }

	p1 = &id1
	p2 = &id2

	if p1 == p2 { t.Errorf("TestStructs is broken - pointer match") }
	if *p1 == *p2 { t.Errorf("TestStructs is broken - deferenced pointer match") }
}

func TestObjectId(t *testing.T) {

	id1 := bson.ObjectIdHex("532b19b784a8f7f139f3e338")
	id2 := bson.ObjectIdHex("532b19b784a8f7f139f3e338")

	if id1 != id2 { t.Errorf("TestObjectId is broken - no match") }

	p1 := &id1
	p2 := &id2

	if p1 == p2 { t.Errorf("TestObjectId is broken - pointer match") }
	if *p1 != *p2 { t.Errorf("TestObjectId is broken - deferenced pointer - no match") }

	id1 = bson.ObjectIdHex("532b19b784a8f7f139f3e338")
	id2 = bson.ObjectIdHex("532b19b884a8f7f139f3e339")

	if id1 == id2 { t.Errorf("TestStructs is broken - match") }

	p1 = &id1
	p2 = &id2

	if p1 == p2 { t.Errorf("TestStructs is broken - pointer match") }
	if *p1 == *p2 { t.Errorf("TestStructs is broken - deferenced pointer match") }
}

// Confirm the way errors behave.
func TestErrors(t *testing.T) {

	err := errors.New("test")

	if nil == err {
		t.Errorf("TestErrors is broken - nil == error")
	}

	if err != err {
		t.Errorf("TestErrors is broken - error == error")
	}
}

// Confirm the way slices behave.
func TestSlices(t *testing.T) {

	var slice []byte

	if slice != nil {
		t.Errorf("TestSlice is broken - slice is not nil")
	}

	if len(slice) != 0 {
		t.Errorf("TestSlice is broken - slice length is not zero")
	}
}

// Confirm the way data types behave.
func TestDataTypes(t *testing.T) {

	val := fmt.Sprintf("%t", true)
	if val != "true" {
		t.Errorf("TestDataTypes is broken - true != true")
	}

	val = fmt.Sprintf("%t", false)
	if val != "false" {
		t.Errorf("TestDataTypes is broken - false != false")
	}
}

// Confirm the way range behaves.
func TestRange(t *testing.T) {

	var test map[string]string

	// This should not panic
	for _, _ = range test { }

	// Just to be clear again
	test = nil

	// This should not panic
	for _, _ = range test { }
}

// Confirm the way maps behave.
func TestMaps(t *testing.T) {

	mapTest := make(map[string]string)
	mapTest["one"] = "one"
	mapTest["two"] = "two"
	mapTest["three"] = "three"

	if _, found := mapTest["four"]; found {
		t.Errorf("TestMaps is broken - something found that does not exist")
	}

	if _, found := mapTest["one"]; !found {
		t.Errorf("TestMaps is broken - something not found that should be found")
	}

	// A missing key, should not panic.
	emptyStr := mapTest["four"]

	if len(emptyStr) != 0 {
		t.Errorf("TestMaps is broken - the empty value has something")
	}

	var testMap map[string]string

	// Make sure a var map is nil
	if testMap != nil {
		t.Errorf("TestMaps is broken - uninitiated map should be nil")
	}
}

<|endoftext|>


<commit_before>// library to do nagios-style http checks from a list of ips/urls
//
// to be embedded in programs that can access a cloud provisioning api
// then you can put this single check across the entire cluster as it grows and changes
//
// ips/urls are to be provided by a bufio.Scanner
//
package check_http_bulk

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
)

// Silence: bool determining if a string of failing hosts should be returned
//
// Urls: bool indicating the incoming data stream is full urls, not just IPs
//
// Path: the path to append to IPs if the input is just IPs
//
// Port: the target port for the checks
//
// Auth: an auth string is authentication is needed
//
// Verbose: display details about its operation as it runs
//
// Bad: the number of ips/urls that failed the check
//
// Total: the total number of ips or urls
//
// Timeout: http timeout so it won't get stuck for too long
//
// Workers: how many workers to use when doing checks in parallel
type BulkCheck struct {
	Silence  bool
	Urls     bool
	Path     string
	Port     int
	Auth     string
	Verbose  bool
	replies  chan getReply
	requests chan getRequest
	BadHosts []byte
	Bad      int
	Total    int
	Timeout  int
	received int
	done     chan string
	Workers  int
}

type getRequest struct {
	path     string
	port     int
	hostname string
}

type getReply struct {
	hostname string
	err      error
	rv       bool
}

func (this BulkCheck) vLogger(msg string, args ...interface{}) {

	if this.Verbose {
		fmt.Fprintf(os.Stderr, msg, args...)
	}

}

// the worker func that does the gets
// these run async according to the Workers int
func (this *BulkCheck) get(request chan getRequest, client *http.Client) {

	var err error

	for args := range request {

		this.vLogger("fetching:hostname:%s:\n", args.hostname)

		res := &http.Response{}

		if this.Urls {

			_url := args.hostname

			u, err := url.Parse(_url)
			if err != nil {
				this.replies <- getReply{hostname: args.hostname, rv: false, err: err}
				continue
			}

			req := &http.Request{
				Method: "HEAD",
				URL:    u,
			}

			res, err = client.Do(req)

		} else {

			// had to allocate this or the SetBasicAuth will panic
			headers := make(map[string][]string)
			hostPort := fmt.Sprintf("%s:%d", args.hostname, this.Port)

			this.vLogger("adding hostPort:%s:%d:path:%s:\n", args.hostname, this.Port, this.Path)

			req := &http.Request{
				Method: "HEAD",
				// Host:  hostPort,
				URL: &url.URL{
					Host:   hostPort,
					Scheme: "http",
					Opaque: this.Path,
				},
				Header: headers,
			}

			if this.Auth != "" {

				up := strings.SplitN(this.Auth, ":", 2)

				this.vLogger("Doing auth with:username:%s:password:%s:", up[0], up[1])
				req.SetBasicAuth(up[0], up[1])

			}

			if this.Verbose {

				dump, _ := httputil.DumpRequestOut(req, true)
				this.vLogger("%s", dump)

			}

			res, err = client.Do(req)

		}

		if this.Verbose && res != nil {

			fmt.Println(res.Status)
			for k, v := range res.Header {
				fmt.Println(k+":", v)
			}

		}

		if err != nil {
			fmt.Println(err)
			this.replies <- getReply{hostname: args.hostname, rv: false, err: err}
		} else if res != nil && res.StatusCode == http.StatusOK {
			this.replies <- getReply{hostname: args.hostname, rv: true}
			io.Copy(ioutil.Discard, res.Body)
			res.Body.Close()
		} else {
			this.replies <- getReply{hostname: args.hostname, rv: false}
		}

	}

}

// this reads all the responses from the get workers
func (this *BulkCheck) readAll() {
	for {
		select {

		case result := <-this.replies:

			this.received++
			err := result.err
			goodCheck := result.rv

			if err != nil {

				if !this.Silence {
					this.BadHosts = append(this.BadHosts, result.hostname...)
					this.BadHosts = append(this.BadHosts, ", "...)
				}
				this.Bad++

			} else if !goodCheck {
				if !this.Silence {
					this.BadHosts = append(this.BadHosts, result.hostname...)
					this.BadHosts = append(this.BadHosts, ", "...)
				}
				this.Bad++
			}

		}

		this.vLogger("checking if done:total:%d:received:%d:\n", this.Total, this.received)
		if this.Total == this.received {
			this.done <- "done"
		}
	}

}

// this is the exposed access point for the library
//
// users create a BulkCheck and call DoChecks on it, passing it a Scanner
//
// eg. scanner := bufio.NewScanner(inputSource)
//
// The scanner provides the list of urls or IPs
func (this *BulkCheck) DoChecks(scanner *bufio.Scanner) (err error) {

	for scanner.Scan() {

		hostname := scanner.Text()

		if len(hostname) == 0 {

			this.vLogger("skipping blank:\n")

			continue
		}

		if hostname[0] == "#"[0] {

			this.vLogger("skipping:%s:\n", hostname)

			continue
		}

		this.Total++

		this.vLogger("working on:%s:\n", hostname)

		//send the request off to the workers
		this.requests <- getRequest{hostname: hostname, port: this.Port, path: this.Path}

	}

	close(this.requests)
	if err = scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}

	//wait for all responses
	this.vLogger("waiting for done:total:%d:received:%d:\n", this.Total, this.received)
	<-this.done

	//fix badHosts
	if len(this.BadHosts) > 2 {
		this.BadHosts = this.BadHosts[:len(this.BadHosts)-2]
	}
	return

}

// start up the workers
//
// the number of workers is specified by BulkCheck.Workers
//
// default value is 1
func (this *BulkCheck) workerPool() chan getRequest {
	requests := make(chan getRequest, 1)

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

	for i := 0; i < this.Workers; i++ {
		go this.get(requests, client)
	}

	go this.readAll()

	this.vLogger("exec'd:%d:workers:\n", this.Workers)

	return requests
}

// initializer for using a BulkCheck
//
//
// call it like this
//
// check_http_bulk.New(&check_http_bulk.BulkCheck{Silence: *silence,
//     Urls: *urls,
//     Path: *path,
//     Port: *port,
//     Auth: *auth,
//     Verbose: *verbose,
//     Timeout: *timeout,
//     Workers: *workers
// })
//
// then call DoCheck on it passing it a scanner.  See bufio.Scanner
//
// it creates the channels and a slice for a result string
func New(checker *BulkCheck) *BulkCheck {

	badHosts := []byte{}

	repliesChannel := make(chan getReply, 1)

	checker.BadHosts = badHosts
	checker.requests = checker.workerPool()
	checker.replies = repliesChannel
	checker.done = make(chan string)

	if checker.Workers == 0 {
		checker.Workers = 1
	}

	return checker

}
<commit_msg>close the others too<commit_after>// library to do nagios-style http checks from a list of ips/urls
//
// to be embedded in programs that can access a cloud provisioning api
// then you can put this single check across the entire cluster as it grows and changes
//
// ips/urls are to be provided by a bufio.Scanner
//
package check_http_bulk

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
)

// Silence: bool determining if a string of failing hosts should be returned
//
// Urls: bool indicating the incoming data stream is full urls, not just IPs
//
// Path: the path to append to IPs if the input is just IPs
//
// Port: the target port for the checks
//
// Auth: an auth string is authentication is needed
//
// Verbose: display details about its operation as it runs
//
// Bad: the number of ips/urls that failed the check
//
// Total: the total number of ips or urls
//
// Timeout: http timeout so it won't get stuck for too long
//
// Workers: how many workers to use when doing checks in parallel
type BulkCheck struct {
	Silence  bool
	Urls     bool
	Path     string
	Port     int
	Auth     string
	Verbose  bool
	replies  chan getReply
	requests chan getRequest
	BadHosts []byte
	Bad      int
	Total    int
	Timeout  int
	received int
	done     chan string
	Workers  int
}

type getRequest struct {
	path     string
	port     int
	hostname string
}

type getReply struct {
	hostname string
	err      error
	rv       bool
}

func (this BulkCheck) vLogger(msg string, args ...interface{}) {

	if this.Verbose {
		fmt.Fprintf(os.Stderr, msg, args...)
	}

}

// the worker func that does the gets
// these run async according to the Workers int
func (this *BulkCheck) get(request chan getRequest, client *http.Client) {

	var err error

	for args := range request {

		this.vLogger("fetching:hostname:%s:\n", args.hostname)

		res := &http.Response{}

		if this.Urls {

			_url := args.hostname

			u, err := url.Parse(_url)
			if err != nil {
				this.replies <- getReply{hostname: args.hostname, rv: false, err: err}
				continue
			}

			req := &http.Request{
				Method: "HEAD",
				URL:    u,
			}

			res, err = client.Do(req)

		} else {

			// had to allocate this or the SetBasicAuth will panic
			headers := make(map[string][]string)
			hostPort := fmt.Sprintf("%s:%d", args.hostname, this.Port)

			this.vLogger("adding hostPort:%s:%d:path:%s:\n", args.hostname, this.Port, this.Path)

			req := &http.Request{
				Method: "HEAD",
				// Host:  hostPort,
				URL: &url.URL{
					Host:   hostPort,
					Scheme: "http",
					Opaque: this.Path,
				},
				Header: headers,
			}

			if this.Auth != "" {

				up := strings.SplitN(this.Auth, ":", 2)

				this.vLogger("Doing auth with:username:%s:password:%s:", up[0], up[1])
				req.SetBasicAuth(up[0], up[1])

			}

			if this.Verbose {

				dump, _ := httputil.DumpRequestOut(req, true)
				this.vLogger("%s", dump)

			}

			res, err = client.Do(req)

		}

		if this.Verbose && res != nil {

			fmt.Println(res.Status)
			for k, v := range res.Header {
				fmt.Println(k+":", v)
			}

		}

		if err != nil {
			fmt.Println(err)
			this.replies <- getReply{hostname: args.hostname, rv: false, err: err}
		} else if res != nil && res.StatusCode == http.StatusOK {
			this.replies <- getReply{hostname: args.hostname, rv: true}
			io.Copy(ioutil.Discard, res.Body)
			res.Body.Close()
		} else {
			this.replies <- getReply{hostname: args.hostname, rv: false}
		}

	}

}

// this reads all the responses from the get workers
func (this *BulkCheck) readAll() {
	for {
		select {

		case result := <-this.replies:

			this.received++
			err := result.err
			goodCheck := result.rv

			if err != nil {

				if !this.Silence {
					this.BadHosts = append(this.BadHosts, result.hostname...)
					this.BadHosts = append(this.BadHosts, ", "...)
				}
				this.Bad++

			} else if !goodCheck {
				if !this.Silence {
					this.BadHosts = append(this.BadHosts, result.hostname...)
					this.BadHosts = append(this.BadHosts, ", "...)
				}
				this.Bad++
			}

		}

		this.vLogger("checking if done:total:%d:received:%d:\n", this.Total, this.received)
		if this.Total == this.received {
			this.done <- "done"
		}
	}

}

// this is the exposed access point for the library
//
// users create a BulkCheck and call DoChecks on it, passing it a Scanner
//
// eg. scanner := bufio.NewScanner(inputSource)
//
// The scanner provides the list of urls or IPs
func (this *BulkCheck) DoChecks(scanner *bufio.Scanner) (err error) {

	for scanner.Scan() {

		hostname := scanner.Text()

		if len(hostname) == 0 {

			this.vLogger("skipping blank:\n")

			continue
		}

		if hostname[0] == "#"[0] {

			this.vLogger("skipping:%s:\n", hostname)

			continue
		}

		this.Total++

		this.vLogger("working on:%s:\n", hostname)

		//send the request off to the workers
		this.requests <- getRequest{hostname: hostname, port: this.Port, path: this.Path}

	}
	close(this.requests)

	if err = scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}

	//wait for all responses
	this.vLogger("waiting for done:total:%d:received:%d:\n", this.Total, this.received)
	<-this.done
	close(this.done)
	close(this.replies)

	//fix badHosts
	if len(this.BadHosts) > 2 {
		this.BadHosts = this.BadHosts[:len(this.BadHosts)-2]
	}
	return

}

// start up the workers
//
// the number of workers is specified by BulkCheck.Workers
//
// default value is 1
func (this *BulkCheck) workerPool() chan getRequest {
	requests := make(chan getRequest, 1)

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

	for i := 0; i < this.Workers; i++ {
		go this.get(requests, client)
	}

	go this.readAll()

	this.vLogger("exec'd:%d:workers:\n", this.Workers)

	return requests
}

// initializer for using a BulkCheck
//
//
// call it like this
//
// check_http_bulk.New(&check_http_bulk.BulkCheck{Silence: *silence,
//     Urls: *urls,
//     Path: *path,
//     Port: *port,
//     Auth: *auth,
//     Verbose: *verbose,
//     Timeout: *timeout,
//     Workers: *workers
// })
//
// then call DoCheck on it passing it a scanner.  See bufio.Scanner
//
// it creates the channels and a slice for a result string
func New(checker *BulkCheck) *BulkCheck {

	badHosts := []byte{}

	repliesChannel := make(chan getReply, 1)

	checker.BadHosts = badHosts
	checker.requests = checker.workerPool()
	checker.replies = repliesChannel
	checker.done = make(chan string)

	if checker.Workers == 0 {
		checker.Workers = 1
	}

	return checker

}
<|endoftext|>


<commit_before>package integration_test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

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

var _ = Describe("In", func() {
	var inDestination string
	var gitRepo string

	var output inResponse

	BeforeEach(func() {
		var err error
		inDestination, err = ioutil.TempDir("", "in-destination")
		gitRepo, err = ioutil.TempDir("", "git-repo")
		Ω(err).ShouldNot(HaveOccurred())
	})

	AfterEach(func() {
		err := os.RemoveAll(inDestination)
		Ω(err).ShouldNot(HaveOccurred())

		err = os.RemoveAll(gitRepo)
		Ω(err).ShouldNot(HaveOccurred())
	})

	Context("when the config is incomplete", func() {
		var session *gexec.Session

		BeforeEach(func() {
			session = runIn("{}", inDestination, 1)
		})

		It("returns all config errors", func() {
			errorMessages := string(session.Err.Contents())

			Ω(errorMessages).Should(ContainSubstring("invalid payload (missing uri)"))
			Ω(errorMessages).Should(ContainSubstring("invalid payload (missing branch)"))
			Ω(errorMessages).Should(ContainSubstring("invalid payload (missing pool)"))
		})
	})

	Context("when given version removed a lock", func() {
		BeforeEach(func() {
			var err error

			setupGitRepo(gitRepo)

			claimLock := exec.Command("bash", "-e", "-c", `
				git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
				git commit -m 'claiming some-lock'

				git rm lock-pool/claimed/some-lock
				git commit -m 'removing some-lock'
			`)
			claimLock.Dir = gitRepo

			err = claimLock.Run()
			Ω(err).ShouldNot(HaveOccurred())
		})

		It("return succesfully", func() {
			gitVersion := exec.Command("git", "rev-parse", "HEAD")
			gitVersion.Dir = gitRepo
			sha, err := gitVersion.Output()
			Ω(err).ShouldNot(HaveOccurred())
			shaStr := strings.TrimSpace(string(sha))

			jsonIn := fmt.Sprintf(`
				{
					"source": {
						"uri": "%s",
						"branch": "master",
						"pool": "lock-pool"
					},
					"version": {
						"ref": "%s"
					}
				}`, gitRepo, shaStr)

			session := runIn(jsonIn, inDestination, 0)

			err = json.Unmarshal(session.Out.Contents(), &output)
			Ω(err).ShouldNot(HaveOccurred())
			Ω(output).Should(Equal(inResponse{
				Version: version{
					Ref: shaStr,
				},
				Metadata: []metadataPair{
					{Name: "lock_name", Value: "some-lock"},
					{Name: "pool_name", Value: "lock-pool"},
				},
			}))
		})
	})

	Context("when a previous version is given", func() {
		BeforeEach(func() {
			var err error

			setupGitRepo(gitRepo)

			claimLock := exec.Command("bash", "-e", "-c", `
				git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
				git commit -m 'claiming some-lock'
			`)
			claimLock.Dir = gitRepo

			err = claimLock.Run()
			Ω(err).ShouldNot(HaveOccurred())
		})

		It("outputs the metadata for the environment", func() {
			gitVersion := exec.Command("git", "rev-parse", "HEAD")
			gitVersion.Dir = gitRepo
			sha, err := gitVersion.Output()
			Ω(err).ShouldNot(HaveOccurred())
			shaStr := strings.TrimSpace(string(sha))

			jsonIn := fmt.Sprintf(`
				{
					"source": {
						"uri": "%s",
						"branch": "master",
						"pool": "lock-pool"
					},
					"version": {
						"ref": "%s"
					}
				}`, gitRepo, shaStr)

			session := runIn(jsonIn, inDestination, 0)

			err = json.Unmarshal(session.Out.Contents(), &output)
			Ω(err).ShouldNot(HaveOccurred())

			metaDataFile := filepath.Join(inDestination, "metadata")
			Ω(metaDataFile).Should(BeARegularFile())

			fileContents, err := ioutil.ReadFile(metaDataFile)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(fileContents).Should(MatchJSON(`{"some":"json"}`))

			lockNameFile := filepath.Join(inDestination, "name")
			Ω(lockNameFile).Should(BeARegularFile())

			fileContents, err = ioutil.ReadFile(lockNameFile)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(strings.TrimSpace(string(fileContents))).Should(Equal("some-lock"))

			Ω(output).Should(Equal(inResponse{
				Version: version{
					Ref: shaStr,
				},
				Metadata: []metadataPair{
					{Name: "lock_name", Value: "some-lock"},
					{Name: "pool_name", Value: "lock-pool"},
				},
			}))
		})

		Context("when the lock from the previous version has been released and we are trying to run it again", func() {
			var shaStr string

			Context("when the given commit claimed the lock but the lock was unclaimed afterwards", func() {
				BeforeEach(func() {
					var err error
					gitVersion := exec.Command("git", "rev-parse", "HEAD")
					gitVersion.Dir = gitRepo
					sha, err := gitVersion.Output()
					Ω(err).ShouldNot(HaveOccurred())
					shaStr = strings.TrimSpace(string(sha))

					unclaimLock := exec.Command("bash", "-e", "-c", `
						git mv lock-pool/claimed/some-lock lock-pool/unclaimed/some-lock
						git commit -m 'unclaiming: some-lock'
					`)
					unclaimLock.Dir = gitRepo

					err = unclaimLock.Run()
					Ω(err).ShouldNot(HaveOccurred())
				})

				It("fails with a useful error message because the lock is no longer safe to use", func() {
					jsonIn := fmt.Sprintf(`
						{
							"source": {
								"uri": "%s",
								"branch": "master",
								"pool": "lock-pool"
							},
							"version": {
								"ref": "%s"
							}
						}`, gitRepo, shaStr)

					session := runIn(jsonIn, inDestination, 1)

					Ω(session.Err).Should(gbytes.Say("error: lock instance is no longer acquired"))
				})

				Context("when the lock is acquired again but by another pipeline run and is run", func() {
					BeforeEach(func() {
						claimLock := exec.Command("bash", "-e", "-c", `
							git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
							git commit -m 'claiming some-lock'
						`)
						claimLock.Dir = gitRepo

						err := claimLock.Run()
						Ω(err).ShouldNot(HaveOccurred())
					})

					It("fails with a useful error message because the lock has been acquired by another pipeline run", func() {
						jsonIn := fmt.Sprintf(`
						{
							"source": {
								"uri": "%s",
								"branch": "master",
								"pool": "lock-pool"
							},
							"version": {
								"ref": "%s"
							}
						}`, gitRepo, shaStr)

						session := runIn(jsonIn, inDestination, 1)

						Ω(session.Err).Should(gbytes.Say("error: lock instance is no longer acquired"))
					})
				})
			})
			Context("when the given commit claimed the lock and the file was modified in place afterwards", func() {
				BeforeEach(func() {
					var err error
					gitVersion := exec.Command("git", "rev-parse", "HEAD")
					gitVersion.Dir = gitRepo
					sha, err := gitVersion.Output()
					Ω(err).ShouldNot(HaveOccurred())
					shaStr = strings.TrimSpace(string(sha))

					changeFile := exec.Command("bash", "-e", "-c", `
					echo "additional_key: additional_value" >> lock-pool/claimed/some-lock
					  git add lock-pool/claimed/some-lock
					  git commit -m 'changing: some-lock'
					`)
					changeFile.Dir = gitRepo

					err = changeFile.Run()
					Ω(err).ShouldNot(HaveOccurred())
				})

				It("returns successfully", func() {
					jsonIn := fmt.Sprintf(`
						{
							"source": {
								"uri": "%s",
								"branch": "master",
								"pool": "lock-pool"
							},
							"version": {
								"ref": "%s"
							}
						}`, gitRepo, shaStr)

					session := runIn(jsonIn, inDestination, 0)

					err := json.Unmarshal(session.Out.Contents(), &output)
					Ω(err).ShouldNot(HaveOccurred())
					Ω(output).Should(Equal(inResponse{
						Version: version{
							Ref: shaStr,
						},
						Metadata: []metadataPair{
							{Name: "lock_name", Value: "some-lock"},
							{Name: "pool_name", Value: "lock-pool"},
						},
					}))
				})
			})
			Context("when the commit itself unclaimed the lock", func() {
				var shaStr string
				BeforeEach(func() {
					var err error

					unclaimLock := exec.Command("bash", "-e", "-c", `
						git mv lock-pool/claimed/some-lock lock-pool/unclaimed/some-lock
						git commit -m 'unclaiming: some-lock'
					`)
					unclaimLock.Dir = gitRepo

					err = unclaimLock.Run()
					Ω(err).ShouldNot(HaveOccurred())

					gitVersion := exec.Command("git", "rev-parse", "HEAD")
					gitVersion.Dir = gitRepo

					sha, err := gitVersion.Output()
					Ω(err).ShouldNot(HaveOccurred())
					shaStr = strings.TrimSpace(string(sha))

					someOtherCommit := exec.Command("bash", "-e", "-c", `
						git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
						git commit -m 'doing something some-lock'
					`)
					someOtherCommit.Dir = gitRepo

					err = someOtherCommit.Run()
					Ω(err).ShouldNot(HaveOccurred())
				})

				It("outputs the metadata for the environment", func() {
					jsonIn := fmt.Sprintf(`
							{
								"source": {
									"uri": "%s",
									"branch": "master",
									"pool": "lock-pool"
								},
								"version": {
									"ref": "%s"
								}
							}`, gitRepo, shaStr)

					session := runIn(jsonIn, inDestination, 0)

					Consistently(session.Err).ShouldNot(gbytes.Say("error: lock instance is no longer acquired"))
					err := json.Unmarshal(session.Out.Contents(), &output)
					Ω(err).ShouldNot(HaveOccurred())

					metaDataFile := filepath.Join(inDestination, "metadata")
					Ω(metaDataFile).Should(BeARegularFile())

					fileContents, err := ioutil.ReadFile(metaDataFile)
					Ω(err).ShouldNot(HaveOccurred())

					Ω(fileContents).Should(MatchJSON(`{"some":"json"}`))

					lockNameFile := filepath.Join(inDestination, "name")
					Ω(lockNameFile).Should(BeARegularFile())

					fileContents, err = ioutil.ReadFile(lockNameFile)
					Ω(err).ShouldNot(HaveOccurred())

					Ω(strings.TrimSpace(string(fileContents))).Should(Equal("some-lock"))

					Ω(output).Should(Equal(inResponse{
						Version: version{
							Ref: shaStr,
						},
						Metadata: []metadataPair{
							{Name: "lock_name", Value: "some-lock"},
							{Name: "pool_name", Value: "lock-pool"},
						},
					}))
				})
			})
		})
	})
})
<commit_msg>Revert "Add integration test for modified file without move"<commit_after>package integration_test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

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

var _ = Describe("In", func() {
	var inDestination string
	var gitRepo string

	var output inResponse

	BeforeEach(func() {
		var err error
		inDestination, err = ioutil.TempDir("", "in-destination")
		gitRepo, err = ioutil.TempDir("", "git-repo")
		Ω(err).ShouldNot(HaveOccurred())
	})

	AfterEach(func() {
		err := os.RemoveAll(inDestination)
		Ω(err).ShouldNot(HaveOccurred())

		err = os.RemoveAll(gitRepo)
		Ω(err).ShouldNot(HaveOccurred())
	})

	Context("when the config is incomplete", func() {
		var session *gexec.Session

		BeforeEach(func() {
			session = runIn("{}", inDestination, 1)
		})

		It("returns all config errors", func() {
			errorMessages := string(session.Err.Contents())

			Ω(errorMessages).Should(ContainSubstring("invalid payload (missing uri)"))
			Ω(errorMessages).Should(ContainSubstring("invalid payload (missing branch)"))
			Ω(errorMessages).Should(ContainSubstring("invalid payload (missing pool)"))
		})
	})

	Context("when given version removed a lock", func() {
		BeforeEach(func() {
			var err error

			setupGitRepo(gitRepo)

			claimLock := exec.Command("bash", "-e", "-c", `
				git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
				git commit -m 'claiming some-lock'

				git rm lock-pool/claimed/some-lock
				git commit -m 'removing some-lock'
			`)
			claimLock.Dir = gitRepo

			err = claimLock.Run()
			Ω(err).ShouldNot(HaveOccurred())
		})

		It("return succesfully", func() {
			gitVersion := exec.Command("git", "rev-parse", "HEAD")
			gitVersion.Dir = gitRepo
			sha, err := gitVersion.Output()
			Ω(err).ShouldNot(HaveOccurred())
			shaStr := strings.TrimSpace(string(sha))

			jsonIn := fmt.Sprintf(`
				{
					"source": {
						"uri": "%s",
						"branch": "master",
						"pool": "lock-pool"
					},
					"version": {
						"ref": "%s"
					}
				}`, gitRepo, shaStr)

			session := runIn(jsonIn, inDestination, 0)

			err = json.Unmarshal(session.Out.Contents(), &output)
			Ω(err).ShouldNot(HaveOccurred())
			Ω(output).Should(Equal(inResponse{
				Version: version{
					Ref: shaStr,
				},
				Metadata: []metadataPair{
					{Name: "lock_name", Value: "some-lock"},
					{Name: "pool_name", Value: "lock-pool"},
				},
			}))
		})
	})

	Context("when a previous version is given", func() {
		BeforeEach(func() {
			var err error

			setupGitRepo(gitRepo)

			claimLock := exec.Command("bash", "-e", "-c", `
				git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
				git commit -m 'claiming some-lock'
			`)
			claimLock.Dir = gitRepo

			err = claimLock.Run()
			Ω(err).ShouldNot(HaveOccurred())
		})

		It("outputs the metadata for the environment", func() {
			gitVersion := exec.Command("git", "rev-parse", "HEAD")
			gitVersion.Dir = gitRepo
			sha, err := gitVersion.Output()
			Ω(err).ShouldNot(HaveOccurred())
			shaStr := strings.TrimSpace(string(sha))

			jsonIn := fmt.Sprintf(`
				{
					"source": {
						"uri": "%s",
						"branch": "master",
						"pool": "lock-pool"
					},
					"version": {
						"ref": "%s"
					}
				}`, gitRepo, shaStr)

			session := runIn(jsonIn, inDestination, 0)

			err = json.Unmarshal(session.Out.Contents(), &output)
			Ω(err).ShouldNot(HaveOccurred())

			metaDataFile := filepath.Join(inDestination, "metadata")
			Ω(metaDataFile).Should(BeARegularFile())

			fileContents, err := ioutil.ReadFile(metaDataFile)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(fileContents).Should(MatchJSON(`{"some":"json"}`))

			lockNameFile := filepath.Join(inDestination, "name")
			Ω(lockNameFile).Should(BeARegularFile())

			fileContents, err = ioutil.ReadFile(lockNameFile)
			Ω(err).ShouldNot(HaveOccurred())

			Ω(strings.TrimSpace(string(fileContents))).Should(Equal("some-lock"))

			Ω(output).Should(Equal(inResponse{
				Version: version{
					Ref: shaStr,
				},
				Metadata: []metadataPair{
					{Name: "lock_name", Value: "some-lock"},
					{Name: "pool_name", Value: "lock-pool"},
				},
			}))
		})

		Context("when the lock from the previous version has been released and we are trying to run it again", func() {
			var shaStr string

			Context("when the given commit claimed the lock but the lock was unclaimed afterwards", func() {
				BeforeEach(func() {
					var err error
					gitVersion := exec.Command("git", "rev-parse", "HEAD")
					gitVersion.Dir = gitRepo
					sha, err := gitVersion.Output()
					Ω(err).ShouldNot(HaveOccurred())
					shaStr = strings.TrimSpace(string(sha))

					unclaimLock := exec.Command("bash", "-e", "-c", `
						git mv lock-pool/claimed/some-lock lock-pool/unclaimed/some-lock
						git commit -m 'unclaiming: some-lock'
					`)
					unclaimLock.Dir = gitRepo

					err = unclaimLock.Run()
					Ω(err).ShouldNot(HaveOccurred())
				})

				It("fails with a useful error message because the lock is no longer safe to use", func() {
					jsonIn := fmt.Sprintf(`
						{
							"source": {
								"uri": "%s",
								"branch": "master",
								"pool": "lock-pool"
							},
							"version": {
								"ref": "%s"
							}
						}`, gitRepo, shaStr)

					session := runIn(jsonIn, inDestination, 1)

					Ω(session.Err).Should(gbytes.Say("error: lock instance is no longer acquired"))
				})

				Context("when the lock is acquired again but by another pipeline run and is run", func() {
					BeforeEach(func() {
						claimLock := exec.Command("bash", "-e", "-c", `
							git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
							git commit -m 'claiming some-lock'
						`)
						claimLock.Dir = gitRepo

						err := claimLock.Run()
						Ω(err).ShouldNot(HaveOccurred())
					})

					It("fails with a useful error message because the lock has been acquired by another pipeline run", func() {
						jsonIn := fmt.Sprintf(`
						{
							"source": {
								"uri": "%s",
								"branch": "master",
								"pool": "lock-pool"
							},
							"version": {
								"ref": "%s"
							}
						}`, gitRepo, shaStr)

						session := runIn(jsonIn, inDestination, 1)

						Ω(session.Err).Should(gbytes.Say("error: lock instance is no longer acquired"))
					})
				})
			})

			Context("when the commit itself unclaimed the lock", func() {
				var shaStr string
				BeforeEach(func() {
					var err error

					unclaimLock := exec.Command("bash", "-e", "-c", `
						git mv lock-pool/claimed/some-lock lock-pool/unclaimed/some-lock
						git commit -m 'unclaiming: some-lock'
					`)
					unclaimLock.Dir = gitRepo

					err = unclaimLock.Run()
					Ω(err).ShouldNot(HaveOccurred())

					gitVersion := exec.Command("git", "rev-parse", "HEAD")
					gitVersion.Dir = gitRepo

					sha, err := gitVersion.Output()
					Ω(err).ShouldNot(HaveOccurred())
					shaStr = strings.TrimSpace(string(sha))

					someOtherCommit := exec.Command("bash", "-e", "-c", `
						git mv lock-pool/unclaimed/some-lock lock-pool/claimed/some-lock
						git commit -m 'doing something some-lock'
					`)
					someOtherCommit.Dir = gitRepo

					err = someOtherCommit.Run()
					Ω(err).ShouldNot(HaveOccurred())
				})

				It("outputs the metadata for the environment", func() {
					jsonIn := fmt.Sprintf(`
							{
								"source": {
									"uri": "%s",
									"branch": "master",
									"pool": "lock-pool"
								},
								"version": {
									"ref": "%s"
								}
							}`, gitRepo, shaStr)

					session := runIn(jsonIn, inDestination, 0)

					Consistently(session.Err).ShouldNot(gbytes.Say("error: lock instance is no longer acquired"))
					err := json.Unmarshal(session.Out.Contents(), &output)
					Ω(err).ShouldNot(HaveOccurred())

					metaDataFile := filepath.Join(inDestination, "metadata")
					Ω(metaDataFile).Should(BeARegularFile())

					fileContents, err := ioutil.ReadFile(metaDataFile)
					Ω(err).ShouldNot(HaveOccurred())

					Ω(fileContents).Should(MatchJSON(`{"some":"json"}`))

					lockNameFile := filepath.Join(inDestination, "name")
					Ω(lockNameFile).Should(BeARegularFile())

					fileContents, err = ioutil.ReadFile(lockNameFile)
					Ω(err).ShouldNot(HaveOccurred())

					Ω(strings.TrimSpace(string(fileContents))).Should(Equal("some-lock"))

					Ω(output).Should(Equal(inResponse{
						Version: version{
							Ref: shaStr,
						},
						Metadata: []metadataPair{
							{Name: "lock_name", Value: "some-lock"},
							{Name: "pool_name", Value: "lock-pool"},
						},
					}))
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>package interceptor

import (
	"fmt"
	"strings"

	"cloud.google.com/go/trace"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

const (
	headerKey  = "google_cloud_trace_context"
	headerTmpl = "%s/%d;o=1"
)

// ServerTrace returns a new unary server interceptor used for parsing
// the google trace header from metadata and creating new tracing span.
func ServerTrace(traceClient *trace.Client) grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (resp interface{}, err error) {
		md, ok := metadata.FromContext(ctx)
		if !ok {
			md = metadata.New(nil)
		}

		header := strings.Join(md[headerKey], "")
		span := traceClient.SpanFromHeader(info.FullMethod, header)
		defer span.Finish()

		ctx = trace.NewContext(ctx, span)
		return handler(ctx, req)
	}
}

// ClientTrace returns a new unary client interceptor used for injecting
// the google trace header into the context metadata.
func ClientTrace(traceClient *trace.Client) grpc.UnaryClientInterceptor {
	return func(
		ctx context.Context,
		method string,
		req, resp interface{},
		cc *grpc.ClientConn,
		invoker grpc.UnaryInvoker,
		opts ...grpc.CallOption,
	) error {
		span := trace.FromContext(ctx).NewChild(
			fmt.Sprintf("/grpc.Sent%s", method),
		)
		defer span.Finish()

		md, ok := metadata.FromContext(ctx)
		if !ok {
			md = metadata.New(nil)
		} else {
			md = md.Copy()
		}
		md[headerKey] = append(md[headerKey], traceHeader(span.TraceID()))

		ctx = metadata.NewContext(ctx, md)
		return invoker(ctx, method, req, resp, cc, opts...)
	}
}

// force google trace header specification
// https://cloud.google.com/trace/docs/faq
func traceHeader(traceID string) string {
	return fmt.Sprintf(headerTmpl, traceID, 0)
}
<commit_msg>Rename package to intercept<commit_after>package intercept

import (
	"fmt"
	"strings"

	"cloud.google.com/go/trace"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

const (
	headerKey  = "google_cloud_trace_context"
	headerTmpl = "%s/%d;o=1"
)

// ServerTrace returns a new unary server interceptor used for parsing
// the google trace header from metadata and creating new tracing span.
func ServerTrace(traceClient *trace.Client) grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (resp interface{}, err error) {
		md, ok := metadata.FromContext(ctx)
		if !ok {
			md = metadata.New(nil)
		}

		header := strings.Join(md[headerKey], "")
		span := traceClient.SpanFromHeader(info.FullMethod, header)
		defer span.Finish()

		ctx = trace.NewContext(ctx, span)
		return handler(ctx, req)
	}
}

// ClientTrace returns a new unary client interceptor used for injecting
// the google trace header into the context metadata.
func ClientTrace(traceClient *trace.Client) grpc.UnaryClientInterceptor {
	return func(
		ctx context.Context,
		method string,
		req, resp interface{},
		cc *grpc.ClientConn,
		invoker grpc.UnaryInvoker,
		opts ...grpc.CallOption,
	) error {
		span := trace.FromContext(ctx).NewChild(
			fmt.Sprintf("/grpc.Sent%s", method),
		)
		defer span.Finish()

		md, ok := metadata.FromContext(ctx)
		if !ok {
			md = metadata.New(nil)
		} else {
			md = md.Copy()
		}
		md[headerKey] = append(md[headerKey], traceHeader(span.TraceID()))

		ctx = metadata.NewContext(ctx, md)
		return invoker(ctx, method, req, resp, cc, opts...)
	}
}

// force google trace header specification
// https://cloud.google.com/trace/docs/faq
func traceHeader(traceID string) string {
	return fmt.Sprintf(headerTmpl, traceID, 0)
}
<|endoftext|>