<commit_before>package properties

import (
	"bufio"
	"bytes"
	"container/list"
	"fmt"
	"io"
	"strconv"
	"strings"
	"unicode"
)

type element struct {
	//  #   注释行
	//  !   注释行
	//  ' ' 空白行或者空行
	//  =   等号分隔的属性行
	//  :   冒号分隔的属性行
	typo  byte   //  行类型
	value string //  行的内容,如果是注释注释引导符也包含在内
	key   string //  如果是属性行这里表示属性的key
}

// PropertiesDocument The properties document in memory.
type PropertiesDocument struct {
	elems *list.List
	props map[string]*list.Element
}

// New is used to create a new and empty properties document.
//
// It's used to generate a new document.
func New() *PropertiesDocument {
	doc := new(PropertiesDocument)
	doc.elems = list.New()
	doc.props = make(map[string]*list.Element)
	return doc
}

// Save is used to save the doc to file or stream.
func Save(doc *PropertiesDocument, writer io.Writer) error {
	var err error

	doc.Accept(func(typo byte, value string, key string) bool {
		switch typo {
		case '#', '!', ' ':
			_, err = fmt.Fprintln(writer, value)
		case '=', ':':
			_, err = fmt.Fprintf(writer, "%s%c%s\n", key, typo, value)
		}

		return nil == err
	})

	return err
}

// Load is used to create the properties document from a file or a stream.
func Load(reader io.Reader) (doc *PropertiesDocument, err error) {

	//  创建一个Properties对象
	doc = New()

	//  创建一个扫描器
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		//  逐行读取
		line := scanner.Bytes()

		//  遇到空行
		if 0 == len(line) {
			doc.elems.PushBack(&element{typo: ' ', value: string(line)})
			continue
		}

		//  找到第一个非空白字符
		pos := bytes.IndexFunc(line, func(r rune) bool {
			return !unicode.IsSpace(r)
		})

		//  遇到空白行
		if -1 == pos {
			doc.elems.PushBack(&element{typo: ' ', value: string(line)})
			continue
		}

		//  遇到注释行
		if '#' == line[pos] {
			doc.elems.PushBack(&element{typo: '#', value: string(line)})
			continue
		}

		if '!' == line[pos] {
			doc.elems.PushBack(&element{typo: '!', value: string(line)})
			continue
		}

		//  找到第一个等号的位置
		end := bytes.IndexFunc(line[pos+1:], func(r rune) bool {
			return ('=' == r) || (':' == r)
		})

		//  没有=，说明该配置项只有key
		key := ""
		value := ""
		if -1 == end {
			key = string(bytes.TrimRightFunc(line[pos:], func(r rune) bool {
				return unicode.IsSpace(r)
			}))
		} else {
			key = string(bytes.TrimRightFunc(line[pos:pos+1+end], func(r rune) bool {
				return unicode.IsSpace(r)
			}))

			value = string(bytes.TrimSpace(line[pos+1+end+1:]))
		}

		var typo byte = '='
		if end > 0 {
			typo = line[pos+1+end]
		}
		elem := &element{typo: typo, key: key, value: value}
		listelem := doc.elems.PushBack(elem)
		doc.props[key] = listelem
	}

	if err = scanner.Err(); nil != err {
		return nil, err
	}

	return doc, nil
}

// Get Retrive the value from PropertiesDocument.
// If the item is not exist, the exist is false.
func (p PropertiesDocument) Get(key string) (value string, exist bool) {
	e, ok := p.props[key]
	if !ok {
		return "", ok
	}

	return e.Value.(*element).value, ok
}

// Set Update the value of the item of the key.
// Create a new item if the item of the key is not exist.
func (p *PropertiesDocument) Set(key string, value string) {
	e, ok := p.props[key]
	if !ok {
		p.props[key] = p.elems.PushBack(&element{typo: '=', key: key, value: value})
		return
	}

	e.Value.(*element).value = value
	return
}

// Del Delete the exist item.
// If the item is not exist, return false.
func (p *PropertiesDocument) Del(key string) bool {
	e, ok := p.props[key]
	if !ok {
		return false
	}

	p.Uncomment(key)
	p.elems.Remove(e)
	delete(p.props, key)
	return true
}

// Comment Append comments for the special item.
// Return false if the special item is not exist.
func (p *PropertiesDocument) Comment(key string, comments string) bool {
	e, ok := p.props[key]
	if !ok {
		return false
	}

	//  如果所有注释为空
	if len(comments) <= 0 {
		p.elems.InsertBefore(&element{typo: '#', value: "#"}, e)
		return true
	}

	//  创建一个新的Scanner
	scanner := bufio.NewScanner(strings.NewReader(comments))
	for scanner.Scan() {
		p.elems.InsertBefore(&element{typo: '#', value: "#" + scanner.Text()}, e)
	}

	return true
}

// Uncomment Remove all of the comments for the special item.
// Return false if the special item is not exist.
func (p *PropertiesDocument) Uncomment(key string) bool {
	e, ok := p.props[key]
	if !ok {
		return false
	}

	for item := e.Prev(); nil != item; {
		del := item
		item = item.Prev()

		if ('=' == del.Value.(*element).typo) ||
			(':' == del.Value.(*element).typo) ||
			(' ' == del.Value.(*element).typo) {
			break
		}

		p.elems.Remove(del)
	}

	return true
}

// Accept Traverse every element of the document, include comment.
// The typo parameter special the element type.
// If typo is '#' or '!' means current element is a comment.
// If typo is ' ' means current element is a empty or a space line.
// If typo is '=' or ':' means current element is a key-value pair.
// The traverse will be terminated if f return false.
func (p PropertiesDocument) Accept(f func(typo byte, value string, key string) bool) {
	for e := p.elems.Front(); e != nil; e = e.Next() {
		elem := e.Value.(*element)
		continues := f(elem.typo, elem.value, elem.key)
		if !continues {
			return
		}
	}
}

// Foreach Traverse all of the key-value pairs in the document.
// The traverse will be terminated if f return false.
func (p PropertiesDocument) Foreach(f func(value string, key string) bool) {
	for e := p.elems.Front(); e != nil; e = e.Next() {
		elem := e.Value.(*element)
		if ('=' == elem.typo) ||
			(':' == elem.typo) {
			continues := f(elem.value, elem.key)
			if !continues {
				return
			}
		}
	}
}

// StringDefault   Retrive the string value by key.
// If the element is not exist, the def will be returned.
func (p PropertiesDocument) StringDefault(key string, def string) string {
	e, ok := p.props[key]
	if ok {
		return e.Value.(*element).value
	}

	return def
}

