<commit_before>package yo

import (
	"encoding/json"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/igungor/ilberbot"
)

func init() {
	ilberbot.RegisterCommand("/yo", image)
}

var (
	baseURL = "https://ajax.googleapis.com/ajax/services/search/images"
)

type Response struct {
	ResponseData struct {
		Results []Image `json:"results"`
	} `json:"responseData"`
}

type Image struct {
	UnescapedURL string `json:"unescapedURL"`
}

func image(args ...string) string {
	if args == nil {
		log.Println("image: no argument supplied")
		return ""
	}

	arg := strings.Join(args, "+")

	u, _ := url.Parse(baseURL)
	v := u.Query()
	v.Set("q", "Yiğit+Özgür+"+arg)
	v.Set("v", "1.0")
	u.RawQuery = v.Encode()

	resp, err := http.Get(u.String())
	if err != nil {
		log.Println(err)
		return ""
	}
	defer resp.Body.Close()

	var response Response
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		log.Printf("decode error: %v", err)
		return ""
	}

	if len(response.ResponseData.Results) == 0 {
		return "yok boyle bisi"
	}

	return response.ResponseData.Results[0].UnescapedURL

}
<commit_msg>rename yo function signature<commit_after>package yo

import (
	"encoding/json"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/igungor/ilberbot"
)

func init() {
	ilberbot.RegisterCommand("/yo", yo)
}

var (
	baseURL = "https://ajax.googleapis.com/ajax/services/search/images"
)

type Response struct {
	ResponseData struct {
		Results []Image `json:"results"`
	} `json:"responseData"`
}

type Image struct {
	UnescapedURL string `json:"unescapedURL"`
}

func yo(args ...string) string {
	if args == nil {
		log.Println("image: no argument supplied")
		return ""
	}

	arg := strings.Join(args, "+")

	u, _ := url.Parse(baseURL)
	v := u.Query()
	v.Set("q", "Yiğit+Özgür+"+arg)
	v.Set("v", "1.0")
	u.RawQuery = v.Encode()

	resp, err := http.Get(u.String())
	if err != nil {
		log.Println(err)
		return ""
	}
	defer resp.Body.Close()

	var response Response
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		log.Printf("decode error: %v", err)
		return ""
	}

	if len(response.ResponseData.Results) == 0 {
		return "yok boyle bisi"
	}

	return response.ResponseData.Results[0].UnescapedURL

}
<|endoftext|>


<commit_before>// sample run : ./server --cand testCand.csv --quiz testYML

package main

import (
	"bytes"
	"encoding/csv"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"os"
	"reflect"
	"time"

	"google.golang.org/grpc"

	"github.com/dgraph-io/dgraph/x"
	"github.com/dgraph-io/gru/server/interact"
	"golang.org/x/net/context"
	"gopkg.in/yaml.v2"
)

var data string
var glog = x.Log("Gru Server")

type server struct{}

func (s *server) SendQuestion(ctx context.Context,
	req *interact.Req) (*interact.Question, error) {

	var que *interact.Question
	var err error

	que = getNextQuestion()

	return que, err
}

func (s *server) SendAnswer(ctx context.Context,
	resp *interact.Response) (*interact.Status, error) {

	var status *interact.Status
	var err error

	fmt.Println(resp.Aid)

	status.Status, err = isCorrectAnswer(resp.Qid, resp.Aid, resp.Token)

	// Check for end of test and change status
	return status, err
}

func isCorrectAnswer(qid string, opts []string, token string) (int64, error) {

	for _, que := range quizInfo[candidateInfo[token].tname] {
		if que.Qid == qid {
			if reflect.DeepEqual(opts, que.Correct) {
				return 1, nil
			} else {
				return 2, nil
			}
		}
	}

	return -1, errors.New("No matching question")
}

func getNextQuestion() *interact.Question {

	idx := rand.Intn(3)

	var opts []*interact.Answer

	for _, mp := range quizInfo["test"][idx].Opt {
		it := &interact.Answer{
			Id:  mp["uid"],
			Ans: mp["str"],
		}

		opts = append(opts, it)
	}

	que := &interact.Question{
		Qid:      quizInfo["test"][idx].Qid,
		Question: quizInfo["test"][idx].Question,
		Options:  opts,
		/*[]*interact.Answer{
			&interact.Answer{
				Id:  "A",
				Ans: "Alice",
			},
			&interact.Answer{
				Id:  "B",
				Ans: "BoB",
			},
			&interact.Answer{
				Id:  "C",
				Ans: "A\nB",
			},
			&interact.Answer{
				Id:  "D",
				Ans: "Mallory",
			},
		},*/
		IsMultiple: true,
		Positive:   quizInfo["test"][idx].Score,
		Negative:   quizInfo["test"][idx].Score,
		Totscore:   15.0,
	}
	return que
}

func runGrpcServer(address string) {
	ln, err := net.Listen("tcp", address)
	if err != nil {
		glog.WithField("err", err).Fatalf("Error running quiz server")
		return
	}
	glog.WithField("address", ln.Addr()).Info("Server listening")

	s := grpc.NewServer()
	interact.RegisterGruQuizServer(s, &server{})
	if err = s.Serve(ln); err != nil {
		glog.Fatalf("While serving gRpc requests", err)
	}
	return
}

type Option struct {
	Uid string
	Str string
}

type T struct {
	Qid      string
	Question string
	Correct  []string
	Opt      []map[string]string
	Score    float32
	Tag      string
}

type candidate struct {
	token          string
	name           string
	candidateEmail string
	valid          time.Duration
	invitorEmail   string
	tname          string
}

type session struct {
	id                string
	questionSeq       []string
	attemptedQuestion map[string]string
	totScore          float32
}

var (
	quizFile      = flag.String("quiz", "", "Input question file")
	port          = flag.String("port", ":8888", "Port on which server listens")
	candFile      = flag.String("cand", "", "Candidate inforamation file")
	quizInfo      map[interface{}][]T
	candidateInfo map[string]*candidate
	sessionInfo   map[string]*session
)

func parseCandidateInfo(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return err
	}
	defer f.Close()

	csvr := csv.NewReader(f)

	for {
		row, err := csvr.Read()
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return err
		}

		cand := &candidate{}

		fmt.Println(row, cand)
		// store info in struct

	}
	return nil
}

func main() {
	flag.Parse()

	buf := bytes.NewBuffer(nil)
	f, _ := os.Open(*quizFile)
	io.Copy(buf, f)
	data := buf.Bytes()

	parseCandidateInfo(*candFile)

	err := yaml.Unmarshal(data, &quizInfo)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- m:\n%v\n\n", quizInfo["test"])

	_, err = yaml.Marshal(&quizInfo)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	//fmt.Printf("--- m dump:\n%s\n\n", string(d))

	runGrpcServer(*port)
}
<commit_msg>handle isMultiple, update scores, check correctness<commit_after>// sample run : ./server --cand testCand.csv --quiz testYML

package main

import (
	"bytes"
	"encoding/csv"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"os"
	"reflect"
	"time"

	"google.golang.org/grpc"

	"github.com/dgraph-io/dgraph/x"
	"github.com/dgraph-io/gru/server/interact"
	"golang.org/x/net/context"
	"gopkg.in/yaml.v2"
)

var data string
var glog = x.Log("Gru Server")
var totScore float32

type server struct{}

func (s *server) SendQuestion(ctx context.Context,
	req *interact.Req) (*interact.Question, error) {

	var que *interact.Question
	var err error

	que = getNextQuestion()

	return que, err
}

func (s *server) SendAnswer(ctx context.Context,
	resp *interact.Response) (*interact.Status, error) {

	var status interact.Status
	var err error
	var idx int

	fmt.Println(resp.Aid)

	status.Status, err = isCorrectAnswer(resp.Qid, resp.Aid, resp.Token)

	for i, que := range quizInfo["test"] {
		if que.Qid == resp.Qid {
			idx = i
		}
	}

	if status.Status == 1 {
		totScore += quizInfo["test"][idx].Score
	} else {
		totScore -= quizInfo["test"][idx].Score
	}

	fmt.Println(status.Status, totScore)

	// Check for end of test and change status
	return &status, err
}

func isCorrectAnswer(qid string, opts []string, token string) (int64, error) {

	for _, que := range quizInfo["test"] {
		if que.Qid == qid {
			if reflect.DeepEqual(opts, que.Correct) {
				return 1, nil
			} else {
				return 2, nil
			}
		}
	}

	return -1, errors.New("No matching question")
}

func getNextQuestion() *interact.Question {

	idx := rand.Intn(3)

	var opts []*interact.Answer

	for _, mp := range quizInfo["test"][idx].Opt {
		it := &interact.Answer{
			Id:  mp["uid"],
			Ans: mp["str"],
		}

		opts = append(opts, it)
	}

	var isM bool

	if len(quizInfo["test"][idx].Correct) > 1 {
		isM = true
	}

	que := &interact.Question{
		Qid:      quizInfo["test"][idx].Qid,
		Question: quizInfo["test"][idx].Question,
		Options:  opts,
		IsMultiple: isM,
		Positive:   quizInfo["test"][idx].Score,
		Negative:   quizInfo["test"][idx].Score,
		Totscore:   totScore,
	}
	return que
}

func runGrpcServer(address string) {
	ln, err := net.Listen("tcp", address)
	if err != nil {
		glog.WithField("err", err).Fatalf("Error running quiz server")
		return
	}
	glog.WithField("address", ln.Addr()).Info("Server listening")

	s := grpc.NewServer()
	interact.RegisterGruQuizServer(s, &server{})
	if err = s.Serve(ln); err != nil {
		glog.Fatalf("While serving gRpc requests", err)
	}
	return
}

type Option struct {
	Uid string
	Str string
}

type T struct {
	Qid      string
	Question string
	Correct  []string
	Opt      []map[string]string
	Score    float32
	Tag      string
}

type candidate struct {
	token          string
	name           string
	candidateEmail string
	valid          time.Duration
	invitorEmail   string
	tname          string
}

type session struct {
	id                string
	questionSeq       []string
	attemptedQuestion map[string]string
	totScore          float32
}

var (
	quizFile      = flag.String("quiz", "", "Input question file")
	port          = flag.String("port", ":8888", "Port on which server listens")
	candFile      = flag.String("cand", "", "Candidate inforamation file")
	quizInfo      map[interface{}][]T
	candidateInfo map[string]*candidate
	sessionInfo   map[string]*session
)

func parseCandidateInfo(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return err
	}
	defer f.Close()

	csvr := csv.NewReader(f)

	for {
		row, err := csvr.Read()
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return err
		}

		cand := &candidate{}

		fmt.Println(row, cand)
		// store info in struct

	}
	return nil
}

func main() {
	flag.Parse()

	buf := bytes.NewBuffer(nil)
	f, _ := os.Open(*quizFile)
	io.Copy(buf, f)
	data := buf.Bytes()

	parseCandidateInfo(*candFile)

	err := yaml.Unmarshal(data, &quizInfo)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	fmt.Printf("--- m:\n%v\n\n", quizInfo["test"])

	_, err = yaml.Marshal(&quizInfo)
	if err != nil {
		log.Fatalf("error: %v", err)
	}
	//fmt.Printf("--- m dump:\n%s\n\n", string(d))

	runGrpcServer(*port)
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"github.com/mikeyhu/glipso/interfaces"
)

func plusAll(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	all := I(0)
	for _, v := range arguments {
		all += v.(I)
	}
	return all
}

func minusAll(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var all I
	head := true
	for _, v := range arguments {
		if head {
			all = v.(I)
			head = false
		} else {
			all -= v.(I)
		}
	}
	return all
}

func multiplyAll(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var all I
	head := true
	for _, v := range arguments {
		if head {
			all = v.(I)
			head = false
		} else {
			all *= v.(I)
		}
	}
	return all
}

func mod(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	a, aok := arguments[0].(I)
	b, bok := arguments[1].(I)
	if aok && bok {
		return I(a % b)
	}
	panic("Mod : unsupported type")
}

