<commit_before>package main

import (
	"archive/zip"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
)

const version string = "1.1.0"

var argZip string
var argTarget string
var argVersion bool
var argWatch bool
var config Config
var srcFiles []string
var srcDirs []string

func main() {
	initFlags()

	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "init":
			initializeEmptyProject()
		case "clean":
			loadConfigAndClean()
		default:
			run(nil, true)
		}
	} else {
		run(nil, true)
	}
}

func initFlags() {
	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n  web-build [COMMAND] OR web-build [FLAGS]\n\n", os.Args[0])
		fmt.Printf("  Commands:\n  init\n\tInitialize an empty project complete with: source directory, 'common' target and a default 'web-build.json.'\n  clean\n\tClear the build directory\n\n")
		fmt.Printf("  Flags:\n")
		flag.PrintDefaults()
	}
	flag.StringVar(&argTarget, "target", "", "Specify the target to build. This will override the target specified in 'web-build.json'.")
	flag.StringVar(&argZip, "zip", "", "Compress the build upon completion of program. Specify the location and name of the zip file. Example: './app.zip'")
	flag.BoolVar(&argVersion, "version", false, "Show the current version")
	flag.BoolVar(&argWatch, "watch", false, "Runs web-build and watches all files specified by user configuration globs for changes.")
	flag.Parse()

	if argVersion {
		fmt.Printf("web-build version %s\n", version)
		os.Exit(0)
	}
}

func loadConfigAndClean() {
	var err error
	config, err = parseConfig()
	if err != nil {
		errorMsg("Could not parse configuration file.", err)
		return
	}
	clean()
}

func clean() {
	var err error
	err = os.RemoveAll(config.BuildDir)
	if err != nil {
		errorMsg(fmt.Sprintf("Could not remove all contents from %s.", config.BuildDir), err)
		return
	}
}

func initializeEmptyProject() {
	files, err := ioutil.ReadDir("./")
	if err != nil {
		errorMsg("Cannot access current working directory to initialize web-build", err)
		os.Exit(1)
	} else if len(files) > 0 {
		errorMsg("Cannot initialize web-build in a non-empty directory", nil)
		os.Exit(1)
	}

	data, err := configTemplate()
	if err != nil {
		errorMsg("Could not initialize due to template decoding error. Exiting.", err)
		os.Exit(1)
	}

	err = ioutil.WriteFile("web-build.json", data, 0744)
	if err != nil {
		errorMsg("Could not write web-build.json template. Exiting.", err)
		os.Exit(1)
	}

	err = os.MkdirAll("./src/common", 0744)
	if err != nil {
		errorMsg("Could not create source directory. Exiting.", err)
		os.Exit(1)
	}
}

func run(done chan<- bool, runWatcher bool) {
	var err error
	start := timestamp()

	if done != nil {
		defer func() { done <- true }()
	}

	config, err = parseConfig()
	if err != nil {
		errorMsg("Could not parse configuration file.", err)
		return
	}

	err = setup()
	if err != nil {
		errorMsg("Error during setup.", err)
		return
	}

	if len(config.Targets) > 0 {
		fmt.Printf("Building target: %s\n", fmtCyan(config.Target))
	}
	fmt.Printf("Running Tasks...\n")
	runTasks(config.Tasks)
	fmt.Printf("Completed in: %s\n\n", fmtCyan(timestamp()-start, "ms"))

	if argZip != "" {
		createZip(argZip)
	}

	if runWatcher && argWatch {
		watch()
	}
}

func setup() error {
	if argTarget != "" {
		config.Target = argTarget
		if !checkValidTarget(argTarget, config) {
			errorMsg(fmt.Sprintf("The target '%s' is invalid.", argTarget), nil)
			return &invalidTargetError{argTarget}
		}
	}

	if _, err := os.Stat(config.SrcDir); err != nil {
		errorMsg(fmt.Sprintf("Source directory '%s' does not exist.", config.SrcDir), nil)
		return err
	} else if config.SrcDir == config.BuildDir {
		errorMsg("Source directory cannot be the same as the build directory.", nil)
		return err
	}

	// Prevent relative paths in config (i.e. use of ../../) from messing things up for SrcDir and BuildDir
	config.SrcDir, _ = filepath.Abs(config.SrcDir)
	config.SrcDir = filepath.ToSlash(config.SrcDir)
	config.BuildDir, _ = filepath.Abs(config.BuildDir)
	config.BuildDir = filepath.ToSlash(config.BuildDir)

	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		clean()
	}()
	go func() {
		defer wg.Done()
		var err error
		srcFiles, srcDirs, err = filesInPath(config.SrcDir)
		if err != nil {
			errorMsg(fmt.Sprintf("Folder '%s' not found.\n", config.SrcDir), err)
		}
	}()
	wg.Wait()
	return nil
}

func runTasks(tasks map[string]Task) {
	var wg sync.WaitGroup
	for name, task := range tasks {
		name := name
		task := task
		wg.Add(1)
		go func() {
			defer wg.Done()
			runTask(name, task)
		}()
	}
	wg.Wait()
}

func runTask(name string, task Task) {
	start := timestamp()
	var files []string
	if len(config.Targets) == 0 {
		files = glob(task.Globs, config.SrcDir)
	} else {
		files = resolveTargetFiles(task.Globs)
	}
	prevOutput := files

	if len(files) == 0 {
		printFinishedTask(name, start)
		return
	}

	for _, action := range task.Actions {
		var actioner Actioner
		switch action.Action {
		case "collate":
			actioner = collateAction{}
		case "concat":
			actioner = concatAction{}
		case "js-minify":
			actioner = jsMinifyAction{}
		case "sass":
			actioner = sassAction{}
		case "shell":
			actioner = shellAction{}
		default:
			continue
		}
		prevOutput = actioner.Action(prevOutput, action.Options)
	}
	printFinishedTask(name, start)
}

func printFinishedTask(name string, start int64) {
	fmt.Printf("  %s: %s\n", fmtGreen(name), fmtCyan(timestamp()-start, "ms"))
}

func createZip(outputPath string) {
	fmt.Printf("Creating archive...\n")
	of, err := os.Create(outputPath)
	if err != nil {
		errorMsg(fmt.Sprintf("Could not create zip file '%s'", outputPath), err)
		return
	}
	defer of.Close()

	w := zip.NewWriter(of)
	defer w.Close()

	files, _, err := filesInPath(config.BuildDir)
	if err != nil {
		errorMsg(fmt.Sprintf("Folder '%s' not found.\n", config.BuildDir), err)
		return
	}

	for _, file := range files {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not read file '%s' while creating zip archive. Removing archive", file), err)
			return
		}

		file = strings.Replace(file, config.BuildDir, "", -1)[1:]
		f, err := w.Create(file)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not add file '%s' to archive.", file), err)
			return
		}

		_, err = f.Write(data)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not write file '%s' to archive.", file), err)
			return
		}
	}
	fmt.Printf("Created archive '%s'.\n\n", outputPath)
}

func resolveTargetFiles(globs []string) []string {
	var files []string
	var keyOrder []string
	fileCache := make(map[string]string)
	target := config.Target
	dependencies := []string{target}

	// Get dependencies
	for config.Targets[target].Dependency != "" {
		target = config.Targets[target].Dependency
		dependencies = append([]string{target}, dependencies...)
	}

	// Resolve the file list
	for _, innerTarget := range dependencies {
		globFiles := glob(globs, fmt.Sprintf("%s/%s", config.SrcDir, innerTarget))
		for _, file := range globFiles {
			relativePath := strings.Replace(file, fmt.Sprintf("%s/%s", config.SrcDir, innerTarget), "", -1)
			if _, ok := fileCache[relativePath]; !ok {
				keyOrder = append(keyOrder, relativePath)
			}
			fileCache[relativePath] = file
		}
	}

	for _, key := range keyOrder {
		files = append(files, fileCache[key])
	}

	return files
}

func glob(globs []string, baseDir string) []string {
	var foundFiles []string
	baseDirLen := len(baseDir)

	for _, glob := range globs {
		exclusion := []rune(glob)[0] == []rune("!")[0]
		glob = strings.Replace(glob, ".", "\\.", -1)
		glob = strings.Replace(glob, "**", "__double-star-placeholder__", -1) // Have to use a placeholder so that the single asterisk replacement doesn't affect this
		glob = strings.Replace(glob, "*", "[^\\/]*", -1)
		glob = strings.Replace(glob, "__double-star-placeholder__", ".*", -1)
		glob = fmt.Sprintf("%s%s", glob, "$")

		if exclusion {
			glob = glob[1:]
		}

		r, err := regexp.Compile(glob)
		if err != nil {
			errorMsg("Invalid regular expression in glob.", err)
			continue
		} else if exclusion {
			for j := 0; j < len(foundFiles); j++ {
				if r.MatchString(foundFiles[j][baseDirLen:]) {
					foundFiles = append(foundFiles[:j], foundFiles[j+1:]...)
					j--
				}
			}
		} else {
			for _, file := range srcFiles {
				if len(file) <= baseDirLen || file[:baseDirLen] != baseDir {
					continue
				} else if r.MatchString(file[baseDirLen:]) {
					foundFiles = append(foundFiles, file)
				}
			}
		}
	}

	return foundFiles
}

func targetPathRegex() (*regexp.Regexp, error) {
	expression := fmt.Sprintf("%s/(", config.SrcDir)
	count := 0
	for k := range config.Targets {
		if count > 0 {
			expression += "|"
		}
		expression += k
		count++
	}
	expression += ")"
	return regexp.Compile(expression)
}

func checkValidTarget(targetName string, c Config) bool {
	if targetName == "" && len(c.Targets) == 0 {
		return true
	}
	for target := range c.Targets {
		if target == targetName {
			return true
		}
	}
	return false
}

func watch() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		errorMsg("Error creating file watcher.", err)
		return
	}
	defer watcher.Close()

	file, _ := filepath.Abs(configFile)
	err = watcher.Add(file)
	if err != nil {
		errorMsg(fmt.Sprintf("Could not add file '%s'", file), err)
		os.Exit(1)
	}

	watches := make(map[string]bool)
	for _, dir := range srcDirs {
		watches[dir] = true
		err = watcher.Add(dir)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not add file '%s'", dir), err)
			os.Exit(1)
		}
	}

	go handleWatcherEvent(watcher, &watches)
	done := make(chan bool)
	<-done
}

func handleWatcherEvent(watcher *fsnotify.Watcher, watchesMap *map[string]bool) {
	watches := *watchesMap
	busy := false
	done := make(chan bool)

	for {
		select {
		case event := <-watcher.Events:
			info, err := os.Stat(event.Name)
			if err != nil {
				errorMsg("Could not stat file in watcher", err)
				continue
			} else if info.IsDir() {
				if event.Op&fsnotify.Remove == fsnotify.Remove {
					watcher.Remove(event.Name)
					delete(watches, event.Name)
				} else if event.Op&fsnotify.Create == fsnotify.Create {
					if _, ok := watches[event.Name]; !ok {
						watches[event.Name] = true
						watcher.Add(event.Name)
					}
				}
			}

			if busy {
				continue
			}
			busy = true
			go run(done, false)
		case <-done:
			busy = false
		case err := <-watcher.Errors:
			errorMsg("Error while watching files", err)
		}
	}
}
<commit_msg>Fixed bug with collate action in projects without targets<commit_after>package main

import (
	"archive/zip"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
)

const version string = "1.1.1"

var argZip string
var argTarget string
var argVersion bool
var argWatch bool
var config Config
var srcFiles []string
var srcDirs []string

func main() {
	initFlags()

	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "init":
			initializeEmptyProject()
		case "clean":
			loadConfigAndClean()
		default:
			run(nil, true)
		}
	} else {
		run(nil, true)
	}
}

func initFlags() {
	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n  web-build [COMMAND] OR web-build [FLAGS]\n\n", os.Args[0])
		fmt.Printf("  Commands:\n  init\n\tInitialize an empty project complete with: source directory, 'common' target and a default 'web-build.json.'\n  clean\n\tClear the build directory\n\n")
		fmt.Printf("  Flags:\n")
		flag.PrintDefaults()
	}
	flag.StringVar(&argTarget, "target", "", "Specify the target to build. This will override the target specified in 'web-build.json'.")
	flag.StringVar(&argZip, "zip", "", "Compress the build upon completion of program. Specify the location and name of the zip file. Example: './app.zip'")
	flag.BoolVar(&argVersion, "version", false, "Show the current version")
	flag.BoolVar(&argWatch, "watch", false, "Runs web-build and watches all files specified by user configuration globs for changes.")
	flag.Parse()

	if argVersion {
		fmt.Printf("web-build version %s\n", version)
		os.Exit(0)
	}
}

func loadConfigAndClean() {
	var err error
	config, err = parseConfig()
	if err != nil {
		errorMsg("Could not parse configuration file.", err)
		return
	}
	clean()
}

func clean() {
	var err error
	err = os.RemoveAll(config.BuildDir)
	if err != nil {
		errorMsg(fmt.Sprintf("Could not remove all contents from %s.", config.BuildDir), err)
		return
	}
}

