<commit_before>package presence

import (
	"errors"
	"fmt"
	"strconv"
	"sync"
	"time"

	gredis "github.com/garyburd/redigo/redis"
	"github.com/koding/redis"
)

var (
	// Prefix for presence package
	PresencePrefix = "presence"

	// ErrInvalidID for stating the event id is not valid
	ErrInvalidID = errors.New("invalid id")

	// ErrInvalidStatus for stating the event status is not valid
	ErrInvalidStatus = errors.New("invalid status")
)

// Redis holds the required connection data for redis
type Redis struct {
	// main redis connection
	redis *redis.RedisSession

	// inactiveDuration specifies no-probe allowance time
	inactiveDuration string

	// receiving offline events pattern
	becameOfflinePattern string

	// receiving online events pattern
	becameOnlinePattern string

	// errChan pipe all errors  the this channel
	errChan chan error

	// closed holds the status of connection
	closed bool

	//psc holds the pubsub channel if opened
	psc *gredis.PubSubConn

	// holds event channel
	events chan Event

	// lock for Redis struct
	mu sync.Mutex
}

// NewRedis creates a Redis for any broker system that is architected to use,
// communicate, forward events to the presence system
func NewRedis(server string, db int, inactiveDuration time.Duration) (Backend, error) {
	redis, err := redis.NewRedisSession(&redis.RedisConf{Server: server, DB: db})
	if err != nil {
		return nil, err
	}
	redis.SetPrefix(PresencePrefix)

	return &Redis{
		redis:                redis,
		becameOfflinePattern: fmt.Sprintf("__keyevent@%d__:expired", db),
		becameOnlinePattern:  fmt.Sprintf("__keyevent@%d__:set", db),
		inactiveDuration:     strconv.Itoa(int(inactiveDuration.Seconds())),
		errChan:              make(chan error, 1),
	}, nil
}

// Online resets the expiration time for any given key
// if key doesnt exists, it means user is now online and should be set as online
// Whenever application gets any probe from a client
// should call this function
func (s *Redis) Online(ids ...string) error {
	existance, err := s.multiExpire(ids, s.inactiveDuration)
	if err == nil {
		return s.multiSetIfRequired(ids, existance, Error{})
	}

	// if err is not a multi err, return it
	e, ok := err.(Error)
	if !ok {
		return err
	}

	errs := s.multiSetIfRequired(ids, existance, e)
	if errs != nil {
		return errs
	}

	if e.Len() > 0 {
		return e
	}

	return nil
}

// Offline sets given ids as offline
func (s *Redis) Offline(ids ...string) error {
	const zeroTimeString = "0"
	_, err := s.multiExpire(ids, zeroTimeString)
	return err
}

// Status returns the current status of multiple keys from system
func (s *Redis) Status(ids ...string) ([]Event, error) {
	// get one connection from pool
	c := s.redis.Pool().Get()
	// close connection
	defer c.Close()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXISTS", s.redis.AddPrefix(id))
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return nil, err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return nil, err
	}

	res := make([]Event, len(values))
	for i, value := range values {
		status, err := s.redis.Int(value)
		if err != nil {
			return nil, err
		}

		res[i] = Event{
			ID: ids[i],
			// cast redis response to Status
			Status: redisResToStatus[status],
		}
	}

	return res, nil
}

// Error returns error if it happens while listening  to status changes
func (s *Redis) Error() chan error {
	return s.errChan
}

// Close closes the redis connection gracefully
func (s *Redis) Close() error {
	return s.close()
}

// ListenStatusChanges subscribes with a pattern to the redis and
// gets online and offline status changes from it
func (s *Redis) ListenStatusChanges() chan Event {
	s.psc = s.redis.CreatePubSubConn()
	s.psc.PSubscribe(s.becameOnlinePattern, s.becameOfflinePattern)

	s.events = make(chan Event)
	go s.listenEvents()
	return s.events
}

var redisResToStatus = map[int]Status{
	// redis exists response is 0 when the id is not in the system
	0: Offline,

	// redis exists response is 1 when the id is in the system
	1: Online,
}

func (s *Redis) close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return errors.New("closing of already closed connection")
	}

	s.closed = true

	if s.events != nil {
		close(s.events)
	}

	if s.psc != nil {
		s.psc.PUnsubscribe()
	}

	return s.redis.Close()
}

// createEvent Creates the event with the required properties
func (s *Redis) listenEvents() {
	for {
		s.mu.Lock()
		if s.closed {
			s.mu.Unlock()
			return
		}
		s.mu.Unlock()

		switch n := s.psc.Receive().(type) {
		case gredis.PMessage:
			s.events <- s.createEvent(n)
		case error:
			s.errChan <- n
			return
		}
	}
}

// createEvent Creates the event with the required properties
func (s *Redis) createEvent(n gredis.PMessage) Event {
	e := Event{}

	// if incoming data len is smaller than our prefix, do not process the event
	if len(n.Data) < len(PresencePrefix) {
		s.errChan <- ErrInvalidID
		return e
	}

	e.ID = string(n.Data[len(PresencePrefix)+1:])

	switch n.Pattern {
	case s.becameOfflinePattern:
		e.Status = Offline
	case s.becameOnlinePattern:
		e.Status = Online
	default:
		// todo - replace this with a custom err
		s.errChan <- ErrInvalidStatus
	}

	return e
}

// multiSetIfRequired accepts set of ids and their existtance status
// traverse over them and any key is not exists in db, set them in a multi/exec
// request
func (s *Redis) multiSetIfRequired(ids []string, existance []int, e Error) error {
	if len(ids) != len(existance) {
		return fmt.Errorf("length is not same Ids: %d Existance: %d", len(ids), len(existance))
	}

	// get one connection from pool
	c := s.redis.Pool().Get()
	// do not forget to close the connection
	defer c.Close()

	// item count for non-existent members
	notExistsCount := 0

	for i, exists := range existance {
		// `0` means, member doesnt exists in presence system
		if exists != 0 {
			continue
		}

		// if we got any error for the current id, do not process it
		if e.Has(ids[i]) {
			continue
		}

		// init multi command lazily
		if notExistsCount == 0 {
			c.Send("MULTI")
		}

		notExistsCount++
		c.Send("SETEX", s.redis.AddPrefix(ids[i]), s.inactiveDuration, ids[i])
	}

	// execute multi command if only we flushed some to connection
	if notExistsCount != 0 {
		// ignore values
		if _, err := c.Do("EXEC"); err != nil {
			return err
		}
	}

	return nil
}

// multiExpire if the system tries to update more than one key at a time
// inorder to leverage rtt, send multi expire
func (s *Redis) multiExpire(ids []string, duration string) ([]int, error) {
	// get one connection from pool
	c := s.redis.Pool().Get()
	// close connection
	defer c.Close()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXPIRE", s.redis.AddPrefix(id), duration)
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return nil, err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return nil, err
	}

	e := Error{}
	res := make([]int, len(values))
	for i, value := range values {
		res[i], err = s.redis.Int(value)
		if err != nil {
			e.Append(ids[i], err)
		}

	}

	// if we got any error, return them all along with result set
	if e.Len() > 0 {
		return res, e
	}

	return res, nil
}
<commit_msg>Redis: s/PresencePrefix/Prefix<commit_after>package presence

import (
	"errors"
	"fmt"
	"strconv"
	"sync"
	"time"

	gredis "github.com/garyburd/redigo/redis"
	"github.com/koding/redis"
)

var (
	// Prefix for presence package
	Prefix = "presence"

	// ErrInvalidID for stating the event id is not valid
	ErrInvalidID = errors.New("invalid id")

	// ErrInvalidStatus for stating the event status is not valid
	ErrInvalidStatus = errors.New("invalid status")
)

// Redis holds the required connection data for redis
type Redis struct {
	// main redis connection
	redis *redis.RedisSession

	// inactiveDuration specifies no-probe allowance time
	inactiveDuration string

	// receiving offline events pattern
	becameOfflinePattern string

	// receiving online events pattern
	becameOnlinePattern string

	// errChan pipe all errors  the this channel
	errChan chan error

	// closed holds the status of connection
	closed bool

	//psc holds the pubsub channel if opened
	psc *gredis.PubSubConn

	// holds event channel
	events chan Event

	// lock for Redis struct
	mu sync.Mutex
}

// NewRedis creates a Redis for any broker system that is architected to use,
// communicate, forward events to the presence system
func NewRedis(server string, db int, inactiveDuration time.Duration) (Backend, error) {
	redis, err := redis.NewRedisSession(&redis.RedisConf{Server: server, DB: db})
	if err != nil {
		return nil, err
	}
	redis.SetPrefix(Prefix)

	return &Redis{
		redis:                redis,
		becameOfflinePattern: fmt.Sprintf("__keyevent@%d__:expired", db),
		becameOnlinePattern:  fmt.Sprintf("__keyevent@%d__:set", db),
		inactiveDuration:     strconv.Itoa(int(inactiveDuration.Seconds())),
		errChan:              make(chan error, 1),
	}, nil
}

// Online resets the expiration time for any given key
// if key doesnt exists, it means user is now online and should be set as online
// Whenever application gets any probe from a client
// should call this function
func (s *Redis) Online(ids ...string) error {
	existance, err := s.multiExpire(ids, s.inactiveDuration)
	if err == nil {
		return s.multiSetIfRequired(ids, existance, Error{})
	}

	// if err is not a multi err, return it
	e, ok := err.(Error)
	if !ok {
		return err
	}

	errs := s.multiSetIfRequired(ids, existance, e)
	if errs != nil {
		return errs
	}

	if e.Len() > 0 {
		return e
	}

	return nil
}

// Offline sets given ids as offline
func (s *Redis) Offline(ids ...string) error {
	const zeroTimeString = "0"
	_, err := s.multiExpire(ids, zeroTimeString)
	return err
}

// Status returns the current status of multiple keys from system
func (s *Redis) Status(ids ...string) ([]Event, error) {
	// get one connection from pool
	c := s.redis.Pool().Get()
	// close connection
	defer c.Close()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXISTS", s.redis.AddPrefix(id))
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return nil, err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return nil, err
	}

	res := make([]Event, len(values))
	for i, value := range values {
		status, err := s.redis.Int(value)
		if err != nil {
			return nil, err
		}

		res[i] = Event{
			ID: ids[i],
			// cast redis response to Status
			Status: redisResToStatus[status],
		}
	}

	return res, nil
}

// Error returns error if it happens while listening  to status changes
func (s *Redis) Error() chan error {
	return s.errChan
}

// Close closes the redis connection gracefully
func (s *Redis) Close() error {
	return s.close()
}

// ListenStatusChanges subscribes with a pattern to the redis and
// gets online and offline status changes from it
func (s *Redis) ListenStatusChanges() chan Event {
	s.psc = s.redis.CreatePubSubConn()
	s.psc.PSubscribe(s.becameOnlinePattern, s.becameOfflinePattern)

	s.events = make(chan Event)
	go s.listenEvents()
	return s.events
}