func equals(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Equalable)
	second, sok := arguments[1].(interfaces.Equalable)

	if fok && sok {
		return first.Equals(second)
	}
	panic(fmt.Sprintf("Equals : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func lessThan(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) < 0)
	}
	panic(fmt.Sprintf("LessThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func lessThanEqual(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) <= 0)
	}
	panic(fmt.Sprintf("LessThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func greaterThan(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) > 0)
	}
	panic(fmt.Sprintf("GreaterThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func greaterThanEqual(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) >= 0)
	}
	panic(fmt.Sprintf("GreaterThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func cons(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	if len(arguments) == 0 {
		return P{}
	} else if len(arguments) == 1 {
		return P{arguments[0], nil}
	} else if len(arguments) == 2 {
		tail, ok := arguments[1].(P)
		if ok {
			return P{arguments[0], &tail}
		}
	}
	return P{}
}

func first(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	pair, ok := arguments[0].(P)
	if ok {
		return pair.head
	}
	fmt.Printf("pair? %v : %v\n", arguments[0], pair)
	panic("Panic - Cannot get head of non Pair type")
}

func tail(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	pair, ok := arguments[0].(P)
	if ok {
		return *pair.tail
	}
	panic("Panic - Cannot get tail of non Pair type")
}

func apply(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	if ap, okEval := arguments[1].(interfaces.Evaluatable); okEval {
		arguments[1] = ap.Evaluate(sco)
	}
	s, okRef := arguments[0].(REF)
	p, okPair := arguments[1].(interfaces.Iterable)
	if !okRef {
		panic(fmt.Sprintf("Panic - expected function, found %v", arguments[0]))
	} else if !okPair {
		panic(fmt.Sprintf("Panic - expected pair, found %v", arguments[1]))
	}
	return &EXP{Function: s, Arguments: p.ToSlice(sco.NewChildScope())}
}

func iff(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var test interfaces.Type
	if exp, ok := arguments[0].(*EXP); ok {
		test = exp.Evaluate(sco)
	} else {
		test = arguments[0]
	}
	if test.(B).Bool() {
		return arguments[1]
	}
	return arguments[2]
}

func def(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var value interfaces.Type
	if eval, ok := arguments[1].(interfaces.Evaluatable); ok {
		value = eval.Evaluate(sco.NewChildScope())
	} else {
		value = arguments[1]
	}
	return GlobalEnvironment.CreateRef(arguments[0].(REF).EvaluateToRef(sco.NewChildScope()), value)
}

func do(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var result interfaces.Type
	for _, a := range arguments {
		if e, ok := a.(interfaces.Evaluatable); ok {
			result = e.Evaluate(sco.NewChildScope())
		} else {
			result = a
		}
	}
	return result
}

func rnge(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	start := arguments[0].(I)
	end := arguments[1].(I)
	if start < end {
		return LAZYP{
			start,
			&EXP{Function: REF("range"), Arguments: []interfaces.Type{
				I(start.Int() + 1),
				end,
			}}}
	}
	return LAZYP{end, nil}

}

func fn(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var argVec VEC
	if args, ok := arguments[0].(REF); ok {
		argVec = args.Evaluate(sco).(VEC)
	} else {
		argVec = arguments[0].(VEC)
	}

	if arg1, ok := arguments[1].(REF); ok {
		return FN{argVec, arg1.Evaluate(sco.NewChildScope()).(*EXP)}
	}
	return FN{argVec, arguments[1].(*EXP)}
}

func filter(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	fn, fnok := arguments[0].(FN)
	pair, pok := arguments[1].(P)

	var flt func(*P) *P
	flt = func(p *P) *P {
		head := p.head
		res := (&EXP{Function: fn, Arguments: []interfaces.Type{head}}).Evaluate(sco.NewChildScope())
		if include, iok := res.(B); iok {
			if bool(include) {
				if p.tail != nil {
					return &P{head, flt(p.tail)}
				}
				return &P{head, nil}
			} else if p.tail != nil {
				return flt(p.tail)
			}
			return nil
		}
		panic(fmt.Sprintf("filter : expected boolean value, recieved %v", res))
	}

	if fnok && pok {
		return *flt(&pair)
	}
	panic("filter : expected function and list")
}

func mapp(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	fn, fnok := arguments[0].(FN)
	pair, pok := arguments[1].(P)

	var mp func(*P) *P
	mp = func(p *P) *P {
		head := p.head
		res := (&EXP{Function: fn, Arguments: []interfaces.Type{head}}).Evaluate(sco.NewChildScope())
		if p.tail != nil {
			return &P{res, mp(p.tail)}
		}
		return &P{res, nil}
	}

	if fnok && pok {
		return *mp(&pair)
	}
	panic("map : expected function and list ")
}

func lazypair(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	fmt.Printf("lazypair\n")
	sco.(Environment).DisplayEnvironment()
	head := arguments[0]
	if h, ok := head.(interfaces.Evaluatable); ok {
		head = h.Evaluate(sco)
	}
	if len(arguments) > 1 {
		if tail, ok := arguments[1].(interfaces.Evaluatable); ok {
			fmt.Println("Adding Deferred Eval to tail: ", tail)
			sco.(Environment).DisplayEnvironment()

			return LAZYP{head, BindEvaluation(tail, sco)}
		}
		panic(fmt.Sprintf("lazypair : expected EXP got %v", arguments[1]))
	}
	return LAZYP{head, nil}
}

type evaluator func([]interfaces.Type, interfaces.Scope) interfaces.Type

type FunctionInfo struct {
	function     evaluator
	evaluateArgs bool
}

var inbuilt map[string]FunctionInfo

func init() {
	inbuilt = map[string]FunctionInfo{
		"cons":     {cons, true},
		"first":    {first, true},
		"tail":     {tail, true},
		"=":        {equals, true},
		"+":        {plusAll, true},
		"-":        {minusAll, true},
		"*":        {multiplyAll, true},
		"%":        {mod, true},
		"<":        {lessThan, true},
		">":        {greaterThan, true},
		"<=":       {lessThanEqual, true},
		">=":       {greaterThanEqual, true},
		"apply":    {apply, false},
		"if":       {iff, false},
		"def":      {def, false},
		"do":       {do, false},
		"range":    {rnge, true},
		"fn":       {fn, false},
		"filter":   {filter, true},
		"map":      {mapp, true},
		"lazypair": {lazypair, false},
	}
}
<commit_msg>Reorder and move inbuilt function lookup<commit_after>package common

import (
	"fmt"
	"github.com/mikeyhu/glipso/interfaces"
)

type evaluator func([]interfaces.Type, interfaces.Scope) interfaces.Type

type FunctionInfo struct {
	function     evaluator
	evaluateArgs bool
}

var inbuilt map[string]FunctionInfo

func init() {
	inbuilt = map[string]FunctionInfo{
		"=":        {equals, true},
		"+":        {plusAll, true},
		"-":        {minusAll, true},
		"*":        {multiplyAll, true},
		"%":        {mod, true},
		"<":        {lessThan, true},
		">":        {greaterThan, true},
		"<=":       {lessThanEqual, true},
		">=":       {greaterThanEqual, true},
		"apply":    {apply, false},
		"cons":     {cons, true},
		"def":      {def, false},
		"do":       {do, false},
		"if":       {iff, false},
		"filter":   {filter, true},
		"first":    {first, true},
		"fn":       {fn, false},
		"lazypair": {lazypair, false},
		"map":      {mapp, true},
		"range":    {rnge, true},
		"tail":     {tail, true},
	}
}

func plusAll(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	all := I(0)
	for _, v := range arguments {
		all += v.(I)
	}
	return all
}

func minusAll(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var all I
	head := true
	for _, v := range arguments {
		if head {
			all = v.(I)
			head = false
		} else {
			all -= v.(I)
		}
	}
	return all
}

func multiplyAll(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var all I
	head := true
	for _, v := range arguments {
		if head {
			all = v.(I)
			head = false
		} else {
			all *= v.(I)
		}
	}
	return all
}

func mod(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	a, aok := arguments[0].(I)
	b, bok := arguments[1].(I)
	if aok && bok {
		return I(a % b)
	}
	panic("Mod : unsupported type")
}

func equals(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Equalable)
	second, sok := arguments[1].(interfaces.Equalable)

	if fok && sok {
		return first.Equals(second)
	}
	panic(fmt.Sprintf("Equals : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func lessThan(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) < 0)
	}
	panic(fmt.Sprintf("LessThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func lessThanEqual(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) <= 0)
	}
	panic(fmt.Sprintf("LessThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func greaterThan(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) > 0)
	}
	panic(fmt.Sprintf("GreaterThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func greaterThanEqual(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	first, fok := arguments[0].(interfaces.Comparable)
	second, sok := arguments[1].(interfaces.Comparable)
	if fok && sok {
		return B(first.CompareTo(second) >= 0)
	}
	panic(fmt.Sprintf("GreaterThan : unsupported type %v  or %v\n", arguments[0], arguments[1]))
}

func cons(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	if len(arguments) == 0 {
		return P{}
	} else if len(arguments) == 1 {
		return P{arguments[0], nil}
	} else if len(arguments) == 2 {
		tail, ok := arguments[1].(P)
		if ok {
			return P{arguments[0], &tail}
		}
	}
	return P{}
}

func first(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	pair, ok := arguments[0].(P)
	if ok {
		return pair.head
	}
	fmt.Printf("pair? %v : %v\n", arguments[0], pair)
	panic("Panic - Cannot get head of non Pair type")
}

func tail(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	pair, ok := arguments[0].(P)
	if ok {
		return *pair.tail
	}
	panic("Panic - Cannot get tail of non Pair type")
}

func apply(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	if ap, okEval := arguments[1].(interfaces.Evaluatable); okEval {
		arguments[1] = ap.Evaluate(sco)
	}
	s, okRef := arguments[0].(REF)
	p, okPair := arguments[1].(interfaces.Iterable)
	if !okRef {
		panic(fmt.Sprintf("Panic - expected function, found %v", arguments[0]))
	} else if !okPair {
		panic(fmt.Sprintf("Panic - expected pair, found %v", arguments[1]))
	}
	return &EXP{Function: s, Arguments: p.ToSlice(sco.NewChildScope())}
}

func iff(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var test interfaces.Type
	if exp, ok := arguments[0].(*EXP); ok {
		test = exp.Evaluate(sco)
	} else {
		test = arguments[0]
	}
	if test.(B).Bool() {
		return arguments[1]
	}
	return arguments[2]
}

func def(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var value interfaces.Type
	if eval, ok := arguments[1].(interfaces.Evaluatable); ok {
		value = eval.Evaluate(sco.NewChildScope())
	} else {
		value = arguments[1]
	}
	return GlobalEnvironment.CreateRef(arguments[0].(REF).EvaluateToRef(sco.NewChildScope()), value)
}

func do(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var result interfaces.Type
	for _, a := range arguments {
		if e, ok := a.(interfaces.Evaluatable); ok {
			result = e.Evaluate(sco.NewChildScope())
		} else {
			result = a
		}
	}
	return result
}

func rnge(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	start := arguments[0].(I)
	end := arguments[1].(I)
	if start < end {
		return LAZYP{
			start,
			&EXP{Function: REF("range"), Arguments: []interfaces.Type{
				I(start.Int() + 1),
				end,
			}}}
	}
	return LAZYP{end, nil}

}

func fn(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	var argVec VEC
	if args, ok := arguments[0].(REF); ok {
		argVec = args.Evaluate(sco).(VEC)
	} else {
		argVec = arguments[0].(VEC)
	}

	if arg1, ok := arguments[1].(REF); ok {
		return FN{argVec, arg1.Evaluate(sco.NewChildScope()).(*EXP)}
	}
	return FN{argVec, arguments[1].(*EXP)}
}

func filter(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	fn, fnok := arguments[0].(FN)
	pair, pok := arguments[1].(P)

	var flt func(*P) *P
	flt = func(p *P) *P {
		head := p.head
		res := (&EXP{Function: fn, Arguments: []interfaces.Type{head}}).Evaluate(sco.NewChildScope())
		if include, iok := res.(B); iok {
			if bool(include) {
				if p.tail != nil {
					return &P{head, flt(p.tail)}
				}
				return &P{head, nil}
			} else if p.tail != nil {
				return flt(p.tail)
			}
			return nil
		}
		panic(fmt.Sprintf("filter : expected boolean value, recieved %v", res))
	}

	if fnok && pok {
		return *flt(&pair)
	}
	panic("filter : expected function and list")
}

func mapp(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	fn, fnok := arguments[0].(FN)
	pair, pok := arguments[1].(P)

	var mp func(*P) *P
	mp = func(p *P) *P {
		head := p.head
		res := (&EXP{Function: fn, Arguments: []interfaces.Type{head}}).Evaluate(sco.NewChildScope())
		if p.tail != nil {
			return &P{res, mp(p.tail)}
		}
		return &P{res, nil}
	}

	if fnok && pok {
		return *mp(&pair)
	}
	panic("map : expected function and list ")
}

func lazypair(arguments []interfaces.Type, sco interfaces.Scope) interfaces.Type {
	fmt.Printf("lazypair\n")
	sco.(Environment).DisplayEnvironment()
	head := arguments[0]
	if h, ok := head.(interfaces.Evaluatable); ok {
		head = h.Evaluate(sco)
	}
	if len(arguments) > 1 {
		if tail, ok := arguments[1].(interfaces.Evaluatable); ok {
			fmt.Println("Adding Deferred Eval to tail: ", tail)
			sco.(Environment).DisplayEnvironment()

			return LAZYP{head, BindEvaluation(tail, sco)}
		}
		panic(fmt.Sprintf("lazypair : expected EXP got %v", arguments[1]))
	}
	return LAZYP{head, nil}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"strconv"
	"time"

	"github.com/brotherlogic/goserver"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pbd "github.com/brotherlogic/discovery/proto"
	pb "github.com/brotherlogic/gobuildmaster/proto"
	pbs "github.com/brotherlogic/gobuildslave/proto"
)

const (
	intentWait = 1000
)

// Server the main server type
type Server struct {
	*goserver.GoServer
	config  *pb.Config
	serving bool
}

type mainChecker struct{}

func getIP(servertype, servername string) (string, int) {
	conn, _ := grpc.Dial("192.168.86.64:50055", grpc.WithInsecure())
	defer conn.Close()

	registry := pbd.NewDiscoveryServiceClient(conn)
	r, err := registry.ListAllServices(context.Background(), &pbd.Empty{})
	if err != nil {
		return "", -1
	}
	for _, s := range r.Services {
		log.Printf("Does %v = %v and %v = %v?", s.Name, servertype, s.Identifier, servername)
		if s.Name == servertype && s.Identifier == servername {
			log.Printf("RETURNING: %v", s)
			return s.Ip, int(s.Port)
		}
	}

	return "", -1
}

func (t *mainChecker) assess(server string) (*pbs.JobList, *pbs.Config) {
	list := &pbs.JobList{}
	conf := &pbs.Config{}

	log.Printf("Assessing server %v", server)
	ip, port := getIP("gobuildslave", server)
	conn, _ := grpc.Dial(ip+":"+strconv.Itoa(port), grpc.WithInsecure())
	defer conn.Close()

	slave := pbs.NewGoBuildSlaveClient(conn)
	r, err := slave.List(context.Background(), &pbs.Empty{})
	if err != nil {
		log.Printf("Err %v", err)
		return list, conf
	}

	r2, err := slave.GetConfig(context.Background(), &pbs.Empty{})
	if err != nil {
		log.Printf("Err %v", err)
		return list, conf
	}

	return r, r2
}

func runJob(job *pbs.JobSpec, server string) {
	log.Printf("RUNNING: %v on %v", job, server)
	if server != "" {
		ip, port := getIP("gobuildslave", server)
		conn, _ := grpc.Dial(ip+":"+strconv.Itoa(port), grpc.WithInsecure())
		defer conn.Close()

		slave := pbs.NewGoBuildSlaveClient(conn)
		slave.Run(context.Background(), job)
		log.Printf("RUN COMMAND SENT %v", job)
	}
}

func (t *mainChecker) discover() *pbd.ServiceList {
	ret := &pbd.ServiceList{}

	conn, _ := grpc.Dial("192.168.86.64:50055", grpc.WithInsecure())
	defer conn.Close()

	registry := pbd.NewDiscoveryServiceClient(conn)
	r, err := registry.ListAllServices(context.Background(), &pbd.Empty{})
	log.Printf("DISCOVERED: %v", r)
	if err == nil {
		for _, s := range r.Services {
			ret.Services = append(ret.Services, s)
		}
	}

	return ret
}

// DoRegister Registers this server
func (s Server) DoRegister(server *grpc.Server) {
	pb.RegisterGoBuildMasterServer(server, &s)
}

// ReportHealth determines if the server is healthy
func (s Server) ReportHealth() bool {
	return true
}

// Mote promotes/demotes this server
func (s Server) Mote(master bool) error {
	return nil
}

//Compare compares current state to desired state
func (s Server) Compare(ctx context.Context, in *pb.Empty) (*pb.CompareResponse, error) {
	resp := &pb.CompareResponse{}
	list, _ := getFleetStatus(&mainChecker{})
	cc := &pb.Config{}
	for _, jlist := range list {
		for _, job := range jlist.GetDetails() {
			cc.Intents = append(cc.Intents, &pb.Intent{Spec: job.GetSpec()})
		}
	}
	resp.Current = cc
	resp.Desired = s.config

	return resp, nil
}

func getConfig(c checker) *pb.Config {
	list, _ := getFleetStatus(c)
	config := &pb.Config{}

	for _, jlist := range list {
		for _, job := range jlist.Details {
			found := false
			for _, ij := range config.Intents {
				if job.Spec.Name == ij.Spec.Name {
					ij.Count++
					found = true
				}
			}

			if !found {
				config.Intents = append(config.Intents, &pb.Intent{Spec: &pbs.JobSpec{Name: job.Spec.Name}, Count: 1})
			}
		}
	}

	return config
}

// MatchIntent tries to match the intent with the state of production
func (s Server) MatchIntent() {
	for s.serving {
		time.Sleep(intentWait)

		state := getConfig(&mainChecker{})
		diff := configDiff(s.config, state)
		joblist := runJobs(diff)
		log.Printf("FOUND %v from %v and %v", joblist, state, s.config)
		for _, job := range joblist {
			runJob(job, chooseServer(job, &mainChecker{}))
		}
	}
}

func main() {
	config, err := loadConfig("config.pb")
	if err != nil {
		log.Fatalf("Fatal loading of config: %v", err)
	}

	var sync = flag.Bool("once", false, "One pass intent match")
	s := Server{&goserver.GoServer{}, config, true}

	var quiet = flag.Bool("quiet", true, "Show all output")
	flag.Parse()

	if *quiet {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}

	if *sync {
		s.MatchIntent()
	} else {
		s.Register = s
		s.PrepServer()
		s.RegisterServer("gobuildmaster", false)
		s.RegisterServingTask(s.MatchIntent)
		s.Serve()
	}
}
<commit_msg>Fixed time spec<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"strconv"
	"time"

	"github.com/brotherlogic/goserver"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pbd "github.com/brotherlogic/discovery/proto"
	pb "github.com/brotherlogic/gobuildmaster/proto"
	pbs "github.com/brotherlogic/gobuildslave/proto"
)

const (
	intentWait = time.Second
)

// Server the main server type
type Server struct {
	*goserver.GoServer
	config  *pb.Config
	serving bool
}

type mainChecker struct{}

func getIP(servertype, servername string) (string, int) {
	conn, _ := grpc.Dial("192.168.86.64:50055", grpc.WithInsecure())
	defer conn.Close()

	registry := pbd.NewDiscoveryServiceClient(conn)
	r, err := registry.ListAllServices(context.Background(), &pbd.Empty{})
	if err != nil {
		return "", -1
	}
	for _, s := range r.Services {
		log.Printf("Does %v = %v and %v = %v?", s.Name, servertype, s.Identifier, servername)
		if s.Name == servertype && s.Identifier == servername {
			log.Printf("RETURNING: %v", s)
			return s.Ip, int(s.Port)
		}
	}

	return "", -1
}

func (t *mainChecker) assess(server string) (*pbs.JobList, *pbs.Config) {
	list := &pbs.JobList{}
	conf := &pbs.Config{}

	log.Printf("Assessing server %v", server)
	ip, port := getIP("gobuildslave", server)
	conn, _ := grpc.Dial(ip+":"+strconv.Itoa(port), grpc.WithInsecure())
	defer conn.Close()

	slave := pbs.NewGoBuildSlaveClient(conn)
	r, err := slave.List(context.Background(), &pbs.Empty{})
	if err != nil {
		log.Printf("Err %v", err)
		return list, conf
	}

	r2, err := slave.GetConfig(context.Background(), &pbs.Empty{})
	if err != nil {
		log.Printf("Err %v", err)
		return list, conf
	}

	return r, r2
}

func runJob(job *pbs.JobSpec, server string) {
	log.Printf("RUNNING: %v on %v", job, server)
	if server != "" {
		ip, port := getIP("gobuildslave", server)
		conn, _ := grpc.Dial(ip+":"+strconv.Itoa(port), grpc.WithInsecure())
		defer conn.Close()

		slave := pbs.NewGoBuildSlaveClient(conn)
		slave.Run(context.Background(), job)
		log.Printf("RUN COMMAND SENT %v", job)
	}
}

func (t *mainChecker) discover() *pbd.ServiceList {
	ret := &pbd.ServiceList{}

	conn, _ := grpc.Dial("192.168.86.64:50055", grpc.WithInsecure())
	defer conn.Close()

	registry := pbd.NewDiscoveryServiceClient(conn)
	r, err := registry.ListAllServices(context.Background(), &pbd.Empty{})
	log.Printf("DISCOVERED: %v", r)
	if err == nil {
		for _, s := range r.Services {
			ret.Services = append(ret.Services, s)
		}
	}

	return ret
}

// DoRegister Registers this server
func (s Server) DoRegister(server *grpc.Server) {
	pb.RegisterGoBuildMasterServer(server, &s)
}

// ReportHealth determines if the server is healthy
func (s Server) ReportHealth() bool {
	return true
}

// Mote promotes/demotes this server
func (s Server) Mote(master bool) error {
	return nil
}

//Compare compares current state to desired state
func (s Server) Compare(ctx context.Context, in *pb.Empty) (*pb.CompareResponse, error) {
	resp := &pb.CompareResponse{}
	list, _ := getFleetStatus(&mainChecker{})
	cc := &pb.Config{}
	for _, jlist := range list {
		for _, job := range jlist.GetDetails() {
			cc.Intents = append(cc.Intents, &pb.Intent{Spec: job.GetSpec()})
		}
	}
	resp.Current = cc
	resp.Desired = s.config

	return resp, nil
}

func getConfig(c checker) *pb.Config {
	list, _ := getFleetStatus(c)
	config := &pb.Config{}

	for _, jlist := range list {
		for _, job := range jlist.Details {
			found := false
			for _, ij := range config.Intents {
				if job.Spec.Name == ij.Spec.Name {
					ij.Count++
					found = true
				}
			}

			if !found {
				config.Intents = append(config.Intents, &pb.Intent{Spec: &pbs.JobSpec{Name: job.Spec.Name}, Count: 1})
			}
		}
	}

	return config
}

// MatchIntent tries to match the intent with the state of production
func (s Server) MatchIntent() {
	for s.serving {
		time.Sleep(intentWait)

		state := getConfig(&mainChecker{})
		diff := configDiff(s.config, state)
		joblist := runJobs(diff)
		log.Printf("FOUND %v from %v and %v", joblist, state, s.config)
		for _, job := range joblist {
			runJob(job, chooseServer(job, &mainChecker{}))
		}
	}
}

func main() {
	config, err := loadConfig("config.pb")
	if err != nil {
		log.Fatalf("Fatal loading of config: %v", err)
	}

	var sync = flag.Bool("once", false, "One pass intent match")
	s := Server{&goserver.GoServer{}, config, true}

	var quiet = flag.Bool("quiet", true, "Show all output")
	flag.Parse()

	if *quiet {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}

	if *sync {
		s.MatchIntent()
	} else {
		s.Register = s
		s.PrepServer()
		s.RegisterServer("gobuildmaster", false)
		s.RegisterServingTask(s.MatchIntent)
		s.Serve()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/hfern/goseq"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type MasterQueryOptions struct {
	Region string `long:"region" short:"r" default:"USW" description:"Region code to get results for. See --list-regions"`
	Serial bool   `long:"serial" short:"s" default:"false" description:"Force serial querying of the servers."`
	Fields string `long:"fields" default:"ip=21,name" description:"The fields to be included. Optionally includes the min-length space. See --list-fields" `
	// TODO(hunter): Add this
	ShowFields bool `long:"show-fields" default:"false" description:"Print details on each available field."`
	// TODO(hunter): Add this
	MasterIP string `long:"ip" default:"" description:"IP of the Master server to query."`
	Divider  string `long:"divider" default:" ¦ " description:"Characters used to seperate fields."`
	// TODO(hunter): Add this
	StartIP          string `long:"start" default:"" description:"Where to start reading IPs from. Defaults to start of list."`
	Limit            int    `long:"limit" short:"l" default:"0" description:"Limit the result set to n successful rows."`
	NoHeader         bool   `long:"no-header" default:"false" description:"Don't show header w/ column names."`
	ShowUnreachable  bool   `long:"unreachable" short:"U" default:"false" description:"Show unreachable servers (couldn't be connected to)."`
	ShowErrorSummary bool   `long:"errors" short:"E" default:"false" description:"Show error summary at end of list."`
	// TODO(hunter): Add this
	Filters map[string]string `long:"filter" short:"f" description:"Filters to use. See --list-filters"`
	// TODO(hunter): Add this
	ListFilters bool `long:"list-filters" default:"false" description:"List known filters." group:"Lists"`
	ListRegions bool `long:"list-regions" default:"false" description:"List Regions." group:"Lists"`
	ListFields  bool `long:"list-fields" default:"false" description:"List Server Fields." group:"Lists"`
}

var masterOptions MasterQueryOptions

var _fieldregexp = regexp.MustCompile(`\s*(([a-z]+)(=(\d+))?)\s*,?\s*`)

type infoPrinter func()

func masterctx() {
	log.SetFlags(0)

	if printInfo() {
		return
	}

	unreachable := 0
	errorsEncountererd := make([]error, 0)

	userRegionStr := strings.ToUpper(masterOptions.Region)

	region, found := regionData[userRegionStr]

	if !found {
		fmt.Errorf("Region '%s' does not exist.", masterOptions.Region)
		return
	}

	fields, err := parseFields(masterOptions.Fields, serverFieldProperties)

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

	master := goseq.NewMasterServer()
	master.SetRegion(region)
	master.SetAddr(goseq.MasterSourceServers[0])

	startIp := string(goseq.NoAddress)

	if masterOptions.StartIP != "" {
		startIp = masterOptions.StartIP
	}

	// Apply filters
	filt := master.GetFilter()
	for fname, fval := range masterOptions.Filters {
		filt.Set(fname, fval)
	}
	master.SetFilter(filt)

	servers, err := master.Query(startIp)
	numServers := len(servers)

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

	rec := make(chan SvResponse)
	printer := make(chan SvResponse)

	//tups := make([]SvResponse, 0, numServers)

	if !masterOptions.Serial {
		go AsyncQueryServers(rec, servers, 1*time.Second)
	} else {
		go serialQueryServers(rec, servers, 1*time.Second)
	}

	var writer Printer = &textWriter{}

	writer.Init(fields, printer)
	go writer.Run()

	if !masterOptions.NoHeader {
		printHeaderLine(fields, serverFieldProperties)
	}

	for i := 0; i < numServers; i++ {
		//tups = append(tups, <-rec)
		recd := <-rec

		if recd.err != nil {
			errorsEncountererd = append(errorsEncountererd, recd.err)
		}

		if recd.err != nil && !masterOptions.ShowUnreachable {
			unreachable++
			continue
		}

		if masterOptions.Limit > 0 && i >= masterOptions.Limit {
			continue
		}

		printer <- recd
	}

	writer.Done()

	close(rec)
	close(printer)

	log.Println()

	if !masterOptions.ShowUnreachable {
		log.Println(unreachable, "unreachable servers were hidden.")
	}

	if masterOptions.ShowErrorSummary {
		log.Printf("Errors Encountered (%dx):\n", len(errorsEncountererd))

		for _, detail := range errorsEncountererd {
			log.Println("\t", detail)
		}
	}
}

func serialQueryServers(send chan SvResponse, servers []goseq.Server, timeout time.Duration) {
	for _, server := range servers {
		info, err := server.Info(timeout)
		if err != nil {
			send <- SvResponse{err: err, server: server}
		}
		send <- SvResponse{err: err, server: server, info: info}
	}
}

func AsyncQueryServers(send chan SvResponse, servers []goseq.Server, timeout time.Duration) {
	for _, server := range servers {
		go serialQueryServers(send, []goseq.Server{server}, timeout)
	}
}

func parseFields(spec string, properties map[string]FieldProperty) ([]FieldSpec, error) {
	specs := make([]FieldSpec, 0, 1)
	found := _fieldregexp.FindAllStringSubmatch(spec, -1)

	if len(found) == 0 && len(strings.TrimSpace(spec)) != 0 {
		return nil, errors.New("Couldn't parse field list.")
	}

	for _, match := range found {

		if _, ok := serverMethodAccessors[match[2]]; !ok {
			if _, ok = serverProperties[match[2]]; !ok {
				return nil, errors.New("Attempted to use an unregistered field!")
			}
		}

		sp := FieldSpec{
			name:   match[2],
			length: -1,
		}

		if def, ok := properties[match[2]]; ok {
			sp.length = def.size
		}

		if match[4] != "" {
			val, err := strconv.Atoi(match[4])
			if err == nil {
				sp.length = val
			}
		}

		specs = append(specs, sp)
	}

	return specs, nil
}

func printHeaderLine(fields []FieldSpec, props map[string]FieldProperty) {
	for i, field := range fields {

		if i > 0 {
			fmt.Print(masterOptions.Divider)
		}

		title := field.name

		if prop, ok := props[field.name]; ok {
			title = prop.name
		}

		sz := len(title)

		var padL int = 0
		var padR int = 0

		if sz < field.length {
			var rem int = field.length - sz
			padL = rem / 2
			padR = rem - padL
		}

		for j := 0; j < padL; j++ {
			fmt.Print(" ")
		}

		fmt.Print(title)

		for j := 0; j < padR; j++ {
			fmt.Print(" ")
		}
	}
	fmt.Print("\n")
}

type ErrorCount struct {
	err   error
	count int
}

func printInfo() (done bool) {
	done = false

	infos := []struct {
		f       infoPrinter
		enabled bool
	}{
		{printKnownFiltersInfo, masterOptions.ListFilters},
		{printRegionInfo, masterOptions.ListRegions},
		{printServerFieldProperties, masterOptions.ListFields},
	}

	for _, info := range infos {
		if info.enabled {
			info.f()
			done = true
		}
	}

	return
}
<commit_msg>Add --json flag<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/hfern/goseq"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type MasterQueryOptions struct {
	Region string `long:"region" short:"r" default:"USW" description:"Region code to get results for. See --list-regions"`
	Serial bool   `long:"serial" short:"s" default:"false" description:"Force serial querying of the servers."`
	Fields string `long:"fields" default:"ip=21,name" description:"The fields to be included. Optionally includes the min-length space. See --list-fields" `
	// TODO(hunter): Add this
	ShowFields bool `long:"show-fields" default:"false" description:"Print details on each available field."`
	// TODO(hunter): Add this
	MasterIP string `long:"ip" default:"" description:"IP of the Master server to query."`
	Divider  string `long:"divider" default:" ¦ " description:"Characters used to seperate fields."`
	// TODO(hunter): Add this
	StartIP          string `long:"start" default:"" description:"Where to start reading IPs from. Defaults to start of list."`
	Limit            int    `long:"limit" short:"l" default:"0" description:"Limit the result set to n successful rows."`
	NoHeader         bool   `long:"no-header" default:"false" description:"Don't show header w/ column names."`
	ShowUnreachable  bool   `long:"unreachable" short:"U" default:"false" description:"Show unreachable servers (couldn't be connected to)."`
	ShowErrorSummary bool   `long:"errors" short:"E" default:"false" description:"Show error summary at end of list."`
	// TODO(hunter): Add this
	Filters map[string]string `long:"filter" short:"f" description:"Filters to use. See --list-filters"`
	// TODO(hunter): Add this
	ListFilters bool `long:"list-filters" default:"false" description:"List known filters." group:"Lists"`
	ListRegions bool `long:"list-regions" default:"false" description:"List Regions." group:"Lists"`
	ListFields  bool `long:"list-fields" default:"false" description:"List Server Fields." group:"Lists"`
	Json        bool `long:"json" default:"false" description:"Output as JSON to StdOut"`
}

var masterOptions MasterQueryOptions

var _fieldregexp = regexp.MustCompile(`\s*(([a-z]+)(=(\d+))?)\s*,?\s*`)

type infoPrinter func()

func masterctx() {
	log.SetFlags(0)

	if printInfo() {
		return
	}

	unreachable := 0
	errorsEncountererd := make([]error, 0)

	userRegionStr := strings.ToUpper(masterOptions.Region)

	region, found := regionData[userRegionStr]

	if !found {
		fmt.Errorf("Region '%s' does not exist.", masterOptions.Region)
		return
	}

	fields, err := parseFields(masterOptions.Fields, serverFieldProperties)

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

	master := goseq.NewMasterServer()
	master.SetRegion(region)
	master.SetAddr(goseq.MasterSourceServers[0])

	startIp := string(goseq.NoAddress)

	if masterOptions.StartIP != "" {
		startIp = masterOptions.StartIP
	}

	// Apply filters
	filt := master.GetFilter()
	for fname, fval := range masterOptions.Filters {
		filt.Set(fname, fval)
	}
	master.SetFilter(filt)

	servers, err := master.Query(startIp)
	numServers := len(servers)

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

	rec := make(chan SvResponse)
	printer := make(chan SvResponse)

	//tups := make([]SvResponse, 0, numServers)

	if !masterOptions.Serial {
		go AsyncQueryServers(rec, servers, 1*time.Second)
	} else {
		go serialQueryServers(rec, servers, 1*time.Second)
	}

	var writer Printer

	if masterOptions.Json {
		writer = &jsonWriter{}
	} else {
		writer = &textWriter{}
	}

	writer.Init(fields, printer)
	go writer.Run()

	if !masterOptions.NoHeader && !masterOptions.Json {
		printHeaderLine(fields, serverFieldProperties)
	}

	for i := 0; i < numServers; i++ {
		//tups = append(tups, <-rec)
		recd := <-rec

		if recd.err != nil {
			errorsEncountererd = append(errorsEncountererd, recd.err)
		}

		if recd.err != nil && !masterOptions.ShowUnreachable {
			unreachable++
			continue
		}

		if masterOptions.Limit > 0 && i >= masterOptions.Limit {
			continue
		}

		printer <- recd
	}

	writer.Done()

	close(rec)
	close(printer)

	log.Println()

	if !masterOptions.ShowUnreachable && !masterOptions.Json {
		log.Println(unreachable, "unreachable servers were hidden.")
	}

	if masterOptions.ShowErrorSummary {
		log.Printf("Errors Encountered (%dx):\n", len(errorsEncountererd))

		for _, detail := range errorsEncountererd {
			log.Println("\t", detail)
		}
	}
}

func serialQueryServers(send chan SvResponse, servers []goseq.Server, timeout time.Duration) {
	for _, server := range servers {
		info, err := server.Info(timeout)
		if err != nil {
			send <- SvResponse{err: err, server: server}
		}
		send <- SvResponse{err: err, server: server, info: info}
	}
}

func AsyncQueryServers(send chan SvResponse, servers []goseq.Server, timeout time.Duration) {
	for _, server := range servers {
		go serialQueryServers(send, []goseq.Server{server}, timeout)
	}
}

func parseFields(spec string, properties map[string]FieldProperty) ([]FieldSpec, error) {
	specs := make([]FieldSpec, 0, 1)
	found := _fieldregexp.FindAllStringSubmatch(spec, -1)

	if len(found) == 0 && len(strings.TrimSpace(spec)) != 0 {
		return nil, errors.New("Couldn't parse field list.")
	}

	for _, match := range found {

		if _, ok := serverMethodAccessors[match[2]]; !ok {
			if _, ok = serverProperties[match[2]]; !ok {
				return nil, errors.New("Attempted to use an unregistered field!")
			}
		}

		sp := FieldSpec{
			name:   match[2],
			length: -1,
		}

		if def, ok := properties[match[2]]; ok {
			sp.length = def.size
		}

		if match[4] != "" {
			val, err := strconv.Atoi(match[4])
			if err == nil {
				sp.length = val
			}
		}

		specs = append(specs, sp)
	}

	return specs, nil
}

func printHeaderLine(fields []FieldSpec, props map[string]FieldProperty) {
	for i, field := range fields {

		if i > 0 {
			fmt.Print(masterOptions.Divider)
		}

		title := field.name

		if prop, ok := props[field.name]; ok {
			title = prop.name
		}

		sz := len(title)

		var padL int = 0
		var padR int = 0

		if sz < field.length {
			var rem int = field.length - sz
			padL = rem / 2
			padR = rem - padL
		}

		for j := 0; j < padL; j++ {
			fmt.Print(" ")
		}

		fmt.Print(title)

		for j := 0; j < padR; j++ {
			fmt.Print(" ")
		}
	}
	fmt.Print("\n")
}

type ErrorCount struct {
	err   error
	count int
}

func printInfo() (done bool) {
	done = false

	infos := []struct {
		f       infoPrinter
		enabled bool
	}{
		{printKnownFiltersInfo, masterOptions.ListFilters},
		{printRegionInfo, masterOptions.ListRegions},
		{printServerFieldProperties, masterOptions.ListFields},
	}

	for _, info := range infos {
		if info.enabled {
			info.f()
			done = true
		}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/boltdb/bolt"

	"golang.org/x/crypto/ssh/terminal"
)

// BASEDIR is the location of all the netskel files.
var BASEDIR = "/usr/local/netskel"

// verbose controls the verbosity of program output.
var verbose bool

// showDisabled controls whether or not we hide disabled hosts.
var showDisabled bool

// Debug logs to syslog at the debug level.
func Debug(format string, a ...interface{}) {
	if verbose {
		fmt.Printf(format+"\n", a...)
	}
}

func clientList() {
	screenWidth, _, _ := terminal.GetSize(0)

	if screenWidth < 80 {
		// Deal with it
	}

	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return
	}
	defer db.Close()

	var (
		uuidList  []string
		hostNames map[string]string
		lastTimes map[string]string
		Disableds map[string]string
	)

	hostNames = make(map[string]string)
	hostWidth := 0

	lastTimes = make(map[string]string)
	lastWidth := 0

	Disableds = make(map[string]string)

	err = db.View(func(tx *bolt.Tx) error {

		return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
			uuid := string(name)

			b := tx.Bucket(name)
			c := b.Cursor()

			isDisabled := b.Get([]byte("disabled"))
			if showDisabled && isDisabled != nil {
				Disableds[uuid] = "X"
			} else {
				Disableds[uuid] = ""
			}
			if isDisabled == nil || showDisabled {
				uuidList = append(uuidList, uuid)

				for k, v := c.First(); k != nil; k, v = c.Next() {
					switch string(k) {
					case "hostname":
						hostNames[uuid] = string(v)
						if len(string(v)) > hostWidth {
							hostWidth = len(string(v))
						}
					case "lastSeen":
						v = transformKey(k, v)
						lastTimes[uuid] = string(v)
						if len(string(v)) > lastWidth {
							lastWidth = len(string(v))
						}
					}
				}
			}

			return nil
		})
	})

	// fmt.Println(screenWidth)

	formatString := "%-36s  %-" + strconv.Itoa(hostWidth) + "s  %-" + strconv.Itoa(lastWidth) + "s %1s"

	formatString += "\n"

	fmt.Printf(formatString, "Client ID", "Hostname", "Last Seen", "")
	fmt.Printf(formatString,
		strings.Repeat("=", 36),
		strings.Repeat("=", hostWidth),
		strings.Repeat("=", lastWidth),
		"",
	)

	for _, uuid := range uuidList {
		hostName := hostNames[uuid]
		lastSeen := lastTimes[uuid]
		disabled := Disableds[uuid]

		fmt.Printf(formatString, uuid, hostName, lastSeen, disabled)
	}
}

func transformKey(k, v []byte) []byte {
	retbuf := v

	switch string(k) {
	case "created", "lastSeen", "disabled":
		epoch, _ := strconv.ParseInt(string(v), 10, 64)
		retbuf = []byte(time.Unix(epoch, 0).Format("Mon Jan 2 2006 @ 15:04:05 MST"))
	}

	return retbuf
}

func clientInfo(search string, days int) {
	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return
	}
	defer db.Close()

	headerPrinted := false
	header := fmt.Sprintf("Netskel hosts matching '%s':\n", search)
	if days > 0 {
		header = fmt.Sprintf("The following netskel hosts have not been seen in over %d days:\n", days)
	}

	widestK := 0

	err = db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
			searchHit := false

			if search == "" || strings.Contains(strings.ToLower(string(name)), strings.ToLower(search)) {
				searchHit = true
			}

			b := tx.Bucket(name)
			c := b.Cursor()

			for k, v := c.First(); k != nil; k, v = c.Next() {
				if len(string(k)) > widestK {
					widestK = len(string(k))
				}
				if strings.Contains(strings.ToLower(string(v)), strings.ToLower(search)) {
					searchHit = true
				}
			}

			if days > 0 {
				lastSeen, err := strconv.Atoi(string(b.Get([]byte("lastSeen"))))
				if err == nil {
					last64 := int64(lastSeen)
					timeDiff := time.Now().Unix() - last64
					daysDiff := timeDiff / 86400

					if daysDiff < int64(days) {
						Debug("%s seen %d days ago, skipping for audit", name, daysDiff)
						searchHit = false
					}
				}
			}

			isDisabled := b.Get([]byte("disabled"))
			if isDisabled != nil && !showDisabled {
				searchHit = false
			}

			if searchHit == true {
				if !headerPrinted {
					headerPrinted = true
					fmt.Println(header)
				}
				fmt.Printf("[%s]\n", name)
				for k, v := c.First(); k != nil; k, v = c.Next() {
					v = transformKey(k, v)
					formatString := "  %-" + strconv.Itoa(widestK) + "s: %s\n"
					fmt.Printf(formatString, k, v)
				}
				fmt.Println("")
			}

			return nil
		})
	})
}