// IntDefault   Retrive the int64 value by key.
// If the element is not exist, the def will be returned.
func (p PropertiesDocument) IntDefault(key string, def int64) int64 {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseInt(e.Value.(*element).value, 10, 64)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// UintDefault Same as IntDefault, but the return type is uint64.
func (p PropertiesDocument) UintDefault(key string, def uint64) uint64 {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseUint(e.Value.(*element).value, 10, 64)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// FloatDefault   Retrive the float64 value by key.
// If the element is not exist, the def will be returned.
func (p PropertiesDocument) FloatDefault(key string, def float64) float64 {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseFloat(e.Value.(*element).value, 64)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// BoolDefault   Retrive the bool value by key.
// If the element is not exist, the def will be returned.
// This function mapping "1", "t", "T", "true", "TRUE", "True" as true.
// This function mapping "0", "f", "F", "false", "FALSE", "False" as false.
// If the element is not exist of can not map to value of bool,the def will be returned.
func (p PropertiesDocument) BoolDefault(key string, def bool) bool {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseBool(e.Value.(*element).value)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// ObjectDefault Map the value of the key to any object.
// The f is the customized mapping function.
// Return def if the element is not exist of f have a error returned.
func (p PropertiesDocument) ObjectDefault(key string, def interface{}, f func(k string, v string) (interface{}, error)) interface{} {
	e, ok := p.props[key]
	if ok {
		v, err := f(key, e.Value.(*element).value)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// String Same as StringDefault but the def is ""
func (p PropertiesDocument) String(key string) string {
	return p.StringDefault(key, "")
}

// Int Same as IntDefault but the def is 0
func (p PropertiesDocument) Int(key string) int64 {
	return p.IntDefault(key, 0)
}

func (p PropertiesDocument) Uint(key string) uint64 {
	return p.UintDefault(key, 0)
}

// Float Same as FloatDefault but the def is 0.0
func (p PropertiesDocument) Float(key string) float64 {
	return p.FloatDefault(key, 0.0)
}

// Bool Same as BoolDefault but the def is false
func (p PropertiesDocument) Bool(key string) bool {
	return p.BoolDefault(key, false)
}

// Object Same as ObjectDefault but the def is nil
func (p PropertiesDocument) Object(key string, f func(k string, v string) (interface{}, error)) interface{} {
	return p.ObjectDefault(key, interface{}(nil), f)
}
<commit_msg>准备将几个开源的构建工程利用起来<commit_after>// Package properties is used to read or write or modify the properties document.
package properties

import (
	"bufio"
	"bytes"
	"container/list"
	"fmt"
	"io"
	"strconv"
	"strings"
	"unicode"
)

type element struct {
	//  #   注释行
	//  !   注释行
	//  ' ' 空白行或者空行
	//  =   等号分隔的属性行
	//  :   冒号分隔的属性行
	typo  byte   //  行类型
	value string //  行的内容,如果是注释注释引导符也包含在内
	key   string //  如果是属性行这里表示属性的key
}

// PropertiesDocument The properties document in memory.
type PropertiesDocument struct {
	elems *list.List
	props map[string]*list.Element
}

// New is used to create a new and empty properties document.
//
// It's used to generate a new document.
func New() *PropertiesDocument {
	doc := new(PropertiesDocument)
	doc.elems = list.New()
	doc.props = make(map[string]*list.Element)
	return doc
}

// Save is used to save the doc to file or stream.
func Save(doc *PropertiesDocument, writer io.Writer) error {
	var err error

	doc.Accept(func(typo byte, value string, key string) bool {
		switch typo {
		case '#', '!', ' ':
			_, err = fmt.Fprintln(writer, value)
		case '=', ':':
			_, err = fmt.Fprintf(writer, "%s%c%s\n", key, typo, value)
		}

		return nil == err
	})

	return err
}

// Load is used to create the properties document from a file or a stream.
func Load(reader io.Reader) (doc *PropertiesDocument, err error) {

	//  创建一个Properties对象
	doc = New()

	//  创建一个扫描器
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		//  逐行读取
		line := scanner.Bytes()

		//  遇到空行
		if 0 == len(line) {
			doc.elems.PushBack(&element{typo: ' ', value: string(line)})
			continue
		}

		//  找到第一个非空白字符
		pos := bytes.IndexFunc(line, func(r rune) bool {
			return !unicode.IsSpace(r)
		})

		//  遇到空白行
		if -1 == pos {
			doc.elems.PushBack(&element{typo: ' ', value: string(line)})
			continue
		}

		//  遇到注释行
		if '#' == line[pos] {
			doc.elems.PushBack(&element{typo: '#', value: string(line)})
			continue
		}

		if '!' == line[pos] {
			doc.elems.PushBack(&element{typo: '!', value: string(line)})
			continue
		}

		//  找到第一个等号的位置
		end := bytes.IndexFunc(line[pos+1:], func(r rune) bool {
			return ('=' == r) || (':' == r)
		})

		//  没有=，说明该配置项只有key
		key := ""
		value := ""
		if -1 == end {
			key = string(bytes.TrimRightFunc(line[pos:], func(r rune) bool {
				return unicode.IsSpace(r)
			}))
		} else {
			key = string(bytes.TrimRightFunc(line[pos:pos+1+end], func(r rune) bool {
				return unicode.IsSpace(r)
			}))

			value = string(bytes.TrimSpace(line[pos+1+end+1:]))
		}

		var typo byte = '='
		if end > 0 {
			typo = line[pos+1+end]
		}
		elem := &element{typo: typo, key: key, value: value}
		listelem := doc.elems.PushBack(elem)
		doc.props[key] = listelem
	}

	if err = scanner.Err(); nil != err {
		return nil, err
	}

	return doc, nil
}

// Get Retrive the value from PropertiesDocument.
//
// If the item is not exist, the exist is false.
func (p PropertiesDocument) Get(key string) (value string, exist bool) {
	e, ok := p.props[key]
	if !ok {
		return "", ok
	}

	return e.Value.(*element).value, ok
}

// Set Update the value of the item of the key.
//
// Create a new item if the item of the key is not exist.
func (p *PropertiesDocument) Set(key string, value string) {
	e, ok := p.props[key]
	if !ok {
		p.props[key] = p.elems.PushBack(&element{typo: '=', key: key, value: value})
		return
	}

	e.Value.(*element).value = value
	return
}

// Del Delete the exist item.
//
// If the item is not exist, return false.
func (p *PropertiesDocument) Del(key string) bool {
	e, ok := p.props[key]
	if !ok {
		return false
	}

	p.Uncomment(key)
	p.elems.Remove(e)
	delete(p.props, key)
	return true
}

// Comment Append comments for the special item.
//
// Return false if the special item is not exist.
func (p *PropertiesDocument) Comment(key string, comments string) bool {
	e, ok := p.props[key]
	if !ok {
		return false
	}

	//  如果所有注释为空
	if len(comments) <= 0 {
		p.elems.InsertBefore(&element{typo: '#', value: "#"}, e)
		return true
	}

	//  创建一个新的Scanner
	scanner := bufio.NewScanner(strings.NewReader(comments))
	for scanner.Scan() {
		p.elems.InsertBefore(&element{typo: '#', value: "#" + scanner.Text()}, e)
	}

	return true
}

// Uncomment Remove all of the comments for the special item.
//
// Return false if the special item is not exist.
func (p *PropertiesDocument) Uncomment(key string) bool {
	e, ok := p.props[key]
	if !ok {
		return false
	}

	for item := e.Prev(); nil != item; {
		del := item
		item = item.Prev()

		if ('=' == del.Value.(*element).typo) ||
			(':' == del.Value.(*element).typo) ||
			(' ' == del.Value.(*element).typo) {
			break
		}

		p.elems.Remove(del)
	}

	return true
}

// Accept Traverse every element of the document, include comment.
//
// The typo parameter special the element type.
// If typo is '#' or '!' means current element is a comment.
// If typo is ' ' means current element is a empty or a space line.
// If typo is '=' or ':' means current element is a key-value pair.
// The traverse will be terminated if f return false.
func (p PropertiesDocument) Accept(f func(typo byte, value string, key string) bool) {
	for e := p.elems.Front(); e != nil; e = e.Next() {
		elem := e.Value.(*element)
		continues := f(elem.typo, elem.value, elem.key)
		if !continues {
			return
		}
	}
}

// Foreach Traverse all of the key-value pairs in the document.
// The traverse will be terminated if f return false.
func (p PropertiesDocument) Foreach(f func(value string, key string) bool) {
	for e := p.elems.Front(); e != nil; e = e.Next() {
		elem := e.Value.(*element)
		if ('=' == elem.typo) ||
			(':' == elem.typo) {
			continues := f(elem.value, elem.key)
			if !continues {
				return
			}
		}
	}
}

// StringDefault   Retrive the string value by key.
// If the element is not exist, the def will be returned.
func (p PropertiesDocument) StringDefault(key string, def string) string {
	e, ok := p.props[key]
	if ok {
		return e.Value.(*element).value
	}

	return def
}

// IntDefault   Retrive the int64 value by key.
// If the element is not exist, the def will be returned.
func (p PropertiesDocument) IntDefault(key string, def int64) int64 {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseInt(e.Value.(*element).value, 10, 64)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// UintDefault Same as IntDefault, but the return type is uint64.
func (p PropertiesDocument) UintDefault(key string, def uint64) uint64 {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseUint(e.Value.(*element).value, 10, 64)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// FloatDefault   Retrive the float64 value by key.
// If the element is not exist, the def will be returned.
func (p PropertiesDocument) FloatDefault(key string, def float64) float64 {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseFloat(e.Value.(*element).value, 64)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// BoolDefault   Retrive the bool value by key.
// If the element is not exist, the def will be returned.
// This function mapping "1", "t", "T", "true", "TRUE", "True" as true.
// This function mapping "0", "f", "F", "false", "FALSE", "False" as false.
// If the element is not exist of can not map to value of bool,the def will be returned.
func (p PropertiesDocument) BoolDefault(key string, def bool) bool {
	e, ok := p.props[key]
	if ok {
		v, err := strconv.ParseBool(e.Value.(*element).value)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// ObjectDefault Map the value of the key to any object.
// The f is the customized mapping function.
// Return def if the element is not exist of f have a error returned.
func (p PropertiesDocument) ObjectDefault(key string, def interface{}, f func(k string, v string) (interface{}, error)) interface{} {
	e, ok := p.props[key]
	if ok {
		v, err := f(key, e.Value.(*element).value)
		if nil != err {
			return def
		}

		return v
	}

	return def
}

// String Same as StringDefault but the def is "".
func (p PropertiesDocument) String(key string) string {
	return p.StringDefault(key, "")
}

// Int is ame as IntDefault but the def is 0 .
func (p PropertiesDocument) Int(key string) int64 {
	return p.IntDefault(key, 0)
}

// Uint Same as UintDefault but the def is 0 .
func (p PropertiesDocument) Uint(key string) uint64 {
	return p.UintDefault(key, 0)
}

// Float is same as FloatDefault but the def is 0.0 .
func (p PropertiesDocument) Float(key string) float64 {
	return p.FloatDefault(key, 0.0)
}

// Bool is same as BoolDefault but the def is false.
func (p PropertiesDocument) Bool(key string) bool {
	return p.BoolDefault(key, false)
}

// Object is same as ObjectDefault but the def is nil.
//
// Notice: If the return value can not be assign to nil, this function will panic/
func (p PropertiesDocument) Object(key string, f func(k string, v string) (interface{}, error)) interface{} {
	return p.ObjectDefault(key, interface{}(nil), f)
}
<|endoftext|>


<commit_before>package webpack

import (
	"os/exec"
	"path/filepath"

	"github.com/gobuffalo/buffalo/generators"
	"github.com/gobuffalo/buffalo/generators/assets/standard"
	"github.com/gobuffalo/makr"
	"github.com/gobuffalo/packr"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// TemplateBox contains all templates needed for the webpack generator
var TemplateBox = packr.NewBox("../webpack/templates")

// BinPath is the path to the local install of webpack
var BinPath = filepath.Join("node_modules", ".bin", "webpack")

// Run webpack generator
func (w Generator) Run(root string, data makr.Data) error {
	g := makr.New()

	// if there's no npm, return!
	if _, err := exec.LookPath("npm"); err != nil {
		logrus.Info("Could not find npm. Skipping webpack generation.")

		return standard.Run(root, data)
	}

	command := "yarnpkg"

	if !w.WithYarn {
		command = "npm"
	} else {
		err := installYarn(data)
		if err != nil {
			return errors.WithStack(err)
		}
	}

	files, err := generators.FindByBox(TemplateBox)
	if err != nil {
		return errors.WithStack(err)
	}

	for _, f := range files {
		g.Add(makr.NewFile(f.WritePath, f.Body))
	}

	args := []string{"install", "--no-progress", "--save"}
	g.Add(makr.NewCommand(exec.Command(command, args...)))
	data["opts"] = w
	return g.Run(root, data)
}

func installYarn(data makr.Data) error {
	// if there's no yarn, install it!
	_, err := exec.LookPath("yarnpkg")
	// A new makr is necessary to have yarn available in path
	if err != nil {
		yg := makr.New()
		yargs := []string{"install", "-g", "yarn"}
		yg.Add(makr.NewCommand(exec.Command("npm", yargs...)))
		err = yg.Run(".", data)
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg> Fail on npm/node issues during buffalo new fixes #1368 (#1408)<commit_after>package webpack

import (
	"os/exec"
	"path/filepath"

	"github.com/gobuffalo/buffalo/generators"
	"github.com/gobuffalo/makr"
	"github.com/gobuffalo/packr"
	"github.com/pkg/errors"
)

// TemplateBox contains all templates needed for the webpack generator
var TemplateBox = packr.NewBox("../webpack/templates")

// BinPath is the path to the local install of webpack
var BinPath = filepath.Join("node_modules", ".bin", "webpack")

// Run webpack generator
func (w Generator) Run(root string, data makr.Data) error {
	g := makr.New()

	// if there's no node, return!

	if _, err := exec.LookPath("node"); err != nil {
		return errors.New("could not find node installed. either install node or run with the --skip-webpack flag")
	}

	command := "yarnpkg"

	if !w.WithYarn {
		command = "npm"
	} else {
		err := installYarn(data)
		if err != nil {
			return errors.WithStack(err)
		}
	}

	// if there's no npm, return!
	if _, err := exec.LookPath("npm"); err != nil {
		return errors.New("could not find npm installed. either install node or run with the --skip-webpack flag")
	}

	files, err := generators.FindByBox(TemplateBox)
	if err != nil {
		return errors.WithStack(err)
	}

	for _, f := range files {
		g.Add(makr.NewFile(f.WritePath, f.Body))
	}

	args := []string{"install", "--no-progress", "--save"}
	g.Add(makr.NewCommand(exec.Command(command, args...)))
	data["opts"] = w
	return g.Run(root, data)
}

func installYarn(data makr.Data) error {
	// if there's no yarn, install it!
	_, err := exec.LookPath("yarnpkg")
	// A new makr is necessary to have yarn available in path
	if err != nil {
		yg := makr.New()
		yargs := []string{"install", "-g", "yarn"}
		yg.Add(makr.NewCommand(exec.Command("npm", yargs...)))
		err = yg.Run(".", data)
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gcp

import (
	"cups-connector/lib"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/golang/oauth2"
)

const baseURL = "https://www.google.com/cloudprint/"

// Interface between Go and the Google Cloud Print API.
type GoogleCloudPrint struct {
	xmppJID        string
	xmppClient     *gcpXMPP
	robotTransport *oauth2.Transport
	userTransport  *oauth2.Transport
	proxyName      string
}

func NewGoogleCloudPrint(xmppJID, robotRefreshToken, userRefreshToken, proxyName string) (*GoogleCloudPrint, error) {
	robotTransport, err := newTransport(robotRefreshToken, lib.ScopeCloudPrint, lib.ScopeGoogleTalk)
	if err != nil {
		return nil, err
	}

	var userTransport *oauth2.Transport
	if userRefreshToken != "" {
		userTransport, err = newTransport(userRefreshToken, lib.ScopeCloudPrint)
		if err != nil {
			return nil, err
		}
	}

	gcp := &GoogleCloudPrint{xmppJID, nil, robotTransport, userTransport, proxyName}
	gcp.restartXMPP()
	return gcp, nil
}

func newTransport(refreshToken string, scopes ...string) (*oauth2.Transport, error) {
	options := &oauth2.Options{
		ClientID:     lib.ClientID,
		ClientSecret: lib.ClientSecret,
		RedirectURL:  lib.RedirectURL,
		Scopes:       scopes,
	}
	oauthConfig, err := oauth2.NewConfig(options, lib.AuthURL, lib.TokenURL)
	if err != nil {
		return nil, err
	}

	transport := oauthConfig.NewTransport()
	transport.SetToken(&oauth2.Token{RefreshToken: refreshToken})
	// Get first access token to be sure we can.
	if err = transport.RefreshToken(); err != nil {
		return nil, err
	}

	return transport, nil
}

func (gcp *GoogleCloudPrint) Quit() {
	gcp.xmppClient.quit()
}

func (gcp *GoogleCloudPrint) CanShare() bool {
	return gcp.userTransport != nil
}

// Tries to start an XMPP conversation multiple times, then panics.
func (gcp *GoogleCloudPrint) restartXMPP() {
	if gcp.xmppClient != nil {
		gcp.xmppClient.quit()
	}

	var err error
	for i := 0; i < 4; i++ {
		var xmpp *gcpXMPP
		xmpp, err = newXMPP(gcp.xmppJID, gcp.robotTransport.Token().AccessToken, gcp.proxyName)
		if err == nil {
			gcp.xmppClient = xmpp
			glog.Warning("Started XMPP successfully")
			return
		}
		// Sleep for 1, 2, 4, 8 seconds.
		time.Sleep(time.Duration((i+1)*2) * time.Second)
	}
	glog.Fatalf("Failed to start XMPP conversation: %s", err)
	panic("unreachable")
}

// Waits for the next batch of jobs from GCP. Blocks until batch arrives.
//
// Calls google.com/cloudprint/fetch.
func (gcp *GoogleCloudPrint) NextJobBatch() ([]lib.Job, error) {
	printerIDb64, err := gcp.xmppClient.nextWaitingPrinter()
	if err != nil {
		if err == Closed {
			return nil, err
		}

		glog.Warningf("Restarting XMPP conversation because: %s", err)
		gcp.restartXMPP()

		// Now try again.
		printerIDb64, err = gcp.xmppClient.nextWaitingPrinter()
		if err != nil {
			if err == Closed {
				return nil, err
			}
			glog.Fatalf("Failed to wait for next printer twice: %s", err)
		}
	}

	printerIDbyte, err := base64.StdEncoding.DecodeString(printerIDb64)
	if err != nil {
		return nil, err
	}

	return gcp.Fetch(string(printerIDbyte))
}

// Calls google.com/cloudprint/control.
func (gcp *GoogleCloudPrint) Control(jobID string, status lib.GCPJobStatus, code, message string) error {
	form := url.Values{}
	form.Set("jobid", jobID)
	form.Set("status", string(status))
	form.Set("code", code)
	form.Set("message", message)

	if _, _, err := post(gcp.robotTransport, "control", form); err != nil {
		return err
	}

	return nil
}

// Calls google.com/cloudprint/delete.
func (gcp *GoogleCloudPrint) Delete(gcpID string) error {
	form := url.Values{}
	form.Set("printerid", gcpID)

	if _, _, err := post(gcp.robotTransport, "delete", form); err != nil {
		return err
	}

	return nil
}

// Gets the outstanding print jobs for a printer.
//
// Calls google.com/cloudprint/fetch.
func (gcp *GoogleCloudPrint) Fetch(gcpID string) ([]lib.Job, error) {
	form := url.Values{}
	form.Set("printerid", gcpID)

	responseBody, errorCode, err := post(gcp.robotTransport, "fetch", form)
	if err != nil {
		if errorCode == 413 {
			// 413 means "Zero print jobs returned", which isn't really an error.
			return []lib.Job{}, nil
		}
		return nil, err
	}

	var jobsData struct {
		Jobs []struct {
			ID        string
			FileURL   string
			TicketURL string
			OwnerID   string
		}
	}
	if err = json.Unmarshal(responseBody, &jobsData); err != nil {
		return nil, err
	}

	jobs := make([]lib.Job, 0, len(jobsData.Jobs))

	for _, jobData := range jobsData.Jobs {
		job := lib.Job{
			GCPPrinterID: gcpID,
			GCPJobID:     jobData.ID,
			FileURL:      jobData.FileURL,
			TicketURL:    jobData.TicketURL,
			OwnerID:      jobData.OwnerID,
		}
		jobs = append(jobs, job)
	}

	return jobs, nil
}

// Gets all GCP printers assigned to the configured proxy.
//
// Calls google.com/cloudprint/list.
func (gcp *GoogleCloudPrint) List() ([]lib.Printer, error) {
	form := url.Values{}
	form.Set("proxy", gcp.proxyName)

	responseBody, _, err := post(gcp.robotTransport, "list", form)
	if err != nil {
		return nil, err
	}

	var listData struct {
		Printers []struct {
			Id                 string
			Name               string
			DefaultDisplayName string
			Description        string
			Status             string
			CapsHash           string
			Tags               []string
		}
	}
	if err = json.Unmarshal(responseBody, &listData); err != nil {
		return nil, err
	}

	printers := make([]lib.Printer, 0, len(listData.Printers))
	for _, p := range listData.Printers {
		tags := make(map[string]string)
		for _, tag := range p.Tags {
			if !strings.HasPrefix(tag, "cups-") {
				continue
			}
			s := strings.SplitN(tag, "=", 2)
			key := s[0][5:]
			var value string
			if len(s) > 1 {
				value = s[1]
			}
			tags[key] = value
		}

		printer := lib.Printer{
			GCPID:              p.Id,
			Name:               p.Name,
			DefaultDisplayName: p.DefaultDisplayName,
			Description:        p.Description,
			Status:             lib.PrinterStatusFromString(p.Status),
			CapsHash:           p.CapsHash,
			Tags:               tags,
		}
		printers = append(printers, printer)
	}

	return printers, nil
}

// Registers a Google Cloud Print Printer. Sets the GCPID field in the printer arg.
//
// Calls google.com/cloudprint/register.
func (gcp *GoogleCloudPrint) Register(printer *lib.Printer, ppd string) error {
	if len(ppd) <= 0 {
		return errors.New("GCP requires a non-empty PPD")
	}

	form := url.Values{}
	form.Set("name", printer.Name)
	form.Set("default_display_name", printer.DefaultDisplayName)
	form.Set("proxy", gcp.proxyName)
	form.Set("capabilities", string(ppd))
	form.Set("description", printer.Description)
	form.Set("status", string(printer.Status))
	form.Set("capsHash", printer.CapsHash)
	form.Set("content_types", "application/pdf")
	for key, value := range printer.Tags {
		form.Add("tag", fmt.Sprintf("cups-%s=%s", key, value))
	}

	responseBody, _, err := post(gcp.robotTransport, "register", form)
	if err != nil {
		return err
	}

	var registerData struct {
		Printers []struct {
			Id string
		}
	}
	if err = json.Unmarshal(responseBody, &registerData); err != nil {
		return err
	}

	printer.GCPID = registerData.Printers[0].Id

	return nil
}

// Updates a Google Cloud Print Printer.
//
// Calls google.com/cloudprint/update.
func (gcp *GoogleCloudPrint) Update(diff *lib.PrinterDiff, ppd string) error {
	form := url.Values{}
	form.Set("printerid", diff.Printer.GCPID)
	form.Set("proxy", gcp.proxyName)

	// Ignore Name field because it never changes.
	if diff.DefaultDisplayNameChanged {
		form.Set("default_display_name", diff.Printer.DefaultDisplayName)
	}

	if diff.DescriptionChanged {
		form.Set("description", diff.Printer.Description)
	}

	if diff.StatusChanged {
		form.Set("status", string(diff.Printer.Status))
	}

	if diff.CapsHashChanged {
		form.Set("capsHash", diff.Printer.CapsHash)
		form.Set("capabilities", ppd)
	}

	if diff.TagsChanged {
		for key, value := range diff.Printer.Tags {
			form.Add("tag", fmt.Sprintf("cups-%s=%s", key, value))
		}
		form.Set("remove_tag", "^cups-.*")
	}

	if _, _, err := post(gcp.robotTransport, "update", form); err != nil {
		return err
	}

	return nil
}

// Shares a GCP printer.
//
// Calls google.com/cloudprint/share.
func (gcp *GoogleCloudPrint) Share(gcpID, shareScope string) error {
	if gcp.userTransport == nil {
		return errors.New("Cannot share because user OAuth credentials not provided.")
	}

	form := url.Values{}
	form.Set("printerid", gcpID)
	form.Set("scope", shareScope)
	form.Set("role", "USER")
	form.Set("skip_notification", "true")

	if _, _, err := post(gcp.userTransport, "share", form); err != nil {
		return err
	}

	return nil
}

// Downloads a url (print job) to a Writer.
func (gcp *GoogleCloudPrint) Download(dst io.Writer, url string) error {
	response, err := get(gcp.robotTransport, url)
	if err != nil {
		return err
	}

	_, err = io.Copy(dst, response.Body)
	if err != nil {
		return err
	}

	return nil
}

// Gets a ticket (job options), returns it as a map.
func (gcp *GoogleCloudPrint) Ticket(ticketURL string) (map[string]string, error) {
	response, err := get(gcp.robotTransport, ticketURL)
	if err != nil {
		return nil, err
	}

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	var m map[string]string
	err = json.Unmarshal(responseBody, &m)
	if err != nil {
		return nil, err
	}

	return m, nil
}

// GETs to a URL. Returns the response object, in case the body is very large.
func get(t *oauth2.Transport, url string) (*http.Response, error) {
	request, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	request.Header.Set("X-CloudPrint-Proxy", "cups-cloudprint-"+runtime.GOOS)

	response, err := t.RoundTrip(request)
	if err != nil {
		return nil, err
	}
	if response.StatusCode != 200 {
		return nil, fmt.Errorf("GET failed: %s %s", url, response.Status)
	}

	return response, nil
}

// POSTs to a GCP method. Returns the body of the response.
//
// On error, the last two return values are non-zero values.
func post(t *oauth2.Transport, method string, form url.Values) ([]byte, uint, error) {
	requestBody := strings.NewReader(form.Encode())
	request, err := http.NewRequest("POST", baseURL+method, requestBody)
	if err != nil {
		return nil, 0, err
	}
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	request.Header.Set("X-CloudPrint-Proxy", "cups-cloudprint-"+runtime.GOOS)

	response, err := t.RoundTrip(request)
	if err != nil {
		return nil, 0, err
	}
	if response.StatusCode != 200 {
		return nil, 0, fmt.Errorf("/%s call failed: %s", method, response.Status)
	}

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, 0, err
	}

	var responseStatus struct {
		Success   bool
		Message   string
		ErrorCode uint
	}
	if err = json.Unmarshal(responseBody, &responseStatus); err != nil {
		return nil, 0, err
	}
	if !responseStatus.Success {
		return nil, responseStatus.ErrorCode, fmt.Errorf(
			"/%s call failed: %s", method, responseStatus.Message)
	}

	return responseBody, 0, nil
}
<commit_msg>Check for expired token before restarting XMPP conversation<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gcp

import (
	"cups-connector/lib"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/golang/oauth2"
)

const baseURL = "https://www.google.com/cloudprint/"

// Interface between Go and the Google Cloud Print API.
type GoogleCloudPrint struct {
	xmppJID        string
	xmppClient     *gcpXMPP
	robotTransport *oauth2.Transport
	userTransport  *oauth2.Transport
	proxyName      string
}

func NewGoogleCloudPrint(xmppJID, robotRefreshToken, userRefreshToken, proxyName string) (*GoogleCloudPrint, error) {
	robotTransport, err := newTransport(robotRefreshToken, lib.ScopeCloudPrint, lib.ScopeGoogleTalk)
	if err != nil {
		return nil, err
	}

	var userTransport *oauth2.Transport
	if userRefreshToken != "" {
		userTransport, err = newTransport(userRefreshToken, lib.ScopeCloudPrint)
		if err != nil {
			return nil, err
		}
	}

	gcp := &GoogleCloudPrint{xmppJID, nil, robotTransport, userTransport, proxyName}
	gcp.restartXMPP()
	return gcp, nil
}

func newTransport(refreshToken string, scopes ...string) (*oauth2.Transport, error) {
	options := &oauth2.Options{
		ClientID:     lib.ClientID,
		ClientSecret: lib.ClientSecret,
		RedirectURL:  lib.RedirectURL,
		Scopes:       scopes,
	}
	oauthConfig, err := oauth2.NewConfig(options, lib.AuthURL, lib.TokenURL)
	if err != nil {
		return nil, err
	}

	transport := oauthConfig.NewTransport()
	transport.SetToken(&oauth2.Token{RefreshToken: refreshToken})
	// Get first access token to be sure we can.
	if err = transport.RefreshToken(); err != nil {
		return nil, err
	}

	return transport, nil
}

func (gcp *GoogleCloudPrint) Quit() {
	gcp.xmppClient.quit()
}

func (gcp *GoogleCloudPrint) CanShare() bool {
	return gcp.userTransport != nil
}

// Tries to start an XMPP conversation multiple times, then panics.
func (gcp *GoogleCloudPrint) restartXMPP() {
	if gcp.xmppClient != nil {
		go gcp.xmppClient.quit()
	}

	var err error
	for i := 0; i < 4; i++ {
		if gcp.robotTransport.Token().Expired() {
			err = gcp.robotTransport.RefreshToken()
		}

		if err == nil {
			var xmpp *gcpXMPP
			xmpp, err = newXMPP(gcp.xmppJID, gcp.robotTransport.Token().AccessToken, gcp.proxyName)
			if err == nil {
				gcp.xmppClient = xmpp
				glog.Warning("Started XMPP successfully")
				return
			}
		}

		// Sleep for 1, 2, 4, 8 seconds.
		time.Sleep(time.Duration((i+1)*2) * time.Second)
	}
	glog.Fatalf("Failed to start XMPP conversation: %s", err)
	panic("unreachable")
}

// Waits for the next batch of jobs from GCP. Blocks until batch arrives.
//
// Calls google.com/cloudprint/fetch.
func (gcp *GoogleCloudPrint) NextJobBatch() ([]lib.Job, error) {
	printerIDb64, err := gcp.xmppClient.nextWaitingPrinter()
	if err != nil {
		if err == Closed {
			return nil, err
		}

		glog.Warningf("Restarting XMPP conversation because: %s", err)
		gcp.restartXMPP()

		// Now try again.
		printerIDb64, err = gcp.xmppClient.nextWaitingPrinter()
		if err != nil {
			if err == Closed {
				return nil, err
			}
			glog.Fatalf("Failed to wait for next printer twice: %s", err)
		}
	}

	printerIDbyte, err := base64.StdEncoding.DecodeString(printerIDb64)
	if err != nil {
		return nil, err
	}

	return gcp.Fetch(string(printerIDbyte))
}

// Calls google.com/cloudprint/control.
func (gcp *GoogleCloudPrint) Control(jobID string, status lib.GCPJobStatus, code, message string) error {
	form := url.Values{}
	form.Set("jobid", jobID)
	form.Set("status", string(status))
	form.Set("code", code)
	form.Set("message", message)

	if _, _, err := post(gcp.robotTransport, "control", form); err != nil {
		return err
	}

	return nil
}

// Calls google.com/cloudprint/delete.
func (gcp *GoogleCloudPrint) Delete(gcpID string) error {
	form := url.Values{}
	form.Set("printerid", gcpID)

	if _, _, err := post(gcp.robotTransport, "delete", form); err != nil {
		return err
	}

	return nil
}

// Gets the outstanding print jobs for a printer.
//
// Calls google.com/cloudprint/fetch.
func (gcp *GoogleCloudPrint) Fetch(gcpID string) ([]lib.Job, error) {
	form := url.Values{}
	form.Set("printerid", gcpID)

	responseBody, errorCode, err := post(gcp.robotTransport, "fetch", form)
	if err != nil {
		if errorCode == 413 {
			// 413 means "Zero print jobs returned", which isn't really an error.
			return []lib.Job{}, nil
		}
		return nil, err
	}

	var jobsData struct {
		Jobs []struct {
			ID        string
			FileURL   string
			TicketURL string
			OwnerID   string
		}
	}
	if err = json.Unmarshal(responseBody, &jobsData); err != nil {
		return nil, err
	}

	jobs := make([]lib.Job, 0, len(jobsData.Jobs))

	for _, jobData := range jobsData.Jobs {
		job := lib.Job{
			GCPPrinterID: gcpID,
			GCPJobID:     jobData.ID,
			FileURL:      jobData.FileURL,
			TicketURL:    jobData.TicketURL,
			OwnerID:      jobData.OwnerID,
		}
		jobs = append(jobs, job)
	}

	return jobs, nil
}

// Gets all GCP printers assigned to the configured proxy.
//
// Calls google.com/cloudprint/list.
func (gcp *GoogleCloudPrint) List() ([]lib.Printer, error) {
	form := url.Values{}
	form.Set("proxy", gcp.proxyName)

	responseBody, _, err := post(gcp.robotTransport, "list", form)
	if err != nil {
		return nil, err
	}

	var listData struct {
		Printers []struct {
			Id                 string
			Name               string
			DefaultDisplayName string
			Description        string
			Status             string
			CapsHash           string
			Tags               []string
		}
	}
	if err = json.Unmarshal(responseBody, &listData); err != nil {
		return nil, err
	}

	printers := make([]lib.Printer, 0, len(listData.Printers))
	for _, p := range listData.Printers {
		tags := make(map[string]string)
		for _, tag := range p.Tags {
			if !strings.HasPrefix(tag, "cups-") {
				continue
			}
			s := strings.SplitN(tag, "=", 2)
			key := s[0][5:]
			var value string
			if len(s) > 1 {
				value = s[1]
			}
			tags[key] = value
		}

		printer := lib.Printer{
			GCPID:              p.Id,
			Name:               p.Name,
			DefaultDisplayName: p.DefaultDisplayName,
			Description:        p.Description,
			Status:             lib.PrinterStatusFromString(p.Status),
			CapsHash:           p.CapsHash,
			Tags:               tags,
		}
		printers = append(printers, printer)
	}

	return printers, nil
}

// Registers a Google Cloud Print Printer. Sets the GCPID field in the printer arg.
//
// Calls google.com/cloudprint/register.
func (gcp *GoogleCloudPrint) Register(printer *lib.Printer, ppd string) error {
	if len(ppd) <= 0 {
		return errors.New("GCP requires a non-empty PPD")
	}

	form := url.Values{}
	form.Set("name", printer.Name)
	form.Set("default_display_name", printer.DefaultDisplayName)
	form.Set("proxy", gcp.proxyName)
	form.Set("capabilities", string(ppd))
	form.Set("description", printer.Description)
	form.Set("status", string(printer.Status))
	form.Set("capsHash", printer.CapsHash)
	form.Set("content_types", "application/pdf")
	for key, value := range printer.Tags {
		form.Add("tag", fmt.Sprintf("cups-%s=%s", key, value))
	}

	responseBody, _, err := post(gcp.robotTransport, "register", form)
	if err != nil {
		return err
	}

	var registerData struct {
		Printers []struct {
			Id string
		}
	}
	if err = json.Unmarshal(responseBody, &registerData); err != nil {
		return err
	}

	printer.GCPID = registerData.Printers[0].Id

	return nil
}

// Updates a Google Cloud Print Printer.
//
// Calls google.com/cloudprint/update.
func (gcp *GoogleCloudPrint) Update(diff *lib.PrinterDiff, ppd string) error {
	form := url.Values{}
	form.Set("printerid", diff.Printer.GCPID)
	form.Set("proxy", gcp.proxyName)

	// Ignore Name field because it never changes.
	if diff.DefaultDisplayNameChanged {
		form.Set("default_display_name", diff.Printer.DefaultDisplayName)
	}

	if diff.DescriptionChanged {
		form.Set("description", diff.Printer.Description)
	}

	if diff.StatusChanged {
		form.Set("status", string(diff.Printer.Status))
	}

	if diff.CapsHashChanged {
		form.Set("capsHash", diff.Printer.CapsHash)
		form.Set("capabilities", ppd)
	}

	if diff.TagsChanged {
		for key, value := range diff.Printer.Tags {
			form.Add("tag", fmt.Sprintf("cups-%s=%s", key, value))
		}
		form.Set("remove_tag", "^cups-.*")
	}

	if _, _, err := post(gcp.robotTransport, "update", form); err != nil {
		return err
	}

	return nil
}

// Shares a GCP printer.
//
// Calls google.com/cloudprint/share.
func (gcp *GoogleCloudPrint) Share(gcpID, shareScope string) error {
	if gcp.userTransport == nil {
		return errors.New("Cannot share because user OAuth credentials not provided.")
	}

	form := url.Values{}
	form.Set("printerid", gcpID)
	form.Set("scope", shareScope)
	form.Set("role", "USER")
	form.Set("skip_notification", "true")

	if _, _, err := post(gcp.userTransport, "share", form); err != nil {
		return err
	}

	return nil
}

// Downloads a url (print job) to a Writer.
func (gcp *GoogleCloudPrint) Download(dst io.Writer, url string) error {
	response, err := get(gcp.robotTransport, url)
	if err != nil {
		return err
	}

	_, err = io.Copy(dst, response.Body)
	if err != nil {
		return err
	}

	return nil
}

// Gets a ticket (job options), returns it as a map.
func (gcp *GoogleCloudPrint) Ticket(ticketURL string) (map[string]string, error) {
	response, err := get(gcp.robotTransport, ticketURL)
	if err != nil {
		return nil, err
	}

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	var m map[string]string
	err = json.Unmarshal(responseBody, &m)
	if err != nil {
		return nil, err
	}

	return m, nil
}

// GETs to a URL. Returns the response object, in case the body is very large.
func get(t *oauth2.Transport, url string) (*http.Response, error) {
	request, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	request.Header.Set("X-CloudPrint-Proxy", "cups-cloudprint-"+runtime.GOOS)

	response, err := t.RoundTrip(request)
	if err != nil {
		return nil, err
	}
	if response.StatusCode != 200 {
		return nil, fmt.Errorf("GET failed: %s %s", url, response.Status)
	}

	return response, nil
}

// POSTs to a GCP method. Returns the body of the response.
//
// On error, the last two return values are non-zero values.
func post(t *oauth2.Transport, method string, form url.Values) ([]byte, uint, error) {
	requestBody := strings.NewReader(form.Encode())
	request, err := http.NewRequest("POST", baseURL+method, requestBody)
	if err != nil {
		return nil, 0, err
	}
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	request.Header.Set("X-CloudPrint-Proxy", "cups-cloudprint-"+runtime.GOOS)

	response, err := t.RoundTrip(request)
	if err != nil {
		return nil, 0, err
	}
	if response.StatusCode != 200 {
		return nil, 0, fmt.Errorf("/%s call failed: %s", method, response.Status)
	}

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, 0, err
	}

	var responseStatus struct {
		Success   bool
		Message   string
		ErrorCode uint
	}
	if err = json.Unmarshal(responseBody, &responseStatus); err != nil {
		return nil, 0, err
	}
	if !responseStatus.Success {
		return nil, responseStatus.ErrorCode, fmt.Errorf(
			"/%s call failed: %s", method, responseStatus.Message)
	}

	return responseBody, 0, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"golang.org/x/crypto/ed25519"
	"golang.org/x/crypto/scrypt"
)

// DeterministicRand - Deterministic random function
type DeterministicRand struct {
	pool []byte
	pos  int
}

var deterministicRand DeterministicRand

func initDeterministicRand(leKey []byte, poolLen int) {
	key, err := scrypt.Key(leKey, []byte{}, 16384, 12, 1, poolLen)
	if err != nil {
		log.Panic(err)
	}
	deterministicRand.pool, deterministicRand.pos = key, 0
}

func (DeterministicRand) Read(p []byte) (n int, err error) {
	reqLen := len(p)
	left := len(deterministicRand.pool) - deterministicRand.pos
	if left < reqLen {
		log.Panic(fmt.Sprintf("rand pool exhaustion (%v left, %v needed)",
			left, reqLen))
	}
	copy(p, deterministicRand.pool[deterministicRand.pos:deterministicRand.pos+reqLen])
	for i := 0; i < reqLen; i++ {
		deterministicRand.pool[i] = 0
	}
	deterministicRand.pos = deterministicRand.pos + reqLen

	return reqLen, nil
}

func genKeys(conf Conf, configFile string, leKey string) {
	randRead, randReader := rand.Read, io.Reader(nil)
	if len(leKey) > 0 {
		initDeterministicRand([]byte(leKey), 96)
		randRead, randReader = deterministicRand.Read, deterministicRand
	}
	psk := make([]byte, 32)
	if _, err := randRead(psk); err != nil {
		log.Fatal(err)
	}
	pskHex := hex.EncodeToString(psk)

	encryptSk := make([]byte, 32)
	if _, err := randRead(encryptSk); err != nil {
		log.Fatal(err)
	}
	encryptSkHex := hex.EncodeToString(encryptSk)

	signPk, signSk, err := ed25519.GenerateKey(randReader)
	if err != nil {
		log.Fatal(err)
	}
	signPkHex := hex.EncodeToString(signPk)
	signSkHex := hex.EncodeToString(signSk[0:32])

	fmt.Printf("\n\n--- Create a file named %s with only the lines relevant to your configuration ---\n\n\n", configFile)
	fmt.Printf("# Configuration for a client\n\n")
	fmt.Printf("Connect   = %q\t# Edit appropriately\n", conf.Connect)
	fmt.Printf("Psk       = %q\n", pskHex)
	fmt.Printf("SignPk    = %q\n", signPkHex)
	fmt.Printf("SignSk    = %q\n", signSkHex)
	fmt.Printf("EncryptSk = %q\n", encryptSkHex)

	fmt.Printf("\n\n")

	fmt.Printf("# Configuration for a server\n\n")
	fmt.Printf("Listen = %q\t# Edit appropriately\n", conf.Listen)
	fmt.Printf("Psk    = %q\n", pskHex)
	fmt.Printf("SignPk = %q\n", signPkHex)

	fmt.Printf("\n\n")

	fmt.Printf("# Hybrid configuration\n\n")
	fmt.Printf("Connect   = %q\t# Edit appropriately\n", conf.Connect)
	fmt.Printf("Listen    = %q\t# Edit appropriately\n", conf.Listen)
	fmt.Printf("Psk       = %q\n", pskHex)
	fmt.Printf("SignPk    = %q\n", signPkHex)
	fmt.Printf("SignSk    = %q\n", signSkHex)
	fmt.Printf("EncryptSk = %q\n", encryptSkHex)
}

func getPassword(prompt string) string {
	os.Stdout.Write([]byte(prompt))
	reader := bufio.NewReader(os.Stdin)
	password, err := reader.ReadString('\n')
	if err != nil {
		log.Fatal(err)
	}
	return strings.TrimSpace(password)
}
<commit_msg>Simplify Go code (#23)<commit_after>package main

import (
	"bufio"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"golang.org/x/crypto/ed25519"
	"golang.org/x/crypto/scrypt"
)

// DeterministicRand - Deterministic random function
type DeterministicRand struct {
	pool []byte
	pos  int
}

var deterministicRand DeterministicRand

func initDeterministicRand(leKey []byte, poolLen int) {
	key, err := scrypt.Key(leKey, []byte{}, 16384, 12, 1, poolLen)
	if err != nil {
		log.Panic(err)
	}
	deterministicRand.pool, deterministicRand.pos = key, 0
}

func (DeterministicRand) Read(p []byte) (n int, err error) {
	reqLen := len(p)
	left := len(deterministicRand.pool) - deterministicRand.pos
	if left < reqLen {
		log.Panic(fmt.Sprintf("rand pool exhaustion (%v left, %v needed)",
			left, reqLen))
	}
	copy(p, deterministicRand.pool[deterministicRand.pos:deterministicRand.pos+reqLen])
	for i := 0; i < reqLen; i++ {
		deterministicRand.pool[i] = 0
	}
	deterministicRand.pos += reqLen

	return reqLen, nil
}

func genKeys(conf Conf, configFile string, leKey string) {
	randRead, randReader := rand.Read, io.Reader(nil)
	if len(leKey) > 0 {
		initDeterministicRand([]byte(leKey), 96)
		randRead, randReader = deterministicRand.Read, deterministicRand
	}
	psk := make([]byte, 32)
	if _, err := randRead(psk); err != nil {
		log.Fatal(err)
	}
	pskHex := hex.EncodeToString(psk)

	encryptSk := make([]byte, 32)
	if _, err := randRead(encryptSk); err != nil {
		log.Fatal(err)
	}
	encryptSkHex := hex.EncodeToString(encryptSk)

	signPk, signSk, err := ed25519.GenerateKey(randReader)
	if err != nil {
		log.Fatal(err)
	}
	signPkHex := hex.EncodeToString(signPk)
	signSkHex := hex.EncodeToString(signSk[0:32])

	fmt.Printf("\n\n--- Create a file named %s with only the lines relevant to your configuration ---\n\n\n", configFile)
	fmt.Printf("# Configuration for a client\n\n")
	fmt.Printf("Connect   = %q\t# Edit appropriately\n", conf.Connect)
	fmt.Printf("Psk       = %q\n", pskHex)
	fmt.Printf("SignPk    = %q\n", signPkHex)
	fmt.Printf("SignSk    = %q\n", signSkHex)
	fmt.Printf("EncryptSk = %q\n", encryptSkHex)

	fmt.Printf("\n\n")

	fmt.Printf("# Configuration for a server\n\n")
	fmt.Printf("Listen = %q\t# Edit appropriately\n", conf.Listen)
	fmt.Printf("Psk    = %q\n", pskHex)
	fmt.Printf("SignPk = %q\n", signPkHex)

	fmt.Printf("\n\n")

	fmt.Printf("# Hybrid configuration\n\n")
	fmt.Printf("Connect   = %q\t# Edit appropriately\n", conf.Connect)
	fmt.Printf("Listen    = %q\t# Edit appropriately\n", conf.Listen)
	fmt.Printf("Psk       = %q\n", pskHex)
	fmt.Printf("SignPk    = %q\n", signPkHex)
	fmt.Printf("SignSk    = %q\n", signSkHex)
	fmt.Printf("EncryptSk = %q\n", encryptSkHex)
}

func getPassword(prompt string) string {
	os.Stdout.Write([]byte(prompt))
	reader := bufio.NewReader(os.Stdin)
	password, err := reader.ReadString('\n')
	if err != nil {
		log.Fatal(err)
	}
	return strings.TrimSpace(password)
}
<|endoftext|>


<commit_before>package agent

import (
	"flag"
	"fmt"
	"github.com/hashicorp/logutils"
	"github.com/hashicorp/memberlist"
	"github.com/hashicorp/serf/serf"
	"github.com/mitchellh/cli"
	"io"
	"log"
	"net"
	"os"
	"strings"
	"sync"
	"time"
)

// Command is a Command implementation that runs a Serf agent.
// The command will not end unless a shutdown message is sent on the
// ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly
// exit.
type Command struct {
	ShutdownCh <-chan struct{}
	Ui         cli.Ui

	lock         sync.Mutex
	shuttingDown bool
}

// readConfig is responsible for setup of our configuration using
// the command line and any file configs
func (c *Command) readConfig(args []string) *Config {
	var cmdConfig Config
	var configFiles []string
	cmdFlags := flag.NewFlagSet("agent", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind listeners to")
	cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-file",
		"json file to read config from")
	cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-dir",
		"directory of json files to read")
	cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "encryption key")
	cmdFlags.Var((*AppendSliceValue)(&cmdConfig.EventHandlers), "event-handler",
		"command to execute when events occur")
	cmdFlags.Var((*AppendSliceValue)(&cmdConfig.StartJoin), "join",
		"address of agent to join on startup")
	cmdFlags.BoolVar(&cmdConfig.ReplayOnJoin, "replay", false,
		"replay events for startup join")
	cmdFlags.StringVar(&cmdConfig.LogLevel, "log-level", "", "log level")
	cmdFlags.StringVar(&cmdConfig.NodeName, "node", "", "node name")
	cmdFlags.IntVar(&cmdConfig.Protocol, "protocol", -1, "protocol version")
	cmdFlags.StringVar(&cmdConfig.Role, "role", "", "role name")
	cmdFlags.StringVar(&cmdConfig.RPCAddr, "rpc-addr", "",
		"address to bind RPC listener to")
	cmdFlags.StringVar(&cmdConfig.Profile, "profile", "", "timing profile to use (lan, wan, local)")
	cmdFlags.StringVar(&cmdConfig.SnapshotPath, "snapshot", "", "path to the snapshot file")
	if err := cmdFlags.Parse(args); err != nil {
		return nil
	}

	config := DefaultConfig
	if len(configFiles) > 0 {
		fileConfig, err := ReadConfigPaths(configFiles)
		if err != nil {
			c.Ui.Error(err.Error())
			return nil
		}

		config = MergeConfig(config, fileConfig)
	}

	config = MergeConfig(config, &cmdConfig)

	if config.NodeName == "" {
		hostname, err := os.Hostname()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error determining hostname: %s", err))
			return nil
		}
		config.NodeName = hostname
	}

	eventScripts := config.EventScripts()
	for _, script := range eventScripts {
		if !script.Valid() {
			c.Ui.Error(fmt.Sprintf("Invalid event script: %s", script.String()))
			return nil
		}
	}

	return config
}

// setupAgent is used to create the agent we use
func (c *Command) setupAgent(config *Config, logOutput io.Writer) *Agent {
	bindIP, bindPort, err := config.BindAddrParts()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Invalid bind address: %s", err))
		return nil
	}

	encryptKey, err := config.EncryptBytes()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err))
		return nil
	}

	serfConfig := serf.DefaultConfig()
	switch config.Profile {
	case "lan":
		serfConfig.MemberlistConfig = memberlist.DefaultLANConfig()
	case "wan":
		serfConfig.MemberlistConfig = memberlist.DefaultWANConfig()
	case "local":
		serfConfig.MemberlistConfig = memberlist.DefaultLocalConfig()
	default:
		c.Ui.Error(fmt.Sprintf("Unknown profile: %s", config.Profile))
		return nil
	}

	serfConfig.MemberlistConfig.BindAddr = bindIP
	serfConfig.MemberlistConfig.Port = bindPort
	serfConfig.MemberlistConfig.SecretKey = encryptKey
	serfConfig.NodeName = config.NodeName
	serfConfig.Role = config.Role
	serfConfig.SnapshotPath = config.SnapshotPath
	serfConfig.ProtocolVersion = uint8(config.Protocol)
	serfConfig.CoalescePeriod = 3 * time.Second
	serfConfig.QuiescentPeriod = time.Second
	serfConfig.UserCoalescePeriod = 3 * time.Second
	serfConfig.UserQuiescentPeriod = time.Second

	// Start Serf
	c.Ui.Output("Starting Serf agent...")
	agent, err := Create(serfConfig, logOutput)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Failed to start the Serf agent: %v", err))
		return nil
	}
	return agent
}

// setupLoggers is used to setup the logGate, logWriter, and our logOutput
func (c *Command) setupLoggers(config *Config) (*GatedWriter, *logWriter, io.Writer) {
	// Setup logging. First create the gated log writer, which will
	// store logs until we're ready to show them. Then create the level
	// filter, filtering logs of the specified level.
	logGate := &GatedWriter{
		Writer: &cli.UiWriter{Ui: c.Ui},
	}

	logLevelFilter := LevelFilter()
	logLevelFilter.MinLevel = logutils.LogLevel(strings.ToUpper(config.LogLevel))
	logLevelFilter.Writer = logGate
	if !ValidateLevelFilter(logLevelFilter) {
		c.Ui.Error(fmt.Sprintf(
			"Invalid log level: %s. Valid log levels are: %v",
			logLevelFilter.MinLevel, logLevelFilter.Levels))
		return nil, nil, nil
	}

	// Create a log writer, and wrap a logOutput around it
	logWriter := NewLogWriter(512)
	logOutput := io.MultiWriter(logLevelFilter, logWriter)
	return logGate, logWriter, logOutput
}

// startAgent is used to start the agent and IPC
func (c *Command) startAgent(config *Config, agent *Agent,
	logWriter *logWriter, logOutput io.Writer) *AgentIPC {
	// Add the script event handlers
	scriptEH := &ScriptEventHandler{
		Self: serf.Member{
			Name: config.NodeName,
			Role: config.Role,
		},
		Scripts: config.EventScripts(),
		Logger:  log.New(logOutput, "", log.LstdFlags),
	}
	agent.RegisterEventHandler(scriptEH)

	// Start the agent after the handler is registered
	if err := agent.Start(); err != nil {
		c.Ui.Error(fmt.Sprintf("Failed to start the Serf agent: %v", err))
		return nil
	}

	// Setup the RPC listener
	rpcListener, err := net.Listen("tcp", config.RPCAddr)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error starting RPC listener: %s", err))
		return nil
	}

	// Start the IPC layer
	c.Ui.Output("Starting Serf agent RPC...")
	ipc := NewAgentIPC(agent, rpcListener, logOutput, logWriter)

	bindIP, bindPort, err := config.BindAddrParts()
	bindAddr := (&net.TCPAddr{IP: net.ParseIP(bindIP), Port: bindPort}).String()
	c.Ui.Output("Serf agent running!")
	c.Ui.Info(fmt.Sprintf("Node name: '%s'", config.NodeName))
	c.Ui.Info(fmt.Sprintf("Bind addr: '%s'", bindAddr))
	c.Ui.Info(fmt.Sprintf(" RPC addr: '%s'", config.RPCAddr))
	c.Ui.Info(fmt.Sprintf("Encrypted: %#v", config.EncryptKey != ""))
	c.Ui.Info(fmt.Sprintf(" Snapshot: %v", config.SnapshotPath != ""))
	c.Ui.Info(fmt.Sprintf("  Profile: %s", config.Profile))
	return ipc
}

// startupJoin is invoked to handle any joins specified to take place at start time
func (c *Command) startupJoin(config *Config, agent *Agent) error {
	if len(config.StartJoin) == 0 {
		return nil
	}

	c.Ui.Output(fmt.Sprintf("Joining cluster...(replay: %v)", config.ReplayOnJoin))
	n, err := agent.Join(config.StartJoin, config.ReplayOnJoin)
	if err != nil {
		return err
	}

	c.Ui.Info(fmt.Sprintf("Join completed. Synced with %d initial agents", n))
	return nil
}

func (c *Command) Run(args []string) int {
	c.Ui = &cli.PrefixedUi{
		OutputPrefix: "==> ",
		InfoPrefix:   "    ",
		ErrorPrefix:  "==> ",
		Ui:           c.Ui,
	}

	// Parse our configs
	config := c.readConfig(args)
	if config == nil {
		return 1
	}

	// Setup the log outputs
	logGate, logWriter, logOutput := c.setupLoggers(config)
	if logWriter == nil {
		return 1
	}

	// Setup serf
	agent := c.setupAgent(config, logOutput)
	if agent == nil {
		return 1
	}
	defer agent.Shutdown()

	// Start the agent
	ipc := c.startAgent(config, agent, logWriter, logOutput)
	if ipc == nil {
		return 1
	}
	defer ipc.Shutdown()

	// Join startup nodes if specified
	if err := c.startupJoin(config, agent); err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	// Enable log streaming
	c.Ui.Info("")
	c.Ui.Output("Log data will now stream in as it occurs:\n")
	logGate.Flush()

	// Wait to exit
	graceful, forceful := c.startShutdownWatcher(agent)
	select {
	case <-graceful:
	case <-forceful:
		// Forcefully shut down, return a bad exit status.
		return 1
	}
	return 0
}

func (c *Command) startShutdownWatcher(agent *Agent) (graceful <-chan struct{}, forceful <-chan struct{}) {
	g := make(chan struct{})
	f := make(chan struct{})
	graceful = g
	forceful = f

	go func() {
		<-c.ShutdownCh

		c.lock.Lock()
		c.shuttingDown = true
		c.lock.Unlock()

		c.Ui.Output("Gracefully shutting down agent...")
		go func() {
			if err := agent.Shutdown(); err != nil {
				c.Ui.Error(fmt.Sprintf("Error: %s", err))
				return
			}
			close(g)
		}()

		select {
		case <-g:
			// Gracefully shut down properly
		case <-c.ShutdownCh:
			close(f)
		}
	}()

	return
}

func (c *Command) Synopsis() string {
	return "Runs a Serf agent"
}

func (c *Command) Help() string {
	helpText := `
Usage: serf agent [options]

  Starts the Serf agent and runs until an interrupt is received. The
  agent represents a single node in a cluster.

Options:

  -bind=0.0.0.0            Address to bind network listeners to
  -config-file=foo         Path to a JSON file to read configuration from.
                           This can be specified multiple times.
  -config-dir=foo          Path to a directory to read configuration files
                           from. This will read every file ending in ".json"
                           as configuration in this directory in alphabetical
                           order.
  -encrypt=foo             Key for encrypting network traffic within Serf.
                           Must be a base64-encoded 16-byte key.
  -event-handler=foo       Script to execute when events occur. This can
                           be specified multiple times. See the event scripts
                           section below for more info.
  -join=addr               An initial agent to join with. This flag can be
                           specified multiple times.
  -log-level=info          Log level of the agent.
  -node=hostname           Name of this node. Must be unique in the cluster
  -profile=[lan|wan|local] Profile is used to control the timing profiles used in Serf.
						   The default if not provided is lan.
  -protocol=n              Serf protocol version to use. This defaults to
                           the latest version, but can be set back for upgrades.
  -role=foo                The role of this node, if any. This can be used
                           by event scripts to differentiate different types
                           of nodes that may be part of the same cluster.
  -rpc-addr=127.0.0.1:7373 Address to bind the RPC listener.
  -snapshot=path/to/file   The snapshot file is used to store alive nodes and
                           event information so that Serf can rejoin a cluster
						   and avoid event replay on restart.

Event handlers:

  For more information on what event handlers are, please read the
  Serf documentation. This section will document how to configure them
  on the command-line. There are three methods of specifying an event
  handler:

  - The value can be a plain script, such as "event.sh". In this case,
    Serf will send all events to this script, and you'll be responsible
    for differentiating between them based on the SERF_EVENT.

  - The value can be in the format of "TYPE=SCRIPT", such as
    "member-join=join.sh". With this format, Serf will only send events
    of that type to that script.

  - The value can be in the format of "user:EVENT=SCRIPT", such as
    "user:deploy=deploy.sh". This means that Serf will only invoke this
    script in the case of user events named "deploy".
`
	return strings.TrimSpace(helpText)
}
<commit_msg>agent: handle signals directly for configurable behavior<commit_after>package agent

import (
	"flag"
	"fmt"
	"github.com/hashicorp/logutils"
	"github.com/hashicorp/memberlist"
	"github.com/hashicorp/serf/serf"
	"github.com/mitchellh/cli"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

// gracefulTimeout controls how long we wait before forcefully terminating
var gracefulTimeout = 3 * time.Second

// Command is a Command implementation that runs a Serf agent.
// The command will not end unless a shutdown message is sent on the
// ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly
// exit.
type Command struct {
	Ui         cli.Ui
	ShutdownCh chan struct{}
}

// readConfig is responsible for setup of our configuration using
// the command line and any file configs
func (c *Command) readConfig(args []string) *Config {
	var cmdConfig Config
	var configFiles []string
	cmdFlags := flag.NewFlagSet("agent", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind listeners to")
	cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-file",
		"json file to read config from")
	cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-dir",
		"directory of json files to read")
	cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "encryption key")
	cmdFlags.Var((*AppendSliceValue)(&cmdConfig.EventHandlers), "event-handler",
		"command to execute when events occur")
	cmdFlags.Var((*AppendSliceValue)(&cmdConfig.StartJoin), "join",
		"address of agent to join on startup")
	cmdFlags.BoolVar(&cmdConfig.ReplayOnJoin, "replay", false,
		"replay events for startup join")
	cmdFlags.StringVar(&cmdConfig.LogLevel, "log-level", "", "log level")
	cmdFlags.StringVar(&cmdConfig.NodeName, "node", "", "node name")
	cmdFlags.IntVar(&cmdConfig.Protocol, "protocol", -1, "protocol version")
	cmdFlags.StringVar(&cmdConfig.Role, "role", "", "role name")
	cmdFlags.StringVar(&cmdConfig.RPCAddr, "rpc-addr", "",
		"address to bind RPC listener to")
	cmdFlags.StringVar(&cmdConfig.Profile, "profile", "", "timing profile to use (lan, wan, local)")
	cmdFlags.StringVar(&cmdConfig.SnapshotPath, "snapshot", "", "path to the snapshot file")
	if err := cmdFlags.Parse(args); err != nil {
		return nil
	}

	config := DefaultConfig
	if len(configFiles) > 0 {
		fileConfig, err := ReadConfigPaths(configFiles)
		if err != nil {
			c.Ui.Error(err.Error())
			return nil
		}

		config = MergeConfig(config, fileConfig)
	}

	config = MergeConfig(config, &cmdConfig)

	if config.NodeName == "" {
		hostname, err := os.Hostname()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error determining hostname: %s", err))
			return nil
		}
		config.NodeName = hostname
	}

	eventScripts := config.EventScripts()
	for _, script := range eventScripts {
		if !script.Valid() {
			c.Ui.Error(fmt.Sprintf("Invalid event script: %s", script.String()))
			return nil
		}
	}

	return config
}

// setupAgent is used to create the agent we use
func (c *Command) setupAgent(config *Config, logOutput io.Writer) *Agent {
	bindIP, bindPort, err := config.BindAddrParts()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Invalid bind address: %s", err))
		return nil
	}

	encryptKey, err := config.EncryptBytes()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Invalid encryption key: %s", err))
		return nil
	}

	serfConfig := serf.DefaultConfig()
	switch config.Profile {
	case "lan":
		serfConfig.MemberlistConfig = memberlist.DefaultLANConfig()
	case "wan":
		serfConfig.MemberlistConfig = memberlist.DefaultWANConfig()
	case "local":
		serfConfig.MemberlistConfig = memberlist.DefaultLocalConfig()
	default:
		c.Ui.Error(fmt.Sprintf("Unknown profile: %s", config.Profile))
		return nil
	}

	serfConfig.MemberlistConfig.BindAddr = bindIP
	serfConfig.MemberlistConfig.Port = bindPort
	serfConfig.MemberlistConfig.SecretKey = encryptKey
	serfConfig.NodeName = config.NodeName
	serfConfig.Role = config.Role
	serfConfig.SnapshotPath = config.SnapshotPath
	serfConfig.ProtocolVersion = uint8(config.Protocol)
	serfConfig.CoalescePeriod = 3 * time.Second
	serfConfig.QuiescentPeriod = time.Second
	serfConfig.UserCoalescePeriod = 3 * time.Second
	serfConfig.UserQuiescentPeriod = time.Second

	// Start Serf
	c.Ui.Output("Starting Serf agent...")
	agent, err := Create(serfConfig, logOutput)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Failed to start the Serf agent: %v", err))
		return nil
	}
	return agent
}