var redisResToStatus = map[int]Status{
	// redis exists response is 0 when the id is not in the system
	0: Offline,

	// redis exists response is 1 when the id is in the system
	1: Online,
}

func (s *Redis) close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return errors.New("closing of already closed connection")
	}

	s.closed = true

	if s.events != nil {
		close(s.events)
	}

	if s.psc != nil {
		s.psc.PUnsubscribe()
	}

	return s.redis.Close()
}

// createEvent Creates the event with the required properties
func (s *Redis) listenEvents() {
	for {
		s.mu.Lock()
		if s.closed {
			s.mu.Unlock()
			return
		}
		s.mu.Unlock()

		switch n := s.psc.Receive().(type) {
		case gredis.PMessage:
			s.events <- s.createEvent(n)
		case error:
			s.errChan <- n
			return
		}
	}
}

// createEvent Creates the event with the required properties
func (s *Redis) createEvent(n gredis.PMessage) Event {
	e := Event{}

	// if incoming data len is smaller than our prefix, do not process the event
	if len(n.Data) < len(Prefix) {
		s.errChan <- ErrInvalidID
		return e
	}

	e.ID = string(n.Data[len(Prefix)+1:])

	switch n.Pattern {
	case s.becameOfflinePattern:
		e.Status = Offline
	case s.becameOnlinePattern:
		e.Status = Online
	default:
		// todo - replace this with a custom err
		s.errChan <- ErrInvalidStatus
	}

	return e
}

// multiSetIfRequired accepts set of ids and their existtance status
// traverse over them and any key is not exists in db, set them in a multi/exec
// request
func (s *Redis) multiSetIfRequired(ids []string, existance []int, e Error) error {
	if len(ids) != len(existance) {
		return fmt.Errorf("length is not same Ids: %d Existance: %d", len(ids), len(existance))
	}

	// get one connection from pool
	c := s.redis.Pool().Get()
	// do not forget to close the connection
	defer c.Close()

	// item count for non-existent members
	notExistsCount := 0

	for i, exists := range existance {
		// `0` means, member doesnt exists in presence system
		if exists != 0 {
			continue
		}

		// if we got any error for the current id, do not process it
		if e.Has(ids[i]) {
			continue
		}

		// init multi command lazily
		if notExistsCount == 0 {
			c.Send("MULTI")
		}

		notExistsCount++
		c.Send("SETEX", s.redis.AddPrefix(ids[i]), s.inactiveDuration, ids[i])
	}

	// execute multi command if only we flushed some to connection
	if notExistsCount != 0 {
		// ignore values
		if _, err := c.Do("EXEC"); err != nil {
			return err
		}
	}

	return nil
}

// multiExpire if the system tries to update more than one key at a time
// inorder to leverage rtt, send multi expire
func (s *Redis) multiExpire(ids []string, duration string) ([]int, error) {
	// get one connection from pool
	c := s.redis.Pool().Get()
	// close connection
	defer c.Close()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXPIRE", s.redis.AddPrefix(id), duration)
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return nil, err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return nil, err
	}

	e := Error{}
	res := make([]int, len(values))
	for i, value := range values {
		res[i], err = s.redis.Int(value)
		if err != nil {
			e.Append(ids[i], err)
		}

	}

	// if we got any error, return them all along with result set
	if e.Len() > 0 {
		return res, e
	}

	return res, nil
}
<|endoftext|>


<commit_before>package limio

import "io"

type LimitedWriter struct {
	W io.Writer
	N int64
}

func (l *LimitedWriter) Write(p []byte) (n int, err error) {
	if l.N <= 0 {
		return 0, io.ErrShortWrite
	}
	if int64(len(p)) > l.N {
		p = p[0:l.N]
		err = io.ErrShortWrite
	}
	n, er := l.W.Write(p)
	if er != nil {
		err = er
	}
	l.N -= int64(n)
	return n, err
}

func LimitWriter(w io.Writer, n int64) io.Writer {
	return &LimitedWriter{W: w, N: n}
}
<commit_msg>added godoc to LimitWriter<commit_after>package limio

import "io"

type limitedWriter struct {
	W io.Writer
	N int64
}

func (l *limitedWriter) Write(p []byte) (n int, err error) {
	if l.N <= 0 {
		return 0, io.ErrShortWrite
	}
	if int64(len(p)) > l.N {
		p = p[0:l.N]
		err = io.ErrShortWrite
	}
	n, er := l.W.Write(p)
	if er != nil {
		err = er
	}
	l.N -= int64(n)
	return n, err
}

// LimitWriter works like io.LimitReader. It writes at most n bytes
// to the underlying Writer. It returns io.ErrShortWrite if more than n
// bytes are attempted to be written.
func LimitWriter(w io.Writer, n int64) io.Writer {
	return &limitedWriter{W: w, N: n}
}
<|endoftext|>


<commit_before>package project

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	lib "github.com/bazooka-ci/bazooka-lib"
	"github.com/bazooka-ci/bazooka-lib/mongo"
	docker "github.com/bywan/go-dockercommand"
	"github.com/gorilla/mux"
	"github.com/haklop/bazooka/server/context"
)

const (
	buildFolderPattern = "%s/build/%s/%s"     // $bzk_home/build/$projectId/$buildId
	logFolderPattern   = "%s/build/%s/%s/log" // $bzk_home/build/$projectId/$buildId/log
	// keyFolderPattern   = "%s/key/%s"         // $bzk_home/key/$keyName
)

type Handlers struct {
	mongoConnector *mongo.MongoConnector
	env            map[string]string
	dockerEndpoint string
}

func (p *Handlers) SetHandlers(r *mux.Router, serverContext context.Context) {
	p.mongoConnector = serverContext.Connector
	p.env = serverContext.Env
	p.dockerEndpoint = serverContext.DockerEndpoint

	r.HandleFunc("/", p.createProject).Methods("POST")
	r.HandleFunc("/", p.getProjects).Methods("GET")
	r.HandleFunc("/{id}", p.getProject).Methods("GET")
	r.HandleFunc("/{id}/job", p.startBuild).Methods("POST")
	r.HandleFunc("/{id}/job/", p.getJobs).Methods("GET")
	r.HandleFunc("/{id}/job/{job_id}", p.getJob).Methods("GET")
	r.HandleFunc("/{id}/job/{job_id}/logs", p.getJob).Methods("GET")
}

func (p *Handlers) createProject(res http.ResponseWriter, req *http.Request) {
	var project lib.Project

	decoder := json.NewDecoder(req.Body)
	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	err := decoder.Decode(&project)

	if err != nil {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "Unable to decode your json : " + err.Error(),
		})
		return
	}

	if len(project.ScmURI) == 0 {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "scm_uri is mandatory",
		})

		return
	}

	if len(project.ScmType) == 0 {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "scm_type is mandatory",
		})

		return
	}

	existantProject, err := p.mongoConnector.GetProject(project.ScmType, project.ScmURI)
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
	}

	if len(existantProject.ScmURI) > 0 {
		res.WriteHeader(409)
		encoder.Encode(&context.ErrorResponse{
			Code:    409,
			Message: "scm_uri is already known",
		})

		return
	}

	// TODO : validate scm_type
	// TODO : validate data by scm_type

	err = p.mongoConnector.AddProject(&project)
	res.Header().Set("Location", "/project/"+project.ID)

	res.WriteHeader(201)
	encoder.Encode(&project)

}

func (p *Handlers) getProject(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)

	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	project, err := p.mongoConnector.GetProjectById(params["id"])
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "project not found",
		})

		return
	}

	res.WriteHeader(200)
	encoder.Encode(&project)
}

func (p *Handlers) getProjects(res http.ResponseWriter, req *http.Request) {
	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	projects, err := p.mongoConnector.GetProjects()
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	res.WriteHeader(200)
	encoder.Encode(&projects)
}

func (p *Handlers) startBuild(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)
	var startJob lib.StartJob

	decoder := json.NewDecoder(req.Body)
	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	err := decoder.Decode(&startJob)
	if err != nil {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "Invalid body : " + err.Error(),
		})
		return
	}

	if len(startJob.ScmReference) == 0 {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "reference is mandatory",
		})

		return
	}

	project, err := p.mongoConnector.GetProjectById(params["id"])
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	client, err := docker.NewDocker(p.dockerEndpoint)
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	var runningJob lib.Job
	runningJob.ID = strconv.FormatInt(time.Now().Unix(), 10)
	runningJob.ProjectID = project.ID

	buildFolder := fmt.Sprintf(buildFolderPattern, p.env[context.BazookaEnvHome], runningJob.ProjectID, runningJob.ID)
	orchestrationEnv := map[string]string{
		"BZK_SCM":           "git",
		"BZK_SCM_URL":       project.ScmURI,
		"BZK_SCM_REFERENCE": startJob.ScmReference,
		"BZK_SCM_KEYFILE":   p.env[context.BazookaEnvSCMKeyfile], //TODO use keyfile per project
		"BZK_HOME":          buildFolder,
		"BZK_PROJECT_ID":    project.ID,
		"BZK_JOB_ID":        runningJob.ID, // TODO handle job number and tasks and save it
		"BZK_DOCKERSOCK":    p.env[context.BazookaEnvDockerSock],
	}

	container, err := client.Run(&docker.RunOptions{
		Image:       "bazooka/orchestration",
		VolumeBinds: []string{fmt.Sprintf("%s:/bazooka", buildFolder), fmt.Sprintf("%s:/var/run/docker.sock", p.env[context.BazookaEnvDockerSock])},
		Env:         orchestrationEnv,
		Detach:      true,
	})

	logFolder := fmt.Sprintf(logFolderPattern, context.BazookaHome, runningJob.ProjectID, runningJob.ID)
	os.MkdirAll(logFolder, 0755)

	logFileWriter, err := os.Create(logFolder + "/job.log")
	if err != nil {
		panic(err)
	}

	runningJob.OrchestrationID = container.ID()
	orchestrationLog := log.New(logFileWriter, "", log.LstdFlags)
	orchestrationLog.Printf("Start job %s on project %s with container %s\n", runningJob.ID, runningJob.ProjectID, runningJob.OrchestrationID)

	if err != nil {
		orchestrationLog.Println(err.Error())
		context.WriteError(err, res, encoder)
		return
	}

	r, w := io.Pipe()
	container.StreamLogs(w)
	go func(reader io.Reader, logFileWriter *os.File) {
		scanner := bufio.NewScanner(reader)
		for scanner.Scan() {
			orchestrationLog.Printf("%s \n", scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			orchestrationLog.Println("There was an error with the scanner in attached container", err)
		}
		logFileWriter.Close()
	}(r, logFileWriter)

	err = p.mongoConnector.AddJob(&runningJob)
	res.Header().Set("Location", "/project/"+project.ID+"/job/"+runningJob.ID)

	res.WriteHeader(202)
	encoder.Encode(&runningJob)
}

func (p *Handlers) getJob(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)

	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	job, err := p.mongoConnector.GetJobByID(params["job_id"])
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "job not found",
		})
		return
	}

	if params["id"] != job.ProjectID {
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "project not found",
		})
		return
	}

	res.WriteHeader(200)
	encoder.Encode(&job)
}