func clientPut(uuid, key, value string) (err error) {
	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		Warn("Unable to open client database: %v\n", err)
		return err
	}
	defer db.Close()

	berr := db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(uuid))
		if b == nil {
			return fmt.Errorf("Unknown host")
		}

		if value != "" {
			oldVal := string(b.Get([]byte(key)))
			err = b.Put([]byte(key), []byte(value))
			Debug("%s: %v -> %v", key, oldVal, value)
		} else {
			err = b.Delete([]byte(key))
		}

		return err
	})

	if berr != nil {
		fmt.Printf("%v\n", berr)
	}

	return berr
}

func disableClient(uuid string) error {
	now := time.Now()
	secs := strconv.Itoa(int(now.Unix()))
	clientPut(uuid, "disabled", secs)
	return nil
}

func enableClient(uuid string) error {
	clientPut(uuid, "disabled", "")
	return nil
}

func deleteClient(uuid string) error {
	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return err
	}
	defer db.Close()

	berr := db.Update(func(tx *bolt.Tx) error {
		err := tx.DeleteBucket([]byte(uuid))
		return err
	})

	if berr != nil {
		fmt.Printf("%v\n", berr)
	}

	return berr
}

func getArg(pos int, def string) string {
	if len(flag.Args()) < pos {
		Debug("Can't get pos %d from len %d", pos, len(flag.Args()))
		return def
	}

	val := flag.Arg(pos)

	if val == "" {
		return def
	}

	return val
}