// setupLoggers is used to setup the logGate, logWriter, and our logOutput
func (c *Command) setupLoggers(config *Config) (*GatedWriter, *logWriter, io.Writer) {
	// Setup logging. First create the gated log writer, which will
	// store logs until we're ready to show them. Then create the level
	// filter, filtering logs of the specified level.
	logGate := &GatedWriter{
		Writer: &cli.UiWriter{Ui: c.Ui},
	}

	logLevelFilter := LevelFilter()
	logLevelFilter.MinLevel = logutils.LogLevel(strings.ToUpper(config.LogLevel))
	logLevelFilter.Writer = logGate
	if !ValidateLevelFilter(logLevelFilter) {
		c.Ui.Error(fmt.Sprintf(
			"Invalid log level: %s. Valid log levels are: %v",
			logLevelFilter.MinLevel, logLevelFilter.Levels))
		return nil, nil, nil
	}

	// Create a log writer, and wrap a logOutput around it
	logWriter := NewLogWriter(512)
	logOutput := io.MultiWriter(logLevelFilter, logWriter)
	return logGate, logWriter, logOutput
}

// startAgent is used to start the agent and IPC
func (c *Command) startAgent(config *Config, agent *Agent,
	logWriter *logWriter, logOutput io.Writer) *AgentIPC {
	// Add the script event handlers
	scriptEH := &ScriptEventHandler{
		Self: serf.Member{
			Name: config.NodeName,
			Role: config.Role,
		},
		Scripts: config.EventScripts(),
		Logger:  log.New(logOutput, "", log.LstdFlags),
	}
	agent.RegisterEventHandler(scriptEH)

	// Start the agent after the handler is registered
	if err := agent.Start(); err != nil {
		c.Ui.Error(fmt.Sprintf("Failed to start the Serf agent: %v", err))
		return nil
	}

	// Setup the RPC listener
	rpcListener, err := net.Listen("tcp", config.RPCAddr)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error starting RPC listener: %s", err))
		return nil
	}

	// Start the IPC layer
	c.Ui.Output("Starting Serf agent RPC...")
	ipc := NewAgentIPC(agent, rpcListener, logOutput, logWriter)

	bindIP, bindPort, err := config.BindAddrParts()
	bindAddr := (&net.TCPAddr{IP: net.ParseIP(bindIP), Port: bindPort}).String()
	c.Ui.Output("Serf agent running!")
	c.Ui.Info(fmt.Sprintf("Node name: '%s'", config.NodeName))
	c.Ui.Info(fmt.Sprintf("Bind addr: '%s'", bindAddr))
	c.Ui.Info(fmt.Sprintf(" RPC addr: '%s'", config.RPCAddr))
	c.Ui.Info(fmt.Sprintf("Encrypted: %#v", config.EncryptKey != ""))
	c.Ui.Info(fmt.Sprintf(" Snapshot: %v", config.SnapshotPath != ""))
	c.Ui.Info(fmt.Sprintf("  Profile: %s", config.Profile))
	return ipc
}