func (p *Handlers) getJobs(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)

	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	jobs, err := p.mongoConnector.GetJobs(params["id"])
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	res.WriteHeader(200)
	encoder.Encode(&jobs)
}
<commit_msg>Fix http code<commit_after>package project

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	lib "github.com/bazooka-ci/bazooka-lib"
	"github.com/bazooka-ci/bazooka-lib/mongo"
	docker "github.com/bywan/go-dockercommand"
	"github.com/gorilla/mux"
	"github.com/haklop/bazooka/server/context"
)

const (
	buildFolderPattern = "%s/build/%s/%s"     // $bzk_home/build/$projectId/$buildId
	logFolderPattern   = "%s/build/%s/%s/log" // $bzk_home/build/$projectId/$buildId/log
	// keyFolderPattern   = "%s/key/%s"         // $bzk_home/key/$keyName
)

type Handlers struct {
	mongoConnector *mongo.MongoConnector
	env            map[string]string
	dockerEndpoint string
}

func (p *Handlers) SetHandlers(r *mux.Router, serverContext context.Context) {
	p.mongoConnector = serverContext.Connector
	p.env = serverContext.Env
	p.dockerEndpoint = serverContext.DockerEndpoint

	r.HandleFunc("/", p.createProject).Methods("POST")
	r.HandleFunc("/", p.getProjects).Methods("GET")
	r.HandleFunc("/{id}", p.getProject).Methods("GET")
	r.HandleFunc("/{id}/job", p.startBuild).Methods("POST")
	r.HandleFunc("/{id}/job/", p.getJobs).Methods("GET")
	r.HandleFunc("/{id}/job/{job_id}", p.getJob).Methods("GET")
}

func (p *Handlers) createProject(res http.ResponseWriter, req *http.Request) {
	var project lib.Project

	decoder := json.NewDecoder(req.Body)
	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	err := decoder.Decode(&project)

	if err != nil {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "Unable to decode your json : " + err.Error(),
		})
		return
	}

	if len(project.ScmURI) == 0 {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "scm_uri is mandatory",
		})

		return
	}

	if len(project.ScmType) == 0 {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "scm_type is mandatory",
		})

		return
	}

	existantProject, err := p.mongoConnector.GetProject(project.ScmType, project.ScmURI)
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
	}

	if len(existantProject.ScmURI) > 0 {
		res.WriteHeader(409)
		encoder.Encode(&context.ErrorResponse{
			Code:    409,
			Message: "scm_uri is already known",
		})

		return
	}

	// TODO : validate scm_type
	// TODO : validate data by scm_type

	err = p.mongoConnector.AddProject(&project)
	res.Header().Set("Location", "/project/"+project.ID)

	res.WriteHeader(201)
	encoder.Encode(&project)

}

func (p *Handlers) getProject(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)

	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	project, err := p.mongoConnector.GetProjectById(params["id"])
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "project not found",
		})

		return
	}

	res.WriteHeader(200)
	encoder.Encode(&project)
}

func (p *Handlers) getProjects(res http.ResponseWriter, req *http.Request) {
	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	projects, err := p.mongoConnector.GetProjects()
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	res.WriteHeader(200)
	encoder.Encode(&projects)
}

func (p *Handlers) startBuild(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)
	var startJob lib.StartJob

	decoder := json.NewDecoder(req.Body)
	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	err := decoder.Decode(&startJob)
	if err != nil {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "Invalid body : " + err.Error(),
		})
		return
	}

	if len(startJob.ScmReference) == 0 {
		res.WriteHeader(400)
		encoder.Encode(&context.ErrorResponse{
			Code:    400,
			Message: "reference is mandatory",
		})

		return
	}

	project, err := p.mongoConnector.GetProjectById(params["id"])
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "project not found",
		})
		return
	}

	client, err := docker.NewDocker(p.dockerEndpoint)
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	var runningJob lib.Job
	runningJob.ID = strconv.FormatInt(time.Now().Unix(), 10)
	runningJob.ProjectID = project.ID

	buildFolder := fmt.Sprintf(buildFolderPattern, p.env[context.BazookaEnvHome], runningJob.ProjectID, runningJob.ID)
	orchestrationEnv := map[string]string{
		"BZK_SCM":           "git",
		"BZK_SCM_URL":       project.ScmURI,
		"BZK_SCM_REFERENCE": startJob.ScmReference,
		"BZK_SCM_KEYFILE":   p.env[context.BazookaEnvSCMKeyfile], //TODO use keyfile per project
		"BZK_HOME":          buildFolder,
		"BZK_PROJECT_ID":    project.ID,
		"BZK_JOB_ID":        runningJob.ID, // TODO handle job number and tasks and save it
		"BZK_DOCKERSOCK":    p.env[context.BazookaEnvDockerSock],
	}

	container, err := client.Run(&docker.RunOptions{
		Image:       "bazooka/orchestration",
		VolumeBinds: []string{fmt.Sprintf("%s:/bazooka", buildFolder), fmt.Sprintf("%s:/var/run/docker.sock", p.env[context.BazookaEnvDockerSock])},
		Env:         orchestrationEnv,
		Detach:      true,
	})

	logFolder := fmt.Sprintf(logFolderPattern, context.BazookaHome, runningJob.ProjectID, runningJob.ID)
	os.MkdirAll(logFolder, 0755)

	logFileWriter, err := os.Create(logFolder + "/job.log")
	if err != nil {
		panic(err)
	}

	runningJob.OrchestrationID = container.ID()
	orchestrationLog := log.New(logFileWriter, "", log.LstdFlags)
	orchestrationLog.Printf("Start job %s on project %s with container %s\n", runningJob.ID, runningJob.ProjectID, runningJob.OrchestrationID)

	if err != nil {
		orchestrationLog.Println(err.Error())
		context.WriteError(err, res, encoder)
		return
	}

	r, w := io.Pipe()
	container.StreamLogs(w)
	go func(reader io.Reader, logFileWriter *os.File) {
		scanner := bufio.NewScanner(reader)
		for scanner.Scan() {
			orchestrationLog.Printf("%s \n", scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			orchestrationLog.Println("There was an error with the scanner in attached container", err)
		}
		logFileWriter.Close()
	}(r, logFileWriter)

	err = p.mongoConnector.AddJob(&runningJob)
	res.Header().Set("Location", "/project/"+project.ID+"/job/"+runningJob.ID)

	res.WriteHeader(202)
	encoder.Encode(&runningJob)
}

func (p *Handlers) getJob(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)

	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	job, err := p.mongoConnector.GetJobByID(params["job_id"])
	if err != nil {
		if err.Error() != "not found" {
			context.WriteError(err, res, encoder)
			return
		}
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "job not found",
		})
		return
	}

	if params["id"] != job.ProjectID {
		res.WriteHeader(404)
		encoder.Encode(&context.ErrorResponse{
			Code:    404,
			Message: "project not found",
		})
		return
	}

	res.WriteHeader(200)
	encoder.Encode(&job)
}

func (p *Handlers) getJobs(res http.ResponseWriter, req *http.Request) {
	params := mux.Vars(req)

	encoder := json.NewEncoder(res)
	res.Header().Set("Content-Type", "application/json; charset=utf-8")

	jobs, err := p.mongoConnector.GetJobs(params["id"])
	if err != nil {
		context.WriteError(err, res, encoder)
		return
	}

	res.WriteHeader(200)
	encoder.Encode(&jobs)
}
<|endoftext|>


<commit_before>package pg

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

const (
	RecurringID     = "RecurringID"
	ChargeDay       = "ChargeDay"
	ChargeMonth     = "ChargeMonth"
	ChargeStartDate = "ChargeStartDate"
	ChargeStopDate  = "ChargeStopDate"
	RegistType      = "RegistType"
)

func (r *Recurring) ToValues() url.Values {
	vs := url.Values{}
	vs.Add(RecurringID, r.ID)
	vs.Add(Amount, strconv.Itoa(r.Amount))
	vs.Add(Tax, strconv.Itoa(r.Tax))
	vs.Add(ChargeDay, r.ChargeDay)
	vs.Add(ChargeMonth, r.ChargeMonth)
	vs.Add(ChargeStartDate, r.ChargeStartDate)
	vs.Add(ChargeStopDate, r.ChargeStopDate)
	vs.Add(RegistType, r.RegistType)
	vs.Add(MemberID, r.Member.ID)
	return vs
}

// RegistMemberRecurring store recurring object for member
func (c *Client) RegistMemberRecurring(r *Recurring) (*RecurringResponse, *ErrorResponses) {
	v := c.mergeValues(r.ToValues())
	bodyString, err := c.post(fmt.Sprintf(c.APIBaseURL, "RegisterRecurringCredit"), strings.NewReader(v.Encode()))

	if err != nil {
		return nil, nil
	}

	rr, errors := ConvertToMemberRrecurring(bodyString)

	if errors != nil && errors.Count > 0 {
		return nil, errors
	}

	return rr, nil
}
<commit_msg>add cancel method<commit_after>package pg

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
)

const (
	RecurringID     = "RecurringID"
	ChargeDay       = "ChargeDay"
	ChargeMonth     = "ChargeMonth"
	ChargeStartDate = "ChargeStartDate"
	ChargeStopDate  = "ChargeStopDate"
	RegistType      = "RegistType"
)

func (r *Recurring) ToValues() url.Values {
	vs := url.Values{}
	vs.Add(RecurringID, r.ID)
	vs.Add(Amount, strconv.Itoa(r.Amount))
	vs.Add(Tax, strconv.Itoa(r.Tax))
	vs.Add(ChargeDay, r.ChargeDay)
	vs.Add(ChargeMonth, r.ChargeMonth)
	vs.Add(ChargeStartDate, r.ChargeStartDate)
	vs.Add(ChargeStopDate, r.ChargeStopDate)
	vs.Add(RegistType, r.RegistType)
	vs.Add(MemberID, r.Member.ID)
	return vs
}

// RegisterMemberRecurring store recurring object for member
func (c *Client) RegisterMemberRecurring(r *Recurring) (*RecurringResponse, *ErrorResponses) {
	v := c.mergeValues(r.ToValues())
	bodyString, err := c.post(fmt.Sprintf(c.APIBaseURL, "RegisterRecurringCredit"), strings.NewReader(v.Encode()))

	if err != nil {
		return nil, nil
	}

	rr, errors := ConvertToMemberRrecurring(bodyString)

	if errors != nil && errors.Count > 0 {
		return nil, errors
	}

	return rr, nil
}

// UnregisterRecurring cancel recurring object
func (c *Client) UnregisterRecurring(r *Recurring) (*RecurringResponse, *ErrorResponses) {
	//only ShopID,ShopPass,RecurringID
	v := url.Values{}
	v.Add(ShopID, c.ShopID)
	v.Add(ShopPass, c.ShopPass)
	v.Add(RecurringID, r.ID)

	bodyString, err := c.post(fmt.Sprintf(c.APIBaseURL, "UnregisterRecurring"), strings.NewReader(v.Encode()))

	if err != nil {
		return nil, nil
	}

	rr, errors := ConvertToMemberRrecurring(bodyString)

	if errors != nil && errors.Count > 0 {
		return nil, errors
	}

	return rr, nil
}
<|endoftext|>