func getArgInt(pos int, def int) int {
	strArg := getArg(pos, "")

	if strArg == "" {
		return def
	}

	i, err := strconv.Atoi(strArg)
	if err != nil {
		return def
	}
	return i
}

// Usage prints helpful information on how to use the command.
func Usage() {
	fmt.Printf("usage: netskelctl [flags] <command>\n\n")
	fmt.Println("Flags:")
	flag.PrintDefaults()
	fmt.Println("\nCommands:")
	fmt.Println("  list               List all known Netskel hosts")
	fmt.Println("  info <uuid|search> Show detailed info for single host")
	fmt.Println("  disable <uuid>     Disable single host")
	fmt.Println("  enable <uuid>      Disable single host")
	fmt.Println("  delete <uuid>      Delete single host")
	fmt.Println("  audit <days>       Show hosts not seen in <days> days")
	os.Exit(1)
}

func main() {
	var (
		help bool
	)
	flag.BoolVar(&help, "h", false, "Show this usage information")
	flag.BoolVar(&verbose, "v", false, "Show verbose output")
	flag.BoolVar(&showDisabled, "a", false, "Include all (disabled) hosts")
	flag.Parse()

	if len(flag.Args()) == 0 || help {
		Usage()
	}
	command := flag.Args()[0]

	switch command {
	case "list":
		clientList()
	case "info":
		clientInfo(getArg(1, "netskelnotfound"), 0)
	case "audit":
		days := getArgInt(1, 7)
		clientInfo("", days)
	case "disable":
		disableClient(getArg(1, "netskelnotfound"))
	case "enable":
		enableClient(getArg(1, "netskelnotfound"))
	case "delete":
		deleteClient(getArg(1, "netskelnotfound"))
	}

	os.Exit(0)
}
<commit_msg>Oops, warn doesn't exist here<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/boltdb/bolt"

	"golang.org/x/crypto/ssh/terminal"
)