func initializeEmptyProject() {
	files, err := ioutil.ReadDir("./")
	if err != nil {
		errorMsg("Cannot access current working directory to initialize web-build", err)
		os.Exit(1)
	} else if len(files) > 0 {
		errorMsg("Cannot initialize web-build in a non-empty directory", nil)
		os.Exit(1)
	}

	data, err := configTemplate()
	if err != nil {
		errorMsg("Could not initialize due to template decoding error. Exiting.", err)
		os.Exit(1)
	}

	err = ioutil.WriteFile("web-build.json", data, 0744)
	if err != nil {
		errorMsg("Could not write web-build.json template. Exiting.", err)
		os.Exit(1)
	}

	err = os.MkdirAll("./src/common", 0744)
	if err != nil {
		errorMsg("Could not create source directory. Exiting.", err)
		os.Exit(1)
	}
}

func run(done chan<- bool, runWatcher bool) {
	var err error
	start := timestamp()

	if done != nil {
		defer func() { done <- true }()
	}

	config, err = parseConfig()
	if err != nil {
		errorMsg("Could not parse configuration file.", err)
		return
	}

	err = setup()
	if err != nil {
		errorMsg("Error during setup.", err)
		return
	}

	if len(config.Targets) > 0 {
		fmt.Printf("Building target: %s\n", fmtCyan(config.Target))
	}
	fmt.Printf("Running Tasks...\n")
	runTasks(config.Tasks)
	fmt.Printf("Completed in: %s\n\n", fmtCyan(timestamp()-start, "ms"))

	if argZip != "" {
		createZip(argZip)
	}

	if runWatcher && argWatch {
		watch()
	}
}

func setup() error {
	if argTarget != "" {
		config.Target = argTarget
		if !checkValidTarget(argTarget, config) {
			errorMsg(fmt.Sprintf("The target '%s' is invalid.", argTarget), nil)
			return &invalidTargetError{argTarget}
		}
	}

	if _, err := os.Stat(config.SrcDir); err != nil {
		errorMsg(fmt.Sprintf("Source directory '%s' does not exist.", config.SrcDir), nil)
		return err
	} else if config.SrcDir == config.BuildDir {
		errorMsg("Source directory cannot be the same as the build directory.", nil)
		return err
	}

	// Prevent relative paths in config (i.e. use of ../../) from messing things up for SrcDir and BuildDir
	config.SrcDir, _ = filepath.Abs(config.SrcDir)
	config.SrcDir = filepath.ToSlash(config.SrcDir)
	config.BuildDir, _ = filepath.Abs(config.BuildDir)
	config.BuildDir = filepath.ToSlash(config.BuildDir)

	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		clean()
	}()
	go func() {
		defer wg.Done()
		var err error
		srcFiles, srcDirs, err = filesInPath(config.SrcDir)
		if err != nil {
			errorMsg(fmt.Sprintf("Folder '%s' not found.\n", config.SrcDir), err)
		}
	}()
	wg.Wait()
	return nil
}

func runTasks(tasks map[string]Task) {
	var wg sync.WaitGroup
	for name, task := range tasks {
		name := name
		task := task
		wg.Add(1)
		go func() {
			defer wg.Done()
			runTask(name, task)
		}()
	}
	wg.Wait()
}

func runTask(name string, task Task) {
	start := timestamp()
	var files []string
	if len(config.Targets) == 0 {
		files = glob(task.Globs, config.SrcDir)
	} else {
		files = resolveTargetFiles(task.Globs)
	}
	prevOutput := files

	if len(files) == 0 {
		printFinishedTask(name, start)
		return
	}

	for _, action := range task.Actions {
		var actioner Actioner
		switch action.Action {
		case "collate":
			actioner = collateAction{}
		case "concat":
			actioner = concatAction{}
		case "js-minify":
			actioner = jsMinifyAction{}
		case "sass":
			actioner = sassAction{}
		case "shell":
			actioner = shellAction{}
		default:
			continue
		}
		prevOutput = actioner.Action(prevOutput, action.Options)
	}
	printFinishedTask(name, start)
}

func printFinishedTask(name string, start int64) {
	fmt.Printf("  %s: %s\n", fmtGreen(name), fmtCyan(timestamp()-start, "ms"))
}

func createZip(outputPath string) {
	fmt.Printf("Creating archive...\n")
	of, err := os.Create(outputPath)
	if err != nil {
		errorMsg(fmt.Sprintf("Could not create zip file '%s'", outputPath), err)
		return
	}
	defer of.Close()

	w := zip.NewWriter(of)
	defer w.Close()

	files, _, err := filesInPath(config.BuildDir)
	if err != nil {
		errorMsg(fmt.Sprintf("Folder '%s' not found.\n", config.BuildDir), err)
		return
	}

	for _, file := range files {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not read file '%s' while creating zip archive. Removing archive", file), err)
			return
		}

		file = strings.Replace(file, config.BuildDir, "", -1)[1:]
		f, err := w.Create(file)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not add file '%s' to archive.", file), err)
			return
		}

		_, err = f.Write(data)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not write file '%s' to archive.", file), err)
			return
		}
	}
	fmt.Printf("Created archive '%s'.\n\n", outputPath)
}

func resolveTargetFiles(globs []string) []string {
	var files []string
	var keyOrder []string
	fileCache := make(map[string]string)
	target := config.Target
	dependencies := []string{target}

	// Get dependencies
	for config.Targets[target].Dependency != "" {
		target = config.Targets[target].Dependency
		dependencies = append([]string{target}, dependencies...)
	}

	// Resolve the file list
	for _, innerTarget := range dependencies {
		globFiles := glob(globs, fmt.Sprintf("%s/%s", config.SrcDir, innerTarget))
		for _, file := range globFiles {
			relativePath := strings.Replace(file, fmt.Sprintf("%s/%s", config.SrcDir, innerTarget), "", -1)
			if _, ok := fileCache[relativePath]; !ok {
				keyOrder = append(keyOrder, relativePath)
			}
			fileCache[relativePath] = file
		}
	}

	for _, key := range keyOrder {
		files = append(files, fileCache[key])
	}

	return files
}

func glob(globs []string, baseDir string) []string {
	var foundFiles []string
	baseDirLen := len(baseDir)

	for _, glob := range globs {
		exclusion := []rune(glob)[0] == []rune("!")[0]
		glob = strings.Replace(glob, ".", "\\.", -1)
		glob = strings.Replace(glob, "**", "__double-star-placeholder__", -1) // Have to use a placeholder so that the single asterisk replacement doesn't affect this
		glob = strings.Replace(glob, "*", "[^\\/]*", -1)
		glob = strings.Replace(glob, "__double-star-placeholder__", ".*", -1)
		glob = fmt.Sprintf("%s%s", glob, "$")

		if exclusion {
			glob = glob[1:]
		}

		r, err := regexp.Compile(glob)
		if err != nil {
			errorMsg("Invalid regular expression in glob.", err)
			continue
		} else if exclusion {
			for j := 0; j < len(foundFiles); j++ {
				if r.MatchString(foundFiles[j][baseDirLen:]) {
					foundFiles = append(foundFiles[:j], foundFiles[j+1:]...)
					j--
				}
			}
		} else {
			for _, file := range srcFiles {
				if len(file) <= baseDirLen || file[:baseDirLen] != baseDir {
					continue
				} else if r.MatchString(file[baseDirLen:]) {
					foundFiles = append(foundFiles, file)
				}
			}
		}
	}

	return foundFiles
}

func targetPathRegex() (*regexp.Regexp, error) {
	expression := config.SrcDir
	if len(config.Targets) > 0 {
		expression = fmt.Sprintf("%s/(", expression)
		count := 0
		for k := range config.Targets {
			if count > 0 {
				expression += "|"
			}
			expression += k
			count++
		}
		expression += ")"
	}
	return regexp.Compile(expression)
}

func checkValidTarget(targetName string, c Config) bool {
	if targetName == "" && len(c.Targets) == 0 {
		return true
	}
	for target := range c.Targets {
		if target == targetName {
			return true
		}
	}
	return false
}

func watch() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		errorMsg("Error creating file watcher.", err)
		return
	}
	defer watcher.Close()

	file, _ := filepath.Abs(configFile)
	err = watcher.Add(file)
	if err != nil {
		errorMsg(fmt.Sprintf("Could not add file '%s'", file), err)
		os.Exit(1)
	}

	watches := make(map[string]bool)
	for _, dir := range srcDirs {
		watches[dir] = true
		err = watcher.Add(dir)
		if err != nil {
			errorMsg(fmt.Sprintf("Could not add file '%s'", dir), err)
			os.Exit(1)
		}
	}

	go handleWatcherEvent(watcher, &watches)
	done := make(chan bool)
	<-done
}

func handleWatcherEvent(watcher *fsnotify.Watcher, watchesMap *map[string]bool) {
	watches := *watchesMap
	busy := false
	done := make(chan bool)

	for {
		select {
		case event := <-watcher.Events:
			info, err := os.Stat(event.Name)
			if err != nil {
				errorMsg("Could not stat file in watcher", err)
				continue
			} else if info.IsDir() {
				if event.Op&fsnotify.Remove == fsnotify.Remove {
					watcher.Remove(event.Name)
					delete(watches, event.Name)
				} else if event.Op&fsnotify.Create == fsnotify.Create {
					if _, ok := watches[event.Name]; !ok {
						watches[event.Name] = true
						watcher.Add(event.Name)
					}
				}
			}

			if busy {
				continue
			}
			busy = true
			go run(done, false)
		case <-done:
			busy = false
		case err := <-watcher.Errors:
			errorMsg("Error while watching files", err)
		}
	}
}
<|endoftext|>


<commit_before>package schematic

import (
	"bytes"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"text/template"
	"unicode"
	"unicode/utf8"
)

var helpers = template.FuncMap{
	"initialCap":       initialCap,
	"initialLow":       initialLow,
	"methodCap":        methodCap,
	"asComment":        asComment,
	"fieldTag":         fieldTag,
	"params":           params,
	"requestParams":    requestParams,
	"args":             args,
	"values":           values,
	"goType":           goType,
	"linkGoType":       linkGoType,
	"returnType":       returnType,
	"defineCustomType": defineCustomType,
	"paramType":        paramType,
}

var (
	newlines  = regexp.MustCompile(`(?m:\s*$)`)
	acronyms  = regexp.MustCompile(`(Url|Http|Id|Io|Uuid|Api|Uri|Ssl|Cname|Oauth|Otp|Cidr|Nat|Vpn)$`)
	camelcase = regexp.MustCompile(`(?m)[-.$/:_{}\s]+`)
)

func goType(p *Schema) string {
	return p.GoType()
}

func linkGoType(l *Link) string {
	t, _ := l.GoType()
	return t
}

func required(n string, def *Schema) bool {
	return contains(n, def.Required)
}

func fieldTag(n string, required bool) string {
	return fmt.Sprintf("`%s %s`", jsonTag(n, required), urlTag(n, required))
}

func jsonTag(n string, required bool) string {
	tags := []string{n}
	if !required {
		tags = append(tags, "omitempty")
	}
	return fmt.Sprintf("json:\"%s\"", strings.Join(tags, ","))
}

func urlTag(n string, required bool) string {
	tags := []string{n}
	if !required {
		tags = append(tags, "omitempty")
	}
	tags = append(tags, "key")
	return fmt.Sprintf("url:\"%s\"", strings.Join(tags, ","))
}

func contains(n string, r []string) bool {
	for _, r := range r {
		if r == n {
			return true
		}
	}
	return false
}

func initialCap(ident string) string {
	if ident == "" {
		panic("blank identifier")
	}
	return depunct(ident, true)
}

func methodCap(ident string) string {
	return initialCap(strings.ToLower(ident))
}

func initialLow(ident string) string {
	if ident == "" {
		panic("blank identifier")
	}
	return depunct(ident, false)
}

func depunct(ident string, initialCap bool) string {
	matches := camelcase.Split(ident, -1)
	for i, m := range matches {
		if initialCap || i > 0 {
			m = capFirst(m)
		}
		matches[i] = acronyms.ReplaceAllStringFunc(m, func(c string) string {
			if len(c) > 4 {
				return strings.ToUpper(c[:2]) + c[2:]
			}
			return strings.ToUpper(c)
		})
	}
	return strings.Join(matches, "")
}

func capFirst(ident string) string {
	r, n := utf8.DecodeRuneInString(ident)
	return string(unicode.ToUpper(r)) + ident[n:]
}

func asComment(c string) string {
	var buf bytes.Buffer
	const maxLen = 70
	r := []rune(c)
	for len(r) > 0 {
		line := r
		if len(line) < maxLen {
			fmt.Fprintf(&buf, "// %s\n", removeNewlines(line))
			break
		}
		line = line[:maxLen]
		si := lastIndex(line, func(r rune) bool {
			return unicode.IsSpace(r)
		})
		if si != -1 {
			line = line[:si]
		}
		fmt.Fprintf(&buf, "// %s\n", removeNewlines(line))
		r = r[len(line):]
		if si != -1 {
			r = r[1:]
		}
	}
	return buf.String()
}