<commit_before>package lib

import (
	"fmt"
	"github.com/namsral/flag"
	"os"
	"strings"
)

// ReservedSpace -
const ReservedSpace = 450000000 // 450Mb

// Config -
type Config struct {
	Folders        []string `json:"folders"`
	DryRun         bool     `json:"dryRun"`
	NotifyCalc     int      `json:"notifyCalc"`
	NotifyMove     int      `json:"notifyMove"`
	ReservedAmount int64    `json:"reservedAmount"`
	ReservedUnit   string   `json:"reservedUnit"`
	RsyncFlags     []string `json:"rsyncFlags"`
	Version        string   `json:"version"`
}

// NotifyCalc/NotifyMove possible values
// 0 - no notification
// 1 - simple notification
// 2 - detailed notification

// Settings -
type Settings struct {
	Config

	Conf       string
	Port       string
	Log        string
	APIFolders []string
	// ReservedSpace int64
}

// NewSettings -
func NewSettings(version string) (*Settings, error) {
	var config, port, log, folders, rsyncFlags, apiFolders, service, runas string
	var dryRun bool
	var notifyCalc, notifyMove int

	found, err := Exists("/boot/config/plugins/unbalance/unbalance.conf")
	if !found || err != nil {
		msg := "Unable to find /boot/config/plugins/unbalance/unbalance.conf. Either create this file or re-install the plugin."
		if err != nil {
			msg += fmt.Sprintf(" (%s)", err)
		}
		fmt.Printf("%s\n", msg)
		os.Exit(3)
	}

	// /boot/config/plugins/unbalance/
	flag.StringVar(&config, "config", "", "config location")
	flag.StringVar(&port, "PORT", "6237", "port to run the server")
	flag.StringVar(&log, "log", "/boot/logs/unbalance.log", "pathname where log file will be written to")
	flag.StringVar(&folders, "folders", "", "folders that will be scanned for media")
	flag.BoolVar(&dryRun, "dryRun", true, "perform a dry-run rather than actual work")
	flag.IntVar(&notifyCalc, "notifyCalc", 0, "notify via email after calculation operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.IntVar(&notifyMove, "notifyMove", 0, "notify via email after move operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.StringVar(&rsyncFlags, "rsyncFlags", "", "custom rsync flags")
	flag.StringVar(&apiFolders, "apiFolders", "/var/local/emhttp", "folders to look for api endpoints")
	flag.StringVar(&service, "SERVICE", "disable", "which state should the plugin start in")
	flag.StringVar(&runas, "RUNAS", "nobody", "which users should own the plugin process")

	flag.Set("config", "/boot/config/plugins/unbalance/unbalance.conf")
	flag.Parse()

	// fmt.Printf("folders: %s\nconfig: %s\n", folders, config)

	s := &Settings{}

	if folders == "" {
		s.Folders = make([]string, 0)
	} else {
		s.Folders = strings.Split(folders, "|")
	}

	if rsyncFlags == "" {
		s.RsyncFlags = []string{"-avRX", "--partial"}
	} else {
		s.RsyncFlags = strings.Split(rsyncFlags, "|")
	}

	s.DryRun = dryRun
	s.NotifyCalc = notifyCalc
	s.NotifyMove = notifyMove
	s.ReservedAmount = ReservedSpace / 1000 / 1000
	s.ReservedUnit = "Mb"
	s.Version = version

	s.Conf = config
	s.Port = port
	s.Log = log
	s.APIFolders = strings.Split(apiFolders, "|")

	return s, nil
}

// AddFolder -
func (s *Settings) AddFolder(folder string) {
	s.Folders = append(s.Folders, folder)
}

// DeleteFolder -
func (s *Settings) DeleteFolder(folder string) {

	index := -1
	for p, v := range s.Folders {
		if v == folder {
			index = p
			break
		}
	}

	if index == -1 {
		return
	}

	s.Folders = append(s.Folders[:index], s.Folders[index+1:]...)
}

// ToggleDryRun -
func (s *Settings) ToggleDryRun() {
	s.DryRun = !s.DryRun
}

// Save -
func (s *Settings) Save() (err error) {
	tmpFile := s.Conf + ".tmp"

	folders := strings.Join(s.Folders, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("folders=%s", folders)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("dryRun=%t", s.DryRun)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyCalc=%d", s.NotifyCalc)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyMove=%d", s.NotifyCalc)); err != nil {
		return err
	}

	rsyncFlags := strings.Join(s.RsyncFlags, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("rsyncFlags=%s", rsyncFlags)); err != nil {
		return err
	}

	os.Rename(tmpFile, s.Conf)

	return
}
<commit_msg>Use unbalance.conf if available<commit_after>package lib

import (
	"fmt"
	"github.com/namsral/flag"
	"os"
	"strings"
)

// ReservedSpace -
const ReservedSpace = 450000000 // 450Mb

// Config -
type Config struct {
	Folders        []string `json:"folders"`
	DryRun         bool     `json:"dryRun"`
	NotifyCalc     int      `json:"notifyCalc"`
	NotifyMove     int      `json:"notifyMove"`
	ReservedAmount int64    `json:"reservedAmount"`
	ReservedUnit   string   `json:"reservedUnit"`
	RsyncFlags     []string `json:"rsyncFlags"`
	Version        string   `json:"version"`
}

// NotifyCalc/NotifyMove possible values
// 0 - no notification
// 1 - simple notification
// 2 - detailed notification

// Settings -
type Settings struct {
	Config

	Conf       string
	Port       string
	Log        string
	APIFolders []string
	// ReservedSpace int64
}

// NewSettings -
func NewSettings(version string) (*Settings, error) {
	var config, port, log, folders, rsyncFlags, apiFolders string
	var dryRun bool
	var notifyCalc, notifyMove int

	// /boot/config/plugins/unbalance/
	flag.StringVar(&config, "config", "/boot/config/plugins/unbalance/unbalance.conf", "config location")
	flag.StringVar(&port, "port", "6237", "port to run the server")
	flag.StringVar(&log, "log", "/boot/logs/unbalance.log", "pathname where log file will be written to")
	flag.StringVar(&folders, "folders", "", "folders that will be scanned for media")
	flag.BoolVar(&dryRun, "dryRun", true, "perform a dry-run rather than actual work")
	flag.IntVar(&notifyCalc, "notifyCalc", 0, "notify via email after calculation operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.IntVar(&notifyMove, "notifyMove", 0, "notify via email after move operation has completed (unraid notifications must be set up first): 0 - No notifications; 1 - Simple notifications; 2 - Detailed notifications")
	flag.StringVar(&rsyncFlags, "rsyncFlags", "", "custom rsync flags")
	flag.StringVar(&apiFolders, "apiFolders", "/var/local/emhttp", "folders to look for api endpoints")

	if found, _ := Exists("/boot/config/plugins/unbalance/unbalance.conf"); found {
		flag.Set("config", "/boot/config/plugins/unbalance/unbalance.conf")
	}

	flag.Parse()

	// fmt.Printf("folders: %s\nconfig: %s\n", folders, config)

	s := &Settings{}

	if folders == "" {
		s.Folders = make([]string, 0)
	} else {
		s.Folders = strings.Split(folders, "|")
	}

	if rsyncFlags == "" {
		s.RsyncFlags = []string{"-avRX", "--partial"}
	} else {
		s.RsyncFlags = strings.Split(rsyncFlags, "|")
	}

	s.DryRun = dryRun
	s.NotifyCalc = notifyCalc
	s.NotifyMove = notifyMove
	s.ReservedAmount = ReservedSpace / 1000 / 1000
	s.ReservedUnit = "Mb"
	s.Version = version

	s.Conf = config
	s.Port = port
	s.Log = log
	s.APIFolders = strings.Split(apiFolders, "|")

	return s, nil
}

// AddFolder -
func (s *Settings) AddFolder(folder string) {
	s.Folders = append(s.Folders, folder)
}

// DeleteFolder -
func (s *Settings) DeleteFolder(folder string) {

	index := -1
	for p, v := range s.Folders {
		if v == folder {
			index = p
			break
		}
	}

	if index == -1 {
		return
	}

	s.Folders = append(s.Folders[:index], s.Folders[index+1:]...)
}

// ToggleDryRun -
func (s *Settings) ToggleDryRun() {
	s.DryRun = !s.DryRun
}

// Save -
func (s *Settings) Save() (err error) {
	tmpFile := s.Conf + ".tmp"

	folders := strings.Join(s.Folders, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("folders=%s", folders)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("dryRun=%t", s.DryRun)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyCalc=%d", s.NotifyCalc)); err != nil {
		return err
	}

	if err = WriteLine(tmpFile, fmt.Sprintf("notifyMove=%d", s.NotifyCalc)); err != nil {
		return err
	}

	rsyncFlags := strings.Join(s.RsyncFlags, "|")
	if err = WriteLine(tmpFile, fmt.Sprintf("rsyncFlags=%s", rsyncFlags)); err != nil {
		return err
	}

	os.Rename(tmpFile, s.Conf)

	return
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"net"
	"time"

	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/minio/pkg/iodine"
)

// isValidRetry - check if we should retry for the given error sequence
func isValidRetry(err error) bool {
	if err == nil {
		return false
	}
	// handle io Errors separately since they are typed as *errors.String
	switch iodine.ToError(err) {
	case io.ErrUnexpectedEOF:
		return true
	}
	// DNSError, Network Operation error
	switch e := iodine.ToError(err).(type) {
	case *net.DNSError:
		return true
	case *net.OpError:
		switch e.Op {
		case "read", "write", "dial":
			return true
		}
	}
	return false
}

// TODO: this is global, make it configurable
var retries = waitTime{
	duration:      5 * time.Second,
	delayDuration: 1 * time.Second,
}

// waitTime represents sequence waiting for an action to complete successfully.
type waitTime struct {
	duration      time.Duration // total duration of retries.
	delayDuration time.Duration // delay interval between each retry
}

type retryOperation struct {
	waittime waitTime
	last     time.Time
	end      time.Time
	force    bool
	count    int
}

// instantiate new sequence of retries for the given waittime.
func (s waitTime) init() *retryOperation {
	now := time.Now()
	console.Error("Retrying... ")
	return &retryOperation{
		waittime: s,
		last:     now,
		end:      now.Add(s.duration),
		force:    true,
	}
}

func (a *retryOperation) retry() bool {
	now := time.Now()
	sleep := a.nextSleep(now)
	if !a.force && !now.Add(sleep).Before(a.end) {
		console.Infoln() // print a new line
		return false
	}
	a.force = false
	if sleep > 0 && a.count > 0 {
		time.Sleep(sleep)
		now = time.Now()
	}
	a.count++
	a.last = now
	console.Infof("%d ", a.count)
	return true
}

func (a *retryOperation) nextSleep(now time.Time) time.Duration {
	sleep := a.waittime.delayDuration - now.Sub(a.last)
	if sleep < 0 {
		return 0
	}
	return sleep
}
<commit_msg>Simplify retry() code further<commit_after>package main

import (
	"io"
	"net"
	"time"

	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/minio/pkg/iodine"
)

// isValidRetry - check if we should retry for the given error sequence
func isValidRetry(err error) bool {
	if err == nil {
		return false
	}
	// handle io Errors separately since they are typed as *errors.String
	switch iodine.ToError(err) {
	case io.ErrUnexpectedEOF:
		return true
	}
	// DNSError, Network Operation error
	switch e := iodine.ToError(err).(type) {
	case *net.DNSError:
		return true
	case *net.OpError:
		switch e.Op {
		case "read", "write", "dial":
			return true
		}
	}
	return false
}

// TODO: this is global, make it configurable
var retries = waitTime{
	duration:      5 * time.Second,
	delayDuration: 1 * time.Second,
}

// waitTime represents sequence waiting for an action to complete successfully.
type waitTime struct {
	duration      time.Duration // total duration of retries.
	delayDuration time.Duration // delay interval between each retry
}

type retryOp struct {
	waittime waitTime
	last     time.Time
	end      time.Time
	count    int
}

// instantiate new sequence of retries for the given waittime.
func (s waitTime) init() *retryOp {
	now := time.Now()
	console.Error("Retrying... ")
	return &retryOp{
		waittime: s,
		last:     now,
		end:      now.Add(s.duration),
	}
}

//
func (a *retryOp) retry() bool {
	// grab current time
	now := time.Now()

	// get sleep duration based on configured waitTime delayDuration
	sleep := func() time.Duration {
		sleep := a.waittime.delayDuration - now.Sub(a.last)
		if sleep < 0 {
			return 0
		}
		return sleep
	}

	// check if we have reached the end, if yes return false
	if !now.Add(sleep()).Before(a.end) {
		console.Infoln() // print a new line
		return false
	}

	// sleep for sleep duration
	if sleep() > 0 && a.count > 0 {
		time.Sleep(sleep())
		now = time.Now()
	}

	// increment retry count
	a.count++

	// copy current time as last time
	a.last = now

	// print current retry count
	console.Infof("%d ", a.count)

	return true
}
<|endoftext|>


<commit_before>package mars

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"time"

	"github.com/agtorre/gocolorize"
	"github.com/robfig/config"
)