// startupJoin is invoked to handle any joins specified to take place at start time
func (c *Command) startupJoin(config *Config, agent *Agent) error {
	if len(config.StartJoin) == 0 {
		return nil
	}

	c.Ui.Output(fmt.Sprintf("Joining cluster...(replay: %v)", config.ReplayOnJoin))
	n, err := agent.Join(config.StartJoin, config.ReplayOnJoin)
	if err != nil {
		return err
	}

	c.Ui.Info(fmt.Sprintf("Join completed. Synced with %d initial agents", n))
	return nil
}

func (c *Command) Run(args []string) int {
	c.Ui = &cli.PrefixedUi{
		OutputPrefix: "==> ",
		InfoPrefix:   "    ",
		ErrorPrefix:  "==> ",
		Ui:           c.Ui,
	}

	// Parse our configs
	config := c.readConfig(args)
	if config == nil {
		return 1
	}

	// Setup the log outputs
	logGate, logWriter, logOutput := c.setupLoggers(config)
	if logWriter == nil {
		return 1
	}

	// Setup serf
	agent := c.setupAgent(config, logOutput)
	if agent == nil {
		return 1
	}
	defer agent.Shutdown()

	// Start the agent
	ipc := c.startAgent(config, agent, logWriter, logOutput)
	if ipc == nil {
		return 1
	}
	defer ipc.Shutdown()

	// Join startup nodes if specified
	if err := c.startupJoin(config, agent); err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	// Enable log streaming
	c.Ui.Info("")
	c.Ui.Output("Log data will now stream in as it occurs:\n")
	logGate.Flush()

	// Wait for exit
	return c.handleSignals(config, agent)
}