func values(n string, s *Schema, l *Link) string {
	v := s.Values(n, l)
	return strings.Join(v, ", ")
}

func params(name string, l *Link) string {
	var p []string
	order, params := l.Parameters(name)
	for _, n := range order {
		p = append(p, fmt.Sprintf("%s %s", initialLow(n), params[n]))
	}
	return strings.Join(p, ", ")
}

func requestParams(l *Link) string {
	_, params := l.Parameters("")
	if strings.ToUpper(l.Method) == "DELETE" {
		return ""
	}
	p := []string{""}
	if _, ok := params["o"]; ok {
		p = append(p, "o")
	} else {
		p = append(p, "nil")
	}
	if _, ok := params["lr"]; ok {
		p = append(p, "lr")
	} else if strings.ToUpper(l.Method) == "GET" {
		p = append(p, "nil")
	}
	return strings.Join(p, ", ")
}

func args(h *HRef) string {
	return strings.Join(h.Order, ", ")
}

func sortedKeys(m map[string]*Schema) (keys []string) {
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return
}

func returnType(name string, s *Schema, l *Link) string {
	if defineCustomType(s, l) {
		return initialCap(fmt.Sprintf("%s-%s-Result", name, l.Title))
	}
	return initialCap(name)
}

func paramType(name string, l *Link) string {
	if l.AcceptsCustomType() {
		return initialCap(fmt.Sprintf("%s-%s-Opts", name, l.Title))
	}
	return initialCap(name)
}

func defineCustomType(s *Schema, l *Link) bool {
	return l.TargetSchema != nil && l.TargetSchema != s
}

func removeNewlines(s []rune) string {
	return strings.Replace(string(s), "\n", "\n// ", -1)
}

func lastIndex(s []rune, f func(rune) bool) int {
	for i := len(s) - 1; i > 0; i-- {
		if f(s[i]) {
			return i
		}
	}
	return -1
}
<commit_msg>Include more common acronyms<commit_after>package schematic

import (
	"bytes"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"text/template"
	"unicode"
	"unicode/utf8"
)

var helpers = template.FuncMap{
	"initialCap":       initialCap,
	"initialLow":       initialLow,
	"methodCap":        methodCap,
	"asComment":        asComment,
	"fieldTag":         fieldTag,
	"params":           params,
	"requestParams":    requestParams,
	"args":             args,
	"values":           values,
	"goType":           goType,
	"linkGoType":       linkGoType,
	"returnType":       returnType,
	"defineCustomType": defineCustomType,
	"paramType":        paramType,
}

var (
	newlines  = regexp.MustCompile(`(?m:\s*$)`)
	acronyms  = regexp.MustCompile(`(Url|Http|Id|Io|Ip|Ike|Uuid|Api|Uri|Ssl|Cname|Oauth|Otp|Cidr|Nat|Vpn)$`)
	camelcase = regexp.MustCompile(`(?m)[-.$/:_{}\s]+`)
)

func goType(p *Schema) string {
	return p.GoType()
}

func linkGoType(l *Link) string {
	t, _ := l.GoType()
	return t
}

func required(n string, def *Schema) bool {
	return contains(n, def.Required)
}

func fieldTag(n string, required bool) string {
	return fmt.Sprintf("`%s %s`", jsonTag(n, required), urlTag(n, required))
}

func jsonTag(n string, required bool) string {
	tags := []string{n}
	if !required {
		tags = append(tags, "omitempty")
	}
	return fmt.Sprintf("json:\"%s\"", strings.Join(tags, ","))
}

func urlTag(n string, required bool) string {
	tags := []string{n}
	if !required {
		tags = append(tags, "omitempty")
	}
	tags = append(tags, "key")
	return fmt.Sprintf("url:\"%s\"", strings.Join(tags, ","))
}

func contains(n string, r []string) bool {
	for _, r := range r {
		if r == n {
			return true
		}
	}
	return false
}

func initialCap(ident string) string {
	if ident == "" {
		panic("blank identifier")
	}
	return depunct(ident, true)
}

func methodCap(ident string) string {
	return initialCap(strings.ToLower(ident))
}

func initialLow(ident string) string {
	if ident == "" {
		panic("blank identifier")
	}
	return depunct(ident, false)
}

func depunct(ident string, initialCap bool) string {
	matches := camelcase.Split(ident, -1)
	for i, m := range matches {
		if initialCap || i > 0 {
			m = capFirst(m)
		}
		matches[i] = acronyms.ReplaceAllStringFunc(m, func(c string) string {
			if len(c) > 4 {
				return strings.ToUpper(c[:2]) + c[2:]
			}
			return strings.ToUpper(c)
		})
	}
	return strings.Join(matches, "")
}

func capFirst(ident string) string {
	r, n := utf8.DecodeRuneInString(ident)
	return string(unicode.ToUpper(r)) + ident[n:]
}

func asComment(c string) string {
	var buf bytes.Buffer
	const maxLen = 70
	r := []rune(c)
	for len(r) > 0 {
		line := r
		if len(line) < maxLen {
			fmt.Fprintf(&buf, "// %s\n", removeNewlines(line))
			break
		}
		line = line[:maxLen]
		si := lastIndex(line, func(r rune) bool {
			return unicode.IsSpace(r)
		})
		if si != -1 {
			line = line[:si]
		}
		fmt.Fprintf(&buf, "// %s\n", removeNewlines(line))
		r = r[len(line):]
		if si != -1 {
			r = r[1:]
		}
	}
	return buf.String()
}

func values(n string, s *Schema, l *Link) string {
	v := s.Values(n, l)
	return strings.Join(v, ", ")
}

func params(name string, l *Link) string {
	var p []string
	order, params := l.Parameters(name)
	for _, n := range order {
		p = append(p, fmt.Sprintf("%s %s", initialLow(n), params[n]))
	}
	return strings.Join(p, ", ")
}

func requestParams(l *Link) string {
	_, params := l.Parameters("")
	if strings.ToUpper(l.Method) == "DELETE" {
		return ""
	}
	p := []string{""}
	if _, ok := params["o"]; ok {
		p = append(p, "o")
	} else {
		p = append(p, "nil")
	}
	if _, ok := params["lr"]; ok {
		p = append(p, "lr")
	} else if strings.ToUpper(l.Method) == "GET" {
		p = append(p, "nil")
	}
	return strings.Join(p, ", ")
}

func args(h *HRef) string {
	return strings.Join(h.Order, ", ")
}

func sortedKeys(m map[string]*Schema) (keys []string) {
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return
}

func returnType(name string, s *Schema, l *Link) string {
	if defineCustomType(s, l) {
		return initialCap(fmt.Sprintf("%s-%s-Result", name, l.Title))
	}
	return initialCap(name)
}

func paramType(name string, l *Link) string {
	if l.AcceptsCustomType() {
		return initialCap(fmt.Sprintf("%s-%s-Opts", name, l.Title))
	}
	return initialCap(name)
}

func defineCustomType(s *Schema, l *Link) bool {
	return l.TargetSchema != nil && l.TargetSchema != s
}

func removeNewlines(s []rune) string {
	return strings.Replace(string(s), "\n", "\n// ", -1)
}

func lastIndex(s []rune, f func(rune) bool) int {
	for i := len(s) - 1; i > 0; i-- {
		if f(s[i]) {
			return i
		}
	}
	return -1
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"testing"
	"time"
)

// HTTP ServeMux with an updateable handler so that tests can pass their own
// anonymous functions in to handle requests.
type CDNServeMux struct {
	Port    int
	handler func(w http.ResponseWriter, r *http.Request)
}

func (s *CDNServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "HEAD" && r.URL.Path == "/" {
		w.Header().Set("PING", "PONG")
		return
	}

	s.handler(w, r)
}

func (s *CDNServeMux) SwitchHandler(h func(w http.ResponseWriter, r *http.Request)) {
	s.handler = h
}

// Start a new server and return the CDNServeMux used.
func StartServer(port int) *CDNServeMux {
	handler := func(w http.ResponseWriter, r *http.Request) {}
	mux := &CDNServeMux{port, handler}
	addr := fmt.Sprintf(":%d", port)

	go func() {
		err := http.ListenAndServe(addr, mux)
		if err != nil {
			log.Fatal(err)
		}
	}()

	log.Printf("Started server on port %d", port)
	return mux
}

// Return a v4 (random) UUID string.
// This might not be strictly RFC4122 compliant, but it will do. Credit:
// https://groups.google.com/d/msg/golang-nuts/Rn13T6BZpgE/dBaYVJ4hB5gJ
func NewUUID() string {
	bs := make([]byte, 16)
	rand.Read(bs)
	bs[6] = (bs[6] & 0x0f) | 0x40
	bs[8] = (bs[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:])
}

// Confirm that the edge (CDN) is working correctly with respect to its perception
// of the state of its backend nodes. This may take some time because our CDNServeMux
// needs to receive and respond to enough probe health checks to be considered up.
//
// We assume that all backends are stopped, so that we can start them in order.
//
func StartBackendsInOrder(edgeHost string) (err error) {

	backupServer2 = StartServer(*backupPort2)
	backupServer2.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Backend-Marker", "backupServer2")
		w.WriteHeader(200)
	})
	err = waitForBackend(edgeHost, "backupServer2")
	if err != nil {
		return
	}

	backupServer1 = StartServer(*backupPort1)
	backupServer1.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Backend-Marker", "backupServer1")
		w.WriteHeader(200)
	})
	err = waitForBackend(edgeHost, "backupServer1")
	if err != nil {
		return
	}

	originServer = StartServer(*originPort)
	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Backend-Marker", "originServer")
		w.WriteHeader(200)
	})
	err = waitForBackend(edgeHost, "originServer")
	if err != nil {
		return
	}

	// All is well
	return nil

}

// Wait for the backend to return with the header we expect. This is designed to
// confirm that requests are hitting this specific backend, rather than a lower-level
// backend that this overrides (for example, origin over a mirror)
//
func waitForBackend(
	edgeHost string,
	expectedBackendMarker string,
) error {

	const maxRetries = 20
	const waitForCdnProbeToPropagate = time.Duration(5 * time.Second)
	const timeBetweenAttempts = time.Duration(2 * time.Second)

	var sourceUrl string

	log.Printf("Checking health of %s...", expectedBackendMarker)
	for try := 0; try <= maxRetries; try++ {
		uuid := NewUUID()
		sourceUrl = fmt.Sprintf("https://%s/?cacheBuster=%s", edgeHost, uuid)
		req, _ := http.NewRequest("GET", sourceUrl, nil)
		resp, err := client.RoundTrip(req)
		if err != nil {
			return err
		}
		if resp.Header.Get("Backend-Marker") == expectedBackendMarker {
			if try != 0 {
				time.Sleep(waitForCdnProbeToPropagate)
			}
			log.Println(expectedBackendMarker + " is up!")
			return nil // all is well!
		}
		time.Sleep(timeBetweenAttempts)
	}

	return fmt.Errorf(
		"%s still not available after %d attempts",
		expectedBackendMarker,
		maxRetries,
	)

}

// Callback function to modify response headers.
type responseHeaderCallback func(h http.Header)

// Helper function to make three requests and verify that we get three
// unique and uncached responses back. A responseHeaderCallback, if not nil,
// will be called to modify the response headers.
func testThreeRequestsNotCached(t *testing.T, req *http.Request, headerCB responseHeaderCallback) {
	requestsReceivedCount := 0
	responseBodies := []string{
		"first response",
		"second response",
		"third response",
	}

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if headerCB != nil {
			headerCB(w.Header())
		}
		w.Write([]byte(responseBodies[requestsReceivedCount]))
		requestsReceivedCount++
	})

	for _, expectedBody := range responseBodies {
		resp, err := client.RoundTrip(req)

		if err != nil {
			t.Fatal(err)
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}

		if receivedBody := string(body); receivedBody != expectedBody {
			t.Errorf("Incorrect response body. Expected %q, got %q", expectedBody, receivedBody)
		}
	}
}
<commit_msg>Add and use Name field on CDNServeMux<commit_after>package main

import (
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"testing"
	"time"
)

// HTTP ServeMux with an updateable handler so that tests can pass their own
// anonymous functions in to handle requests.
type CDNServeMux struct {
	Name    string
	Port    int
	handler func(w http.ResponseWriter, r *http.Request)
}

func (s *CDNServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "HEAD" && r.URL.Path == "/" {
		w.Header().Set("PING", "PONG")
		return
	}

	s.handler(w, r)
}

func (s *CDNServeMux) SwitchHandler(h func(w http.ResponseWriter, r *http.Request)) {
	s.handler = h
}

// Start a new server and return the CDNServeMux used.
func StartServer(name string, port int) *CDNServeMux {
	handler := func(w http.ResponseWriter, r *http.Request) {}
	mux := &CDNServeMux{name, port, handler}
	addr := fmt.Sprintf(":%d", port)

	go func() {
		err := http.ListenAndServe(addr, mux)
		if err != nil {
			log.Fatal(err)
		}
	}()

	log.Printf("Started server on port %d", port)
	return mux
}