const (
	MarsImportPath = "github.com/roblillack/mars"
)

type marsLogs struct {
	c gocolorize.Colorize
	w io.Writer
}

func (r *marsLogs) Write(p []byte) (n int, err error) {
	return r.w.Write([]byte(r.c.Paint(string(p))))
}

var (
	// App details
	AppName    string // e.g. "sample"
	AppRoot    string // e.g. "/app1"
	BasePath   string // e.g. "/Users/robfig/gocode/src/corp/sample"

	ConfigFile = path.Join("conf", "app.conf")

	Config     *MergedConfig
	MimeConfig *MergedConfig
	RunMode    string // Application-defined (by default, "dev" or "prod")
	DevMode    bool   // if true, RunMode is a development mode.

	// Server config.
	//
	// Alert: This is how the app is configured, which may be different from
	// the current process reality.  For example, if the app is configured for
	// port 9000, HttpPort will always be 9000, even though in dev mode it is
	// run on a random port and proxied.
	HttpPort    int    // e.g. 9000
	HttpAddr    string // e.g. "", "127.0.0.1"
	HttpSsl     bool   // e.g. true if using ssl
	HttpSslCert string // e.g. "/path/to/cert.pem"
	HttpSslKey  string // e.g. "/path/to/key.pem"

	// All cookies dropped by the framework begin with this prefix.
	CookiePrefix string
	// Cookie domain
	CookieDomain string
	// Cookie flags
	CookieHttpOnly bool
	CookieSecure   bool

	// Delimiters to use when rendering templates
	TemplateDelims string

	//Logger colors
	colors = map[string]gocolorize.Colorize{
		"trace": gocolorize.NewColor("magenta"),
		"info":  gocolorize.NewColor("green"),
		"warn":  gocolorize.NewColor("yellow"),
		"error": gocolorize.NewColor("red"),
	}

	error_log = marsLogs{c: colors["error"], w: os.Stderr}

	// Loggers
	TRACE = log.New(ioutil.Discard, "TRACE ", log.Ldate|log.Ltime|log.Lshortfile)
	INFO  = log.New(ioutil.Discard, "INFO  ", log.Ldate|log.Ltime|log.Lshortfile)
	WARN  = log.New(ioutil.Discard, "WARN  ", log.Ldate|log.Ltime|log.Lshortfile)
	ERROR = log.New(&error_log, "ERROR ", log.Ldate|log.Ltime|log.Lshortfile)

	MaxAge = time.Hour * 24 // MaxAge specifies the time browsers shall cache static content served using Static.Serve

	Initialized bool

	// Private
	secretKey []byte // Key used to sign cookies. An empty key disables signing.
)

func init() {
	log.SetFlags(INFO.Flags())
}

// InitDefaults initializes Mars based on runtime-loading of config files.
//
// Params:
//   mode - the run mode, which determines which app.conf settings are used.
//   basePath - the path to the configuration, messages, and view directories
func InitDefaults(mode, basePath string) {
	RunMode = mode

	if runtime.GOOS == "windows" {
		gocolorize.SetPlain(true)
	}

	BasePath = filepath.FromSlash(basePath)

	// Load app.conf
	var err error
	Config, err = LoadConfig(path.Join(BasePath, ConfigFile))
	if err != nil || Config == nil {
		log.Fatalln("Failed to load app.conf:", err)
	}

	MimeConfig, _ = LoadConfig(path.Join(BasePath, "conf", "mime-types.conf"))

	// Ensure that the selected runmode appears in app.conf.
	// If empty string is passed as the mode, treat it as "DEFAULT"
	if mode == "" {
		mode = config.DEFAULT_SECTION
	}
	if !Config.HasSection(mode) {
		log.Fatalln("app.conf: No mode found:", mode)
	}
	Config.SetSection(mode)

	// Configure properties from app.conf
	DevMode = Config.BoolDefault("mode.dev", false)
	HttpPort = Config.IntDefault("http.port", 9000)
	HttpAddr = Config.StringDefault("http.addr", "")
	HttpSsl = Config.BoolDefault("http.ssl", false)
	HttpSslCert = Config.StringDefault("http.sslcert", "")
	HttpSslKey = Config.StringDefault("http.sslkey", "")
	if HttpSsl {
		if HttpSslCert == "" {
			log.Fatalln("No http.sslcert provided.")
		}
		if HttpSslKey == "" {
			log.Fatalln("No http.sslkey provided.")
		}
	}

	AppName = Config.StringDefault("app.name", "(not set)")
	AppRoot = Config.StringDefault("app.root", "")
	CookiePrefix = Config.StringDefault("cookie.prefix", "MARS")
	CookieDomain = Config.StringDefault("cookie.domain", "")
	CookieHttpOnly = Config.BoolDefault("cookie.httponly", false)
	CookieSecure = Config.BoolDefault("cookie.secure", false)
	TemplateDelims = Config.StringDefault("template.delimiters", "")
	if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" {
		secretKey = []byte(secretStr)
	}

	// Configure logging
	if !Config.BoolDefault("log.colorize", true) {
		gocolorize.SetPlain(true)
	}

	TRACE = getLogger("trace")
	INFO = getLogger("info")
	WARN = getLogger("warn")
	ERROR = getLogger("error")

	MainTemplateLoader = NewTemplateLoader([]string{path.Join(BasePath, "views")})
	MainTemplateLoader.Refresh()

	Initialized = true
	INFO.Printf("Initialized Mars v%s (%s) for %s", VERSION, BUILD_DATE, MINIMUM_GO)
}

// Create a logger using log.* directives in app.conf plus the current settings
// on the default logger.
func getLogger(name string) *log.Logger {
	var logger *log.Logger

	// Create a logger with the requested output. (default to stderr)
	output := Config.StringDefault("log."+name+".output", "stderr")
	var newlog marsLogs

	switch output {
	case "stdout":
		newlog = marsLogs{c: colors[name], w: os.Stdout}
		logger = newLogger(&newlog)
	case "stderr":
		newlog = marsLogs{c: colors[name], w: os.Stderr}
		logger = newLogger(&newlog)
	default:
		if output == "off" {
			output = os.DevNull
		}

		file, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalln("Failed to open log file", output, ":", err)
		}
		logger = newLogger(file)
	}

	// Set the prefix / flags.
	flags, found := Config.Int("log." + name + ".flags")
	if found {
		logger.SetFlags(flags)
	}

	prefix, found := Config.String("log." + name + ".prefix")
	if found {
		logger.SetPrefix(prefix)
	}

	return logger
}

func newLogger(wr io.Writer) *log.Logger {
	return log.New(wr, "", INFO.Flags())
}

// ResolveImportPath returns the filesystem path for the given import path.
// Returns an error if the import path could not be found.
func ResolveImportPath(importPath string) (string, error) {
	// GO15VENDOREXPERIMENT
	var err error
	var modPkg *build.Package
	for _, p := range []string{
		importPath,
		path.Join(ImportPath, "vendor", importPath),
		path.Join(MarsImportPath, "vendor", importPath)} {
		modPkg, err = build.Import(p, "", build.FindOnly)
		if err == nil {
			break
		}
	}

	if err != nil {
		return "", err
	}

	return modPkg.Dir, nil
}

func CheckInit() {
	if !Initialized {
		panic("Mars has not been initialized!")
	}
}
<commit_msg>Remove Initialized flag.<commit_after>package mars

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"time"

	"github.com/agtorre/gocolorize"
	"github.com/robfig/config"
)

const (
	MarsImportPath = "github.com/roblillack/mars"
)

type marsLogs struct {
	c gocolorize.Colorize
	w io.Writer
}

func (r *marsLogs) Write(p []byte) (n int, err error) {
	return r.w.Write([]byte(r.c.Paint(string(p))))
}