// handleSignals blocks until we get an exit-causing signal
func (c *Command) handleSignals(config *Config, agent *Agent) int {
	signalCh := make(chan os.Signal, 4)
	signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)

	// Wait for a signal
	var sig os.Signal
	select {
	case s := <-signalCh:
		sig = s
	case <-c.ShutdownCh:
		sig = os.Interrupt
	}

	// Check if we should do a graceful leave
	graceful := false
	if sig == os.Interrupt && config.LeaveOnInt {
		graceful = true
	} else if sig == syscall.SIGTERM && config.LeaveOnTerm {
		graceful = true
	}

	// Bail fast if not doing a graceful leave
	if !graceful {
		return 1
	}

	// Attempt a graceful leave
	gracefulCh := make(chan struct{})
	c.Ui.Output("Gracefully shutting down agent...")
	go func() {
		if err := agent.Leave(); err != nil {
			c.Ui.Error(fmt.Sprintf("Error: %s", err))
			return
		}
		close(gracefulCh)
	}()

	// Wait for leave or another signal
	select {
	case <-signalCh:
		return 1
	case <-time.After(gracefulTimeout):
		return 1
	case <-gracefulCh:
		return 0
	}
}

func (c *Command) Synopsis() string {
	return "Runs a Serf agent"
}