// Return a v4 (random) UUID string.
// This might not be strictly RFC4122 compliant, but it will do. Credit:
// https://groups.google.com/d/msg/golang-nuts/Rn13T6BZpgE/dBaYVJ4hB5gJ
func NewUUID() string {
	bs := make([]byte, 16)
	rand.Read(bs)
	bs[6] = (bs[6] & 0x0f) | 0x40
	bs[8] = (bs[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:])
}

// Confirm that the edge (CDN) is working correctly with respect to its perception
// of the state of its backend nodes. This may take some time because our CDNServeMux
// needs to receive and respond to enough probe health checks to be considered up.
//
// We assume that all backends are stopped, so that we can start them in order.
//
func StartBackendsInOrder(edgeHost string) (err error) {

	backupServer2 = StartServer("backup2", *backupPort2)
	backupServer2.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Backend-Marker", backupServer2.Name)
		w.WriteHeader(200)
	})
	err = waitForBackend(edgeHost, backupServer2.Name)
	if err != nil {
		return
	}

	backupServer1 = StartServer("backup1", *backupPort1)
	backupServer1.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Backend-Marker", backupServer1.Name)
		w.WriteHeader(200)
	})
	err = waitForBackend(edgeHost, backupServer1.Name)
	if err != nil {
		return
	}

	originServer = StartServer("origin", *originPort)
	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Backend-Marker", originServer.Name)
		w.WriteHeader(200)
	})
	err = waitForBackend(edgeHost, originServer.Name)
	if err != nil {
		return
	}

	// All is well
	return nil

}

// Wait for the backend to return with the header we expect. This is designed to
// confirm that requests are hitting this specific backend, rather than a lower-level
// backend that this overrides (for example, origin over a mirror)
//
func waitForBackend(
	edgeHost string,
	expectedBackendMarker string,
) error {

	const maxRetries = 20
	const waitForCdnProbeToPropagate = time.Duration(5 * time.Second)
	const timeBetweenAttempts = time.Duration(2 * time.Second)

	var sourceUrl string

	log.Printf("Checking health of %s...", expectedBackendMarker)
	for try := 0; try <= maxRetries; try++ {
		uuid := NewUUID()
		sourceUrl = fmt.Sprintf("https://%s/?cacheBuster=%s", edgeHost, uuid)
		req, _ := http.NewRequest("GET", sourceUrl, nil)
		resp, err := client.RoundTrip(req)
		if err != nil {
			return err
		}
		if resp.Header.Get("Backend-Marker") == expectedBackendMarker {
			if try != 0 {
				time.Sleep(waitForCdnProbeToPropagate)
			}
			log.Println(expectedBackendMarker + " is up!")
			return nil // all is well!
		}
		time.Sleep(timeBetweenAttempts)
	}

	return fmt.Errorf(
		"%s still not available after %d attempts",
		expectedBackendMarker,
		maxRetries,
	)

}

// Callback function to modify response headers.
type responseHeaderCallback func(h http.Header)

// Helper function to make three requests and verify that we get three
// unique and uncached responses back. A responseHeaderCallback, if not nil,
// will be called to modify the response headers.
func testThreeRequestsNotCached(t *testing.T, req *http.Request, headerCB responseHeaderCallback) {
	requestsReceivedCount := 0
	responseBodies := []string{
		"first response",
		"second response",
		"third response",
	}

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if headerCB != nil {
			headerCB(w.Header())
		}
		w.Write([]byte(responseBodies[requestsReceivedCount]))
		requestsReceivedCount++
	})

	for _, expectedBody := range responseBodies {
		resp, err := client.RoundTrip(req)

		if err != nil {
			t.Fatal(err)
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}

		if receivedBody := string(body); receivedBody != expectedBody {
			t.Errorf("Incorrect response body. Expected %q, got %q", expectedBody, receivedBody)
		}
	}
}
<|endoftext|>


<commit_before>/*
Miscellaneous helpers: logging, errors, subprocesses
*/

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"syscall"
)

func fail400(w http.ResponseWriter, context string, err error) {
	http.Error(w, "Bad request", 400)
	logContext(context, err)
}

func fail500(w http.ResponseWriter, context string, err error) {
	http.Error(w, "Internal server error", 500)
	logContext(context, err)
}

func logContext(context string, err error) {
	log.Printf("%s: %v", context, err)
}

// Git subprocess helpers
func gitCommand(gl_id string, name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	// Start the command in its own process group (nice for signalling)
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	// Explicitly set the environment for the Git command
	cmd.Env = []string{
		fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
		fmt.Sprintf("LD_LIBRARY_PATH=%s", os.Getenv("LD_LIBRARY_PATH")),
		fmt.Sprintf("GL_ID=%s", gl_id),
	}
	// If we don't do something with cmd.Stderr, Git errors will be lost
	cmd.Stderr = os.Stderr
	return cmd
}

func cleanUpProcessGroup(cmd *exec.Cmd) {
	if cmd == nil {
		return
	}

	process := cmd.Process
	if process != nil && process.Pid > 0 {
		// Send SIGTERM to the process group of cmd
		syscall.Kill(-process.Pid, syscall.SIGTERM)
	}

	// reap our child process
	cmd.Wait()
}

func forwardResponseToClient(w http.ResponseWriter, r *http.Response) {
	log.Printf("PROXY:%s %q %d", r.Request.Method, r.Request.URL, r.StatusCode)

	for k, v := range r.Header {
		w.Header()[k] = v
	}

	w.WriteHeader(r.StatusCode)
	io.Copy(w, r.Body)
}

func setHttpPostForm(r *http.Request, values url.Values) {
	dataBuffer := strings.NewReader(values.Encode())
	r.Body = ioutil.NopCloser(dataBuffer)
	r.ContentLength = int64(dataBuffer.Len())
	r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
<commit_msg>gitCommand: Pass $HOME to git as well<commit_after>/*
Miscellaneous helpers: logging, errors, subprocesses
*/

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"syscall"
)

func fail400(w http.ResponseWriter, context string, err error) {
	http.Error(w, "Bad request", 400)
	logContext(context, err)
}

func fail500(w http.ResponseWriter, context string, err error) {
	http.Error(w, "Internal server error", 500)
	logContext(context, err)
}

func logContext(context string, err error) {
	log.Printf("%s: %v", context, err)
}

// Git subprocess helpers
func gitCommand(gl_id string, name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	// Start the command in its own process group (nice for signalling)
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	// Explicitly set the environment for the Git command
	cmd.Env = []string{
		fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
		fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
		fmt.Sprintf("LD_LIBRARY_PATH=%s", os.Getenv("LD_LIBRARY_PATH")),
		fmt.Sprintf("GL_ID=%s", gl_id),
	}
	// If we don't do something with cmd.Stderr, Git errors will be lost
	cmd.Stderr = os.Stderr
	return cmd
}

func cleanUpProcessGroup(cmd *exec.Cmd) {
	if cmd == nil {
		return
	}

	process := cmd.Process
	if process != nil && process.Pid > 0 {
		// Send SIGTERM to the process group of cmd
		syscall.Kill(-process.Pid, syscall.SIGTERM)
	}

	// reap our child process
	cmd.Wait()
}

func forwardResponseToClient(w http.ResponseWriter, r *http.Response) {
	log.Printf("PROXY:%s %q %d", r.Request.Method, r.Request.URL, r.StatusCode)

	for k, v := range r.Header {
		w.Header()[k] = v
	}

	w.WriteHeader(r.StatusCode)
	io.Copy(w, r.Body)
}

func setHttpPostForm(r *http.Request, values url.Values) {
	dataBuffer := strings.NewReader(values.Encode())
	r.Body = ioutil.NopCloser(dataBuffer)
	r.ContentLength = int64(dataBuffer.Len())
	r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func HealthCheck(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": "health",
	})
}
<commit_msg>use constant message in health check handler<commit_after>package server

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func HealthCheck(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"message": HealthStatus,
	})
}
<|endoftext|>


<commit_before>package turn

import (
	"bytes"
	"encoding/hex"
	"errors"
	"io"
	"log"
)

// ChannelData represents The ChannelData Message.
//
// See RFC 5766 Section 11.4
type ChannelData struct {
	Data    []byte // can be subslice of Raw
	Length  int    // ignored while encoding, len(Data) is used
	Padding bool   // use  padding
	Number  ChannelNumber
	Raw     []byte
}

// Equal returns true if b == c.
func (c *ChannelData) Equal(b *ChannelData) bool {
	if c == nil && b == nil {
		return true
	}
	if c == nil || b == nil {
		return false
	}
	if c.Number != b.Number {
		return false
	}
	if len(c.Data) != len(b.Data) {
		return false
	}
	return bytes.Equal(c.Data, b.Data)
}

// grow ensures that internal buffer will fit v more bytes and
// increases it capacity if necessary.
//
// Similar to stun.Message.grow method.
func (c *ChannelData) grow(v int) {
	n := len(c.Raw) + v
	for cap(c.Raw) < n {
		c.Raw = append(c.Raw, 0)
	}
	c.Raw = c.Raw[:n]
}

// Reset resets Length, Data and Raw length.
func (c *ChannelData) Reset() {
	c.Raw = c.Raw[:0]
	c.Length = 0
	c.Data = c.Data[:0]
}

// Encode encodes ChannelData Message to Raw.
func (c *ChannelData) Encode() {
	c.Raw = c.Raw[:0]
	c.WriteHeader()
	c.Raw = append(c.Raw, c.Data...)
	if !c.Padding {
		return
	}
	padded := nearestPaddedValueLength(len(c.Raw))
	if bytesToAdd := padded - len(c.Raw); bytesToAdd > 0 {
		for i := 0; i < bytesToAdd; i++ {
			c.Raw = append(c.Raw, 0)
		}
	}
}

// STUN aligns attributes on 32-bit boundaries, attributes whose content
// is not a multiple of 4 bytes are padded with 1, 2, or 3 bytes of
// padding so that its value contains a multiple of 4 bytes.  The
// padding bits are ignored, and may be any value.
//
// https://tools.ietf.org/html/rfc5389#section-15
const padding = 4

func nearestPaddedValueLength(l int) int {
	n := padding * (l / padding)
	if n < l {
		n += padding
	}
	return n
}

// WriteHeader writes channel number and length.
func (c *ChannelData) WriteHeader() {
	if len(c.Raw) < 4 {
		// Making WriteHeader call valid even when c.Raw
		// is nil or len(c.Raw) is less than needed for header.
		c.grow(4)
	}
	// Early bounds check to guarantee safety of writes below.
	_ = c.Raw[:channelDataHeaderSize]
	bin.PutUint16(c.Raw[:2], uint16(c.Number))
	bin.PutUint16(c.Raw[2:4],
		uint16(len(c.Data)),
	)
}

// ErrBadChannelDataLength means that channel data length is not equal
// to actual data length.
var ErrBadChannelDataLength = errors.New("channelData length != len(Data)")

// Decode decodes The ChannelData Message from Raw.
func (c *ChannelData) Decode() error {
	buf := c.Raw
	log.Println(hex.Dump(c.Raw))
	if len(buf) < channelDataHeaderSize {
		return io.ErrUnexpectedEOF
	}
	num := bin.Uint16(buf[0:channelDataNumberSize])
	c.Number = ChannelNumber(num)
	l := bin.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])
	c.Data = buf[channelDataHeaderSize:]
	c.Length = int(l)
	if !c.Number.Valid() {
		return ErrInvalidChannelNumber
	}
	if int(l) < len(c.Data) {
		c.Data = c.Data[:int(l)]
	}
	if int(l) > len(buf[channelDataHeaderSize:]) {
		return ErrBadChannelDataLength
	}
	return nil
}

const (
	channelDataLengthSize = 2
	channelDataNumberSize = channelDataLengthSize
	channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize
)

// IsChannelData returns true if buf looks like the ChannelData Message.
func IsChannelData(buf []byte) bool {
	if len(buf) < channelDataHeaderSize {
		return false
	}
	// Quick check for channel number.
	num := bin.Uint16(buf[0:channelNumberSize])
	return isChannelNumberValid(num)
}
<commit_msg>chandata: remove debug print<commit_after>package turn

import (
	"bytes"
	"errors"
	"io"
)

// ChannelData represents The ChannelData Message.
//
// See RFC 5766 Section 11.4
type ChannelData struct {
	Data    []byte // can be subslice of Raw
	Length  int    // ignored while encoding, len(Data) is used
	Padding bool   // use  padding
	Number  ChannelNumber
	Raw     []byte
}

// Equal returns true if b == c.
func (c *ChannelData) Equal(b *ChannelData) bool {
	if c == nil && b == nil {
		return true
	}
	if c == nil || b == nil {
		return false
	}
	if c.Number != b.Number {
		return false
	}
	if len(c.Data) != len(b.Data) {
		return false
	}
	return bytes.Equal(c.Data, b.Data)
}

// grow ensures that internal buffer will fit v more bytes and
// increases it capacity if necessary.
//
// Similar to stun.Message.grow method.
func (c *ChannelData) grow(v int) {
	n := len(c.Raw) + v
	for cap(c.Raw) < n {
		c.Raw = append(c.Raw, 0)
	}
	c.Raw = c.Raw[:n]
}