var (
	// App details
	AppName    string // e.g. "sample"
	AppRoot    string // e.g. "/app1"
	BasePath   string // e.g. "/Users/robfig/gocode/src/corp/sample"

	ConfigFile = path.Join("conf", "app.conf")

	Config     *MergedConfig
	MimeConfig *MergedConfig
	RunMode    string // Application-defined (by default, "dev" or "prod")
	DevMode    bool   // if true, RunMode is a development mode.

	// Server config.
	//
	// Alert: This is how the app is configured, which may be different from
	// the current process reality.  For example, if the app is configured for
	// port 9000, HttpPort will always be 9000, even though in dev mode it is
	// run on a random port and proxied.
	HttpPort    int    // e.g. 9000
	HttpAddr    string // e.g. "", "127.0.0.1"
	HttpSsl     bool   // e.g. true if using ssl
	HttpSslCert string // e.g. "/path/to/cert.pem"
	HttpSslKey  string // e.g. "/path/to/key.pem"

	// All cookies dropped by the framework begin with this prefix.
	CookiePrefix string
	// Cookie domain
	CookieDomain string
	// Cookie flags
	CookieHttpOnly bool
	CookieSecure   bool

	// Delimiters to use when rendering templates
	TemplateDelims string

	//Logger colors
	colors = map[string]gocolorize.Colorize{
		"trace": gocolorize.NewColor("magenta"),
		"info":  gocolorize.NewColor("green"),
		"warn":  gocolorize.NewColor("yellow"),
		"error": gocolorize.NewColor("red"),
	}

	error_log = marsLogs{c: colors["error"], w: os.Stderr}

	// Loggers
	TRACE = log.New(ioutil.Discard, "TRACE ", log.Ldate|log.Ltime|log.Lshortfile)
	INFO  = log.New(ioutil.Discard, "INFO  ", log.Ldate|log.Ltime|log.Lshortfile)
	WARN  = log.New(ioutil.Discard, "WARN  ", log.Ldate|log.Ltime|log.Lshortfile)
	ERROR = log.New(&error_log, "ERROR ", log.Ldate|log.Ltime|log.Lshortfile)

	MaxAge = time.Hour * 24 // MaxAge specifies the time browsers shall cache static content served using Static.Serve

	// Private
	secretKey []byte // Key used to sign cookies. An empty key disables signing.
)

func init() {
	log.SetFlags(INFO.Flags())
}

// InitDefaults initializes Mars based on runtime-loading of config files.
//
// Params:
//   mode - the run mode, which determines which app.conf settings are used.
//   basePath - the path to the configuration, messages, and view directories
func InitDefaults(mode, basePath string) {
	RunMode = mode

	if runtime.GOOS == "windows" {
		gocolorize.SetPlain(true)
	}

	BasePath = filepath.FromSlash(basePath)

	// Load app.conf
	var err error
	Config, err = LoadConfig(path.Join(BasePath, ConfigFile))
	if err != nil || Config == nil {
		log.Fatalln("Failed to load app.conf:", err)
	}

	MimeConfig, _ = LoadConfig(path.Join(BasePath, "conf", "mime-types.conf"))

	// Ensure that the selected runmode appears in app.conf.
	// If empty string is passed as the mode, treat it as "DEFAULT"
	if mode == "" {
		mode = config.DEFAULT_SECTION
	}
	if !Config.HasSection(mode) {
		log.Fatalln("app.conf: No mode found:", mode)
	}
	Config.SetSection(mode)

	// Configure properties from app.conf
	DevMode = Config.BoolDefault("mode.dev", false)
	HttpPort = Config.IntDefault("http.port", 9000)
	HttpAddr = Config.StringDefault("http.addr", "")
	HttpSsl = Config.BoolDefault("http.ssl", false)
	HttpSslCert = Config.StringDefault("http.sslcert", "")
	HttpSslKey = Config.StringDefault("http.sslkey", "")
	if HttpSsl {
		if HttpSslCert == "" {
			log.Fatalln("No http.sslcert provided.")
		}
		if HttpSslKey == "" {
			log.Fatalln("No http.sslkey provided.")
		}
	}

	AppName = Config.StringDefault("app.name", "(not set)")
	AppRoot = Config.StringDefault("app.root", "")
	CookiePrefix = Config.StringDefault("cookie.prefix", "MARS")
	CookieDomain = Config.StringDefault("cookie.domain", "")
	CookieHttpOnly = Config.BoolDefault("cookie.httponly", false)
	CookieSecure = Config.BoolDefault("cookie.secure", false)
	TemplateDelims = Config.StringDefault("template.delimiters", "")
	if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" {
		secretKey = []byte(secretStr)
	}

	// Configure logging
	if !Config.BoolDefault("log.colorize", true) {
		gocolorize.SetPlain(true)
	}

	TRACE = getLogger("trace")
	INFO = getLogger("info")
	WARN = getLogger("warn")
	ERROR = getLogger("error")

	MainTemplateLoader = NewTemplateLoader([]string{path.Join(BasePath, "views")})
	MainTemplateLoader.Refresh()

	INFO.Printf("Initialized Mars v%s (%s) for %s", VERSION, BUILD_DATE, MINIMUM_GO)
}

// Create a logger using log.* directives in app.conf plus the current settings
// on the default logger.
func getLogger(name string) *log.Logger {
	var logger *log.Logger

	// Create a logger with the requested output. (default to stderr)
	output := Config.StringDefault("log."+name+".output", "stderr")
	var newlog marsLogs

	switch output {
	case "stdout":
		newlog = marsLogs{c: colors[name], w: os.Stdout}
		logger = newLogger(&newlog)
	case "stderr":
		newlog = marsLogs{c: colors[name], w: os.Stderr}
		logger = newLogger(&newlog)
	default:
		if output == "off" {
			output = os.DevNull
		}

		file, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalln("Failed to open log file", output, ":", err)
		}
		logger = newLogger(file)
	}

	// Set the prefix / flags.
	flags, found := Config.Int("log." + name + ".flags")
	if found {
		logger.SetFlags(flags)
	}

	prefix, found := Config.String("log." + name + ".prefix")
	if found {
		logger.SetPrefix(prefix)
	}

	return logger
}

func newLogger(wr io.Writer) *log.Logger {
	return log.New(wr, "", INFO.Flags())
}

// ResolveImportPath returns the filesystem path for the given import path.
// Returns an error if the import path could not be found.
func ResolveImportPath(importPath string) (string, error) {
	// GO15VENDOREXPERIMENT
	var err error
	var modPkg *build.Package
	for _, p := range []string{
		importPath,
		path.Join(ImportPath, "vendor", importPath),
		path.Join(MarsImportPath, "vendor", importPath)} {
		modPkg, err = build.Import(p, "", build.FindOnly)
		if err == nil {
			break
		}
	}

	if err != nil {
		return "", err
	}

	return modPkg.Dir, nil
}