func (c *Command) Help() string {
	helpText := `
Usage: serf agent [options]

  Starts the Serf agent and runs until an interrupt is received. The
  agent represents a single node in a cluster.

Options:

  -bind=0.0.0.0            Address to bind network listeners to
  -config-file=foo         Path to a JSON file to read configuration from.
                           This can be specified multiple times.
  -config-dir=foo          Path to a directory to read configuration files
                           from. This will read every file ending in ".json"
                           as configuration in this directory in alphabetical
                           order.
  -encrypt=foo             Key for encrypting network traffic within Serf.
                           Must be a base64-encoded 16-byte key.
  -event-handler=foo       Script to execute when events occur. This can
                           be specified multiple times. See the event scripts
                           section below for more info.
  -join=addr               An initial agent to join with. This flag can be
                           specified multiple times.
  -log-level=info          Log level of the agent.
  -node=hostname           Name of this node. Must be unique in the cluster
  -profile=[lan|wan|local] Profile is used to control the timing profiles used in Serf.
						   The default if not provided is lan.
  -protocol=n              Serf protocol version to use. This defaults to
                           the latest version, but can be set back for upgrades.
  -role=foo                The role of this node, if any. This can be used
                           by event scripts to differentiate different types
                           of nodes that may be part of the same cluster.
  -rpc-addr=127.0.0.1:7373 Address to bind the RPC listener.
  -snapshot=path/to/file   The snapshot file is used to store alive nodes and
                           event information so that Serf can rejoin a cluster
						   and avoid event replay on restart.

Event handlers:

  For more information on what event handlers are, please read the
  Serf documentation. This section will document how to configure them
  on the command-line. There are three methods of specifying an event
  handler:

  - The value can be a plain script, such as "event.sh". In this case,
    Serf will send all events to this script, and you'll be responsible
    for differentiating between them based on the SERF_EVENT.

  - The value can be in the format of "TYPE=SCRIPT", such as
    "member-join=join.sh". With this format, Serf will only send events
    of that type to that script.

  - The value can be in the format of "user:EVENT=SCRIPT", such as
    "user:deploy=deploy.sh". This means that Serf will only invoke this
    script in the case of user events named "deploy".
`
	return strings.TrimSpace(helpText)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The OpenEBS Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
	"github.com/pkg/errors"
	"k8s.io/client-go/kubernetes"
)