// BASEDIR is the location of all the netskel files.
var BASEDIR = "/usr/local/netskel"

// verbose controls the verbosity of program output.
var verbose bool

// showDisabled controls whether or not we hide disabled hosts.
var showDisabled bool

// Debug logs to syslog at the debug level.
func Debug(format string, a ...interface{}) {
	if verbose {
		fmt.Printf(format+"\n", a...)
	}
}

func clientList() {
	screenWidth, _, _ := terminal.GetSize(0)

	if screenWidth < 80 {
		// Deal with it
	}

	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return
	}
	defer db.Close()

	var (
		uuidList  []string
		hostNames map[string]string
		lastTimes map[string]string
		Disableds map[string]string
	)

	hostNames = make(map[string]string)
	hostWidth := 0

	lastTimes = make(map[string]string)
	lastWidth := 0

	Disableds = make(map[string]string)

	err = db.View(func(tx *bolt.Tx) error {

		return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
			uuid := string(name)

			b := tx.Bucket(name)
			c := b.Cursor()

			isDisabled := b.Get([]byte("disabled"))
			if showDisabled && isDisabled != nil {
				Disableds[uuid] = "X"
			} else {
				Disableds[uuid] = ""
			}
			if isDisabled == nil || showDisabled {
				uuidList = append(uuidList, uuid)

				for k, v := c.First(); k != nil; k, v = c.Next() {
					switch string(k) {
					case "hostname":
						hostNames[uuid] = string(v)
						if len(string(v)) > hostWidth {
							hostWidth = len(string(v))
						}
					case "lastSeen":
						v = transformKey(k, v)
						lastTimes[uuid] = string(v)
						if len(string(v)) > lastWidth {
							lastWidth = len(string(v))
						}
					}
				}
			}

			return nil
		})
	})

	// fmt.Println(screenWidth)

	formatString := "%-36s  %-" + strconv.Itoa(hostWidth) + "s  %-" + strconv.Itoa(lastWidth) + "s %1s"

	formatString += "\n"

	fmt.Printf(formatString, "Client ID", "Hostname", "Last Seen", "")
	fmt.Printf(formatString,
		strings.Repeat("=", 36),
		strings.Repeat("=", hostWidth),
		strings.Repeat("=", lastWidth),
		"",
	)

	for _, uuid := range uuidList {
		hostName := hostNames[uuid]
		lastSeen := lastTimes[uuid]
		disabled := Disableds[uuid]

		fmt.Printf(formatString, uuid, hostName, lastSeen, disabled)
	}
}

func transformKey(k, v []byte) []byte {
	retbuf := v

	switch string(k) {
	case "created", "lastSeen", "disabled":
		epoch, _ := strconv.ParseInt(string(v), 10, 64)
		retbuf = []byte(time.Unix(epoch, 0).Format("Mon Jan 2 2006 @ 15:04:05 MST"))
	}

	return retbuf
}

func clientInfo(search string, days int) {
	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return
	}
	defer db.Close()

	headerPrinted := false
	header := fmt.Sprintf("Netskel hosts matching '%s':\n", search)
	if days > 0 {
		header = fmt.Sprintf("The following netskel hosts have not been seen in over %d days:\n", days)
	}

	widestK := 0

	err = db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
			searchHit := false

			if search == "" || strings.Contains(strings.ToLower(string(name)), strings.ToLower(search)) {
				searchHit = true
			}

			b := tx.Bucket(name)
			c := b.Cursor()

			for k, v := c.First(); k != nil; k, v = c.Next() {
				if len(string(k)) > widestK {
					widestK = len(string(k))
				}
				if strings.Contains(strings.ToLower(string(v)), strings.ToLower(search)) {
					searchHit = true
				}
			}

			if days > 0 {
				lastSeen, err := strconv.Atoi(string(b.Get([]byte("lastSeen"))))
				if err == nil {
					last64 := int64(lastSeen)
					timeDiff := time.Now().Unix() - last64
					daysDiff := timeDiff / 86400

					if daysDiff < int64(days) {
						Debug("%s seen %d days ago, skipping for audit", name, daysDiff)
						searchHit = false
					}
				}
			}

			isDisabled := b.Get([]byte("disabled"))
			if isDisabled != nil && !showDisabled {
				searchHit = false
			}

			if searchHit == true {
				if !headerPrinted {
					headerPrinted = true
					fmt.Println(header)
				}
				fmt.Printf("[%s]\n", name)
				for k, v := c.First(); k != nil; k, v = c.Next() {
					v = transformKey(k, v)
					formatString := "  %-" + strconv.Itoa(widestK) + "s: %s\n"
					fmt.Printf(formatString, k, v)
				}
				fmt.Println("")
			}

			return nil
		})
	})
}

func clientPut(uuid, key, value string) (err error) {
	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return err
	}
	defer db.Close()

	berr := db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(uuid))
		if b == nil {
			return fmt.Errorf("Unknown host")
		}

		if value != "" {
			oldVal := string(b.Get([]byte(key)))
			err = b.Put([]byte(key), []byte(value))
			Debug("%s: %v -> %v", key, oldVal, value)
		} else {
			err = b.Delete([]byte(key))
		}

		return err
	})

	if berr != nil {
		fmt.Printf("%v\n", berr)
	}

	return berr
}

func disableClient(uuid string) error {
	now := time.Now()
	secs := strconv.Itoa(int(now.Unix()))
	clientPut(uuid, "disabled", secs)
	return nil
}

func enableClient(uuid string) error {
	clientPut(uuid, "disabled", "")
	return nil
}

func deleteClient(uuid string) error {
	db, err := bolt.Open(BASEDIR+"/clients.db", 0660, nil)
	if err != nil {
		fmt.Printf("Unable to open client database: %v\n", err)
		return err
	}
	defer db.Close()

	berr := db.Update(func(tx *bolt.Tx) error {
		err := tx.DeleteBucket([]byte(uuid))
		return err
	})

	if berr != nil {
		fmt.Printf("%v\n", berr)
	}

	return berr
}