// Reset resets Length, Data and Raw length.
func (c *ChannelData) Reset() {
	c.Raw = c.Raw[:0]
	c.Length = 0
	c.Data = c.Data[:0]
}

// Encode encodes ChannelData Message to Raw.
func (c *ChannelData) Encode() {
	c.Raw = c.Raw[:0]
	c.WriteHeader()
	c.Raw = append(c.Raw, c.Data...)
	if !c.Padding {
		return
	}
	padded := nearestPaddedValueLength(len(c.Raw))
	if bytesToAdd := padded - len(c.Raw); bytesToAdd > 0 {
		for i := 0; i < bytesToAdd; i++ {
			c.Raw = append(c.Raw, 0)
		}
	}
}

// STUN aligns attributes on 32-bit boundaries, attributes whose content
// is not a multiple of 4 bytes are padded with 1, 2, or 3 bytes of
// padding so that its value contains a multiple of 4 bytes.  The
// padding bits are ignored, and may be any value.
//
// https://tools.ietf.org/html/rfc5389#section-15
const padding = 4

func nearestPaddedValueLength(l int) int {
	n := padding * (l / padding)
	if n < l {
		n += padding
	}
	return n
}

// WriteHeader writes channel number and length.
func (c *ChannelData) WriteHeader() {
	if len(c.Raw) < 4 {
		// Making WriteHeader call valid even when c.Raw
		// is nil or len(c.Raw) is less than needed for header.
		c.grow(4)
	}
	// Early bounds check to guarantee safety of writes below.
	_ = c.Raw[:channelDataHeaderSize]
	bin.PutUint16(c.Raw[:2], uint16(c.Number))
	bin.PutUint16(c.Raw[2:4],
		uint16(len(c.Data)),
	)
}

// ErrBadChannelDataLength means that channel data length is not equal
// to actual data length.
var ErrBadChannelDataLength = errors.New("channelData length != len(Data)")

// Decode decodes The ChannelData Message from Raw.
func (c *ChannelData) Decode() error {
	buf := c.Raw
	if len(buf) < channelDataHeaderSize {
		return io.ErrUnexpectedEOF
	}
	num := bin.Uint16(buf[0:channelDataNumberSize])
	c.Number = ChannelNumber(num)
	l := bin.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])
	c.Data = buf[channelDataHeaderSize:]
	c.Length = int(l)
	if !c.Number.Valid() {
		return ErrInvalidChannelNumber
	}
	if int(l) < len(c.Data) {
		c.Data = c.Data[:int(l)]
	}
	if int(l) > len(buf[channelDataHeaderSize:]) {
		return ErrBadChannelDataLength
	}
	return nil
}

const (
	channelDataLengthSize = 2
	channelDataNumberSize = channelDataLengthSize
	channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize
)

// IsChannelData returns true if buf looks like the ChannelData Message.
func IsChannelData(buf []byte) bool {
	if len(buf) < channelDataHeaderSize {
		return false
	}
	// Quick check for channel number.
	num := bin.Uint16(buf[0:channelNumberSize])
	return isChannelNumberValid(num)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/urfave/cli"
)

var commandChannels = cli.Command{
	Name:  "channels",
	Usage: "List notification channels",
	Description: `
	Lists notification channels.
	Requests APIs under "/api/v0/channels". See https://mackerel.io/api-docs/entry/channels .
	`,
	Action: doChannelsList,
}

func doChannelsList(c *cli.Context) error {
	// Waiting for mackerel-client-go to be bumped to version supporting FindChannels.
	// client := mackerelclient.NewFromContext(c)
	// channels, err := client.FindChannels()
	// logger.DieIf(err)

	// format.PrettyPrintJSON(os.Stdout, channels)
	return nil
}
<commit_msg>uncomment doChannelsList implementation<commit_after>package main

import (
	"os"

	"github.com/mackerelio/mkr/format"
	"github.com/mackerelio/mkr/logger"
	"github.com/mackerelio/mkr/mackerelclient"
	"github.com/urfave/cli"
)

var commandChannels = cli.Command{
	Name:  "channels",
	Usage: "List notification channels",
	Description: `
	Lists notification channels.
	Requests APIs under "/api/v0/channels". See https://mackerel.io/api-docs/entry/channels .
	`,
	Action: doChannelsList,
}

func doChannelsList(c *cli.Context) error {
	// Waiting for mackerel-client-go to be bumped to version supporting FindChannels.
	client := mackerelclient.NewFromContext(c)
	channels, err := client.FindChannels()
	logger.DieIf(err)

	format.PrettyPrintJSON(os.Stdout, channels)
	return nil
}
<|endoftext|>


<commit_before>package chatwork

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
)

const baseURL = "https://api.chatwork.com/v2/"

type ApiKey string

type Chatwork struct {
	apiKey ApiKey
}

func NewChatwork(apiKey string) *Chatwork {
	c := new(Chatwork)
	c.apiKey = ApiKey(apiKey)
	return c
}

type endpoint string

func (c *Chatwork) post(endpoint endpoint, vs url.Values) {
	body := strings.NewReader(vs.Encode())
	request, requestError := http.NewRequest("POST", string(endpoint), body)
	if requestError != nil {
		log.Fatal(requestError)
	}

	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	request.Header.Add("X-ChatWorkToken", string(c.apiKey))

	httpClient := new(http.Client)
	res, error := httpClient.Do(request)
	defer res.Body.Close()
	if error != nil {
		log.Fatal(error)
	}
}

type Text string
type RoomId int64

type Message struct {
	roomId RoomId
	body   Text
}

func NewMessage(roomId int64, body string) *Message {
	m := new(Message)
	m.roomId = RoomId(roomId)
	m.body = Text(body)
	return m
}

func endpointFmt(format string, a ...interface{}) string {
	return fmt.Sprintf(format, a)
}

func (c *Chatwork) CreateMessage(message *Message) {
	endpoint := endpoint(baseURL + fmt.Sprintf("rooms/%d/messages", message.roomId))
	vs := url.Values{}
	vs.Add("body", string(message.body))
	c.post(endpoint, vs)
}
<commit_msg>Add new struct Task<commit_after>package chatwork

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const baseURL = "https://api.chatwork.com/v2/"

type ApiKey string

type Chatwork struct {
	apiKey ApiKey
}

func NewChatwork(apiKey string) *Chatwork {
	c := new(Chatwork)
	c.apiKey = ApiKey(apiKey)
	return c
}

type endpoint string

func (c *Chatwork) post(endpoint endpoint, vs url.Values) {
	body := strings.NewReader(vs.Encode())
	request, requestError := http.NewRequest("POST", string(endpoint), body)
	if requestError != nil {
		log.Fatal(requestError)
	}

	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	request.Header.Add("X-ChatWorkToken", string(c.apiKey))

	httpClient := new(http.Client)
	res, error := httpClient.Do(request)
	defer res.Body.Close()
	if error != nil {
		log.Fatal(error)
	}
}

type Text string
type RoomId int64

type Message struct {
	roomId RoomId
	body   Text
}

func NewMessage(roomId int64, body string) *Message {
	m := new(Message)
	m.roomId = RoomId(roomId)
	m.body = Text(body)
	return m
}

func endpointFmt(format string, a ...interface{}) string {
	return fmt.Sprintf(format, a)
}

func (c *Chatwork) CreateMessage(message *Message) {
	endpoint := endpoint(baseURL + fmt.Sprintf("rooms/%d/messages", message.roomId))
	vs := url.Values{}
	vs.Add("body", string(message.body))
	c.post(endpoint, vs)
}

type UserId int64
type UserIds []UserId

type Task struct {
	roomId    RoomId
	body      Text
	assignees UserIds
	due       time.Time
}

func NewTask(roomId int64, body string, assignees []int64, due time.Time) *Task {
	t := new(Task)
	t.roomId = RoomId(roomId)
	t.body = Text(body)
	t.assignees = make([]UserId, 0)
	for _, a := range assignees {
		t.assignees = append(t.assignees, UserId(a))
	}
	t.due = due
	return t
}

func (c *Chatwork) CreateTask(task *Task) {
	endpoint := endpoint(baseURL + fmt.Sprintf("rooms/%d/tasks", task.roomId))
	vs := url.Values{}
	vs.Add("body", string(task.body))
	vs.Add("to_ids", task.assignees.toString(","))
	c.post(endpoint, vs)
}

func (ids UserIds) toString(sep string) string {
	buf := make([]string, len(ids))
	for i, id := range ids {
		buf[i] = strconv.FormatInt(int64(id), 10)
	}
	return strings.Join(buf, sep)
}
<|endoftext|>


<commit_before>package cloudtoolkit

import (
	"encoding/json"
	"github.com/afex/hystrix-go/hystrix"
	"github.com/spf13/viper"
	"net"
	"net/http"
	"time"
)

func ConfigureHystrix(commands []string, amqpClient *MessagingClient) {

	for _, command := range commands {
		hystrix.ConfigureCommand(command, hystrix.CommandConfig{
			Timeout:                resolveProperty(command, "Timeout"),
			MaxConcurrentRequests:  resolveProperty(command, "MaxConcurrentRequests"),
			ErrorPercentThreshold:  resolveProperty(command, "ErrorPercentThreshold"),
			RequestVolumeThreshold: resolveProperty(command, "RequestVolumeThreshold"),
			SleepWindow:            resolveProperty(command, "SleepWindow"),
		})
	}

	hystrixStreamHandler := hystrix.NewStreamHandler()
	hystrixStreamHandler.Start()
	go http.ListenAndServe(net.JoinHostPort("", "8181"), hystrixStreamHandler)
	Log.Println("Launched hystrixStreamHandler at 8181")

	// Publish presence on RabbitMQ
	publishDiscoveryToken(amqpClient)
}

func publishDiscoveryToken(amqpClient *MessagingClient) {
	token := DiscoveryToken{
		State:   "UP",
		Address: GetLocalIP(),
	}
	json, _ := json.Marshal(token)
	go func() {
		for {
			amqpClient.SendMessage(string(json), "application/json", "discovery")
			time.Sleep(time.Second * 30)
		}
	}()
}

func resolveProperty(command string, prop string) int {
	if viper.IsSet("hystrix.command." + command + "." + prop) {
		return viper.GetInt("hystrix.command." + command + "." + prop)
	} else {
		return getDefaultHystrixConfigPropertyValue(prop)
	}
}
func getDefaultHystrixConfigPropertyValue(prop string) int {
	switch prop {
	case "Timeout":
		return hystrix.DefaultTimeout
	case "MaxConcurrentRequests":
		return hystrix.DefaultMaxConcurrent
	case "RequestVolumeThreshold":
		return hystrix.DefaultVolumeThreshold
	case "SleepWindow":
		return hystrix.DefaultSleepWindow
	case "ErrorPercentThreshold":
		return hystrix.DefaultErrorPercentThreshold
	}
	panic("Got unknown hystrix property: " + prop + ". Panicing!")
}
<commit_msg>Added some debug info at startup of hystrix breakers<commit_after>package cloudtoolkit

import (
	"encoding/json"
	"github.com/afex/hystrix-go/hystrix"
	"github.com/spf13/viper"
	"net"
	"net/http"
	"time"
)

func ConfigureHystrix(commands []string, amqpClient *MessagingClient) {

	for _, command := range commands {
		hystrix.ConfigureCommand(command, hystrix.CommandConfig{
			Timeout:                resolveProperty(command, "Timeout"),
			MaxConcurrentRequests:  resolveProperty(command, "MaxConcurrentRequests"),
			ErrorPercentThreshold:  resolveProperty(command, "ErrorPercentThreshold"),
			RequestVolumeThreshold: resolveProperty(command, "RequestVolumeThreshold"),
			SleepWindow:            resolveProperty(command, "SleepWindow"),
		})
		Log.Printf("Circuit %v settings: %v", command, hystrix.GetCircuitSettings()[command])
	}

	hystrixStreamHandler := hystrix.NewStreamHandler()
	hystrixStreamHandler.Start()
	go http.ListenAndServe(net.JoinHostPort("", "8181"), hystrixStreamHandler)
	Log.Println("Launched hystrixStreamHandler at 8181")

	// Publish presence on RabbitMQ
	publishDiscoveryToken(amqpClient)
}

func publishDiscoveryToken(amqpClient *MessagingClient) {
	token := DiscoveryToken{
		State:   "UP",
		Address: GetLocalIP(),
	}
	json, _ := json.Marshal(token)
	go func() {
		for {
			amqpClient.SendMessage(string(json), "application/json", "discovery")
			time.Sleep(time.Second * 30)
		}
	}()
}

func resolveProperty(command string, prop string) int {
	if viper.IsSet("hystrix.command." + command + "." + prop) {
		return viper.GetInt("hystrix.command." + command + "." + prop)
	} else {
		return getDefaultHystrixConfigPropertyValue(prop)
	}
}
func getDefaultHystrixConfigPropertyValue(prop string) int {
	switch prop {
	case "Timeout":
		return hystrix.DefaultTimeout
	case "MaxConcurrentRequests":
		return hystrix.DefaultMaxConcurrent
	case "RequestVolumeThreshold":
		return hystrix.DefaultVolumeThreshold
	case "SleepWindow":
		return hystrix.DefaultSleepWindow
	case "ErrorPercentThreshold":
		return hystrix.DefaultErrorPercentThreshold
	}
	panic("Got unknown hystrix property: " + prop + ". Panicing!")
}
<|endoftext|>