func CheckInit() {
	if !Initialized {
		panic("Mars has not been initialized!")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/hfern/goseq"
	"log"
	"time"
)

type ServerQueryOptions struct {
	NoPlayers    bool `long:"no-players" short:"P" default:"false" description:"Don't list players."`
	NoInfo       bool `long:"no-info" short:"I" default:"false" description:"Don't list general info."`
	NoRules      bool `long:"no-rules" short:"R" default:"false" description:"Don't list server rules."`
	Serial       bool `long:"serial" short:"s" default:"false" description:"Force serial querying of server attributes."`
	Json         bool `long:"json" default:"false" description:"Output as JSON to StdOut"`
	Timeout      uint `long:"timeout" short:"t" default:"2" description:"Timeout for attribute queries in seconds."`
	OnlyKeywords bool `long:"only-keywords" short:"K" default:"false" description:"Only list the keywords of the servers one per line."`
}

type DoneChannel chan int

const DONE int = 0

type MaybePlayers struct {
	Error   error
	Players []goseq.Player
}

type MaybeInfo struct {
	Error error
	Info  goseq.ServerInfo
}

type MaybeRules struct {
	Error error
	Rules goseq.RuleMap
}

type ServerQueryableAttributes struct {
	Address string
	Players MaybePlayers
	Info    MaybeInfo
	Rules   MaybeRules
}

type ServerAttrPair struct {
	Server goseq.Server
	Attrs  ServerQueryableAttributes
}

type doIf struct {
	cond bool
	call func(DoneChannel)
}

var serverSingleOptions ServerQueryOptions

func serverctx(serverAddresses []string) {
	options := &serverSingleOptions

	if !assertLogicalServerFlags(options) {
		return
	}

	if len(serverAddresses) == 0 {
		log.Fatal(
			"The first argument to sourceq server must be the address of the server. \n" +
				"e.g. sourceq server google.com\n" +
				"or sourceq server google.com:8080\n" +
				"or sourceq server 192.168.1.1:6060\n" +
				"or, for multiple servers: \n\tsourceq server google.com:80 example.org 123.156.178")
		return
	}

	timeout := time.Duration(options.Timeout) * time.Second
	done := make(DoneChannel)

	servers := make([]ServerAttrPair, len(serverAddresses))

	for i, serverAddr := range serverAddresses {
		servers[i].Attrs.Address = serverAddr
		server := goseq.NewServer()
		err := server.SetAddress(serverAddr)
		if err != nil {
			log.Fatal(err)
		}
		servers[i].Server = server
	}

	for i, _ := range servers {
		server := &servers[i]
		go queryServer(options, &server.Server, &server.Attrs, timeout, done)
		if options.Serial {
			<-done
		}
	}

	if !options.Serial {
		for _ = range servers {
			<-done
		}
	}

	if options.Json {
		viewServerJSON(options, servers)
	} else {
		viewServerText(options, servers)
	}
}

func assertLogicalServerFlags(options *ServerQueryOptions) bool {
	if options.OnlyKeywords {
		if options.Json {
			log.Fatal("--only-keywords cannot be used with --json")
			return false
		}
		options.NoRules = true
		options.NoPlayers = true
		options.NoInfo = false
	}
	return true
}

func queryServer(
	options *ServerQueryOptions,
	server *goseq.Server,
	attrs *ServerQueryableAttributes,
	timeout time.Duration,
	serverIsDone DoneChannel) {

	defer func() { serverIsDone <- DONE }()

	done := make(DoneChannel)

	callbacks := []doIf{
		{
			cond: !options.NoInfo,
			call: func(donner DoneChannel) {
				getServerInfo(server, &attrs.Info, timeout, donner)
			},
		},
		{
			cond: !options.NoRules,
			call: func(donner DoneChannel) {
				getServerRules(server, &attrs.Rules, timeout, donner)
			},
		},
		{
			cond: !options.NoPlayers,
			call: func(donner DoneChannel) {
				getServerPlayers(server, &attrs.Players, timeout, donner)
			},
		},
	}

	for _, cb := range callbacks {
		if cb.cond {
			go cb.call(done)
			if options.Serial {
				<-done
			}
		}
	}

	if !options.Serial {
		for _ = range callbacks {
			<-done
		}
	}

}

func getServerInfo(server *goseq.Server, info *MaybeInfo, timeout time.Duration, donner DoneChannel) {
	defer func() { donner <- DONE }()
	info.Info, info.Error = (*server).Info(timeout)
}

func getServerRules(server *goseq.Server, rules *MaybeRules, timeout time.Duration, donner DoneChannel) {
	defer func() { donner <- DONE }()
	rules.Rules, rules.Error = (*server).Rules(timeout)
}

func getServerPlayers(server *goseq.Server, plys *MaybePlayers, timeout time.Duration, donner DoneChannel) {
	defer func() { donner <- DONE }()
	plys.Players, plys.Error = (*server).Players(timeout)
}
<commit_msg>Fix read deadlock when using a --no-* option<commit_after>package main

import (
	"github.com/hfern/goseq"
	"log"
	"time"
)

type ServerQueryOptions struct {
	NoPlayers    bool `long:"no-players" short:"P" default:"false" description:"Don't list players."`
	NoInfo       bool `long:"no-info" short:"I" default:"false" description:"Don't list general info."`
	NoRules      bool `long:"no-rules" short:"R" default:"false" description:"Don't list server rules."`
	Serial       bool `long:"serial" short:"s" default:"false" description:"Force serial querying of server attributes."`
	Json         bool `long:"json" default:"false" description:"Output as JSON to StdOut"`
	Timeout      uint `long:"timeout" short:"t" default:"2" description:"Timeout for attribute queries in seconds."`
	OnlyKeywords bool `long:"only-keywords" short:"K" default:"false" description:"Only list the keywords of the servers one per line."`
}

type DoneChannel chan int

const DONE int = 0

type MaybePlayers struct {
	Error   error
	Players []goseq.Player
}

type MaybeInfo struct {
	Error error
	Info  goseq.ServerInfo
}

type MaybeRules struct {
	Error error
	Rules goseq.RuleMap
}

type ServerQueryableAttributes struct {
	Address string
	Players MaybePlayers
	Info    MaybeInfo
	Rules   MaybeRules
}

type ServerAttrPair struct {
	Server goseq.Server
	Attrs  ServerQueryableAttributes
}

type doIf struct {
	cond bool
	call func(DoneChannel)
}

var serverSingleOptions ServerQueryOptions

func serverctx(serverAddresses []string) {
	options := &serverSingleOptions

	if !assertLogicalServerFlags(options) {
		return
	}

	if len(serverAddresses) == 0 {
		log.Fatal(
			"The first argument to sourceq server must be the address of the server. \n" +
				"e.g. sourceq server google.com\n" +
				"or sourceq server google.com:8080\n" +
				"or sourceq server 192.168.1.1:6060\n" +
				"or, for multiple servers: \n\tsourceq server google.com:80 example.org 123.156.178")
		return
	}

	timeout := time.Duration(options.Timeout) * time.Second
	done := make(DoneChannel)

	servers := make([]ServerAttrPair, len(serverAddresses))

	for i, serverAddr := range serverAddresses {
		servers[i].Attrs.Address = serverAddr
		server := goseq.NewServer()
		err := server.SetAddress(serverAddr)
		if err != nil {
			log.Fatal(err)
		}
		servers[i].Server = server
	}

	for i, _ := range servers {
		server := &servers[i]
		go queryServer(options, &server.Server, &server.Attrs, timeout, done)
		if options.Serial {
			<-done
		}
	}

	if !options.Serial {
		for _ = range servers {
			<-done
		}
	}

	if options.Json {
		viewServerJSON(options, servers)
	} else {
		viewServerText(options, servers)
	}
}

func assertLogicalServerFlags(options *ServerQueryOptions) bool {
	if options.OnlyKeywords {
		if options.Json {
			log.Fatal("--only-keywords cannot be used with --json")
			return false
		}
		options.NoRules = true
		options.NoPlayers = true
		options.NoInfo = false
	}
	return true
}

func queryServer(
	options *ServerQueryOptions,
	server *goseq.Server,
	attrs *ServerQueryableAttributes,
	timeout time.Duration,
	serverIsDone DoneChannel) {

	defer func() { serverIsDone <- DONE }()

	done := make(DoneChannel)

	callbacks := []doIf{
		{
			cond: !options.NoInfo,
			call: func(donner DoneChannel) {
				getServerInfo(server, &attrs.Info, timeout, donner)
			},
		},
		{
			cond: !options.NoRules,
			call: func(donner DoneChannel) {
				getServerRules(server, &attrs.Rules, timeout, donner)
			},
		},
		{
			cond: !options.NoPlayers,
			call: func(donner DoneChannel) {
				getServerPlayers(server, &attrs.Players, timeout, donner)
			},
		},
	}

	for _, cb := range callbacks {
		if cb.cond {
			go cb.call(done)
			if options.Serial {
				<-done
			}
		}
	}

	if !options.Serial {
		for _, cb := range callbacks {
			if cb.cond {
				<-done
			}
		}
	}

}

func getServerInfo(server *goseq.Server, info *MaybeInfo, timeout time.Duration, donner DoneChannel) {
	defer func() { donner <- DONE }()
	info.Info, info.Error = (*server).Info(timeout)
}

func getServerRules(server *goseq.Server, rules *MaybeRules, timeout time.Duration, donner DoneChannel) {
	defer func() { donner <- DONE }()
	rules.Rules, rules.Error = (*server).Rules(timeout)
}

func getServerPlayers(server *goseq.Server, plys *MaybePlayers, timeout time.Duration, donner DoneChannel) {
	defer func() { donner <- DONE }()
	plys.Players, plys.Error = (*server).Players(timeout)
}
<|endoftext|>


<commit_before>// Copyright 2012 Brian "bojo" Jones. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package redistore

import (
	"bytes"
	"encoding/base32"
	"encoding/gob"
	"errors"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"net/http"
	"strings"
	"time"
)

// Amount of time for cookies/redis keys to expire.
var sessionExpire = 86400 * 30

// RediStore stores sessions in a redis backend.
type RediStore struct {
	Pool          *redis.Pool
	Codecs        []securecookie.Codec
	Options       *sessions.Options // default configuration
	DefaultMaxAge int               // default TTL for a MaxAge == 0 session
	maxLength     int
}

// MaxLength sets RediStore.maxLength if the `l` argument is greater or equal 0
// maxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new RediStore is 4096. Redis allows for max.
// value sizes of up to 512MB (http://redis.io/topics/data-types)
// Default: 4096,
func (s *RediStore) MaxLength(l int) {
	if l >= 0 {
		s.maxLength = l
	}
}

// NewRediStore returns a new RediStore.
// size: maximum number of idle connections.
func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) *RediStore {
	return NewRediStoreWithPool(&redis.Pool{
		MaxIdle:     size,
		IdleTimeout: 240 * time.Second,
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial(network, address)
			if err != nil {
				return nil, err
			}
			if password != "" {
				if _, err := c.Do("AUTH", password); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
	}, keyPairs...)
}

// NewRediStoreWithPool instantiates a RediStore with a *redis.Pool passed in.
func NewRediStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) *RediStore {
	return &RediStore{
		// http://godoc.org/github.com/garyburd/redigo/redis#Pool
		Pool:   pool,
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: sessionExpire,
		},
		DefaultMaxAge: 60 * 20, // 20 minutes seems like a reasonable default
		maxLength:     4096,
	}
}

// NewRedisStoreWithDB - like NewRedisStore but accepts `DB` parameter to select
// redis DB instead of using the default one ("0")
func NewRediStoreWithDB(size int, network, address, password, DB string, keyPairs ...[]byte) *RediStore {
	rs := NewRediStore(size, network, address, password, keyPairs...)
	rs.Pool.Dial = func() (redis.Conn, error) {
		c, err := redis.Dial(network, address)
		if err != nil {
			return nil, err
		}
		if password != "" {
			if _, err := c.Do("AUTH", password); err != nil {
				c.Close()
				return nil, err
			}
		}
		if _, err := c.Do("SELECT", DB); err != nil {
			c.Close()
			return nil, err
		}
		return c, err
	}
	return rs
}

// Close closes the underlying *redis.Pool
func (s *RediStore) Close() error {
	return s.Pool.Close()
}

// Get returns a session for the given name after adding it to the registry.
//
// See gorilla/sessions FilesystemStore.Get().
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
	var err error
	session := sessions.NewSession(s, name)
	session.Options = &(*s.Options)
	session.IsNew = true
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			ok, err := s.load(session)
			session.IsNew = !(err == nil && ok) // not new if no error and data available
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	// Marked for deletion.
	if session.Options.MaxAge < 0 {
		if err := s.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
	} else {
		// Build an alphanumeric key for the redis store.
		if session.ID == "" {
			session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
		}
		if err := s.save(session); err != nil {
			return err
		}
		encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
		if err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	}
	return nil
}

// Delete removes the session from redis, and sets the cookie to expire.
//
// WARNING: This method should be considered deprecated since it is not exposed via the gorilla/sessions interface.
// Set session.Options.MaxAge = -1 and call Save instead. - July 18th, 2013
func (s *RediStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	// Set cookie to expire.
	options := *session.Options
	options.MaxAge = -1
	http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
	// Clear session values.
	for k := range session.Values {
		delete(session.Values, k)
	}
	return nil
}

// save stores the session in redis.
func (s *RediStore) save(session *sessions.Session) error {
	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	err := enc.Encode(session.Values)
	if err != nil {
		return err
	}
	b := buf.Bytes()
	if s.maxLength != 0 && len(b) > s.maxLength {
		return errors.New("SessionStore: the value to store is too big")
	}
	conn := s.Pool.Get()
	defer conn.Close()
	if err = conn.Err(); err != nil {
		return err
	}
	age := session.Options.MaxAge
	if age == 0 {
		age = s.DefaultMaxAge
	}
	_, err = conn.Do("SETEX", "session_"+session.ID, age, b)
	return err
}

// load reads the session from redis.
// returns true if there is a sessoin data in DB
func (s *RediStore) load(session *sessions.Session) (bool, error) {
	conn := s.Pool.Get()
	defer conn.Close()
	if err := conn.Err(); err != nil {
		return false, err
	}
	data, err := conn.Do("GET", "session_"+session.ID)
	if err != nil {
		return false, err
	}
	if data == nil {
		return false, nil // no data was associated with this key
	}
	b, err := redis.Bytes(data, err)
	if err != nil {
		return false, err
	}
	dec := gob.NewDecoder(bytes.NewBuffer(b))
	return true, dec.Decode(&session.Values)
}

// delete removes keys from redis if MaxAge<0
func (s *RediStore) delete(session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	return nil
}
<commit_msg>simplify code, extract common part of a dial functoin<commit_after>// Copyright 2012 Brian "bojo" Jones. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package redistore

import (
	"bytes"
	"encoding/base32"
	"encoding/gob"
	"errors"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"net/http"
	"strings"
	"time"
)

// Amount of time for cookies/redis keys to expire.
var sessionExpire = 86400 * 30

// RediStore stores sessions in a redis backend.
type RediStore struct {
	Pool          *redis.Pool
	Codecs        []securecookie.Codec
	Options       *sessions.Options // default configuration
	DefaultMaxAge int               // default TTL for a MaxAge == 0 session
	maxLength     int
}

// MaxLength sets RediStore.maxLength if the `l` argument is greater or equal 0
// maxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new RediStore is 4096. Redis allows for max.
// value sizes of up to 512MB (http://redis.io/topics/data-types)
// Default: 4096,
func (s *RediStore) MaxLength(l int) {
	if l >= 0 {
		s.maxLength = l
	}
}

func dial(network, address, password string) (redis.Conn, error) {
	c, err := redis.Dial(network, address)
	if err != nil {
		return nil, err
	}
	if password != "" {
		if _, err := c.Do("AUTH", password); err != nil {
			c.Close()
			return nil, err
		}
	}
	return c, err
}