func getArg(pos int, def string) string {
	if len(flag.Args()) < pos {
		Debug("Can't get pos %d from len %d", pos, len(flag.Args()))
		return def
	}

	val := flag.Arg(pos)

	if val == "" {
		return def
	}

	return val
}

func getArgInt(pos int, def int) int {
	strArg := getArg(pos, "")

	if strArg == "" {
		return def
	}

	i, err := strconv.Atoi(strArg)
	if err != nil {
		return def
	}
	return i
}

// Usage prints helpful information on how to use the command.
func Usage() {
	fmt.Printf("usage: netskelctl [flags] <command>\n\n")
	fmt.Println("Flags:")
	flag.PrintDefaults()
	fmt.Println("\nCommands:")
	fmt.Println("  list               List all known Netskel hosts")
	fmt.Println("  info <uuid|search> Show detailed info for single host")
	fmt.Println("  disable <uuid>     Disable single host")
	fmt.Println("  enable <uuid>      Disable single host")
	fmt.Println("  delete <uuid>      Delete single host")
	fmt.Println("  audit <days>       Show hosts not seen in <days> days")
	os.Exit(1)
}

func main() {
	var (
		help bool
	)
	flag.BoolVar(&help, "h", false, "Show this usage information")
	flag.BoolVar(&verbose, "v", false, "Show verbose output")
	flag.BoolVar(&showDisabled, "a", false, "Include all (disabled) hosts")
	flag.Parse()

	if len(flag.Args()) == 0 || help {
		Usage()
	}
	command := flag.Args()[0]

	switch command {
	case "list":
		clientList()
	case "info":
		clientInfo(getArg(1, "netskelnotfound"), 0)
	case "audit":
		days := getArgInt(1, 7)
		clientInfo("", days)
	case "disable":
		disableClient(getArg(1, "netskelnotfound"))
	case "enable":
		enableClient(getArg(1, "netskelnotfound"))
	case "delete":
		deleteClient(getArg(1, "netskelnotfound"))
	}

	os.Exit(0)
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package ssh

import (
	"net"
	"time"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils/set"
	"golang.org/x/crypto/ssh"

	"github.com/juju/juju/network"
)

var logger = loggo.GetLogger("juju.network.ssh")

// Dialer defines a Dial() method matching the signature of net.Dial().
type Dialer interface {
	Dial(network, address string) (net.Conn, error)
}

// ReachableChecker tries to find ssh hosts that have a public key that matches
// our expectations.
type ReachableChecker interface {
	// FindHost tries to connect to all of the host+port combinations supplied,
	// and tries to do an SSH key negotiation. The first successful negotiation
	// that includes one of the public keys supplied will be returned. If none
	// of them can be validated, then an error will be returned.
	FindHost(hostPorts []network.HostPort, publicKeys []string) (network.HostPort, error)
}

// NewReachableChecker creates a ReachableChecker that can be used to check for
// Hosts that are viable SSH targets.
// When FindHost is called, we will dial the entries in the given hostPorts, in
// parallel, using the given dialer, closing successfully established
// connections after checking the ssh key. Individual connection errors are
// discarded, and an error is returned only if none of the hostPorts can be
// reached when the given timeout expires.
// If publicKeys is a non empty list, then the SSH host public key will be
// checked. If it is not in the list, that host is not considered valid.
//
// Usually, a net.Dialer initialized with a non-empty Timeout field is passed
// for dialer.
func NewReachableChecker(dialer Dialer, timeout time.Duration) *reachableChecker {
	return &reachableChecker{
		dialer: dialer,
		timeout: timeout,
	}
}

// hostKeyChecker checks if this host matches one of allowed public keys
// it uses the golang/x/crypto/ssh/HostKeyCallback to find the host keys on a
// given connection.
type hostKeyChecker struct {
	// AcceptedKeys is a set of the Marshalled PublicKey content.
	AcceptedKeys set.Strings
	// Stop will be polled for whether we should stop trying to do any work
	Stop <-chan struct{}
	// HostPort is the identifier that corresponds to this connection
	HostPort network.HostPort
	// Accepted will be passed HostPort if it validated the connection
	Accepted chan network.HostPort
	// Dialer is a Dialer that allows us to initiate the underlying TCP connection
	Dialer Dialer
	// Finished will be set an event when we've finished our check (success or failure)
	Finished chan struct{}
}

var hostKeyNotInList = errors.New("host key not in expected set")
var hostKeyAccepted = errors.New("host key was accepted, retry")

func (h *hostKeyChecker) hostKeyCallback(hostname string, remote net.Addr, key ssh.PublicKey) error {
	// Note: we don't do any advanced checking of the PublicKey, like whether
	// the key is revoked or expired. All we care about is whether it matches
	// the public keys that we consider acceptable
	logger.Debugf("checking host key for %q at %v, with key %q", hostname, remote, ssh.MarshalAuthorizedKey(key))

	lookupKey := string(key.Marshal())
	if len(h.AcceptedKeys) == 0 || h.AcceptedKeys.Contains(lookupKey) {
		logger.Debugf("accepted host key for: %q %v", hostname, remote)
		// This key was valid, so return it, but if someone else was found
		// first, still exit.
		select {
		case h.Accepted <- h.HostPort:
		case <-h.Stop:
		}
		return hostKeyAccepted
	}
	logger.Debugf("host key for %q %v not in our accepted set", hostname, remote)
	return hostKeyNotInList
}

// publicKeysToSet converts all the public key values (eg id_rsa.pub) into
// their short hash form. Problems with a key are logged at Warning level, but
// otherwise ignored.
func publicKeysToSet(publicKeys []string) set.Strings {
	acceptedKeys := set.NewStrings()
	for _, pubKey := range publicKeys {
		// key, comment, options, rest, err
		sshKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))
		if err != nil {
			logger.Warningf("unable to handle public key: %q\n", pubKey)
			continue
		}
		acceptedKeys.Add(string(sshKey.Marshal()))
	}
	return acceptedKeys
}

// Check initiates a connection to HostPort and tries to do an SSH key
// exchange to determine the preferred public key of the remote host.
// It then checks if that key is in the accepted set of keys.
func (h *hostKeyChecker) Check() {
	defer func() {
		// send a finished message unless we're already stopped and nobody
		// is listening
		select {
		case h.Finished <- struct{}{}:
		case <-h.Stop:
		}
	}()
	// TODO(jam): 2017-01-24 One limitation of our algorithm, is that we don't
	// try to limit the negotiation of the keys to our set of possible keys.
	// For example, say we only know about the RSA key for the remote host, but
	// it has been updated to use a ECDSA key as well. We
	sshconfig := &ssh.ClientConfig{
		HostKeyCallback: h.hostKeyCallback,
	}
	addr := h.HostPort.NetAddr()
	logger.Debugf("dialing %q to check host keys", addr)
	conn, err := h.Dialer.Dial("tcp", addr)
	if err != nil {
		logger.Debugf("dial %q failed with: %v", addr, err)
		return
	}
	// No need to do the key exchange if we're already stopping
	select {
	case <-h.Stop:
		conn.Close()
		return
	default:
	}
	logger.Debugf("connected to %q, initiating ssh handshake", addr)
	// NewClientConn will close the underlying net.Conn if it gets an error
	client, _, _, err := ssh.NewClientConn(conn, addr, sshconfig)
	if err == nil {
		// We don't expect this case, because we don't support Auth,
		// but make sure to close it anyway.
		client.Close()
	} else {
		// no need to log these two messages, that's already been done
		// in hostKeyCallback
		if err != hostKeyAccepted && err != hostKeyNotInList {
			logger.Debugf("ssh error: %v", err)
		}
	}
}

type reachableChecker struct {
	dialer Dialer
	timeout time.Duration
}

func (r *reachableChecker) FindHost(hostPorts []network.HostPort, publicKeys []string) (network.HostPort, error) {
	uniqueHPs := network.UniqueHostPorts(hostPorts)
	successful := make(chan network.HostPort, 1)
	stop := make(chan struct{}, 0)
	// We use a channel instead of a sync.WaitGroup so that we can return as
	// soon as we get one connected. We'll signal the rest to stop via the
	// 'stop' channel.
	finished := make(chan struct{}, len(uniqueHPs))

	acceptedKeys := publicKeysToSet(publicKeys)
	for _, hostPort := range uniqueHPs {
		checker := &hostKeyChecker{
			AcceptedKeys: acceptedKeys,
			Stop:         stop,
			Accepted:     successful,
			HostPort:     hostPort,
			Dialer:       r.dialer,
			Finished:     finished,
		}
		go checker.Check()
	}

	for finishedCount := 0; finishedCount < len(uniqueHPs); {
		select {
		case result := <-successful:
			logger.Infof("found %v has an acceptable ssh key", result)
			close(stop)
			return result, nil
		case <-finished:
			finishedCount++
		case <-time.After(r.timeout):
			break
		}
	}
	close(stop)
	return network.HostPort{}, errors.Errorf("cannot connect to any address: %v", hostPorts)
}

func ReachableHostPort(hostPorts []network.HostPort, publicKeys []string, dialer Dialer, timeout time.Duration) (network.HostPort, error) {
	checker := NewReachableChecker(dialer, timeout)
	return checker.FindHost(hostPorts, publicKeys)
}
<commit_msg>go fmt<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package ssh

import (
	"net"
	"time"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils/set"
	"golang.org/x/crypto/ssh"

	"github.com/juju/juju/network"
)

var logger = loggo.GetLogger("juju.network.ssh")

// Dialer defines a Dial() method matching the signature of net.Dial().
type Dialer interface {
	Dial(network, address string) (net.Conn, error)
}

// ReachableChecker tries to find ssh hosts that have a public key that matches
// our expectations.
type ReachableChecker interface {
	// FindHost tries to connect to all of the host+port combinations supplied,
	// and tries to do an SSH key negotiation. The first successful negotiation
	// that includes one of the public keys supplied will be returned. If none
	// of them can be validated, then an error will be returned.
	FindHost(hostPorts []network.HostPort, publicKeys []string) (network.HostPort, error)
}

// NewReachableChecker creates a ReachableChecker that can be used to check for
// Hosts that are viable SSH targets.
// When FindHost is called, we will dial the entries in the given hostPorts, in
// parallel, using the given dialer, closing successfully established
// connections after checking the ssh key. Individual connection errors are
// discarded, and an error is returned only if none of the hostPorts can be
// reached when the given timeout expires.
// If publicKeys is a non empty list, then the SSH host public key will be
// checked. If it is not in the list, that host is not considered valid.
//
// Usually, a net.Dialer initialized with a non-empty Timeout field is passed
// for dialer.
func NewReachableChecker(dialer Dialer, timeout time.Duration) *reachableChecker {
	return &reachableChecker{
		dialer:  dialer,
		timeout: timeout,
	}
}

// hostKeyChecker checks if this host matches one of allowed public keys
// it uses the golang/x/crypto/ssh/HostKeyCallback to find the host keys on a
// given connection.
type hostKeyChecker struct {
	// AcceptedKeys is a set of the Marshalled PublicKey content.
	AcceptedKeys set.Strings
	// Stop will be polled for whether we should stop trying to do any work
	Stop <-chan struct{}
	// HostPort is the identifier that corresponds to this connection
	HostPort network.HostPort
	// Accepted will be passed HostPort if it validated the connection
	Accepted chan network.HostPort
	// Dialer is a Dialer that allows us to initiate the underlying TCP connection
	Dialer Dialer
	// Finished will be set an event when we've finished our check (success or failure)
	Finished chan struct{}
}

var hostKeyNotInList = errors.New("host key not in expected set")
var hostKeyAccepted = errors.New("host key was accepted, retry")

func (h *hostKeyChecker) hostKeyCallback(hostname string, remote net.Addr, key ssh.PublicKey) error {
	// Note: we don't do any advanced checking of the PublicKey, like whether
	// the key is revoked or expired. All we care about is whether it matches
	// the public keys that we consider acceptable
	logger.Debugf("checking host key for %q at %v, with key %q", hostname, remote, ssh.MarshalAuthorizedKey(key))

	lookupKey := string(key.Marshal())
	if len(h.AcceptedKeys) == 0 || h.AcceptedKeys.Contains(lookupKey) {
		logger.Debugf("accepted host key for: %q %v", hostname, remote)
		// This key was valid, so return it, but if someone else was found
		// first, still exit.
		select {
		case h.Accepted <- h.HostPort:
		case <-h.Stop:
		}
		return hostKeyAccepted
	}
	logger.Debugf("host key for %q %v not in our accepted set", hostname, remote)
	return hostKeyNotInList
}