<commit_before>package beanstalk

import (
	"strings"
	"sync"
	"time"
)

var (
	keepAliveInterval = 10 * time.Second
)

// Consumer reserves jobs from a beanstalk server and keeps those jobs alive
// until an external consumer has either buried, deleted or released it.
type Consumer struct {
	url       string
	tubes     []string
	jobC      chan<- *Job
	pause     chan bool
	stop      chan struct{}
	isPaused  bool
	options   *Options
	mu        sync.Mutex
	startOnce sync.Once
	stopOnce  sync.Once
}

// NewConsumer returns a new Consumer object.
func NewConsumer(url string, tubes []string, jobC chan<- *Job, options *Options) (*Consumer, error) {
	if options == nil {
		options = DefaultOptions()
	}

	if _, _, err := ParseURL(url); err != nil {
		return nil, err
	}

	return &Consumer{
		url:      url,
		tubes:    tubes,
		jobC:     jobC,
		pause:    make(chan bool, 1),
		stop:     make(chan struct{}, 1),
		isPaused: true,
		options:  options,
	}, nil
}

// Start this consumer.
func (consumer *Consumer) Start() {
	consumer.startOnce.Do(func() {
		go consumer.connectionManager()
	})
}

// Stop this consumer.
func (consumer *Consumer) Stop() {
	consumer.stopOnce.Do(func() {
		close(consumer.stop)
	})
}

// Play allows this consumer to start reserving jobs. Returns true on success
// and false if this consumer was stopped.
func (consumer *Consumer) Play() bool {
	select {
	case <-consumer.stop:
		return false
	default:
	}

	consumer.mu.Lock()
	defer consumer.mu.Unlock()

	select {
	case <-consumer.pause:
	default:
	}

	consumer.pause <- false
	return true
}

// Pause stops this consumer from reserving jobs. Returns true on success and
// false if this consumer was stopped.
func (consumer *Consumer) Pause() bool {
	select {
	case <-consumer.stop:
		return false
	default:
	}

	consumer.mu.Lock()
	defer consumer.mu.Unlock()

	select {
	case <-consumer.pause:
	default:
	}

	consumer.pause <- true
	return true
}

// connectionManager is responsible for setting up a connection to the
// beanstalk server and wrapping it in a Client, which on success is passed
// to the clientManager function.
func (consumer *Consumer) connectionManager() {
	var (
		err     error
		options = consumer.options
	)

	// Start a new connection.
	newConnection, abortConnect := connect(consumer.url, consumer.options)

	for {
		select {
		// This case triggers whenever a new connection was established.
		case conn := <-newConnection:
			client := NewClient(conn, options)

			options.LogInfo("Watching tubes: %s", strings.Join(consumer.tubes, ", "))
			for _, tube := range consumer.tubes {
				if err = client.Watch(tube); err != nil {
					options.LogError("%s: Error watching tube: %s", tube, err)
					break
				}
			}

			if err == nil && !includesString(consumer.tubes, "default") {
				if err := client.Ignore("default"); err != nil {
					options.LogError("default: Unable to ignore tube: %s", err)
				}
			}

			if err := consumer.clientManager(client); err != nil {
				newConnection, abortConnect = connect(consumer.url, consumer.options)
			} else {
				return
			}

		// Keep track of the pause state.
		case consumer.isPaused = <-consumer.pause:

		// Abort this connection and stop this consumer all together when the
		// stop signal was received.
		case <-consumer.stop:
			close(abortConnect)
			return
		}
	}
}

// clientManager is responsible for reserving beanstalk jobs and offering them
// up the the job channel.
func (consumer *Consumer) clientManager(client *Client) (err error) {
	var (
		job          *Job
		jobOffer     chan<- *Job
		jobCommandC  = make(chan *JobCommand)
		jobsOutThere int
	)

	// This is used to pause the select-statement for a bit when the job queue
	// is full or when "reserve-with-timeout 0" yields no job.
	timeout := time.NewTimer(time.Second)
	timeout.Stop()

	// Set up a touch timer that fires whenever the pending reserved job needs
	// to be touched to keep the reservation on that job.
	touchTimer := time.NewTimer(time.Second)
	touchTimer.Stop()

	// When this connection is paused, perform some keep alive operation to keep
	// the connection active and detect if it's still up.
	keepAlive := time.NewTimer(time.Second)
	keepAlive.Stop()

	// Whenever this function returns, clean up the pending job and close the
	// client connection.
	defer func() {
		if job != nil {
			if e := client.Release(job, job.Priority, 0); e != nil {
				consumer.options.LogError("Unable to finish job %d: %s", job.ID, err)
			}
		}

		client.Close()
		touchTimer.Stop()
		close(jobCommandC)
	}()

	// isFatalErr is a convenience function that checks if the returned error
	// from a beanstalk command is fatal, or can be ignored.
	isFatalErr := func() bool {
		if err == ErrNotFound {
			err = nil
		}
		return err != nil
	}

	for {
		// Attempt to reserve a job if the state allows for it.
		if job == nil && !consumer.isPaused {
			if jobsOutThere == 0 {
				job, err = client.Reserve(consumer.options.ReserveTimeout)
			} else {
				job, err = client.Reserve(0)
			}

			switch {
			case err == ErrDraining:
				timeout.Reset(time.Minute)
			case err == ErrDeadlineSoon:
				timeout.Reset(time.Second)
			case err != nil:
				consumer.options.LogError("Error reserving job: %s", err)
				return

			// A new job was reserved.
			case job != nil:
				job.commandC = jobCommandC
				jobOffer = consumer.jobC
				touchTimer.Reset(job.TouchAt())

			// With jobs out there and no successful reserve, wait a bit before
			// attempting another reserve.
			case jobsOutThere != 0:
				timeout.Reset(time.Second)

			// No job reserved and no jobs out there, perform another reserve almost
			// immediately.
			default:
				timeout.Reset(0)
			}
		} else {
			timeout.Reset(time.Second)
		}

		select {
		// Offer the job up on the shared jobs channel.
		case jobOffer <- job:
			job, jobOffer = nil, nil
			touchTimer.Stop()
			jobsOutThere++

		// Wait a bit before trying to reserve a job again, or just fall through.
		case <-timeout.C:

		// Touch the pending job to make sure it doesn't expire.
		case <-touchTimer.C:
			if job != nil {
				if err = client.Touch(job); err != nil {
					consumer.options.LogError("Unable to touch job %d: %s", job.ID, err)
					if isFatalErr() {
						return
					}

					job, jobOffer = nil, nil
				} else {
					touchTimer.Reset(job.TouchAt())
				}
			}

		// Keep the connection alive when the connection state is paused.
		case <-keepAlive.C:
			if _, _, err = client.requestResponse("list-tube-used"); err != nil {
				return
			}

			if consumer.isPaused {
				keepAlive.Reset(keepAliveInterval)
			}

		// Bury, delete or release a reserved job.
		case req := <-jobCommandC:
			if req.Command == Touch {
				if err = client.Touch(req.Job); err != nil {
					consumer.options.LogError("Unable to touch job %d: %s", req.Job.ID, err)
				}
			} else {
				switch req.Command {
				case Bury:
					err = client.Bury(req.Job, req.Priority)
				case Delete:
					err = client.Delete(req.Job)
				case Release:
					err = client.Release(req.Job, req.Priority, req.Delay)
				}

				jobsOutThere--
				if err != nil {
					consumer.options.LogError("Unable to finish job %d: %s", req.Job.ID, err)
				}
			}

			req.Err <- err
			if isFatalErr() {
				return
			}

		// Pause or unpause this connection.
		case consumer.isPaused = <-consumer.pause:
			if consumer.isPaused {
				if job != nil {
					if err = client.Release(job, job.Priority, 0); err != nil {
						consumer.options.LogError("Unable to release job %d: %s", job.ID, err)
						if isFatalErr() {
							return
						}
					}

					job, jobOffer = nil, nil
				}

				keepAlive.Reset(keepAliveInterval)
			} else {
				keepAlive.Stop()
			}

		// Stop this connection and close this consumer down.
		case <-consumer.stop:
			return nil
		}
	}
}
<commit_msg>Properly start the keep alive timer when a consumer connection is paused<commit_after>package beanstalk

import (
	"strings"
	"sync"
	"time"
)

var (
	keepAliveInterval = 10 * time.Second
)

// Consumer reserves jobs from a beanstalk server and keeps those jobs alive
// until an external consumer has either buried, deleted or released it.
type Consumer struct {
	url       string
	tubes     []string
	jobC      chan<- *Job
	pause     chan bool
	stop      chan struct{}
	isPaused  bool
	options   *Options
	mu        sync.Mutex
	startOnce sync.Once
	stopOnce  sync.Once
}

// NewConsumer returns a new Consumer object.
func NewConsumer(url string, tubes []string, jobC chan<- *Job, options *Options) (*Consumer, error) {
	if options == nil {
		options = DefaultOptions()
	}

	if _, _, err := ParseURL(url); err != nil {
		return nil, err
	}

	return &Consumer{
		url:      url,
		tubes:    tubes,
		jobC:     jobC,
		pause:    make(chan bool, 1),
		stop:     make(chan struct{}, 1),
		isPaused: true,
		options:  options,
	}, nil
}

// Start this consumer.
func (consumer *Consumer) Start() {
	consumer.startOnce.Do(func() {
		go consumer.connectionManager()
	})
}

// Stop this consumer.
func (consumer *Consumer) Stop() {
	consumer.stopOnce.Do(func() {
		close(consumer.stop)
	})
}

// Play allows this consumer to start reserving jobs. Returns true on success
// and false if this consumer was stopped.
func (consumer *Consumer) Play() bool {
	select {
	case <-consumer.stop:
		return false
	default:
	}

	consumer.mu.Lock()
	defer consumer.mu.Unlock()

	select {
	case <-consumer.pause:
	default:
	}

	consumer.pause <- false
	return true
}

// Pause stops this consumer from reserving jobs. Returns true on success and
// false if this consumer was stopped.
func (consumer *Consumer) Pause() bool {
	select {
	case <-consumer.stop:
		return false
	default:
	}

	consumer.mu.Lock()
	defer consumer.mu.Unlock()

	select {
	case <-consumer.pause:
	default:
	}

	consumer.pause <- true
	return true
}

// connectionManager is responsible for setting up a connection to the
// beanstalk server and wrapping it in a Client, which on success is passed
// to the clientManager function.
func (consumer *Consumer) connectionManager() {
	var (
		err     error
		options = consumer.options
	)

	// Start a new connection.
	newConnection, abortConnect := connect(consumer.url, consumer.options)

	for {
		select {
		// This case triggers whenever a new connection was established.
		case conn := <-newConnection:
			client := NewClient(conn, options)

			options.LogInfo("Watching tubes: %s", strings.Join(consumer.tubes, ", "))
			for _, tube := range consumer.tubes {
				if err = client.Watch(tube); err != nil {
					options.LogError("%s: Error watching tube: %s", tube, err)
					break
				}
			}

			if err == nil && !includesString(consumer.tubes, "default") {
				if err := client.Ignore("default"); err != nil {
					options.LogError("default: Unable to ignore tube: %s", err)
				}
			}

			if err := consumer.clientManager(client); err != nil {
				newConnection, abortConnect = connect(consumer.url, consumer.options)
			} else {
				return
			}

		// Keep track of the pause state.
		case consumer.isPaused = <-consumer.pause:

		// Abort this connection and stop this consumer all together when the
		// stop signal was received.
		case <-consumer.stop:
			close(abortConnect)
			return
		}
	}
}