// NewRediStore returns a new RediStore.
// size: maximum number of idle connections.
func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) *RediStore {
	return NewRediStoreWithPool(&redis.Pool{
		MaxIdle:     size,
		IdleTimeout: 240 * time.Second,
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
		Dial: func() (redis.Conn, error) {
			return dial(network, address, password)
		},
	}, keyPairs...)
}

// NewRedisStoreWithDB - like NewRedisStore but accepts `DB` parameter to select
// redis DB instead of using the default one ("0")
func NewRediStoreWithDB(size int, network, address, password, DB string, keyPairs ...[]byte) *RediStore {
	rs := NewRediStore(size, network, address, password, keyPairs...)
	rs.Pool.Dial = func() (redis.Conn, error) {
		c, err := dial(network, address, password)
		if err != nil {
			return c, err
		}
		if _, err := c.Do("SELECT", DB); err != nil {
			c.Close()
			return nil, err
		}
		return c, err
	}
	return rs
}

// NewRediStoreWithPool instantiates a RediStore with a *redis.Pool passed in.
func NewRediStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) *RediStore {
	return &RediStore{
		// http://godoc.org/github.com/garyburd/redigo/redis#Pool
		Pool:   pool,
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: sessionExpire,
		},
		DefaultMaxAge: 60 * 20, // 20 minutes seems like a reasonable default
		maxLength:     4096,
	}
}

// Close closes the underlying *redis.Pool
func (s *RediStore) Close() error {
	return s.Pool.Close()
}

// Get returns a session for the given name after adding it to the registry.
//
// See gorilla/sessions FilesystemStore.Get().
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
	var err error
	session := sessions.NewSession(s, name)
	session.Options = s.Options
	session.IsNew = true
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			ok, err := s.load(session)
			session.IsNew = !(err == nil && ok) // not new if no error and data available
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	// Marked for deletion.
	if session.Options.MaxAge < 0 {
		if err := s.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
	} else {
		// Build an alphanumeric key for the redis store.
		if session.ID == "" {
			session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
		}
		if err := s.save(session); err != nil {
			return err
		}
		encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
		if err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	}
	return nil
}

// Delete removes the session from redis, and sets the cookie to expire.
//
// WARNING: This method should be considered deprecated since it is not exposed via the gorilla/sessions interface.
// Set session.Options.MaxAge = -1 and call Save instead. - July 18th, 2013
func (s *RediStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	// Set cookie to expire.
	options := *session.Options
	options.MaxAge = -1
	http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
	// Clear session values.
	for k := range session.Values {
		delete(session.Values, k)
	}
	return nil
}

// save stores the session in redis.
func (s *RediStore) save(session *sessions.Session) error {
	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	err := enc.Encode(session.Values)
	if err != nil {
		return err
	}
	b := buf.Bytes()
	if s.maxLength != 0 && len(b) > s.maxLength {
		return errors.New("SessionStore: the value to store is too big")
	}
	conn := s.Pool.Get()
	defer conn.Close()
	if err = conn.Err(); err != nil {
		return err
	}
	age := session.Options.MaxAge
	if age == 0 {
		age = s.DefaultMaxAge
	}
	_, err = conn.Do("SETEX", "session_"+session.ID, age, b)
	return err
}

// load reads the session from redis.
// returns true if there is a sessoin data in DB
func (s *RediStore) load(session *sessions.Session) (bool, error) {
	conn := s.Pool.Get()
	defer conn.Close()
	if err := conn.Err(); err != nil {
		return false, err
	}
	data, err := conn.Do("GET", "session_"+session.ID)
	if err != nil {
		return false, err
	}
	if data == nil {
		return false, nil // no data was associated with this key
	}
	b, err := redis.Bytes(data, err)
	if err != nil {
		return false, err
	}
	dec := gob.NewDecoder(bytes.NewBuffer(b))
	return true, dec.Decode(&session.Values)
}

// delete removes keys from redis if MaxAge<0
func (s *RediStore) delete(session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package linux

import (
  "bytes"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

var (
	PowerSupplyPath    = "/sys/class/power_supply/"
	LoadAvgPath = "/proc/loadavg"
	VersionPath  = "/proc/version"
	quiet    bool
)

// set quiet: true or false
func SetQuiet(state bool) {
	quiet = state
}

// a representation of /proc/loadavg, leaving of the PID of the last process
type LoadAvg struct {
	Avg1, Avg5, Avg15    string
	Schedulers, Entities string
}

// get the current LoadAvg
func GetLoadAvg() LoadAvg {
	fh, err := os.Open(LoadAvgPath)
	if err != nil {
		if !quiet {
			fmt.Fprintf(os.Stderr, "WARN: %s\n", err)
		}
		return LoadAvg{}
	}
	b, err := ioutil.ReadAll(fh)
	if err != nil {
		if !quiet {
			fmt.Fprintf(os.Stderr, "WARN: %s\n", err)
		}
		return LoadAvg{}
	}
	values := strings.Split(strings.TrimRight(bytes.NewBuffer(b).String(), " \n"), " ")
	sAndE := strings.Split(values[3], "/")
	return LoadAvg{
		values[0],
		values[1],
		values[2],
		sAndE[0],
		sAndE[1],
	}
}

// Version of the current running kernel, /proc/version
func GetVersion() string {
	fh, err := os.Open(VersionPath)
	if err != nil {
		if !quiet {
			fmt.Fprintf(os.Stderr, "WARN: %s\n", err)
		}
		return ""
	}
	b, err := ioutil.ReadAll(fh)
	if err != nil {
		if !quiet {
			fmt.Fprintf(os.Stderr, "WARN: %s\n", err)
		}
		return ""
	}
	return strings.TrimRight(bytes.NewBuffer(b).String(), " \n")
}

/*
Info set on a /sys/class/power_supply item

Time is UnixNano
Values are the file [name]contents
Load is the LoadAvg when that Info was collected
*/
type Info struct {
	Time    int64
	Key     string
	Values  map[string]string
	Load    LoadAvg `json:",omitempty"`
	Version string
}

// Convenience Method for checking files
func IsFile(filename string) bool {
	if fi, _ := os.Stat(filename); fi.Mode().IsRegular() {
		return true
	}
	return false
}
<commit_msg>combined some logic into its own method<commit_after>package linux

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

var (
	PowerSupplyPath = "/sys/class/power_supply/"
	LoadAvgPath     = "/proc/loadavg"
	VersionPath     = "/proc/version"
	quiet           bool
)

// set quiet: true or false
func SetQuiet(state bool) {
	quiet = state
}

// a representation of /proc/loadavg, leaving of the PID of the last process
type LoadAvg struct {
	Avg1, Avg5, Avg15    string
	Schedulers, Entities string
}

// get the current LoadAvg
func GetLoadAvg() LoadAvg {
	str, err := StringFromFile(LoadAvgPath)
	if err != nil {
		if !quiet {
			fmt.Fprintf(os.Stderr, "WARN: %s\n", err)
		}
		return LoadAvg{}
	}
	values := strings.Split(str, " ")
	sAndE := strings.Split(values[3], "/")
	return LoadAvg{
		values[0],
		values[1],
		values[2],
		sAndE[0],
		sAndE[1],
	}
}

// Version of the current running kernel, /proc/version
func GetVersion() string {
	str, err := StringFromFile(VersionPath)
	if err != nil {
		if !quiet {
			fmt.Fprintf(os.Stderr, "WARN: %s\n", err)
		}
		return ""
	}
  return str
}

/*
Info set on a /sys/class/power_supply item

Time is UnixNano
Values are the file [name]contents
Load is the LoadAvg when that Info was collected
*/
type Info struct {
	Time    int64
	Key     string
	Values  map[string]string
	Load    LoadAvg `json:",omitempty"`
	Version string
}

// reusing this all over the place
func StringFromFile(filename string) (string, error) {
	fh, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	b, err := ioutil.ReadAll(fh)
	if err != nil {
		return "", err
	}
	return strings.TrimRight(bytes.NewBuffer(b).String(), " \n"), nil
}

// Convenience Method for checking files
func IsFile(filename string) bool {
	if fi, _ := os.Stat(filename); fi.Mode().IsRegular() {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package config

import (
	"testing"
)

func TestLoadConfig(t *testing.T) {
	var expected = struct {
		clientCert  string
		clientKey   string
		configDir   string
		etcdNodes   []string
		interval    int
		prefix      string
		templateDir string
	}{
		"", "", "/etc/confd/conf.d", []string{"http://127.0.0.1:4001"},
		600, "/", "/etc/confd/templates",
	}
	loadConfig("")
	cc := ClientCert()
	if cc != expected.clientCert {
		t.Errorf("Expected default clientCert = %s, got %s", expected.clientCert, cc)
	}
	ck := ClientKey()
	if ck != expected.clientKey {
		t.Errorf("Expected default clientKey = %s, got %s", expected.clientKey, ck)
	}
	cd := ConfigDir()
	if cd != expected.configDir {
		t.Errorf("Expected default configDir = %s, got %s", expected.configDir, cd)
	}
	en := EtcdNodes()
	if en[0] != expected.etcdNodes[0] {
		t.Errorf("Expected default etcdNodes = %v, got %v", expected.etcdNodes, en)
	}
	i := Interval()
	if i != expected.interval {
		t.Errorf("Expected default interval = %d, got %d", expected.interval, i)
	}
	p := Prefix()
	if p != expected.prefix {
		t.Errorf("Expected default prefix = %s, got %s", expected.prefix, p)
	}
	td := TemplateDir()
	if td != expected.templateDir {
		t.Errorf("Expected default templateDir = %s, got %s", expected.templateDir, td)
	}
}
<commit_msg>Fix broken config package test<commit_after>package config

import (
	"testing"
)

func TestLoadConfig(t *testing.T) {
	var expected = struct {
		clientCert  string
		clientKey   string
		configDir   string
		etcdNodes   []string
		interval    int
		prefix      string
		templateDir string
	}{
		"", "", "/etc/confd/conf.d", []string{"http://127.0.0.1:4001"},
		600, "/", "/etc/confd/templates",
	}
	LoadConfig("")
	cc := ClientCert()
	if cc != expected.clientCert {
		t.Errorf("Expected default clientCert = %s, got %s", expected.clientCert, cc)
	}
	ck := ClientKey()
	if ck != expected.clientKey {
		t.Errorf("Expected default clientKey = %s, got %s", expected.clientKey, ck)
	}
	cd := ConfigDir()
	if cd != expected.configDir {
		t.Errorf("Expected default configDir = %s, got %s", expected.configDir, cd)
	}
	en := EtcdNodes()
	if en[0] != expected.etcdNodes[0] {
		t.Errorf("Expected default etcdNodes = %v, got %v", expected.etcdNodes, en)
	}
	i := Interval()
	if i != expected.interval {
		t.Errorf("Expected default interval = %d, got %d", expected.interval, i)
	}
	p := Prefix()
	if p != expected.prefix {
		t.Errorf("Expected default prefix = %s, got %s", expected.prefix, p)
	}
	td := TemplateDir()
	if td != expected.templateDir {
		t.Errorf("Expected default templateDir = %s, got %s", expected.templateDir, td)
	}
}
<|endoftext|>