// publicKeysToSet converts all the public key values (eg id_rsa.pub) into
// their short hash form. Problems with a key are logged at Warning level, but
// otherwise ignored.
func publicKeysToSet(publicKeys []string) set.Strings {
	acceptedKeys := set.NewStrings()
	for _, pubKey := range publicKeys {
		// key, comment, options, rest, err
		sshKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))
		if err != nil {
			logger.Warningf("unable to handle public key: %q\n", pubKey)
			continue
		}
		acceptedKeys.Add(string(sshKey.Marshal()))
	}
	return acceptedKeys
}

// Check initiates a connection to HostPort and tries to do an SSH key
// exchange to determine the preferred public key of the remote host.
// It then checks if that key is in the accepted set of keys.
func (h *hostKeyChecker) Check() {
	defer func() {
		// send a finished message unless we're already stopped and nobody
		// is listening
		select {
		case h.Finished <- struct{}{}:
		case <-h.Stop:
		}
	}()
	// TODO(jam): 2017-01-24 One limitation of our algorithm, is that we don't
	// try to limit the negotiation of the keys to our set of possible keys.
	// For example, say we only know about the RSA key for the remote host, but
	// it has been updated to use a ECDSA key as well. We
	sshconfig := &ssh.ClientConfig{
		HostKeyCallback: h.hostKeyCallback,
	}
	addr := h.HostPort.NetAddr()
	logger.Debugf("dialing %q to check host keys", addr)
	conn, err := h.Dialer.Dial("tcp", addr)
	if err != nil {
		logger.Debugf("dial %q failed with: %v", addr, err)
		return
	}
	// No need to do the key exchange if we're already stopping
	select {
	case <-h.Stop:
		conn.Close()
		return
	default:
	}
	logger.Debugf("connected to %q, initiating ssh handshake", addr)
	// NewClientConn will close the underlying net.Conn if it gets an error
	client, _, _, err := ssh.NewClientConn(conn, addr, sshconfig)
	if err == nil {
		// We don't expect this case, because we don't support Auth,
		// but make sure to close it anyway.
		client.Close()
	} else {
		// no need to log these two messages, that's already been done
		// in hostKeyCallback
		if err != hostKeyAccepted && err != hostKeyNotInList {
			logger.Debugf("ssh error: %v", err)
		}
	}
}

type reachableChecker struct {
	dialer  Dialer
	timeout time.Duration
}

func (r *reachableChecker) FindHost(hostPorts []network.HostPort, publicKeys []string) (network.HostPort, error) {
	uniqueHPs := network.UniqueHostPorts(hostPorts)
	successful := make(chan network.HostPort, 1)
	stop := make(chan struct{}, 0)
	// We use a channel instead of a sync.WaitGroup so that we can return as
	// soon as we get one connected. We'll signal the rest to stop via the
	// 'stop' channel.
	finished := make(chan struct{}, len(uniqueHPs))

	acceptedKeys := publicKeysToSet(publicKeys)
	for _, hostPort := range uniqueHPs {
		checker := &hostKeyChecker{
			AcceptedKeys: acceptedKeys,
			Stop:         stop,
			Accepted:     successful,
			HostPort:     hostPort,
			Dialer:       r.dialer,
			Finished:     finished,
		}
		go checker.Check()
	}

	for finishedCount := 0; finishedCount < len(uniqueHPs); {
		select {
		case result := <-successful:
			logger.Infof("found %v has an acceptable ssh key", result)
			close(stop)
			return result, nil
		case <-finished:
			finishedCount++
		case <-time.After(r.timeout):
			break
		}
	}
	close(stop)
	return network.HostPort{}, errors.Errorf("cannot connect to any address: %v", hostPorts)
}

func ReachableHostPort(hostPorts []network.HostPort, publicKeys []string, dialer Dialer, timeout time.Duration) (network.HostPort, error) {
	checker := NewReachableChecker(dialer, timeout)
	return checker.FindHost(hostPorts, publicKeys)
}
<|endoftext|>


<commit_before>/* This package implements an image DB cache using memcached.

Items are given an expiry based on their refresh deadline, with a
minimum duration to try and ensure things will expire well after they
would have been refreshed (i.e., only if they truly need garbage
collection).

memcached will still evict things when under memory pressure. We can
recover from that -- we'll just get a cache miss, and fetch it again.

*/
package memcached

import (
	"encoding/binary"
	"fmt"
	"net"
	"sort"
	"sync"
	"time"

	"github.com/bradfitz/gomemcache/memcache"
	"github.com/go-kit/kit/log"
	"github.com/pkg/errors"

	"github.com/weaveworks/flux/registry/cache"
)

const (
	// The minimum expiry given to an entry.
	MinExpiry = time.Hour
)

// MemcacheClient is a memcache client that gets its server list from SRV
// records, and periodically updates that ServerList.
type MemcacheClient struct {
	client     *memcache.Client
	serverList *memcache.ServerList
	hostname   string
	service    string
	logger     log.Logger

	quit chan struct{}
	wait sync.WaitGroup
}

// MemcacheConfig defines how a MemcacheClient should be constructed.
type MemcacheConfig struct {
	Host           string
	Service        string
	Timeout        time.Duration
	UpdateInterval time.Duration
	Logger         log.Logger
	MaxIdleConns   int
}

func NewMemcacheClient(config MemcacheConfig) *MemcacheClient {
	var servers memcache.ServerList
	client := memcache.NewFromSelector(&servers)
	client.Timeout = config.Timeout
	client.MaxIdleConns = config.MaxIdleConns

	newClient := &MemcacheClient{
		client:     client,
		serverList: &servers,
		hostname:   config.Host,
		service:    config.Service,
		logger:     config.Logger,
		quit:       make(chan struct{}),
	}

	err := newClient.updateMemcacheServers()
	if err != nil {
		config.Logger.Log("err", errors.Wrapf(err, "Error setting memcache servers to '%v'", config.Host))
	}

	newClient.wait.Add(1)
	go newClient.updateLoop(config.UpdateInterval)
	return newClient
}

// Does not use DNS, accepts static list of servers.
func NewFixedServerMemcacheClient(config MemcacheConfig, addresses ...string) *MemcacheClient {
	var servers memcache.ServerList
	servers.SetServers(addresses...)
	client := memcache.NewFromSelector(&servers)
	client.Timeout = config.Timeout

	newClient := &MemcacheClient{
		client:     client,
		serverList: &servers,
		hostname:   config.Host,
		service:    config.Service,
		logger:     config.Logger,
		quit:       make(chan struct{}),
	}

	return newClient
}

// GetKey gets the value and its refresh deadline from the cache.
func (c *MemcacheClient) GetKey(k cache.Keyer) ([]byte, time.Time, error) {
	cacheItem, err := c.client.Get(k.Key())
	if err != nil {
		if err == memcache.ErrCacheMiss {
			// Don't log on cache miss
			return []byte{}, time.Time{}, cache.ErrNotCached
		} else {
			c.logger.Log("err", errors.Wrap(err, "Fetching tag from memcache"))
			return []byte{}, time.Time{}, err
		}
	}
	deadlineTime := binary.BigEndian.Uint32(cacheItem.Value)
	return cacheItem.Value[4:], time.Unix(int64(deadlineTime), 0), nil
}

// SetKey sets the value and its refresh deadline at a key. NB the key
// expiry is set _longer_ than the deadline, to give us a grace period
// in which to refresh the value.
func (c *MemcacheClient) SetKey(k cache.Keyer, refreshDeadline time.Time, v []byte) error {
	expiry := refreshDeadline.Sub(time.Now()) * 2
	if expiry < MinExpiry {
		expiry = MinExpiry
	}

	deadlineBytes := make([]byte, 4, 4)
	binary.BigEndian.PutUint32(deadlineBytes, uint32(refreshDeadline.Unix()))
	if err := c.client.Set(&memcache.Item{
		Key:        k.Key(),
		Value:      append(deadlineBytes, v...),
		Expiration: int32(expiry.Seconds()),
	}); err != nil {
		c.logger.Log("err", errors.Wrap(err, "storing in memcache"))
		return err
	}
	return nil
}

// Stop the memcache client.
func (c *MemcacheClient) Stop() {
	close(c.quit)
	c.wait.Wait()
}

func (c *MemcacheClient) updateLoop(updateInterval time.Duration) {
	defer c.wait.Done()
	ticker := time.NewTicker(updateInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			if err := c.updateMemcacheServers(); err != nil {
				c.logger.Log("err", errors.Wrap(err, "error updating memcache servers"))
			}
		case <-c.quit:
			return
		}
	}
}

// updateMemcacheServers sets a memcache server list from SRV records. SRV
// priority & weight are ignored.
func (c *MemcacheClient) updateMemcacheServers() error {
	_, addrs, err := net.LookupSRV(c.service, "tcp", c.hostname)
	if err != nil {
		return err
	}
	var servers []string
	for _, srv := range addrs {
		servers = append(servers, fmt.Sprintf("%s:%d", srv.Target, srv.Port))
	}
	// ServerList deterministically maps keys to _index_ of the server list.
	// Since DNS returns records in different order each time, we sort to
	// guarantee best possible match between nodes.
	sort.Strings(servers)
	return c.serverList.SetServers(servers...)
}
<commit_msg>Refresh memcache server(s) periodically<commit_after>/* This package implements an image DB cache using memcached.

Items are given an expiry based on their refresh deadline, with a
minimum duration to try and ensure things will expire well after they
would have been refreshed (i.e., only if they truly need garbage
collection).

memcached will still evict things when under memory pressure. We can
recover from that -- we'll just get a cache miss, and fetch it again.

*/
package memcached

import (
	"encoding/binary"
	"fmt"
	"net"
	"sort"
	"sync"
	"time"

	"github.com/bradfitz/gomemcache/memcache"
	"github.com/go-kit/kit/log"
	"github.com/pkg/errors"

	"github.com/weaveworks/flux/registry/cache"
)

const (
	// The minimum expiry given to an entry.
	MinExpiry = time.Hour
)

// MemcacheClient is a memcache client that gets its server list from SRV
// records, and periodically updates that ServerList.
type MemcacheClient struct {
	client     *memcache.Client
	serverList *memcache.ServerList
	hostname   string
	service    string
	logger     log.Logger

	quit chan struct{}
	wait sync.WaitGroup
}

// MemcacheConfig defines how a MemcacheClient should be constructed.
type MemcacheConfig struct {
	Host           string
	Service        string
	Timeout        time.Duration
	UpdateInterval time.Duration
	Logger         log.Logger
	MaxIdleConns   int
}

func NewMemcacheClient(config MemcacheConfig) *MemcacheClient {
	var servers memcache.ServerList
	client := memcache.NewFromSelector(&servers)
	client.Timeout = config.Timeout
	client.MaxIdleConns = config.MaxIdleConns

	newClient := &MemcacheClient{
		client:     client,
		serverList: &servers,
		hostname:   config.Host,
		service:    config.Service,
		logger:     config.Logger,
		quit:       make(chan struct{}),
	}

	err := newClient.updateFromSRVRecords()
	if err != nil {
		config.Logger.Log("err", errors.Wrapf(err, "Error setting memcache servers to '%v'", config.Host))
	}

	newClient.wait.Add(1)
	go newClient.updateLoop(config.UpdateInterval, newClient.updateFromSRVRecords)
	return newClient
}

// Does not use DNS, accepts static list of servers.
func NewFixedServerMemcacheClient(config MemcacheConfig, addresses ...string) *MemcacheClient {
	var servers memcache.ServerList
	servers.SetServers(addresses...)
	client := memcache.NewFromSelector(&servers)
	client.Timeout = config.Timeout

	newClient := &MemcacheClient{
		client:     client,
		serverList: &servers,
		hostname:   config.Host,
		service:    config.Service,
		logger:     config.Logger,
		quit:       make(chan struct{}),
	}

	go newClient.updateLoop(config.UpdateInterval, func() error {
		return servers.SetServers(addresses...)
	})
	return newClient
}

// GetKey gets the value and its refresh deadline from the cache.
func (c *MemcacheClient) GetKey(k cache.Keyer) ([]byte, time.Time, error) {
	cacheItem, err := c.client.Get(k.Key())
	if err != nil {
		if err == memcache.ErrCacheMiss {
			// Don't log on cache miss
			return []byte{}, time.Time{}, cache.ErrNotCached
		} else {
			c.logger.Log("err", errors.Wrap(err, "Fetching tag from memcache"))
			return []byte{}, time.Time{}, err
		}
	}
	deadlineTime := binary.BigEndian.Uint32(cacheItem.Value)
	return cacheItem.Value[4:], time.Unix(int64(deadlineTime), 0), nil
}

// SetKey sets the value and its refresh deadline at a key. NB the key
// expiry is set _longer_ than the deadline, to give us a grace period
// in which to refresh the value.
func (c *MemcacheClient) SetKey(k cache.Keyer, refreshDeadline time.Time, v []byte) error {
	expiry := refreshDeadline.Sub(time.Now()) * 2
	if expiry < MinExpiry {
		expiry = MinExpiry
	}

	deadlineBytes := make([]byte, 4, 4)
	binary.BigEndian.PutUint32(deadlineBytes, uint32(refreshDeadline.Unix()))
	if err := c.client.Set(&memcache.Item{
		Key:        k.Key(),
		Value:      append(deadlineBytes, v...),
		Expiration: int32(expiry.Seconds()),
	}); err != nil {
		c.logger.Log("err", errors.Wrap(err, "storing in memcache"))
		return err
	}
	return nil
}