// ClientsetGetter abstracts fetching of kubernetes clientset
type ClientsetGetter interface {
	Get() (*kubernetes.Clientset, error)
}

type clientset struct{}

func Clientset() *clientset {
	return &clientset{}
}

// Get returns a new instance of kubernetes clientset
func (c *clientset) Get() (*kubernetes.Clientset, error) {
	config, err := Config().Get()
	if err != nil {
		return nil, errors.Wrap(err, "failed to get kubernetes clientset")
	}
	return kubernetes.NewForConfig(config)
}
<commit_msg>Fixing golint issues in clientset.go (#651)<commit_after>/*
Copyright 2018 The OpenEBS Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
	"github.com/pkg/errors"
	"k8s.io/client-go/kubernetes"
)

// ClientsetGetter abstracts fetching of kubernetes clientset
type ClientsetGetter interface {
	Get() (*kubernetes.Clientset, error)
}

type clientset struct{}

// Clientset returns a pointer to clientset struct
func Clientset() *clientset {
	return &clientset{}
}

// Get returns a new instance of kubernetes clientset
func (c *clientset) Get() (*kubernetes.Clientset, error) {
	config, err := Config().Get()
	if err != nil {
		return nil, errors.Wrap(err, "failed to get kubernetes clientset")
	}
	return kubernetes.NewForConfig(config)
}
<|endoftext|>