// clientManager is responsible for reserving beanstalk jobs and offering them
// up the the job channel.
func (consumer *Consumer) clientManager(client *Client) (err error) {
	var (
		job          *Job
		jobOffer     chan<- *Job
		jobCommandC  = make(chan *JobCommand)
		jobsOutThere int
		keepAlive    *time.Timer
	)

	// This is used to pause the select-statement for a bit when the job queue
	// is full or when "reserve-with-timeout 0" yields no job.
	timeout := time.NewTimer(time.Second)
	timeout.Stop()

	// Set up a touch timer that fires whenever the pending reserved job needs
	// to be touched to keep the reservation on that job.
	touchTimer := time.NewTimer(time.Second)
	touchTimer.Stop()

	// If this consumer is paused, make sure to start the polling to keep the
	// connection alive. This is necessary in case there is a proxy between the
	// client and server that disconnects idle connections.
	if consumer.isPaused {
		keepAlive = time.NewTimer(keepAliveInterval)
	} else {
		keepAlive = time.NewTimer(time.Second)
		keepAlive.Stop()
	}

	// Whenever this function returns, clean up the pending job and close the
	// client connection.
	defer func() {
		if job != nil {
			if e := client.Release(job, job.Priority, 0); e != nil {
				consumer.options.LogError("Unable to finish job %d: %s", job.ID, err)
			}
		}

		client.Close()
		touchTimer.Stop()
		close(jobCommandC)
	}()

	// isFatalErr is a convenience function that checks if the returned error
	// from a beanstalk command is fatal, or can be ignored.
	isFatalErr := func() bool {
		if err == ErrNotFound {
			err = nil
		}
		return err != nil
	}

	for {
		// Attempt to reserve a job if the state allows for it.
		if job == nil && !consumer.isPaused {
			if jobsOutThere == 0 {
				job, err = client.Reserve(consumer.options.ReserveTimeout)
			} else {
				job, err = client.Reserve(0)
			}

			switch {
			case err == ErrDraining:
				timeout.Reset(time.Minute)
			case err == ErrDeadlineSoon:
				timeout.Reset(time.Second)
			case err != nil:
				consumer.options.LogError("Error reserving job: %s", err)
				return

			// A new job was reserved.
			case job != nil:
				job.commandC = jobCommandC
				jobOffer = consumer.jobC
				touchTimer.Reset(job.TouchAt())

			// With jobs out there and no successful reserve, wait a bit before
			// attempting another reserve.
			case jobsOutThere != 0:
				timeout.Reset(time.Second)

			// No job reserved and no jobs out there, perform another reserve almost
			// immediately.
			default:
				timeout.Reset(0)
			}
		} else {
			timeout.Reset(time.Second)
		}

		select {
		// Offer the job up on the shared jobs channel.
		case jobOffer <- job:
			job, jobOffer = nil, nil
			touchTimer.Stop()
			jobsOutThere++

		// Wait a bit before trying to reserve a job again, or just fall through.
		case <-timeout.C:

		// Touch the pending job to make sure it doesn't expire.
		case <-touchTimer.C:
			if job != nil {
				if err = client.Touch(job); err != nil {
					consumer.options.LogError("Unable to touch job %d: %s", job.ID, err)
					if isFatalErr() {
						return
					}

					job, jobOffer = nil, nil
				} else {
					touchTimer.Reset(job.TouchAt())
				}
			}

		// Keep the connection alive when the connection state is paused.
		case <-keepAlive.C:
			if _, _, err = client.requestResponse("list-tube-used"); err != nil {
				return
			}

			if consumer.isPaused {
				keepAlive.Reset(keepAliveInterval)
			}

		// Bury, delete or release a reserved job.
		case req := <-jobCommandC:
			if req.Command == Touch {
				if err = client.Touch(req.Job); err != nil {
					consumer.options.LogError("Unable to touch job %d: %s", req.Job.ID, err)
				}
			} else {
				switch req.Command {
				case Bury:
					err = client.Bury(req.Job, req.Priority)
				case Delete:
					err = client.Delete(req.Job)
				case Release:
					err = client.Release(req.Job, req.Priority, req.Delay)
				}

				jobsOutThere--
				if err != nil {
					consumer.options.LogError("Unable to finish job %d: %s", req.Job.ID, err)
				}
			}

			req.Err <- err
			if isFatalErr() {
				return
			}

		// Pause or unpause this connection.
		case consumer.isPaused = <-consumer.pause:
			if consumer.isPaused {
				if job != nil {
					if err = client.Release(job, job.Priority, 0); err != nil {
						consumer.options.LogError("Unable to release job %d: %s", job.ID, err)
						if isFatalErr() {
							return
						}
					}

					job, jobOffer = nil, nil
				}

				keepAlive.Reset(keepAliveInterval)
			} else {
				keepAlive.Stop()
			}

		// Stop this connection and close this consumer down.
		case <-consumer.stop:
			return nil
		}
	}
}
<|endoftext|>


<commit_before>package ids

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
)

func Encode(id int64) string {
	b := make([]byte, 20)
	n := binary.PutVarint(b, id)
	return base64.StdEncoding.EncodeToString(b[:n])
}

func Decode(str string) (int64, error) {
	data, err := base64.StdEncoding.DecodeString(str)
	if err != nil {
		return 0, err
	}
	return binary.ReadVarint(bytes.NewReader(data))
}

<commit_msg>change StdEncoding to UrlEncoding<commit_after>package ids

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
)

func Encode(id int64) string {
	b := make([]byte, 20)
	n := binary.PutVarint(b, id)
	return base64.URLEncoding.EncodeToString(b[:n])
}

func Decode(str string) (int64, error) {
	data, err := base64.URLEncoding.DecodeString(str)
	if err != nil {
		return 0, err
	}
	return binary.ReadVarint(bytes.NewReader(data))
}

func EncodeOld(id int64) string {
	b := make([]byte, 20)
	n := binary.PutVarint(b, id)
	return base64.StdEncoding.EncodeToString(b[:n])
}

func DecodeOld(str string) (int64, error) {
	data, err := base64.StdEncoding.DecodeString(str)
	if err != nil {
		return 0, err
	}
	return binary.ReadVarint(bytes.NewReader(data))
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/rpi"
)

var moveCodes map[string]byte = map[string]byte{
	"forward" : 10,
	"back" : 11,
	"left" : 12,
	"right" : 13,
	"stop" : 14,
}

var lookCodes map[string]byte = map[string]byte{
	"center" : 20,
	"left" : 21,
	"right" : 22,
	"up" : 23,
	"down" : 24,
}

var (
    arduino1, arduino2 byte = 4, 5
)

type Bot interface {
    Move(direction string) error
    Look(direction string) error
}

type I2CBus interface {
	ReadByte(addr byte) (value byte, err error)
	WriteByte(addr, value byte) error
	Close() error
}

type CoreBot struct {
    bus I2CBus
}

func (bot CoreBot) Move(direction string) error {
	if code, valid := moveCodes[direction]; valid {
		return bot.bus.WriteByte(arduino1, code)
	}
	return errors.New("invalid move direction")
}

func (bot CoreBot) Look(direction string) error {
	if code, valid := lookCodes[direction]; valid {
		return bot.bus.WriteByte(arduino2, code)
	}
	return errors.New("invalid look direction")
}

func NewCoreBot() CoreBot {
	b := embd.NewI2CBus(1)
	return CoreBot{bus: b}
}
<commit_msg>Update bot.go<commit_after>package main

import (
	"errors"
	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/rpi"
)

var moveCodes map[string]byte = map[string]byte{
	"forward" : 10,
	"back" : 11,
	"left" : 12,
	"right" : 13,
	"stop" : 14,
}

var lookCodes map[string]byte = map[string]byte{
	"center" : 20,
	"left" : 21,
	"right" : 22,
	"up" : 23,
	"down" : 24,
}

var (
    arduino1, arduino2 byte = 4, 5
)

type Bot interface {
    Move(direction string) error
    Look(direction string) error
}

type I2CBus interface {
	ReadByte(addr byte) (value byte, err error)
	WriteByte(addr, value byte) error
	Close() error
}

type CoreBot struct {
    bus I2CBus
}

func (bot CoreBot) Move(direction string) error {
	if code, valid := moveCodes[direction]; valid {
		return bot.bus.WriteByte(arduino1, code)
	}
	return errors.New("invalid move direction")
}

func (bot CoreBot) Look(direction string) error {
	if code, valid := lookCodes[direction]; valid {
		return bot.bus.WriteByte(arduino2, code)
	}
	return errors.New("invalid look direction")
}

func (bot CoreBot) Close() error {
	return nil
}

func NewCoreBot() CoreBot {
	b := embd.NewI2CBus(1)
	return CoreBot{bus: b}
}
<|endoftext|>


<commit_before>package cli

import (
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/99designs/aws-vault/prompt"
	"github.com/99designs/aws-vault/server"
	"github.com/99designs/aws-vault/vault"
	"github.com/99designs/keyring"
	"gopkg.in/alecthomas/kingpin.v2"
)

type ExecCommandInput struct {
	Profile      string
	Command      string
	Args         []string
	Keyring      keyring.Keyring
	Duration     time.Duration
	RoleDuration time.Duration
	MfaToken     string
	MfaPrompt    prompt.PromptFunc
	StartServer  bool
	Signals      chan os.Signal
	NoSession    bool
}

func ConfigureExecCommand(app *kingpin.Application) {
	input := ExecCommandInput{}

	cmd := app.Command("exec", "Executes a command with AWS credentials in the environment")
	cmd.Flag("no-session", "Use root credentials, no session created").
		Short('n').
		BoolVar(&input.NoSession)

	cmd.Flag("session-ttl", "Expiration time for aws session").
		Default("4h").
		OverrideDefaultFromEnvar("AWS_SESSION_TTL").
		Short('t').
		DurationVar(&input.Duration)

	cmd.Flag("assume-role-ttl", "Expiration time for aws assumed role").
		Default("15m").
		OverrideDefaultFromEnvar("AWS_ASSUME_ROLE_TTL").
		DurationVar(&input.RoleDuration)

	cmd.Flag("mfa-token", "The mfa token to use").
		Short('m').
		StringVar(&input.MfaToken)

	cmd.Flag("server", "Run the server in the background for credentials").
		Short('s').
		BoolVar(&input.StartServer)

	cmd.Arg("profile", "Name of the profile").
		Required().
		StringVar(&input.Profile)

	cmd.Arg("cmd", "Command to execute").
		Default(os.Getenv("SHELL")).
		StringVar(&input.Command)

	cmd.Arg("args", "Command arguments").
		StringsVar(&input.Args)

	cmd.Action(func(c *kingpin.ParseContext) error {
		input.Keyring = keyringImpl
		input.MfaPrompt = prompt.Method(GlobalFlags.PromptDriver)
		input.Signals = make(chan os.Signal)
		signal.Notify(input.Signals, os.Interrupt, os.Kill)
		ExecCommand(app, input)
		return nil
	})
}

func ExecCommand(app *kingpin.Application, input ExecCommandInput) {
	if os.Getenv("AWS_VAULT") != "" {
		app.Fatalf("aws-vault sessions should be nested with care, unset $AWS_VAULT to force")
		return
	}

	var setEnv = true

	if input.NoSession && input.StartServer {
		app.Fatalf("Can't start a credential server without a session")
		return
	}

	creds, err := vault.NewVaultCredentials(input.Keyring, input.Profile, vault.VaultOptions{
		SessionDuration:    input.Duration,
		AssumeRoleDuration: input.RoleDuration,
		MfaToken:           input.MfaToken,
		MfaPrompt:          input.MfaPrompt,
		NoSession:          input.NoSession,
		Config:             awsConfig,
	})
	if err != nil {
		app.Fatalf("%v", err)
	}

	val, err := creds.Get()
	if err != nil {
		app.Fatalf(awsConfig.FormatCredentialError(err, input.Profile))
	}

	if input.StartServer {
		if err := server.StartCredentialsServer(creds); err != nil {
			app.Fatalf("Failed to start credential server: %v", err)
		} else {
			setEnv = false
		}
	}

	env := environ(os.Environ())
	env.Set("AWS_VAULT", input.Profile)

	env.Unset("AWS_ACCESS_KEY_ID")
	env.Unset("AWS_SECRET_ACCESS_KEY")
	env.Unset("AWS_CREDENTIAL_FILE")
	env.Unset("AWS_DEFAULT_PROFILE")
	env.Unset("AWS_PROFILE")

	if profile, _ := awsConfig.Profile(input.Profile); profile.Region != "" {
		log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", profile.Region, profile.Region)
		env.Set("AWS_DEFAULT_REGION", profile.Region)
		env.Set("AWS_REGION", profile.Region)
	}

	if setEnv {
		log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
		env.Set("AWS_ACCESS_KEY_ID", val.AccessKeyID)
		env.Set("AWS_SECRET_ACCESS_KEY", val.SecretAccessKey)

		if val.SessionToken != "" {
			log.Println("Setting subprocess env: AWS_SESSION_TOKEN, AWS_SECURITY_TOKEN")
			env.Set("AWS_SESSION_TOKEN", val.SessionToken)
			env.Set("AWS_SECURITY_TOKEN", val.SessionToken)
		}
	}

	cmd := exec.Command(input.Command, input.Args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		app.Fatalf("%v", err)
	}
	// wait for the command to finish
	waitCh := make(chan error, 1)
	go func() {
		waitCh <- cmd.Wait()
		close(waitCh)
	}()

	for {
		select {
		case sig := <-input.Signals:
			if err = cmd.Process.Signal(sig); err != nil {
				app.Errorf("%v", err)
				break
			}
		case err := <-waitCh:
			var waitStatus syscall.WaitStatus
			if exitError, ok := err.(*exec.ExitError); ok {
				waitStatus = exitError.Sys().(syscall.WaitStatus)
				os.Exit(waitStatus.ExitStatus())
			}
			if err != nil {
				app.Fatalf("%v", err)
			}
			return
		}
	}
}

// environ is a slice of strings representing the environment, in the form "key=value".
type environ []string

// Unset an environment variable by key
func (e *environ) Unset(key string) {
	for i := range *e {
		if strings.HasPrefix((*e)[i], key+"=") {
			(*e)[i] = (*e)[len(*e)-1]
			*e = (*e)[:len(*e)-1]
			break
		}
	}
}