// Stop the memcache client.
func (c *MemcacheClient) Stop() {
	close(c.quit)
	c.wait.Wait()
}

func (c *MemcacheClient) updateLoop(updateInterval time.Duration, update func() error) {
	defer c.wait.Done()
	ticker := time.NewTicker(updateInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			if err := update(); err != nil {
				c.logger.Log("err", errors.Wrap(err, "error updating memcache servers"))
			}
		case <-c.quit:
			return
		}
	}
}

// updateMemcacheServers sets a memcache server list from SRV records. SRV
// priority & weight are ignored.
func (c *MemcacheClient) updateFromSRVRecords() error {
	_, addrs, err := net.LookupSRV(c.service, "tcp", c.hostname)
	if err != nil {
		return err
	}
	var servers []string
	for _, srv := range addrs {
		servers = append(servers, fmt.Sprintf("%s:%d", srv.Target, srv.Port))
	}
	// ServerList deterministically maps keys to _index_ of the server list.
	// Since DNS returns records in different order each time, we sort to
	// guarantee best possible match between nodes.
	sort.Strings(servers)
	return c.serverList.SetServers(servers...)
}
<|endoftext|>


<commit_before>package sacloud

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// EWebAccelDomainType ウェブアクセラレータ ドメイン種別
type EWebAccelDomainType string

const (
	// EWebAccelDomainTypeOwn 独自ドメイン
	EWebAccelDomainTypeOwn = EWebAccelDomainType("own_domain")
	// EWebAccelDomainTypeSubDomain サブドメイン
	EWebAccelDomainTypeSubDomain = EWebAccelDomainType("subdomain")
)

// EWebAccelStatus ウェブアクセラレータ ステータス
type EWebAccelStatus string

const (
	// EWebAccelStatusEnabled 状態:有効
	EWebAccelStatusEnabled = EWebAccelStatus("enabled")
	// EWebAccelStatusDisabled 状態:無効
	EWebAccelStatusDisabled = EWebAccelStatus("disabled")
)

// WebAccelSite ウェブアクセラレータ サイト
type WebAccelSite struct {
	ID                 string              // ID
	Name               string              `json:",omitempty"`
	DomainType         EWebAccelDomainType `json:",omitempty"`
	Domain             string              `json:",omitempty"`
	Subdomain          string              `json:",omitempty"`
	ASCIIDomain        string              `json:",omitempty"`
	Origin             string              `json:",omitempty"`
	HostHeader         string
	Status             EWebAccelStatus `json:",omitempty"`
	HasCertificate     bool            `json:",omitempty"`
	HasOldCertificate  bool            `json:",omitempty"`
	GibSentInLastWeek  int64           `json:",omitempty"`
	CertValidNotBefore int64           `json:",omitempty"`
	CertValidNotAfter  int64           `json:",omitempty"`
	*propCreatedAt
}

// SetID ID 設定
func (n *WebAccelSite) SetID(id int64) {
	n.ID = fmt.Sprintf("%d", id)
}

// GetID ID 取得
func (n *WebAccelSite) GetID() int64 {
	if n == nil {
		return -1
	}
	i, err := strconv.ParseInt(n.ID, 10, 64)
	if err != nil {
		return -1
	}
	return i
}

// GetStrID 文字列でID取得
func (n *WebAccelSite) GetStrID() string {
	if n == nil {
		return ""
	}
	return n.ID
}

// WebAccelCert ウェブアクセラレータ証明書
type WebAccelCert struct {
	ID               string `json:",omitempty"`
	SiteID           string `json:",omitempty"`
	CertificateChain string `json:",omitempty"`
	Key              string `json:",omitempty"`
	*propCreatedAt   `json:",omitempty"`
	*propUpdatedAt   `json:",omitempty"`

	SerialNumber string `json:",omitempty"`
	NotBefore    int64  `json:",omitempty"`
	NotAfter     int64  `json:",omitempty"`
	Issuer       *struct {
		Country            string `json:",omitempty"`
		Organization       string `json:",omitempty"`
		OrganizationalUnit string `json:",omitempty"`
		CommonName         string `json:",omitempty"`
	} `json:",omitempty"`
	Subject *struct {
		Country            string `json:",omitempty"`
		Organization       string `json:",omitempty"`
		OrganizationalUnit string `json:",omitempty"`
		Locality           string `json:",omitempty"`
		Province           string `json:",omitempty"`
		StreetAddress      string `json:",omitempty"`
		PostalCode         string `json:",omitempty"`
		SerialNumber       string `json:",omitempty"`
		CommonName         string `json:",omitempty"`
	} `json:",omitempty"`
	DNSNames          []string `json:",omitempty"`
	SHA256Fingerprint string   `json:",omitempty"`
}

// SetID ID 設定
func (n *WebAccelCert) SetID(id int64) {
	n.ID = fmt.Sprintf("%d", id)
}

// GetID ID 取得
func (n *WebAccelCert) GetID() int64 {
	if n == nil {
		return -1
	}
	i, err := strconv.ParseInt(n.ID, 10, 64)
	if err != nil {
		return -1
	}
	return i
}

// GetStrID 文字列でID取得
func (n *WebAccelCert) GetStrID() string {
	if n == nil {
		return ""
	}
	return n.ID
}

// WebAccelCertRequest ウェブアクセラレータ証明書API リクエスト
type WebAccelCertRequest struct {
	CertificateChain string
	Key              string
}

// WebAccelCertResponse ウェブアクセラレータ証明書API レスポンス
type WebAccelCertResponse struct {
	Certificate *WebAccelCertResponseBody `json:",omitempty"`
	ResultFlagValue
}

// WebAccelCertResponseBody ウェブアクセラレータ証明書API レスポンスボディ
type WebAccelCertResponseBody struct {
	Current *WebAccelCert   `json:",omitempty"`
	Old     []*WebAccelCert `json:",omitempty"`
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (s *WebAccelCertResponse) UnmarshalJSON(data []byte) error {
	tmp := &struct {
		Certificate *WebAccelCertResponseBody `json:",omitempty"`
		ResultFlagValue
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	if tmp.Certificate.Current != nil || len(tmp.Certificate.Old) > 0 {
		s.Certificate = tmp.Certificate
	}
	s.ResultFlagValue = tmp.ResultFlagValue
	return nil
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (s *WebAccelCertResponseBody) UnmarshalJSON(data []byte) error {
	targetData := strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1)
	if targetData == `[]` {
		return nil
	}

	tmp := &struct {
		Current *WebAccelCert   `json:",omitempty"`
		Old     []*WebAccelCert `json:",omitempty"`
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	s.Current = tmp.Current
	s.Old = tmp.Old
	return nil
}
<commit_msg>Add Getter/Setter to WebAccelSite<commit_after>package sacloud

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// EWebAccelDomainType ウェブアクセラレータ ドメイン種別
type EWebAccelDomainType string

const (
	// EWebAccelDomainTypeOwn 独自ドメイン
	EWebAccelDomainTypeOwn = EWebAccelDomainType("own_domain")
	// EWebAccelDomainTypeSubDomain サブドメイン
	EWebAccelDomainTypeSubDomain = EWebAccelDomainType("subdomain")
)

// EWebAccelStatus ウェブアクセラレータ ステータス
type EWebAccelStatus string

const (
	// EWebAccelStatusEnabled 状態:有効
	EWebAccelStatusEnabled = EWebAccelStatus("enabled")
	// EWebAccelStatusDisabled 状態:無効
	EWebAccelStatusDisabled = EWebAccelStatus("disabled")
)

// WebAccelSite ウェブアクセラレータ サイト
type WebAccelSite struct {
	ID                 string              // ID
	Name               string              `json:",omitempty"`
	DomainType         EWebAccelDomainType `json:",omitempty"`
	Domain             string              `json:",omitempty"`
	Subdomain          string              `json:",omitempty"`
	ASCIIDomain        string              `json:",omitempty"`
	Origin             string              `json:",omitempty"`
	HostHeader         string
	Status             EWebAccelStatus `json:",omitempty"`
	HasCertificate     bool            `json:",omitempty"`
	HasOldCertificate  bool            `json:",omitempty"`
	GibSentInLastWeek  int64           `json:",omitempty"`
	CertValidNotBefore int64           `json:",omitempty"`
	CertValidNotAfter  int64           `json:",omitempty"`
	*propCreatedAt
}

// SetID ID 設定
func (n *WebAccelSite) SetID(id int64) {
	n.ID = fmt.Sprintf("%d", id)
}

// GetID ID 取得
func (n *WebAccelSite) GetID() int64 {
	if n == nil {
		return -1
	}
	i, err := strconv.ParseInt(n.ID, 10, 64)
	if err != nil {
		return -1
	}
	return i
}

// GetStrID 文字列でID取得
func (n *WebAccelSite) GetStrID() string {
	if n == nil {
		return ""
	}
	return n.ID
}

// GetName 名称取得
func (n *WebAccelSite) GetName() string {
	if n == nil {
		return ""
	}
	return n.Name
}

// SetName 名称取得
func (n *WebAccelSite) SetName(name string) {
	if n == nil {
		return
	}
	n.Name = name
}

// WebAccelCert ウェブアクセラレータ証明書
type WebAccelCert struct {
	ID               string `json:",omitempty"`
	SiteID           string `json:",omitempty"`
	CertificateChain string `json:",omitempty"`
	Key              string `json:",omitempty"`
	*propCreatedAt   `json:",omitempty"`
	*propUpdatedAt   `json:",omitempty"`

	SerialNumber string `json:",omitempty"`
	NotBefore    int64  `json:",omitempty"`
	NotAfter     int64  `json:",omitempty"`
	Issuer       *struct {
		Country            string `json:",omitempty"`
		Organization       string `json:",omitempty"`
		OrganizationalUnit string `json:",omitempty"`
		CommonName         string `json:",omitempty"`
	} `json:",omitempty"`
	Subject *struct {
		Country            string `json:",omitempty"`
		Organization       string `json:",omitempty"`
		OrganizationalUnit string `json:",omitempty"`
		Locality           string `json:",omitempty"`
		Province           string `json:",omitempty"`
		StreetAddress      string `json:",omitempty"`
		PostalCode         string `json:",omitempty"`
		SerialNumber       string `json:",omitempty"`
		CommonName         string `json:",omitempty"`
	} `json:",omitempty"`
	DNSNames          []string `json:",omitempty"`
	SHA256Fingerprint string   `json:",omitempty"`
}

// SetID ID 設定
func (n *WebAccelCert) SetID(id int64) {
	n.ID = fmt.Sprintf("%d", id)
}

// GetID ID 取得
func (n *WebAccelCert) GetID() int64 {
	if n == nil {
		return -1
	}
	i, err := strconv.ParseInt(n.ID, 10, 64)
	if err != nil {
		return -1
	}
	return i
}

// GetStrID 文字列でID取得
func (n *WebAccelCert) GetStrID() string {
	if n == nil {
		return ""
	}
	return n.ID
}

// WebAccelCertRequest ウェブアクセラレータ証明書API リクエスト
type WebAccelCertRequest struct {
	CertificateChain string
	Key              string
}

// WebAccelCertResponse ウェブアクセラレータ証明書API レスポンス
type WebAccelCertResponse struct {
	Certificate *WebAccelCertResponseBody `json:",omitempty"`
	ResultFlagValue
}

// WebAccelCertResponseBody ウェブアクセラレータ証明書API レスポンスボディ
type WebAccelCertResponseBody struct {
	Current *WebAccelCert   `json:",omitempty"`
	Old     []*WebAccelCert `json:",omitempty"`
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (s *WebAccelCertResponse) UnmarshalJSON(data []byte) error {
	tmp := &struct {
		Certificate *WebAccelCertResponseBody `json:",omitempty"`
		ResultFlagValue
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	if tmp.Certificate.Current != nil || len(tmp.Certificate.Old) > 0 {
		s.Certificate = tmp.Certificate
	}
	s.ResultFlagValue = tmp.ResultFlagValue
	return nil
}

// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
func (s *WebAccelCertResponseBody) UnmarshalJSON(data []byte) error {
	targetData := strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1)
	if targetData == `[]` {
		return nil
	}

	tmp := &struct {
		Current *WebAccelCert   `json:",omitempty"`
		Old     []*WebAccelCert `json:",omitempty"`
	}{}
	if err := json.Unmarshal(data, &tmp); err != nil {
		return err
	}

	s.Current = tmp.Current
	s.Old = tmp.Old
	return nil
}
<|endoftext|>