<commit_before>package build

import (
	"fmt"
	"github.com/mitchellh/packer/packer"
	"io/ioutil"
	"log"
	"sync"
)

type Command byte

func (Command) Run(env packer.Environment, args []string) int {
	if len(args) != 1 {
		env.Ui().Error("A single template argument is required.\n")
		return 1
	}

	// Read the file into a byte array so that we can parse the template
	log.Printf("Reading template: %s\n", args[0])
	tplData, err := ioutil.ReadFile(args[0])
	if err != nil {
		env.Ui().Error("Failed to read template file: %s\n", err.Error())
		return 1
	}

	// Parse the template into a machine-usable format
	log.Println("Parsing template...")
	tpl, err := packer.ParseTemplate(tplData)
	if err != nil {
		env.Ui().Error("Failed to parse template: %s\n", err.Error())
		return 1
	}

	// The component finder for our builds
	components := &packer.ComponentFinder{
		Builder: env.Builder,
		Hook:    env.Hook,
	}

	// Go through each builder and compile the builds that we care about
	buildNames := tpl.BuildNames()
	builds := make([]packer.Build, 0, len(buildNames))
	for _, buildName := range buildNames {
		log.Printf("Creating build: %s\n", buildName)
		build, err := tpl.Build(buildName, components)
		if err != nil {
			env.Ui().Error("Failed to create build '%s': \n\n%s\n", buildName, err.Error())
			return 1
		}

		builds = append(builds, build)
	}

	// Compile all the UIs for the builds
	buildUis := make(map[string]packer.Ui)
	for _, b := range builds {
		buildUis[b.Name()] = &packer.PrefixedUi{
			fmt.Sprintf("==> %s", b.Name()),
			env.Ui(),
		}
	}

	// Prepare all the builds
	for _, b := range builds {
		log.Printf("Preparing build: %s\n", b.Name())
		err := b.Prepare()
		if err != nil {
			env.Ui().Error("%s\n", err)
			return 1
		}
	}

	// Run all the builds in parallel and wait for them to complete
	var wg sync.WaitGroup
	for _, b := range builds {
		log.Printf("Starting build run: %s\n", b.Name())

		// Increment the waitgroup so we wait for this item to finish properly
		wg.Add(1)

		// Run the build in a goroutine
		go func() {
			defer wg.Done()
			b.Run(buildUis[b.Name()])
		}()
	}

	wg.Wait()

	env.Ui().Say("YAY!\n")
	return 0
}

func (Command) Synopsis() string {
	return "build image(s) from template"
}
<commit_msg>command/build; Remove end UI stuff<commit_after>package build

import (
	"fmt"
	"github.com/mitchellh/packer/packer"
	"io/ioutil"
	"log"
	"sync"
)

type Command byte

func (Command) Run(env packer.Environment, args []string) int {
	if len(args) != 1 {
		env.Ui().Error("A single template argument is required.\n")
		return 1
	}

	// Read the file into a byte array so that we can parse the template
	log.Printf("Reading template: %s\n", args[0])
	tplData, err := ioutil.ReadFile(args[0])
	if err != nil {
		env.Ui().Error("Failed to read template file: %s\n", err.Error())
		return 1
	}

	// Parse the template into a machine-usable format
	log.Println("Parsing template...")
	tpl, err := packer.ParseTemplate(tplData)
	if err != nil {
		env.Ui().Error("Failed to parse template: %s\n", err.Error())
		return 1
	}

	// The component finder for our builds
	components := &packer.ComponentFinder{
		Builder: env.Builder,
		Hook:    env.Hook,
	}

	// Go through each builder and compile the builds that we care about
	buildNames := tpl.BuildNames()
	builds := make([]packer.Build, 0, len(buildNames))
	for _, buildName := range buildNames {
		log.Printf("Creating build: %s\n", buildName)
		build, err := tpl.Build(buildName, components)
		if err != nil {
			env.Ui().Error("Failed to create build '%s': \n\n%s\n", buildName, err.Error())
			return 1
		}

		builds = append(builds, build)
	}

	// Compile all the UIs for the builds
	buildUis := make(map[string]packer.Ui)
	for _, b := range builds {
		buildUis[b.Name()] = &packer.PrefixedUi{
			fmt.Sprintf("==> %s", b.Name()),
			env.Ui(),
		}
	}

	// Prepare all the builds
	for _, b := range builds {
		log.Printf("Preparing build: %s\n", b.Name())
		err := b.Prepare()
		if err != nil {
			env.Ui().Error("%s\n", err)
			return 1
		}
	}

	// Run all the builds in parallel and wait for them to complete
	var wg sync.WaitGroup
	for _, b := range builds {
		log.Printf("Starting build run: %s\n", b.Name())

		// Increment the waitgroup so we wait for this item to finish properly
		wg.Add(1)

		// Run the build in a goroutine
		go func() {
			defer wg.Done()
			b.Run(buildUis[b.Name()])
		}()
	}

	wg.Wait()
	return 0
}

func (Command) Synopsis() string {
	return "build image(s) from template"
}
<|endoftext|>