// Set adds an environment variable, replacing any existing ones of the same key
func (e *environ) Set(key, val string) {
	e.Unset(key)
	*e = append(*e, key+"="+val)
}
<commit_msg>Add a HintAction to `exec` to let KingPin autocomplete profile names.<commit_after>package cli

import (
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/99designs/aws-vault/prompt"
	"github.com/99designs/aws-vault/server"
	"github.com/99designs/aws-vault/vault"
	"github.com/99designs/keyring"
	"gopkg.in/alecthomas/kingpin.v2"
)

type ExecCommandInput struct {
	Profile      string
	Command      string
	Args         []string
	Keyring      keyring.Keyring
	Duration     time.Duration
	RoleDuration time.Duration
	MfaToken     string
	MfaPrompt    prompt.PromptFunc
	StartServer  bool
	Signals      chan os.Signal
	NoSession    bool
}

func ConfigureExecCommand(app *kingpin.Application) {
	input := ExecCommandInput{}

	cmd := app.Command("exec", "Executes a command with AWS credentials in the environment")
	cmd.Flag("no-session", "Use root credentials, no session created").
		Short('n').
		BoolVar(&input.NoSession)

	cmd.Flag("session-ttl", "Expiration time for aws session").
		Default("4h").
		OverrideDefaultFromEnvar("AWS_SESSION_TTL").
		Short('t').
		DurationVar(&input.Duration)

	cmd.Flag("assume-role-ttl", "Expiration time for aws assumed role").
		Default("15m").
		OverrideDefaultFromEnvar("AWS_ASSUME_ROLE_TTL").
		DurationVar(&input.RoleDuration)

	cmd.Flag("mfa-token", "The mfa token to use").
		Short('m').
		StringVar(&input.MfaToken)

	cmd.Flag("server", "Run the server in the background for credentials").
		Short('s').
		BoolVar(&input.StartServer)

	cmd.Arg("profile", "Name of the profile").
		Required().
		HintAction(ProfileNames).
		StringVar(&input.Profile)

	cmd.Arg("cmd", "Command to execute").
		Default(os.Getenv("SHELL")).
		StringVar(&input.Command)

	cmd.Arg("args", "Command arguments").
		StringsVar(&input.Args)

	cmd.Action(func(c *kingpin.ParseContext) error {
		input.Keyring = keyringImpl
		input.MfaPrompt = prompt.Method(GlobalFlags.PromptDriver)
		input.Signals = make(chan os.Signal)
		signal.Notify(input.Signals, os.Interrupt, os.Kill)
		ExecCommand(app, input)
		return nil
	})
}

func ExecCommand(app *kingpin.Application, input ExecCommandInput) {
	if os.Getenv("AWS_VAULT") != "" {
		app.Fatalf("aws-vault sessions should be nested with care, unset $AWS_VAULT to force")
		return
	}

	var setEnv = true

	if input.NoSession && input.StartServer {
		app.Fatalf("Can't start a credential server without a session")
		return
	}

	creds, err := vault.NewVaultCredentials(input.Keyring, input.Profile, vault.VaultOptions{
		SessionDuration:    input.Duration,
		AssumeRoleDuration: input.RoleDuration,
		MfaToken:           input.MfaToken,
		MfaPrompt:          input.MfaPrompt,
		NoSession:          input.NoSession,
		Config:             awsConfig,
	})
	if err != nil {
		app.Fatalf("%v", err)
	}

	val, err := creds.Get()
	if err != nil {
		app.Fatalf(awsConfig.FormatCredentialError(err, input.Profile))
	}

	if input.StartServer {
		if err := server.StartCredentialsServer(creds); err != nil {
			app.Fatalf("Failed to start credential server: %v", err)
		} else {
			setEnv = false
		}
	}

	env := environ(os.Environ())
	env.Set("AWS_VAULT", input.Profile)

	env.Unset("AWS_ACCESS_KEY_ID")
	env.Unset("AWS_SECRET_ACCESS_KEY")
	env.Unset("AWS_CREDENTIAL_FILE")
	env.Unset("AWS_DEFAULT_PROFILE")
	env.Unset("AWS_PROFILE")

	if profile, _ := awsConfig.Profile(input.Profile); profile.Region != "" {
		log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", profile.Region, profile.Region)
		env.Set("AWS_DEFAULT_REGION", profile.Region)
		env.Set("AWS_REGION", profile.Region)
	}

	if setEnv {
		log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
		env.Set("AWS_ACCESS_KEY_ID", val.AccessKeyID)
		env.Set("AWS_SECRET_ACCESS_KEY", val.SecretAccessKey)

		if val.SessionToken != "" {
			log.Println("Setting subprocess env: AWS_SESSION_TOKEN, AWS_SECURITY_TOKEN")
			env.Set("AWS_SESSION_TOKEN", val.SessionToken)
			env.Set("AWS_SECURITY_TOKEN", val.SessionToken)
		}
	}

	cmd := exec.Command(input.Command, input.Args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		app.Fatalf("%v", err)
	}
	// wait for the command to finish
	waitCh := make(chan error, 1)
	go func() {
		waitCh <- cmd.Wait()
		close(waitCh)
	}()

	for {
		select {
		case sig := <-input.Signals:
			if err = cmd.Process.Signal(sig); err != nil {
				app.Errorf("%v", err)
				break
			}
		case err := <-waitCh:
			var waitStatus syscall.WaitStatus
			if exitError, ok := err.(*exec.ExitError); ok {
				waitStatus = exitError.Sys().(syscall.WaitStatus)
				os.Exit(waitStatus.ExitStatus())
			}
			if err != nil {
				app.Fatalf("%v", err)
			}
			return
		}
	}
}

// environ is a slice of strings representing the environment, in the form "key=value".
type environ []string

// Unset an environment variable by key
func (e *environ) Unset(key string) {
	for i := range *e {
		if strings.HasPrefix((*e)[i], key+"=") {
			(*e)[i] = (*e)[len(*e)-1]
			*e = (*e)[:len(*e)-1]
			break
		}
	}
}

// Set adds an environment variable, replacing any existing ones of the same key
func (e *environ) Set(key, val string) {
	e.Unset(key)
	*e = append(*e, key+"="+val)
}

// ProfileNames returns a slice of profile names from the AWS config
func ProfileNames() []string {
	var profileNames []string
	for _, profile := range awsConfig.Profiles() {
		profileNames = append(profileNames, profile.Name)
	}
	return profileNames
}
<|endoftext|>


<commit_before>/*
Package signature implements signature verification for MessageBird webhooks.

To use define a new validator using your MessageBird Signing key. You can use the
ValidRequest method, just pass the request and base url as parameters:

    validator := signature.NewValidator([]byte("your signing key"))
	baseUrl := "https://messagebird.io"
    if err := validator.ValidRequest(r, baseUrl); err != nil {
        // handle error
    }

Or use the handler as a middleware for your server:

	http.Handle("/path", validator.Validate(YourHandler, baseUrl))

It will reject the requests that contain invalid signatures.
*/
package signature

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"

	"github.com/dgrijalva/jwt-go"
)

const signatureHeader = "MessageBird-Signature-JWT"

// TimeFunc provides the current time same as time.Now but can be overridden for testing.
var TimeFunc = time.Now

// allowedMethods lists the signing methods that we accept.  We only allow symmetric-key
// algorithms as our customer signing keys are currently all simple byte strings.  HMAC is
// also the only symkey signature method that is required by the RFC7518 Section 3.1 and
// thus should be supported by all JWT implementations.
var allowedMethods = []string{
	jwt.SigningMethodHS256.Name,
	jwt.SigningMethodHS384.Name,
	jwt.SigningMethodHS512.Name,
}

// Validator type represents a MessageBird signature validator.
type Validator struct {
	SigningKey []byte // Signing Key provided by MessageBird.
}

// NewValidator returns a signature validator object.
func NewValidator(signingKey []byte) *Validator {
	return &Validator{
		SigningKey: signingKey,
	}
}

// ValidSignature is a method that takes care of the signature validation of
// incoming requests.
func (v *Validator) ValidSignature(signature, url string, payload []byte) error {
	parser := jwt.Parser{ValidMethods: allowedMethods}
	keyFn := func(*jwt.Token) (interface{}, error) { return v.SigningKey, nil }

	claims := Claims{
		receivedTime:   TimeFunc(),
		correctURLHash: sha256Hash([]byte(url)),
	}
	if payload != nil && len(payload) != 0 {
		claims.correctPayloadHash = sha256Hash(payload)
	}

	if _, err := parser.ParseWithClaims(signature, &claims, keyFn); err != nil {
		return fmt.Errorf("invalid jwt: %w", err)
	}

	return nil
}

// ValidRequest is a method that takes care of the signature validation of
// incoming requests.
func (v *Validator) ValidRequest(r *http.Request, baseUrl string) error {
	base, err := url.Parse(baseUrl)
	if err != nil {
		return fmt.Errorf("error parsing base url: %v", err)
	}
	signature := r.Header.Get(signatureHeader)
	if signature == "" {
		return fmt.Errorf("signature not found")
	}
	b, _ := ioutil.ReadAll(r.Body)
	if err := v.ValidSignature(signature, base.ResolveReference(r.URL).String(), b); err != nil {
		return fmt.Errorf("invalid signature: %s", err.Error())
	}
	r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
	return nil
}

// Validate is a handler wrapper that takes care of the signature validation of
// incoming requests and rejects them if invalid or pass them on to your handler
// otherwise.
func (v *Validator) Validate(h http.Handler, baseUrl string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := v.ValidRequest(r, baseUrl); err != nil {
			http.Error(w, "", http.StatusUnauthorized)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func sha256Hash(data []byte) string {
	if data == nil {
		return ""
	}

	h := sha256.Sum256(data)
	return hex.EncodeToString(h[:])
}
<commit_msg>Small signature doc improvement<commit_after>/*
Package signature implements signature verification for MessageBird webhooks.

To use define a new validator using your MessageBird Signing key. You can use the
ValidRequest method, just pass the request and base url as parameters:

    validator := signature.NewValidator([]byte("your signing key"))
	baseUrl := "https://yourdomain.com"
    if err := validator.ValidRequest(r, baseUrl); err != nil {
        // handle error
    }

Or use the handler as a middleware for your server:

	http.Handle("/path", validator.Validate(YourHandler, baseUrl))

It will reject the requests that contain invalid signatures.
*/
package signature

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"

	"github.com/dgrijalva/jwt-go"
)

const signatureHeader = "MessageBird-Signature-JWT"

// TimeFunc provides the current time same as time.Now but can be overridden for testing.
var TimeFunc = time.Now

// allowedMethods lists the signing methods that we accept.  We only allow symmetric-key
// algorithms as our customer signing keys are currently all simple byte strings.  HMAC is
// also the only symkey signature method that is required by the RFC7518 Section 3.1 and
// thus should be supported by all JWT implementations.
var allowedMethods = []string{
	jwt.SigningMethodHS256.Name,
	jwt.SigningMethodHS384.Name,
	jwt.SigningMethodHS512.Name,
}

// Validator type represents a MessageBird signature validator.
type Validator struct {
	SigningKey []byte // Signing Key provided by MessageBird.
}

// NewValidator returns a signature validator object.
func NewValidator(signingKey []byte) *Validator {
	return &Validator{
		SigningKey: signingKey,
	}
}

// ValidSignature is a method that takes care of the signature validation of
// incoming requests.
func (v *Validator) ValidSignature(signature, url string, payload []byte) error {
	parser := jwt.Parser{ValidMethods: allowedMethods}
	keyFn := func(*jwt.Token) (interface{}, error) { return v.SigningKey, nil }

	claims := Claims{
		receivedTime:   TimeFunc(),
		correctURLHash: sha256Hash([]byte(url)),
	}
	if payload != nil && len(payload) != 0 {
		claims.correctPayloadHash = sha256Hash(payload)
	}

	if _, err := parser.ParseWithClaims(signature, &claims, keyFn); err != nil {
		return fmt.Errorf("invalid jwt: %w", err)
	}

	return nil
}

// ValidRequest is a method that takes care of the signature validation of
// incoming requests.
func (v *Validator) ValidRequest(r *http.Request, baseUrl string) error {
	base, err := url.Parse(baseUrl)
	if err != nil {
		return fmt.Errorf("error parsing base url: %v", err)
	}
	signature := r.Header.Get(signatureHeader)
	if signature == "" {
		return fmt.Errorf("signature not found")
	}
	b, _ := ioutil.ReadAll(r.Body)
	if err := v.ValidSignature(signature, base.ResolveReference(r.URL).String(), b); err != nil {
		return fmt.Errorf("invalid signature: %s", err.Error())
	}
	r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
	return nil
}

// Validate is a handler wrapper that takes care of the signature validation of
// incoming requests and rejects them if invalid or pass them on to your handler
// otherwise.
func (v *Validator) Validate(h http.Handler, baseUrl string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := v.ValidRequest(r, baseUrl); err != nil {
			http.Error(w, "", http.StatusUnauthorized)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func sha256Hash(data []byte) string {
	if data == nil {
		return ""
	}

	h := sha256.Sum256(data)
	return hex.EncodeToString(h[:])
}
<|endoftext|>