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

// Index to aid in synchronizing AIM resources with the underlying
// objects that generate them.  We keep an index of the expected state
// of the AIM resources that are associated with a given key, then
// ensure that the corresponding ThirdPartyResource objects in the
// kubernetes API are kept in sync

package controller

import (
	"reflect"
	"sort"
	"strings"

	"github.com/Sirupsen/logrus"

	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"
)

type aimKey struct {
	ktype string
	key   string
}

func newAimKey(ktype string, key string) aimKey {
	return aimKey{ktype, key}
}

func (cont *AciController) clearAimObjects(ktype string, key string) {
	cont.writeAimObjects(ktype, key, nil)
}

func addAimLabels(ktype string, key string, aci *Aci) {
	aci.ObjectMeta.Labels[aimKeyTypeLabel] = ktype
	aci.ObjectMeta.Labels[aimKeyLabel] = key
}

func (cont *AciController) aciObjLogger(aci *Aci) *logrus.Entry {
	return cont.log.WithFields(logrus.Fields{
		"KeyType": aci.ObjectMeta.Labels[aimKeyTypeLabel],
		"Key":     aci.ObjectMeta.Labels[aimKeyLabel],
		"Type":    aci.Spec.Type,
		"Name":    aci.ObjectMeta.Name,
	})
}

func (cont *AciController) reconcileAimObject(aci *Aci) {
	cont.indexMutex.Lock()
	if !cont.syncEnabled {
		cont.indexMutex.Unlock()
		return
	}
	ktype, ktok := aci.ObjectMeta.Labels[aimKeyTypeLabel]
	key, kok := aci.ObjectMeta.Labels[aimKeyLabel]

	var updates aciSlice
	var deletes []string

	if ktok && kok {
		akey := newAimKey(ktype, key)

		delete := false
		if expected, ok := cont.aimDesiredState[akey]; ok {
			found := false
			for _, eobj := range expected {
				if eobj.ObjectMeta.Name == aci.ObjectMeta.Name {
					found = true

					if !aciObjEq(eobj, aci) {
						cont.aciObjLogger(aci).
							Warning("Unexpected ACI object alteration")
						updates = aciSlice{eobj}
						break
					}
				}
			}
			if !found {
				delete = true
			}
		} else {
			delete = true
		}
		if delete {
			cont.aciObjLogger(aci).Warning("Deleting unexpected ACI object")
			deletes = []string{aci.ObjectMeta.Name}
		}
	}

	cont.indexMutex.Unlock()

	cont.executeAimDiff(nil, updates, deletes)
}

func (cont *AciController) reconcileAimDelete(aci *Aci) {
	cont.indexMutex.Lock()
	if !cont.syncEnabled {
		cont.indexMutex.Unlock()
		return
	}

	ktype, ktok := aci.ObjectMeta.Labels[aimKeyTypeLabel]
	key, kok := aci.ObjectMeta.Labels[aimKeyLabel]

	var adds aciSlice

	if ktok && kok {
		akey := newAimKey(ktype, key)

		if expected, ok := cont.aimDesiredState[akey]; ok {
			for _, eobj := range expected {
				if eobj.ObjectMeta.Name == aci.ObjectMeta.Name {
					cont.aciObjLogger(aci).
						Warning("Restoring unexpectedly deleted ACI object")
					adds = aciSlice{eobj}
					break
				}
			}
		}
	}

	cont.indexMutex.Unlock()

	cont.executeAimDiff(adds, nil, nil)
}

// note that writing the same object with multiple keys will result in
// undefined behavior.
func (cont *AciController) writeAimObjects(ktype string,
	key string, objects aciSlice) {

	sort.Sort(objects)
	for _, o := range objects {
		addAimLabels(ktype, key, o)
	}
	k := newAimKey(ktype, key)

	cont.indexMutex.Lock()
	adds, updates, deletes :=
		cont.diffAimState(cont.aimDesiredState[k], objects)
	if objects == nil {
		delete(cont.aimDesiredState, k)
	} else {
		cont.aimDesiredState[k] = objects
	}
	cont.indexMutex.Unlock()

	if cont.syncEnabled {
		cont.executeAimDiff(adds, updates, deletes)
	}
}

func (cont *AciController) aimFullSync() {

	currentState := make(map[aimKey]aciSlice)
	var adds aciSlice
	var updates aciSlice
	var deletes []string

	cache.ListAllByNamespace(cont.aimInformer.GetIndexer(),
		aimNamespace, labels.Everything(),
		func(aimobj interface{}) {
			aim := aimobj.(*Aci)
			ktype, ktok := aim.ObjectMeta.Labels[aimKeyTypeLabel]
			key, kok := aim.ObjectMeta.Labels[aimKeyLabel]
			if ktok && kok {
				akey := newAimKey(ktype, key)
				currentState[akey] = append(currentState[akey], aim)
			}
		})

	cont.indexMutex.Lock()
	for akey, cstate := range currentState {
		sort.Sort(cstate)
		a, u, d := cont.diffAimState(cstate, cont.aimDesiredState[akey])
		adds = append(adds, a...)
		updates = append(updates, u...)
		deletes = append(deletes, d...)
	}

	for akey, dstate := range cont.aimDesiredState {
		if _, ok := currentState[akey]; !ok {
			// entire key not present in current state
			a, _, _ := cont.diffAimState(nil, dstate)
			adds = append(adds, a...)
		}
	}
	cont.indexMutex.Unlock()

	if cont.syncEnabled {
		cont.executeAimDiff(adds, updates, deletes)
	}
}

func aciObjEq(a *Aci, b *Aci) bool {
	return reflect.DeepEqual(a.Spec, b.Spec) &&
		reflect.DeepEqual(a.Labels, b.Labels) &&
		reflect.DeepEqual(a.Annotations, b.Annotations)
}

func (cont *AciController) diffAimState(currentState aciSlice,
	desiredState aciSlice) (adds aciSlice, updates aciSlice, deletes []string) {

	i := 0
	j := 0

	for i < len(currentState) && j < len(desiredState) {
		cmp := strings.Compare(currentState[i].Name, desiredState[j].Name)
		if cmp < 0 {
			deletes = append(deletes, currentState[i].Name)
			i++
		} else if cmp > 0 {
			adds = append(adds, desiredState[j])
			j++
		} else {
			if !aciObjEq(currentState[i], desiredState[j]) {
				updates = append(updates, desiredState[j])
			}

			i++
			j++
		}
	}
	// extra old objects
	for i < len(currentState) {
		deletes = append(deletes, currentState[i].Name)
		i++
	}
	// extra new objects
	for j < len(desiredState) {
		adds = append(adds, desiredState[j])
		j++
	}

	return
}

func (cont *AciController) executeAimDiff(adds aciSlice,
	updates aciSlice, deletes []string) {

	for _, delete := range deletes {
		cont.log.WithFields(logrus.Fields{"Name": delete}).
			Debug("Applying ACI object  delete")
		err := cont.deleteAim(delete, nil)
		if err != nil {
			cont.log.Error("Could not delete AIM object: ", err)
		}
	}
	for _, update := range updates {
		cont.aciObjLogger(update).Debug("Applying ACI object update")
		_, err := cont.updateAim(update)
		if err != nil {
			cont.log.Error("Could not update AIM object: ", err)
		}
	}
	for _, add := range adds {
		cont.aciObjLogger(add).Debug("Applying ACI object  add")
		_, err := cont.addAim(add)
		if err != nil {
			cont.log.Error("Could not add AIM object: ", err)
		}
	}
}
<commit_msg>Fix log message<commit_after>// Copyright 2017 Cisco Systems, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Index to aid in synchronizing AIM resources with the underlying
// objects that generate them.  We keep an index of the expected state
// of the AIM resources that are associated with a given key, then
// ensure that the corresponding ThirdPartyResource objects in the
// kubernetes API are kept in sync

package controller

import (
	"reflect"
	"sort"
	"strings"

	"github.com/Sirupsen/logrus"

	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"
)

type aimKey struct {
	ktype string
	key   string
}

func newAimKey(ktype string, key string) aimKey {
	return aimKey{ktype, key}
}

func (cont *AciController) clearAimObjects(ktype string, key string) {
	cont.writeAimObjects(ktype, key, nil)
}

func addAimLabels(ktype string, key string, aci *Aci) {
	aci.ObjectMeta.Labels[aimKeyTypeLabel] = ktype
	aci.ObjectMeta.Labels[aimKeyLabel] = key
}

func (cont *AciController) aciObjLogger(aci *Aci) *logrus.Entry {
	return cont.log.WithFields(logrus.Fields{
		"KeyType": aci.ObjectMeta.Labels[aimKeyTypeLabel],
		"Key":     aci.ObjectMeta.Labels[aimKeyLabel],
		"Type":    aci.Spec.Type,
		"Name":    aci.ObjectMeta.Name,
	})
}

func (cont *AciController) reconcileAimObject(aci *Aci) {
	cont.indexMutex.Lock()
	if !cont.syncEnabled {
		cont.indexMutex.Unlock()
		return
	}
	ktype, ktok := aci.ObjectMeta.Labels[aimKeyTypeLabel]
	key, kok := aci.ObjectMeta.Labels[aimKeyLabel]

	var updates aciSlice
	var deletes []string

	if ktok && kok {
		akey := newAimKey(ktype, key)

		delete := false
		if expected, ok := cont.aimDesiredState[akey]; ok {
			found := false
			for _, eobj := range expected {
				if eobj.ObjectMeta.Name == aci.ObjectMeta.Name {
					found = true

					if !aciObjEq(eobj, aci) {
						cont.aciObjLogger(aci).
							Warning("Unexpected ACI object alteration")
						updates = aciSlice{eobj}
						break
					}
				}
			}
			if !found {
				delete = true
			}
		} else {
			delete = true
		}
		if delete {
			cont.aciObjLogger(aci).Warning("Deleting unexpected ACI object")
			deletes = []string{aci.ObjectMeta.Name}
		}
	}

	cont.indexMutex.Unlock()

	cont.executeAimDiff(nil, updates, deletes)
}

func (cont *AciController) reconcileAimDelete(aci *Aci) {
	cont.indexMutex.Lock()
	if !cont.syncEnabled {
		cont.indexMutex.Unlock()
		return
	}

	ktype, ktok := aci.ObjectMeta.Labels[aimKeyTypeLabel]
	key, kok := aci.ObjectMeta.Labels[aimKeyLabel]

	var adds aciSlice

	if ktok && kok {
		akey := newAimKey(ktype, key)

		if expected, ok := cont.aimDesiredState[akey]; ok {
			for _, eobj := range expected {
				if eobj.ObjectMeta.Name == aci.ObjectMeta.Name {
					cont.aciObjLogger(aci).
						Warning("Restoring unexpectedly deleted ACI object")
					adds = aciSlice{eobj}
					break
				}
			}
		}
	}

	cont.indexMutex.Unlock()

	cont.executeAimDiff(adds, nil, nil)
}

// note that writing the same object with multiple keys will result in
// undefined behavior.
func (cont *AciController) writeAimObjects(ktype string,
	key string, objects aciSlice) {

	sort.Sort(objects)
	for _, o := range objects {
		addAimLabels(ktype, key, o)
	}
	k := newAimKey(ktype, key)

	cont.indexMutex.Lock()
	adds, updates, deletes :=
		cont.diffAimState(cont.aimDesiredState[k], objects)
	if objects == nil {
		delete(cont.aimDesiredState, k)
	} else {
		cont.aimDesiredState[k] = objects
	}
	cont.indexMutex.Unlock()

	if cont.syncEnabled {
		cont.executeAimDiff(adds, updates, deletes)
	}
}

func (cont *AciController) aimFullSync() {

	currentState := make(map[aimKey]aciSlice)
	var adds aciSlice
	var updates aciSlice
	var deletes []string

	cache.ListAllByNamespace(cont.aimInformer.GetIndexer(),
		aimNamespace, labels.Everything(),
		func(aimobj interface{}) {
			aim := aimobj.(*Aci)
			ktype, ktok := aim.ObjectMeta.Labels[aimKeyTypeLabel]
			key, kok := aim.ObjectMeta.Labels[aimKeyLabel]
			if ktok && kok {
				akey := newAimKey(ktype, key)
				currentState[akey] = append(currentState[akey], aim)
			}
		})

	cont.indexMutex.Lock()
	for akey, cstate := range currentState {
		sort.Sort(cstate)
		a, u, d := cont.diffAimState(cstate, cont.aimDesiredState[akey])
		adds = append(adds, a...)
		updates = append(updates, u...)
		deletes = append(deletes, d...)
	}

	for akey, dstate := range cont.aimDesiredState {
		if _, ok := currentState[akey]; !ok {
			// entire key not present in current state
			a, _, _ := cont.diffAimState(nil, dstate)
			adds = append(adds, a...)
		}
	}
	cont.indexMutex.Unlock()

	if cont.syncEnabled {
		cont.executeAimDiff(adds, updates, deletes)
	}
}

func aciObjEq(a *Aci, b *Aci) bool {
	return reflect.DeepEqual(a.Spec, b.Spec) &&
		reflect.DeepEqual(a.Labels, b.Labels) &&
		reflect.DeepEqual(a.Annotations, b.Annotations)
}

func (cont *AciController) diffAimState(currentState aciSlice,
	desiredState aciSlice) (adds aciSlice, updates aciSlice, deletes []string) {

	i := 0
	j := 0

	for i < len(currentState) && j < len(desiredState) {
		cmp := strings.Compare(currentState[i].Name, desiredState[j].Name)
		if cmp < 0 {
			deletes = append(deletes, currentState[i].Name)
			i++
		} else if cmp > 0 {
			adds = append(adds, desiredState[j])
			j++
		} else {
			if !aciObjEq(currentState[i], desiredState[j]) {
				updates = append(updates, desiredState[j])
			}

			i++
			j++
		}
	}
	// extra old objects
	for i < len(currentState) {
		deletes = append(deletes, currentState[i].Name)
		i++
	}
	// extra new objects
	for j < len(desiredState) {
		adds = append(adds, desiredState[j])
		j++
	}

	return
}

func (cont *AciController) executeAimDiff(adds aciSlice,
	updates aciSlice, deletes []string) {

	for _, delete := range deletes {
		cont.log.WithFields(logrus.Fields{"Name": delete}).
			Debug("Applying ACI object delete")
		err := cont.deleteAim(delete, nil)
		if err != nil {
			cont.log.Error("Could not delete AIM object: ", err)
		}
	}
	for _, update := range updates {
		cont.aciObjLogger(update).Debug("Applying ACI object update")
		_, err := cont.updateAim(update)
		if err != nil {
			cont.log.Error("Could not update AIM object: ", err)
		}
	}
	for _, add := range adds {
		cont.aciObjLogger(add).Debug("Applying ACI object add")
		_, err := cont.addAim(add)
		if err != nil {
			cont.log.Error("Could not add AIM object: ", err)
		}
	}
}
<|endoftext|>


<commit_before>package instrument

import (
	"fmt"
	"strconv"
	"time"

	kitprom "github.com/go-kit/kit/metrics/prometheus"
	"github.com/prometheus/client_golang/prometheus"

	"github.com/lifesum/configsum/pkg/errors"
)

// Labels.
const (
	labelErr        = "err"
	labelHost       = "host"
	labelMethod     = "method"
	labelOp         = "op"
	labelProto      = "proto"
	labelRepo       = "repo"
	labelRoute      = "route"
	labelStatusCode = "statusCode"
	labelStore      = "store"
)

var (
	repoLatencies    = map[string]*kitprom.Histogram{}
	requestLatencies = map[string]*kitprom.Histogram{}
)

// ObserveRepoFunc wraps a histogram to track repo op latencies.
type ObserveRepoFunc func(store, repo, op string, begin time.Time, err error)

// ObserveRepo wraps a histogram to track repo op latencies.
func ObserveRepo(namespace, subsystem string) ObserveRepoFunc {
	key := fmt.Sprintf("%s-%s", namespace, subsystem)

	_, ok := repoLatencies[key]
	if !ok {
		repoLatencies[key] = kitprom.NewHistogramFrom(
			prometheus.HistogramOpts{
				Namespace: namespace,
				Subsystem: subsystem,
				Name:      "op_latency_seconds",
				Help:      "Latency of repo operations.",
			},
			[]string{
				labelErr,
				labelOp,
				labelRepo,
				labelStore,
			},
		)
	}

	return func(store, repo, op string, begin time.Time, err error) {
		errVal := ""

		if e := errors.Cause(err); e != nil {
			errVal = e.Error()
		}

		repoLatencies[key].With(
			labelErr, errVal,
			labelOp, op,
			labelRepo, repo,
			labelStore, store,
		).Observe(time.Since(begin).Seconds())
	}
}

// ObserveRequestFunc wraps a histogram to track request latencies.
type ObserveRequestFunc func(
	code int,
	host, method, proto, route string,
	begin time.Time,
)

// ObserveRequest wraps a histogram to track request latencies.
func ObserveRequest(namespace, subsystem string) ObserveRequestFunc {
	key := fmt.Sprintf("%s-%s", namespace, subsystem)

	_, ok := requestLatencies[key]
	if !ok {
		requestLatencies[key] = kitprom.NewHistogramFrom(
			prometheus.HistogramOpts{
				Namespace: namespace,
				Subsystem: subsystem,
				Name:      "transport_http_latency_seconds",
				Help:      "Total duration of requests in seconds",
			},
			[]string{
				labelHost,
				labelMethod,
				labelProto,
				labelStatusCode,
			},
		)
	}

	return func(code int, host, method, proto, route string, begin time.Time) {
		requestLatencies[key].With(
			labelStatusCode, strconv.Itoa(code),
			labelHost, host,
			labelMethod, method,
			labelProto, proto,
			labelRoute, route,
		).Observe(time.Since(begin).Seconds())
	}
}
<commit_msg>Add missing route label to Histogram<commit_after>package instrument

import (
	"fmt"
	"strconv"
	"time"

	kitprom "github.com/go-kit/kit/metrics/prometheus"
	"github.com/prometheus/client_golang/prometheus"

	"github.com/lifesum/configsum/pkg/errors"
)

// Labels.
const (
	labelErr        = "err"
	labelHost       = "host"
	labelMethod     = "method"
	labelOp         = "op"
	labelProto      = "proto"
	labelRepo       = "repo"
	labelRoute      = "route"
	labelStatusCode = "statusCode"
	labelStore      = "store"
)

var (
	repoLatencies    = map[string]*kitprom.Histogram{}
	requestLatencies = map[string]*kitprom.Histogram{}
)

// ObserveRepoFunc wraps a histogram to track repo op latencies.
type ObserveRepoFunc func(store, repo, op string, begin time.Time, err error)

// ObserveRepo wraps a histogram to track repo op latencies.
func ObserveRepo(namespace, subsystem string) ObserveRepoFunc {
	key := fmt.Sprintf("%s-%s", namespace, subsystem)

	_, ok := repoLatencies[key]
	if !ok {
		repoLatencies[key] = kitprom.NewHistogramFrom(
			prometheus.HistogramOpts{
				Namespace: namespace,
				Subsystem: subsystem,
				Name:      "op_latency_seconds",
				Help:      "Latency of repo operations.",
			},
			[]string{
				labelErr,
				labelOp,
				labelRepo,
				labelStore,
			},
		)
	}

	return func(store, repo, op string, begin time.Time, err error) {
		errVal := ""

		if e := errors.Cause(err); e != nil {
			errVal = e.Error()
		}

		repoLatencies[key].With(
			labelErr, errVal,
			labelOp, op,
			labelRepo, repo,
			labelStore, store,
		).Observe(time.Since(begin).Seconds())
	}
}

// ObserveRequestFunc wraps a histogram to track request latencies.
type ObserveRequestFunc func(
	code int,
	host, method, proto, route string,
	begin time.Time,
)

// ObserveRequest wraps a histogram to track request latencies.
func ObserveRequest(namespace, subsystem string) ObserveRequestFunc {
	key := fmt.Sprintf("%s-%s", namespace, subsystem)

	_, ok := requestLatencies[key]
	if !ok {
		requestLatencies[key] = kitprom.NewHistogramFrom(
			prometheus.HistogramOpts{
				Namespace: namespace,
				Subsystem: subsystem,
				Name:      "transport_http_latency_seconds",
				Help:      "Total duration of requests in seconds",
			},
			[]string{
				labelHost,
				labelMethod,
				labelProto,
				labelStatusCode,
				labelRoute,
			},
		)
	}

	return func(code int, host, method, proto, route string, begin time.Time) {
		requestLatencies[key].With(
			labelStatusCode, strconv.Itoa(code),
			labelHost, host,
			labelMethod, method,
			labelProto, proto,
			labelRoute, route,
		).Observe(time.Since(begin).Seconds())
	}
}
<|endoftext|>


<commit_before>package jsonlog

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"testing"
	"time"

	"github.com/docker/docker/pkg/timeutils"
)

func BenchmarkWriteLog(b *testing.B) {
	var buf bytes.Buffer
	e := json.NewEncoder(&buf)
	testLine := "Line that thinks that it is log line from docker\n"
	for i := 0; i < 30; i++ {
		e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
	}
	r := bytes.NewReader(buf.Bytes())
	w := ioutil.Discard
	format := timeutils.RFC3339NanoFixed
	b.SetBytes(int64(r.Len()))
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		if err := WriteLog(r, w, format); err != nil {
			b.Fatal(err)
		}
		b.StopTimer()
		r.Seek(0, 0)
		b.StartTimer()
	}
}
<commit_msg>Test for jsonlog.WriteLog<commit_after>package jsonlog

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/docker/docker/pkg/timeutils"
)

func TestWriteLog(t *testing.T) {
	var buf bytes.Buffer
	e := json.NewEncoder(&buf)
	testLine := "Line that thinks that it is log line from docker\n"
	for i := 0; i < 30; i++ {
		e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
	}
	w := bytes.NewBuffer(nil)
	format := timeutils.RFC3339NanoFixed
	if err := WriteLog(&buf, w, format); err != nil {
		t.Fatal(err)
	}
	res := w.String()
	t.Logf("Result of WriteLog: %q", res)
	lines := strings.Split(strings.TrimSpace(res), "\n")
	if len(lines) != 30 {
		t.Fatalf("Must be 30 lines but got %d", len(lines))
	}
	logRe := regexp.MustCompile(`\[.*\] Line that thinks that it is log line from docker`)
	for _, l := range lines {
		if !logRe.MatchString(l) {
			t.Fatalf("Log line not in expected format: %q", l)
		}
	}
}

func BenchmarkWriteLog(b *testing.B) {
	var buf bytes.Buffer
	e := json.NewEncoder(&buf)
	testLine := "Line that thinks that it is log line from docker\n"
	for i := 0; i < 30; i++ {
		e.Encode(JSONLog{Log: testLine, Stream: "stdout", Created: time.Now()})
	}
	r := bytes.NewReader(buf.Bytes())
	w := ioutil.Discard
	format := timeutils.RFC3339NanoFixed
	b.SetBytes(int64(r.Len()))
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		if err := WriteLog(r, w, format); err != nil {
			b.Fatal(err)
		}
		b.StopTimer()
		r.Seek(0, 0)
		b.StartTimer()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"

	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/io"
)

// Usage:
// go run kpub.go -z test -c test -t test -ack all -n 1024 -sz 25
// gk peek -z test -c test -t test -body > test.txt
// go run validator.go test.txt
func main() {
	if len(os.Args) != 2 {
		panic("Usage go run validator.go <filename>")
	}

	f, err := os.Open(os.Args[1])
	if err != nil {
		panic(err)
	}

	min, max := 1<<10, 0
	lineN := 0
	reader := bufio.NewReader(f)
	last := -98734
	for {
		l, err := io.ReadLine(reader)
		if err != nil {
			// EOF
			break
		}

		// {000000019} XXXXXXXXXXXXXXXXXXXXXXXXX
		n, err := strconv.Atoi(string(l[1:10]))
		if err != nil {
			fmt.Printf("%s for `%s`\n", err, string(l))
			break
		}

		if n > max {
			max = n
		}
		if n < min {
			min = n
		}
		lineN++

		if last >= 0 && n != last+1 {
			fmt.Println(color.Red("%d %d %d", last, n, n-last))
		}

		last = n
	}

	fmt.Printf("min=%d, max=%d, lines=%d\n", min, max, lineN)
	if lineN-max == 1 {
		// kpub always starts with 0
		fmt.Println("passed")
	} else {
		fmt.Println("failed")
	}
}
<commit_msg>color<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"

	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/io"
)

// Usage:
// go run kpub.go -z test -c test -t test -ack all -n 1024 -sz 25
// gk peek -z test -c test -t test -body > test.txt
// go run validator.go test.txt
func main() {
	if len(os.Args) != 2 {
		panic("Usage go run validator.go <filename>")
	}

	f, err := os.Open(os.Args[1])
	if err != nil {
		panic(err)
	}

	min, max := 1<<10, 0
	lineN := 0
	reader := bufio.NewReader(f)
	last := -98734
	for {
		l, err := io.ReadLine(reader)
		if err != nil {
			// EOF
			break
		}

		// {000000019} XXXXXXXXXXXXXXXXXXXXXXXXX
		n, err := strconv.Atoi(string(l[1:10]))
		if err != nil {
			fmt.Printf("%s for `%s`\n", err, string(l))
			break
		}

		if n > max {
			max = n
		}
		if n < min {
			min = n
		}
		lineN++

		if last >= 0 && n != last+1 {
			fmt.Println(color.Red("%d %d %d", last, n, n-last))
		}

		last = n
	}

	fmt.Printf("min=%d, max=%d, lines=%d\n", min, max, lineN)
	if lineN-max == 1 {
		// kpub always starts with 0
		fmt.Println(color.Green("passed"))
	} else {
		fmt.Println(color.Red("failed"))
	}
}
<|endoftext|>


<commit_before>/*
 * nomad-syncer
 * Copyright (c) 2016 Yieldbot, Inc. (http://github.com/yieldbot/nomad-syncer)
 * For the full copyright and license information, please view the LICENSE.txt file.
 */

package client

import (
	"time"
)

// Jobs represents jobs structure
type Jobs struct {
	ID                string
	Name              string
	Type              string
	Priority          int
	Status            string
	StatusDescription string
	CreateIndex       uint64
	ModifyIndex       uint64
}

// Job represents job structure
type Job struct {
	Region            string
	ID                string
	Name              string
	Type              string
	Priority          int
	AllAtOnce         bool
	Datacenters       []string
	Constraints       []*Constraint
	TaskGroups        []*TaskGroup
	Update            *UpdateStrategy
	Periodic          *PeriodicConfig
	Meta              map[string]string
	Status            string
	StatusDescription string
	CreateIndex       uint64
	ModifyIndex       uint64
}

// Constraint represents constraint structure
type Constraint struct {
	LTarget string
	RTarget string
	Operand string
}

// TaskGroup represents the task group field
type TaskGroup struct {
	Name          string
	Count         int
	Constraints   []*Constraint
	RestartPolicy *RestartPolicy
	Tasks         []*Task
	Meta          map[string]string
}

// RestartPolicy represents the restart policy field
type RestartPolicy struct {
	Interval         time.Duration
	Attempts         int
	Delay            time.Duration
	RestartOnSuccess bool
	Mode             string
}

// Task represents the task field
type Task struct {
	Name        string
	Driver      string
	Config      map[string]interface{}
	Env         map[string]string
	Services    []Service
	Constraints []*Constraint
	Resources   *Resources
	Meta        map[string]string
	KillTimeout time.Duration
}

// Service represents the service field
type Service struct {
	ID        string
	Name      string
	Tags      []string
	PortLabel string `mapstructure:"port"`
	Checks    []ServiceCheck
}

// ServiceCheck represents the service check field
type ServiceCheck struct {
	ID       string
	Name     string
	Type     string
	Script   string
	Path     string
	Protocol string
	Interval time.Duration
	Timeout  time.Duration
}

// Resources represents the resources field
type Resources struct {
	CPU      int
	MemoryMB int
	DiskMB   int
	IOPS     int
	Networks []*NetworkResource
}

// NetworkResource represents the network resource field
type NetworkResource struct {
	Device        string
	CIDR          string
	IP            string
	MBits         int
	ReservedPorts []Port
	DynamicPorts  []Port
	Public        bool
}

// Port represents port structure
type Port struct {
	Label string
	Value int
}

// UpdateStrategy represents the update strategy field
type UpdateStrategy struct {
	Stagger     time.Duration
	MaxParallel int
}

// PeriodicConfig represents the periodic config field
type PeriodicConfig struct {
	Enabled         bool
	Spec            string
	SpecType        string
	ProhibitOverlap bool
}
<commit_msg>Add SyncJob<commit_after>/*
 * nomad-syncer
 * Copyright (c) 2016 Yieldbot, Inc. (http://github.com/yieldbot/nomad-syncer)
 * For the full copyright and license information, please view the LICENSE.txt file.
 */

package client

import (
	"time"
)

// Jobs represents jobs structure
type Jobs struct {
	ID                string
	Name              string
	Type              string
	Priority          int
	Status            string
	StatusDescription string
	CreateIndex       uint64
	ModifyIndex       uint64
}

// Job represents job structure
type Job struct {
	Region            string
	ID                string
	Name              string
	Type              string
	Priority          int
	AllAtOnce         bool
	Datacenters       []string
	Constraints       []*Constraint
	TaskGroups        []*TaskGroup
	Update            *UpdateStrategy
	Periodic          *PeriodicConfig
	Meta              map[string]string
	Status            string
	StatusDescription string
	CreateIndex       uint64
	ModifyIndex       uint64
}

// Constraint represents constraint structure
type Constraint struct {
	LTarget string
	RTarget string
	Operand string
}

// TaskGroup represents the task group field
type TaskGroup struct {
	Name          string
	Count         int
	Constraints   []*Constraint
	RestartPolicy *RestartPolicy
	Tasks         []*Task
	Meta          map[string]string
}

// RestartPolicy represents the restart policy field
type RestartPolicy struct {
	Interval         time.Duration
	Attempts         int
	Delay            time.Duration
	RestartOnSuccess bool
	Mode             string
}

// Task represents the task field
type Task struct {
	Name        string
	Driver      string
	Config      map[string]interface{}
	Env         map[string]string
	Services    []Service
	Constraints []*Constraint
	Resources   *Resources
	Meta        map[string]string
	KillTimeout time.Duration
}

// Service represents the service field
type Service struct {
	ID        string
	Name      string
	Tags      []string
	PortLabel string `mapstructure:"port"`
	Checks    []ServiceCheck
}

// ServiceCheck represents the service check field
type ServiceCheck struct {
	ID       string
	Name     string
	Type     string
	Script   string
	Path     string
	Protocol string
	Interval time.Duration
	Timeout  time.Duration
}

// Resources represents the resources field
type Resources struct {
	CPU      int
	MemoryMB int
	DiskMB   int
	IOPS     int
	Networks []*NetworkResource
}

// NetworkResource represents the network resource field
type NetworkResource struct {
	Device        string
	CIDR          string
	IP            string
	MBits         int
	ReservedPorts []Port
	DynamicPorts  []Port
	Public        bool
}

// Port represents port structure
type Port struct {
	Label string
	Value int
}

// UpdateStrategy represents the update strategy field
type UpdateStrategy struct {
	Stagger     time.Duration
	MaxParallel int
}

// PeriodicConfig represents the periodic config field
type PeriodicConfig struct {
	Enabled         bool
	Spec            string
	SpecType        string
	ProhibitOverlap bool
}

// SyncJob represents sync job structure
type SyncJob struct {
	Job *Job
}
<|endoftext|>


<commit_before>package client

import (
	"github.com/coreos/fleet/schema"
	"github.com/juju/errgo"

	"fmt"
	execPkg "os/exec"
)

const (
	FLEETCTL        = "fleetctl"
	ENDPOINT_OPTION = "--endpoint"
	ENDPOINT_VALUE  = "http://172.17.42.1:4001"
)

type ClientCLI struct {
	etcdPeer string
}

func NewClientCLI() FleetClient {
	return NewClientCLIWithPeer(ENDPOINT_VALUE)
}

func NewClientCLIWithPeer(etcdPeer string) FleetClient {
	return &ClientCLI{
		etcdPeer: etcdPeer,
	}
}

func (this *ClientCLI) Submit(name, filePath string) error {
	cmd := execPkg.Command(FLEETCTL, "--drive", "etcd", ENDPOINT_OPTION, this.etcdPeer, "submit", filePath)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Unit(name string) (*schema.Unit, error) {
	return nil, fmt.Errorf("Method not implemented: ClientCLI.Unit")
}

func (this *ClientCLI) Start(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Stop(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Load(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "load", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Destroy(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "destroy", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}
<commit_msg>fixed typo<commit_after>package client

import (
	"github.com/coreos/fleet/schema"
	"github.com/juju/errgo"

	"fmt"
	execPkg "os/exec"
)

const (
	FLEETCTL        = "fleetctl"
	ENDPOINT_OPTION = "--endpoint"
	ENDPOINT_VALUE  = "http://172.17.42.1:4001"
)

type ClientCLI struct {
	etcdPeer string
}

func NewClientCLI() FleetClient {
	return NewClientCLIWithPeer(ENDPOINT_VALUE)
}

func NewClientCLIWithPeer(etcdPeer string) FleetClient {
	return &ClientCLI{
		etcdPeer: etcdPeer,
	}
}

func (this *ClientCLI) Submit(name, filePath string) error {
	cmd := execPkg.Command(FLEETCTL, "--driver", "etcd", ENDPOINT_OPTION, this.etcdPeer, "submit", filePath)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Unit(name string) (*schema.Unit, error) {
	return nil, fmt.Errorf("Method not implemented: ClientCLI.Unit")
}

func (this *ClientCLI) Start(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Stop(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Load(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "load", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Destroy(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "destroy", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Tamir Duberstein (tamird@gmail.com)

package protoutil

import (
	"fmt"
	"reflect"

	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
	"github.com/gogo/protobuf/proto"
)

var verbotenKinds = [...]reflect.Kind{
	reflect.Array,
}

type typeKey struct {
	typ      reflect.Type
	verboten reflect.Kind
}

var types struct {
	syncutil.Mutex
	known map[typeKey]bool
}

func init() {
	types.known = make(map[typeKey]bool)
}

// Clone uses proto.Clone to return a deep copy of pb. It panics if pb
// recursively contains any instances of types which are known to be
// unsupported by proto.Clone.
//
// This function and its associated lint (see `make check`) exist to ensure we
// do not attempt to proto.Clone types which are not supported by proto.Clone.
// This hackery is necessary because proto.Clone gives no direct indication
// that it has incompletely cloned a type; it merely logs to standard output
// (see https://github.com/golang/protobuf/blob/89238a3/proto/clone.go#L204).
//
// The concrete case against which this is currently guarding may be resolved
// upstream, see https://github.com/gogo/protobuf/issues/147.
func Clone(pb proto.Message) proto.Message {
	for _, verbotenKind := range verbotenKinds {
		if typeIsOrContainsVerboten(reflect.TypeOf(pb), verbotenKind) {
			// Try to find an example of the problematic field.
			if v := findVerboten(reflect.ValueOf(pb), verbotenKind); v != nil {
				panic(fmt.Sprintf("attempt to clone %T, which contains uncloneable %T %+v", pb, v, v))
			}
			// If we couldn't find one, panic anyway.
			panic(fmt.Sprintf("attempt to clone %T, which contains uncloneable fields", pb))
		}
	}

	return proto.Clone(pb)
}

func findVerboten(v reflect.Value, verboten reflect.Kind) interface{} {
	if val := findVerbotenInner(v, verboten); val != nil {
		return val.Interface()
	}
	return nil
}

func findVerbotenInner(v reflect.Value, verboten reflect.Kind) *reflect.Value {
	// Check if v's type can ever contain anything verboten.
	if v.IsValid() && !typeIsOrContainsVerboten(v.Type(), verboten) {
		return nil
	}

	// OK, v might contain something verboten based on its type, now check if it
	// actually does.
	switch v.Kind() {
	case verboten:
		return &v

	case reflect.Ptr:
		return findVerbotenInner(v.Elem(), verboten)

	case reflect.Map:
		for _, key := range v.MapKeys() {
			if elem := findVerbotenInner(v.MapIndex(key), verboten); elem != nil {
				return elem
			}
		}

	case reflect.Array, reflect.Slice:
		for i := 0; i < v.Len(); i++ {
			if elem := findVerbotenInner(v.Index(i), verboten); elem != nil {
				return elem
			}
		}

	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if elem := findVerbotenInner(v.Field(i), verboten); elem != nil {
				return elem
			}
		}

	case reflect.Chan, reflect.Func:
		// Not strictly correct, but cloning these kinds is not allowed.
		return &v
	}

	return nil
}

func typeIsOrContainsVerboten(t reflect.Type, verboten reflect.Kind) bool {
	types.Lock()
	defer types.Unlock()

	return typeIsOrContainsVerbotenLocked(t, verboten)
}

func typeIsOrContainsVerbotenLocked(t reflect.Type, verboten reflect.Kind) bool {
	key := typeKey{t, verboten}
	knownTypeIsOrContainsVerboten, ok := types.known[key]
	if !ok {
		knownTypeIsOrContainsVerboten = typeIsOrContainsVerbotenImpl(t, verboten)
		types.known[key] = knownTypeIsOrContainsVerboten
	}
	return knownTypeIsOrContainsVerboten
}

func typeIsOrContainsVerbotenImpl(t reflect.Type, verboten reflect.Kind) bool {
	switch t.Kind() {
	case verboten:
		return true

	case reflect.Map:
		if typeIsOrContainsVerbotenLocked(t.Key(), verboten) || typeIsOrContainsVerbotenLocked(t.Elem(), verboten) {
			return true
		}

	case reflect.Array, reflect.Ptr, reflect.Slice:
		if typeIsOrContainsVerbotenLocked(t.Elem(), verboten) {
			return true
		}

	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			if typeIsOrContainsVerbotenLocked(t.Field(i).Type, verboten) {
				return true
			}
		}

	case reflect.Chan, reflect.Func:
		// Not strictly correct, but cloning these kinds is not allowed.
		return true

	}

	return false
}
<commit_msg>protoutil: remove unused traversal through values<commit_after>// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Tamir Duberstein (tamird@gmail.com)

package protoutil

import (
	"fmt"
	"reflect"

	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
	"github.com/gogo/protobuf/proto"
)

var verbotenKinds = [...]reflect.Kind{
	reflect.Array,
}

type typeKey struct {
	typ      reflect.Type
	verboten reflect.Kind
}

var types struct {
	syncutil.Mutex
	known map[typeKey]reflect.Type
}

func init() {
	types.known = make(map[typeKey]reflect.Type)
}

// Clone uses proto.Clone to return a deep copy of pb. It panics if pb
// recursively contains any instances of types which are known to be
// unsupported by proto.Clone.
//
// This function and its associated lint (see `make check`) exist to ensure we
// do not attempt to proto.Clone types which are not supported by proto.Clone.
// This hackery is necessary because proto.Clone gives no direct indication
// that it has incompletely cloned a type; it merely logs to standard output
// (see https://github.com/golang/protobuf/blob/89238a3/proto/clone.go#L204).
//
// The concrete case against which this is currently guarding may be resolved
// upstream, see https://github.com/gogo/protobuf/issues/147.
func Clone(pb proto.Message) proto.Message {
	for _, verbotenKind := range verbotenKinds {
		if t := typeIsOrContainsVerboten(reflect.TypeOf(pb), verbotenKind); t != nil {
			panic(fmt.Sprintf("attempt to clone %T, which contains uncloneable field of type %s", pb, t))
		}
	}

	return proto.Clone(pb)
}

func typeIsOrContainsVerboten(t reflect.Type, verboten reflect.Kind) reflect.Type {
	types.Lock()
	defer types.Unlock()

	return typeIsOrContainsVerbotenLocked(t, verboten)
}

func typeIsOrContainsVerbotenLocked(t reflect.Type, verboten reflect.Kind) reflect.Type {
	key := typeKey{t, verboten}
	knownTypeIsOrContainsVerboten, ok := types.known[key]
	if !ok {
		knownTypeIsOrContainsVerboten = typeIsOrContainsVerbotenImpl(t, verboten)
		types.known[key] = knownTypeIsOrContainsVerboten
	}
	return knownTypeIsOrContainsVerboten
}

func typeIsOrContainsVerbotenImpl(t reflect.Type, verboten reflect.Kind) reflect.Type {
	switch t.Kind() {
	case verboten:
		return t

	case reflect.Map:
		if key := typeIsOrContainsVerbotenLocked(t.Key(), verboten); key != nil {
			return key
		}
		if value := typeIsOrContainsVerbotenLocked(t.Elem(), verboten); value != nil {
			return value
		}

	case reflect.Array, reflect.Ptr, reflect.Slice:
		if value := typeIsOrContainsVerbotenLocked(t.Elem(), verboten); value != nil {
			return value
		}

	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			if field := typeIsOrContainsVerbotenLocked(t.Field(i).Type, verboten); field != nil {
				return field
			}
		}

	case reflect.Chan, reflect.Func:
		// Not strictly correct, but cloning these kinds is not allowed.
		return t

	}

	return nil
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"encoding/json"
	"os"
	"path/filepath"

	"github.com/kelseyhightower/envconfig"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/scheduler"
	"github.com/loadimpact/k6/stats/cloud"
	"github.com/loadimpact/k6/stats/datadog"
	"github.com/loadimpact/k6/stats/influxdb"
	"github.com/loadimpact/k6/stats/kafka"
	"github.com/loadimpact/k6/stats/statsd/common"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/afero"
	"github.com/spf13/pflag"
	null "gopkg.in/guregu/null.v3"
)

// configFlagSet returns a FlagSet with the default run configuration flags.
func configFlagSet() *pflag.FlagSet {
	flags := pflag.NewFlagSet("", 0)
	flags.SortFlags = false
	flags.StringArrayP("out", "o", []string{}, "`uri` for an external metrics database")
	flags.BoolP("linger", "l", false, "keep the API server alive past test end")
	flags.Bool("no-usage-report", false, "don't send anonymous stats to the developers")
	flags.Bool("no-thresholds", false, "don't run thresholds")
	flags.Bool("no-summary", false, "don't show the summary at the end of the test")
	return flags
}

type Config struct {
	lib.Options

	Out           []string  `json:"out" envconfig:"out"`
	Linger        null.Bool `json:"linger" envconfig:"linger"`
	NoUsageReport null.Bool `json:"noUsageReport" envconfig:"no_usage_report"`
	NoThresholds  null.Bool `json:"noThresholds" envconfig:"no_thresholds"`
	NoSummary     null.Bool `json:"noSummary" envconfig:"no_summary"`

	Collectors struct {
		InfluxDB influxdb.Config `json:"influxdb"`
		Kafka    kafka.Config    `json:"kafka"`
		Cloud    cloud.Config    `json:"cloud"`
		StatsD   common.Config   `json:"statsd"`
		Datadog  datadog.Config  `json:"datadog"`
	} `json:"collectors"`
}

func (c Config) Apply(cfg Config) Config {
	c.Options = c.Options.Apply(cfg.Options)
	if len(cfg.Out) > 0 {
		c.Out = cfg.Out
	}
	if cfg.Linger.Valid {
		c.Linger = cfg.Linger
	}
	if cfg.NoUsageReport.Valid {
		c.NoUsageReport = cfg.NoUsageReport
	}
	if cfg.NoThresholds.Valid {
		c.NoThresholds = cfg.NoThresholds
	}
	if cfg.NoSummary.Valid {
		c.NoSummary = cfg.NoSummary
	}
	c.Collectors.InfluxDB = c.Collectors.InfluxDB.Apply(cfg.Collectors.InfluxDB)
	c.Collectors.Cloud = c.Collectors.Cloud.Apply(cfg.Collectors.Cloud)
	c.Collectors.Kafka = c.Collectors.Kafka.Apply(cfg.Collectors.Kafka)
	c.Collectors.StatsD = c.Collectors.StatsD.Apply(cfg.Collectors.StatsD)
	c.Collectors.Datadog = c.Collectors.Datadog.Apply(cfg.Collectors.Datadog)
	return c
}

// Gets configuration from CLI flags.
func getConfig(flags *pflag.FlagSet) (Config, error) {
	opts, err := getOptions(flags)
	if err != nil {
		return Config{}, err
	}
	out, err := flags.GetStringArray("out")
	if err != nil {
		return Config{}, err
	}
	return Config{
		Options:       opts,
		Out:           out,
		Linger:        getNullBool(flags, "linger"),
		NoUsageReport: getNullBool(flags, "no-usage-report"),
		NoThresholds:  getNullBool(flags, "no-thresholds"),
		NoSummary:     getNullBool(flags, "no-summary"),
	}, nil
}

// Reads a configuration file from disk and returns it and its path.
func readDiskConfig(fs afero.Fs) (Config, string, error) {
	realConfigFilePath := configFilePath
	if realConfigFilePath == "" {
		// The user didn't specify K6_CONFIG or --config, use the default path
		realConfigFilePath = defaultConfigFilePath
	}

	// Try to see if the file exists in the supplied filesystem
	if _, err := fs.Stat(realConfigFilePath); err != nil {
		if os.IsNotExist(err) && configFilePath == "" {
			// If the file doesn't exist, but it was the default config file (i.e. the user
			// didn't specify anything), silence the error
			err = nil
		}
		return Config{}, realConfigFilePath, err
	}

	data, err := afero.ReadFile(fs, realConfigFilePath)
	if err != nil {
		return Config{}, realConfigFilePath, err
	}
	var conf Config
	err = json.Unmarshal(data, &conf)
	return conf, realConfigFilePath, err
}

// Writes configuration back to disk.
func writeDiskConfig(fs afero.Fs, configPath string, conf Config) error {
	data, err := json.MarshalIndent(conf, "", "  ")
	if err != nil {
		return err
	}

	if err := fs.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
		return err
	}

	return afero.WriteFile(fs, configPath, data, 0644)
}

// Reads configuration variables from the environment.
func readEnvConfig() (conf Config, err error) {
	// TODO: replace envconfig and refactor the whole configuration from the groun up :/
	for _, err := range []error{
		envconfig.Process("k6", &conf),
		envconfig.Process("k6", &conf.Collectors.Cloud),
		envconfig.Process("k6", &conf.Collectors.InfluxDB),
		envconfig.Process("k6", &conf.Collectors.Kafka),
	} {
		return conf, err
	}
	return conf, nil
}

// This checks for conflicting options and turns any shortcut options (i.e. duration, iterations,
// stages) into the proper scheduler configuration
func buildExecutionConfig(conf Config) (Config, error) {
	result := conf
	if conf.Duration.Valid {
		if conf.Iterations.Valid {
			//TODO: make this an error in the next version
			log.Warnf("Specifying both duration and iterations is deprecated and won't be supported in the future k6 versions")
		}

		if conf.Stages != nil {
			//TODO: make this an error in the next version
			log.Warnf("Specifying both duration and stages is deprecated and won't be supported in the future k6 versions")
		}

		if conf.Execution != nil {
			//TODO: use a custom error type
			return result, errors.New("specifying both duration and execution is not supported")
		}

		if conf.Duration.Duration <= 0 {
			//TODO: make this an error in the next version
			log.Warnf("Specifying infinite duration in this way is deprecated and won't be supported in the future k6 versions")
		} else {
			ds := scheduler.NewConstantLoopingVUsConfig(lib.DefaultSchedulerName)
			ds.VUs = conf.VUs
			ds.Duration = conf.Duration
			ds.Interruptible = null.NewBool(true, false) // Preserve backwards compatibility
			result.Execution = scheduler.ConfigMap{lib.DefaultSchedulerName: ds}
		}
	} else if conf.Stages != nil {
		if conf.Iterations.Valid {
			//TODO: make this an error in the next version
			log.Warnf("Specifying both iterations and stages is deprecated and won't be supported in the future k6 versions")
		}

		if conf.Execution != nil {
			return conf, errors.New("specifying both stages and execution is not supported")
		}

		ds := scheduler.NewVariableLoopingVUsConfig(lib.DefaultSchedulerName)
		ds.StartVUs = conf.VUs
		for _, s := range conf.Stages {
			if s.Duration.Valid {
				ds.Stages = append(ds.Stages, scheduler.Stage{Duration: s.Duration, Target: s.Target})
			}
		}
		ds.Interruptible = null.NewBool(true, false) // Preserve backwards compatibility
		result.Execution = scheduler.ConfigMap{lib.DefaultSchedulerName: ds}
	} else if conf.Iterations.Valid {
		if conf.Execution != nil {
			return conf, errors.New("specifying both iterations and execution is not supported")
		}
		// TODO: maybe add a new flag that will be used as a shortcut to per-VU iterations?

		ds := scheduler.NewSharedIterationsConfig(lib.DefaultSchedulerName)
		ds.VUs = conf.VUs
		ds.Iterations = conf.Iterations
		result.Execution = scheduler.ConfigMap{lib.DefaultSchedulerName: ds}
	} else if conf.Execution != nil {
		//TODO: remove this warning in the next version
		log.Warnf("The execution settings are not functional in this k6 release, they will be ignored")
	} else {
		// No execution parameters whatsoever were specified, so we'll create a per-VU iterations config
		// with 1 VU and 1 iteration. We're choosing the per-VU config, since that one could also
		// be executed both locally, and in the cloud.
		result.Execution = scheduler.ConfigMap{
			lib.DefaultSchedulerName: scheduler.NewPerVUIterationsConfig(lib.DefaultSchedulerName),
		}
	}

	//TODO: validate the config; questions:
	// - separately validate the duration, iterations and stages for better error messages?
	// - or reuse the execution validation somehow, at the end? or something mixed?
	// - here or in getConsolidatedConfig() or somewhere else?

	return result, nil
}

// Assemble the final consolidated configuration from all of the different sources:
// - start with the CLI-provided options to get shadowed (non-Valid) defaults in there
// - add the global file config options
// - if supplied, add the Runner-provided options
// - add the environment variables
// - merge the user-supplied CLI flags back in on top, to give them the greatest priority
// - set some defaults if they weren't previously specified
// TODO: add better validation, more explicit default values and improve consistency between formats
// TODO: accumulate all errors and differentiate between the layers?
func getConsolidatedConfig(fs afero.Fs, cliConf Config, runner lib.Runner) (conf Config, err error) {
	cliConf.Collectors.InfluxDB = influxdb.NewConfig().Apply(cliConf.Collectors.InfluxDB)
	cliConf.Collectors.Cloud = cloud.NewConfig().Apply(cliConf.Collectors.Cloud)
	cliConf.Collectors.Kafka = kafka.NewConfig().Apply(cliConf.Collectors.Kafka)

	fileConf, _, err := readDiskConfig(fs)
	if err != nil {
		return conf, err
	}
	envConf, err := readEnvConfig()
	if err != nil {
		return conf, err
	}

	conf = cliConf.Apply(fileConf)
	if runner != nil {
		conf = conf.Apply(Config{Options: runner.GetOptions()})
	}
	conf = conf.Apply(envConf).Apply(cliConf)

	return buildExecutionConfig(conf)
}
<commit_msg>Improve the comments on the funcitons that deal with the file config<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"encoding/json"
	"os"
	"path/filepath"

	"github.com/kelseyhightower/envconfig"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/scheduler"
	"github.com/loadimpact/k6/stats/cloud"
	"github.com/loadimpact/k6/stats/datadog"
	"github.com/loadimpact/k6/stats/influxdb"
	"github.com/loadimpact/k6/stats/kafka"
	"github.com/loadimpact/k6/stats/statsd/common"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/afero"
	"github.com/spf13/pflag"
	null "gopkg.in/guregu/null.v3"
)

// configFlagSet returns a FlagSet with the default run configuration flags.
func configFlagSet() *pflag.FlagSet {
	flags := pflag.NewFlagSet("", 0)
	flags.SortFlags = false
	flags.StringArrayP("out", "o", []string{}, "`uri` for an external metrics database")
	flags.BoolP("linger", "l", false, "keep the API server alive past test end")
	flags.Bool("no-usage-report", false, "don't send anonymous stats to the developers")
	flags.Bool("no-thresholds", false, "don't run thresholds")
	flags.Bool("no-summary", false, "don't show the summary at the end of the test")
	return flags
}

type Config struct {
	lib.Options

	Out           []string  `json:"out" envconfig:"out"`
	Linger        null.Bool `json:"linger" envconfig:"linger"`
	NoUsageReport null.Bool `json:"noUsageReport" envconfig:"no_usage_report"`
	NoThresholds  null.Bool `json:"noThresholds" envconfig:"no_thresholds"`
	NoSummary     null.Bool `json:"noSummary" envconfig:"no_summary"`

	Collectors struct {
		InfluxDB influxdb.Config `json:"influxdb"`
		Kafka    kafka.Config    `json:"kafka"`
		Cloud    cloud.Config    `json:"cloud"`
		StatsD   common.Config   `json:"statsd"`
		Datadog  datadog.Config  `json:"datadog"`
	} `json:"collectors"`
}

func (c Config) Apply(cfg Config) Config {
	c.Options = c.Options.Apply(cfg.Options)
	if len(cfg.Out) > 0 {
		c.Out = cfg.Out
	}
	if cfg.Linger.Valid {
		c.Linger = cfg.Linger
	}
	if cfg.NoUsageReport.Valid {
		c.NoUsageReport = cfg.NoUsageReport
	}
	if cfg.NoThresholds.Valid {
		c.NoThresholds = cfg.NoThresholds
	}
	if cfg.NoSummary.Valid {
		c.NoSummary = cfg.NoSummary
	}
	c.Collectors.InfluxDB = c.Collectors.InfluxDB.Apply(cfg.Collectors.InfluxDB)
	c.Collectors.Cloud = c.Collectors.Cloud.Apply(cfg.Collectors.Cloud)
	c.Collectors.Kafka = c.Collectors.Kafka.Apply(cfg.Collectors.Kafka)
	c.Collectors.StatsD = c.Collectors.StatsD.Apply(cfg.Collectors.StatsD)
	c.Collectors.Datadog = c.Collectors.Datadog.Apply(cfg.Collectors.Datadog)
	return c
}

// Gets configuration from CLI flags.
func getConfig(flags *pflag.FlagSet) (Config, error) {
	opts, err := getOptions(flags)
	if err != nil {
		return Config{}, err
	}
	out, err := flags.GetStringArray("out")
	if err != nil {
		return Config{}, err
	}
	return Config{
		Options:       opts,
		Out:           out,
		Linger:        getNullBool(flags, "linger"),
		NoUsageReport: getNullBool(flags, "no-usage-report"),
		NoThresholds:  getNullBool(flags, "no-thresholds"),
		NoSummary:     getNullBool(flags, "no-summary"),
	}, nil
}

// Reads the configuration file from the supplied filesystem and returns it and its path.
// It will first try to see if the user eplicitly specified a custom config file and will
// try to read that. If there's a custom config specified and it couldn't be read or parsed,
// an error will be returned.
// If there's no custom config specified and no file exists in the default config path, it will
// return an empty config struct, the default config location and *no* error.
func readDiskConfig(fs afero.Fs) (Config, string, error) {
	realConfigFilePath := configFilePath
	if realConfigFilePath == "" {
		// The user didn't specify K6_CONFIG or --config, use the default path
		realConfigFilePath = defaultConfigFilePath
	}

	// Try to see if the file exists in the supplied filesystem
	if _, err := fs.Stat(realConfigFilePath); err != nil {
		if os.IsNotExist(err) && configFilePath == "" {
			// If the file doesn't exist, but it was the default config file (i.e. the user
			// didn't specify anything), silence the error
			err = nil
		}
		return Config{}, realConfigFilePath, err
	}

	data, err := afero.ReadFile(fs, realConfigFilePath)
	if err != nil {
		return Config{}, realConfigFilePath, err
	}
	var conf Config
	err = json.Unmarshal(data, &conf)
	return conf, realConfigFilePath, err
}

// Serializes the configuration to a JSON file and writes it in the supplied
// location on the supplied filesystem
func writeDiskConfig(fs afero.Fs, configPath string, conf Config) error {
	data, err := json.MarshalIndent(conf, "", "  ")
	if err != nil {
		return err
	}

	if err := fs.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
		return err
	}

	return afero.WriteFile(fs, configPath, data, 0644)
}

// Reads configuration variables from the environment.
func readEnvConfig() (conf Config, err error) {
	// TODO: replace envconfig and refactor the whole configuration from the groun up :/
	for _, err := range []error{
		envconfig.Process("k6", &conf),
		envconfig.Process("k6", &conf.Collectors.Cloud),
		envconfig.Process("k6", &conf.Collectors.InfluxDB),
		envconfig.Process("k6", &conf.Collectors.Kafka),
	} {
		return conf, err
	}
	return conf, nil
}

// This checks for conflicting options and turns any shortcut options (i.e. duration, iterations,
// stages) into the proper scheduler configuration
func buildExecutionConfig(conf Config) (Config, error) {
	result := conf
	if conf.Duration.Valid {
		if conf.Iterations.Valid {
			//TODO: make this an error in the next version
			log.Warnf("Specifying both duration and iterations is deprecated and won't be supported in the future k6 versions")
		}

		if conf.Stages != nil {
			//TODO: make this an error in the next version
			log.Warnf("Specifying both duration and stages is deprecated and won't be supported in the future k6 versions")
		}

		if conf.Execution != nil {
			//TODO: use a custom error type
			return result, errors.New("specifying both duration and execution is not supported")
		}

		if conf.Duration.Duration <= 0 {
			//TODO: make this an error in the next version
			log.Warnf("Specifying infinite duration in this way is deprecated and won't be supported in the future k6 versions")
		} else {
			ds := scheduler.NewConstantLoopingVUsConfig(lib.DefaultSchedulerName)
			ds.VUs = conf.VUs
			ds.Duration = conf.Duration
			ds.Interruptible = null.NewBool(true, false) // Preserve backwards compatibility
			result.Execution = scheduler.ConfigMap{lib.DefaultSchedulerName: ds}
		}
	} else if conf.Stages != nil {
		if conf.Iterations.Valid {
			//TODO: make this an error in the next version
			log.Warnf("Specifying both iterations and stages is deprecated and won't be supported in the future k6 versions")
		}

		if conf.Execution != nil {
			return conf, errors.New("specifying both stages and execution is not supported")
		}

		ds := scheduler.NewVariableLoopingVUsConfig(lib.DefaultSchedulerName)
		ds.StartVUs = conf.VUs
		for _, s := range conf.Stages {
			if s.Duration.Valid {
				ds.Stages = append(ds.Stages, scheduler.Stage{Duration: s.Duration, Target: s.Target})
			}
		}
		ds.Interruptible = null.NewBool(true, false) // Preserve backwards compatibility
		result.Execution = scheduler.ConfigMap{lib.DefaultSchedulerName: ds}
	} else if conf.Iterations.Valid {
		if conf.Execution != nil {
			return conf, errors.New("specifying both iterations and execution is not supported")
		}
		// TODO: maybe add a new flag that will be used as a shortcut to per-VU iterations?

		ds := scheduler.NewSharedIterationsConfig(lib.DefaultSchedulerName)
		ds.VUs = conf.VUs
		ds.Iterations = conf.Iterations
		result.Execution = scheduler.ConfigMap{lib.DefaultSchedulerName: ds}
	} else if conf.Execution != nil {
		//TODO: remove this warning in the next version
		log.Warnf("The execution settings are not functional in this k6 release, they will be ignored")
	} else {
		// No execution parameters whatsoever were specified, so we'll create a per-VU iterations config
		// with 1 VU and 1 iteration. We're choosing the per-VU config, since that one could also
		// be executed both locally, and in the cloud.
		result.Execution = scheduler.ConfigMap{
			lib.DefaultSchedulerName: scheduler.NewPerVUIterationsConfig(lib.DefaultSchedulerName),
		}
	}

	//TODO: validate the config; questions:
	// - separately validate the duration, iterations and stages for better error messages?
	// - or reuse the execution validation somehow, at the end? or something mixed?
	// - here or in getConsolidatedConfig() or somewhere else?

	return result, nil
}

// Assemble the final consolidated configuration from all of the different sources:
// - start with the CLI-provided options to get shadowed (non-Valid) defaults in there
// - add the global file config options
// - if supplied, add the Runner-provided options
// - add the environment variables
// - merge the user-supplied CLI flags back in on top, to give them the greatest priority
// - set some defaults if they weren't previously specified
// TODO: add better validation, more explicit default values and improve consistency between formats
// TODO: accumulate all errors and differentiate between the layers?
func getConsolidatedConfig(fs afero.Fs, cliConf Config, runner lib.Runner) (conf Config, err error) {
	cliConf.Collectors.InfluxDB = influxdb.NewConfig().Apply(cliConf.Collectors.InfluxDB)
	cliConf.Collectors.Cloud = cloud.NewConfig().Apply(cliConf.Collectors.Cloud)
	cliConf.Collectors.Kafka = kafka.NewConfig().Apply(cliConf.Collectors.Kafka)

	fileConf, _, err := readDiskConfig(fs)
	if err != nil {
		return conf, err
	}
	envConf, err := readEnvConfig()
	if err != nil {
		return conf, err
	}

	conf = cliConf.Apply(fileConf)
	if runner != nil {
		conf = conf.Apply(Config{Options: runner.GetOptions()})
	}
	conf = conf.Apply(envConf).Apply(cliConf)

	return buildExecutionConfig(conf)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/asaskevich/govalidator"
	"github.com/mebiusashan/beaker/cli"
	"github.com/mebiusashan/beaker/common"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	addWebSiteAlias     string
	addWebSiteUser      string
	addWebSitePassword  string
	addWebSiteIsDefault bool

	configCmd = &cobra.Command{
		Use:   "config",
		Short: "Configure blog information",
		Long: `The configuration command can
set your blog background address`,
		Run: func(cmd *cobra.Command, args []string) {
		},
	}

	addWebSiteCmd = &cobra.Command{
		Use:   "addw",
		Short: "Add blog site information",
		Long: `To add a blog server information,
you need to specify the blog address,
user name and password, and it will be
verified after adding`,
		Args: cobra.ExactArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			url := args[0]
			if !govalidator.IsURL(url) || !govalidator.IsIP(url) {
				common.Err("Blog address format error")
			}

			//check current config , has duplicate config
			website := viper.GetString(addWebSiteAlias)
			if website != "" {
				common.Err("Duplicate alias")
			}
			allsettings := viper.AllSettings()
			for _, data := range allsettings {
				if data == url {
					common.Err("Duplicate host")
				}
			}

			//need login with current website
			//if success login, set info and server key to config file
			//if it's default website, need reset all website config
			pubKey := cli.Ping(url)
			serverPubKey := cli.Login(url, pubKey, addWebSiteUser, addWebSitePassword)
			if cli.Check(url, serverPubKey) {
				viper.Set(addWebSiteAlias, url)
				viper.Set(addWebSiteAlias+"key", serverPubKey)
				if addWebSiteIsDefault {
					viper.Set("defaulutWebsite", addWebSiteAlias)
				}
				viper.SafeWriteConfig()
			} else {
				fmt.Println("login fail")
			}
		},
	}
)

// type websiteConfig struct {
// 	IsDefault bool
// 	Name      string
// 	HOST      string
// 	Key       string
// }

func init() {
	addWebSiteCmd.PersistentFlags().StringVarP(&addWebSiteAlias, "alias", "a", "", "blog alias")
	addWebSiteCmd.PersistentFlags().StringVarP(&addWebSiteUser, "user", "u", "", "blog administrator account name")
	addWebSiteCmd.PersistentFlags().StringVarP(&addWebSitePassword, "password", "p", "", "blog administrator account password")
	addWebSiteCmd.PersistentFlags().BoolVarP(&addWebSiteIsDefault, "defalut", "d", false, "set as default blog")

	configCmd.AddCommand(addWebSiteCmd)
}

func initConfig() {
	home, err := homedir.Dir()
	common.Assert(err)

	viper.AddConfigPath(home)
	viper.SetConfigType("yaml")
	viper.SetConfigName(".beaker")
	viper.SafeWriteConfig()

	err = viper.ReadInConfig()
	common.Assert(err)

	// fmt.Println(viper.GetString("license"))
	// viper.SafeWriteConfig()
	// website := viper.GetStringMap("website")
	// if website == nil || len(website) == 0 {
	// 	er("No blog information is configured. Please check the help for the config command.")
	// }

}
<commit_msg>add config add website command feature<commit_after>package cmd

import (
	"fmt"

	"github.com/asaskevich/govalidator"
	"github.com/mebiusashan/beaker/cli"
	"github.com/mebiusashan/beaker/common"
	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

type websiteConfig struct {
	Alias string
	HOST  string
	Key   string
}

type config struct {
	DefaultWebsite string
	Websites       []websiteConfig
}

var (
	localConfig         config
	addWebSiteAlias     string
	addWebSiteUser      string
	addWebSitePassword  string
	addWebSiteIsDefault bool

	configCmd = &cobra.Command{
		Use:   "config",
		Short: "Configure blog information",
		Long: `The configuration command can
set your blog background address`,
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Help()
		},
	}

	addWebSiteCmd = &cobra.Command{
		Use:   "addw",
		Short: "Add blog site information",
		Long: `To add a blog server information,
you need to specify the blog address,
user name and password, and it will be
verified after adding`,
		Args: cobra.ExactArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			url := args[0]
			if !govalidator.IsURL(url) && !govalidator.IsIP(url) {
				common.Err("Blog address format error")
			}

			if addWebSiteAlias == "" {
				common.Err("Alias cannot be empty")
			}

			//check current config , has duplicate config
			for _, website := range localConfig.Websites {
				if website.Alias == addWebSiteAlias {
					common.Err("Duplicate alias")
				}
				if website.HOST == url {
					common.Err("Duplicate host")
				}
			}

			//need login with current website
			//if success login, set info and server key to config file
			//if it's default website, need reset all website config
			pubKey := cli.Ping(url)
			serverPubKey := cli.Login(url, pubKey, addWebSiteUser, addWebSitePassword)
			if cli.Check(url, serverPubKey) {
				if len(localConfig.Websites) == 0 || localConfig.Websites == nil {
					localConfig.Websites = make([]websiteConfig, 0)
					addWebSiteIsDefault = true
				}
				if addWebSiteIsDefault {
					localConfig.DefaultWebsite = addWebSiteAlias
				}
				d := websiteConfig{Alias: addWebSiteAlias, HOST: url, Key: string(serverPubKey)}

				localConfig.Websites = append(localConfig.Websites, d)
				viper.Set("config", localConfig)
				err := viper.WriteConfig()
				common.Assert(err)
			} else {
				fmt.Println("login fail")
			}
		},
	}
)

func init() {
	addWebSiteCmd.PersistentFlags().StringVarP(&addWebSiteAlias, "alias", "a", "", "blog alias")
	addWebSiteCmd.PersistentFlags().StringVarP(&addWebSiteUser, "user", "u", "", "blog administrator account name")
	addWebSiteCmd.PersistentFlags().StringVarP(&addWebSitePassword, "password", "p", "", "blog administrator account password")
	addWebSiteCmd.PersistentFlags().BoolVarP(&addWebSiteIsDefault, "defalut", "d", false, "set as default blog")

	configCmd.AddCommand(addWebSiteCmd)
}

func initConfig() {
	home, err := homedir.Dir()
	common.Assert(err)

	viper.AddConfigPath(home)
	viper.SetConfigType("yaml")
	viper.SetConfigName(".beaker")
	viper.SafeWriteConfig()

	err = viper.ReadInConfig()
	common.Assert(err)

	err = viper.UnmarshalKey("config", &localConfig)
	common.Assert(err)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"net/http"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/kleister/kleister-api/config"
	"github.com/kleister/kleister-api/router"
	"github.com/kleister/kleister-api/shared/s3client"
	"github.com/urfave/cli"
)

// Server provides the sub-command to start the API server.
func Server() cli.Command {
	return cli.Command{
		Name:  "server",
		Usage: "Start the Kleister API",
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:        "db-driver",
				Value:       "mysql",
				Usage:       "Database driver selection",
				EnvVar:      "KLEISTER_DB_DRIVER",
				Destination: &config.Database.Driver,
			},
			cli.StringFlag{
				Name:        "db-name",
				Value:       "kleister",
				Usage:       "Name for database connection",
				EnvVar:      "KLEISTER_DB_NAME",
				Destination: &config.Database.Name,
			},
			cli.StringFlag{
				Name:        "db-username",
				Value:       "root",
				Usage:       "Username for database connection",
				EnvVar:      "KLEISTER_DB_USERNAME",
				Destination: &config.Database.Username,
			},
			cli.StringFlag{
				Name:        "db-password",
				Value:       "root",
				Usage:       "Password for database connection",
				EnvVar:      "KLEISTER_DB_PASSWORD",
				Destination: &config.Database.Password,
			},
			cli.StringFlag{
				Name:        "db-host",
				Value:       "localhost:3306",
				Usage:       "Host for database connection",
				EnvVar:      "KLEISTER_DB_HOST",
				Destination: &config.Database.Host,
			},
			cli.StringFlag{
				Name:        "host",
				Value:       "http://localhost:8080",
				Usage:       "External access to server",
				EnvVar:      "KLEISTER_SERVER_HOST",
				Destination: &config.Server.Host,
			},
			cli.StringFlag{
				Name:        "addr",
				Value:       ":8080",
				Usage:       "Address to bind the server",
				EnvVar:      "KLEISTER_SERVER_ADDR",
				Destination: &config.Server.Addr,
			},
			cli.StringFlag{
				Name:        "cert",
				Value:       "",
				Usage:       "Path to SSL cert",
				EnvVar:      "KLEISTER_SERVER_CERT",
				Destination: &config.Server.Cert,
			},
			cli.StringFlag{
				Name:        "key",
				Value:       "",
				Usage:       "Path to SSL key",
				EnvVar:      "KLEISTER_SERVER_KEY",
				Destination: &config.Server.Key,
			},
			cli.StringFlag{
				Name:        "root",
				Value:       "/",
				Usage:       "Root folder of the app",
				EnvVar:      "KLEISTER_SERVER_ROOT",
				Destination: &config.Server.Root,
			},
			cli.StringFlag{
				Name:        "storage",
				Value:       "storage/",
				Usage:       "Folder for storing uploads",
				EnvVar:      "KLEISTER_SERVER_STORAGE",
				Destination: &config.Server.Storage,
			},
			cli.DurationFlag{
				Name:        "expire",
				Value:       time.Hour * 24,
				Usage:       "Session expire duration",
				EnvVar:      "KLEISTER_SESSION_EXPIRE",
				Destination: &config.Session.Expire,
			},
			cli.BoolFlag{
				Name:        "s3-enabled",
				Usage:       "Enable S3 uploads",
				EnvVar:      "KLEISTER_S3_ENABLED",
				Destination: &config.S3.Enabled,
			},
			cli.StringFlag{
				Name:        "s3-endpoint",
				Value:       "",
				Usage:       "S3 API endpoint",
				EnvVar:      "KLEISTER_S3_ENDPOINT",
				Destination: &config.S3.Endpoint,
			},
			cli.StringFlag{
				Name:        "s3-bucket",
				Value:       "kleister",
				Usage:       "S3 bucket name",
				EnvVar:      "KLEISTER_S3_BUCKET",
				Destination: &config.S3.Bucket,
			},
			cli.StringFlag{
				Name:        "s3-region",
				Value:       "us-east-1",
				Usage:       "S3 region name",
				EnvVar:      "KLEISTER_S3_REGION",
				Destination: &config.S3.Region,
			},
			cli.StringFlag{
				Name:        "s3-access",
				Value:       "",
				Usage:       "S3 public key",
				EnvVar:      "KLEISTER_S3_ACCESS_KEY",
				Destination: &config.S3.Access,
			},
			cli.StringFlag{
				Name:        "s3-secret",
				Value:       "",
				Usage:       "S3 secret key",
				EnvVar:      "KLEISTER_S3_SECRET_KEY",
				Destination: &config.S3.Secret,
			},
			cli.BoolFlag{
				Name:        "s3-path-style",
				Usage:       "S3 path style",
				EnvVar:      "KLEISTER_S3_PATH_STYLE",
				Destination: &config.S3.PathStyle,
			},
		},
		Before: func(c *cli.Context) error {
			if config.S3.Enabled {
				_, err := s3client.New().List()

				if err != nil {
					if config.Debug {
						return fmt.Errorf("Failed to connect to S3. %s", err)
					} else {
						return fmt.Errorf("Failed to connect to S3.")
					}
				}
			}

			return nil
		},
		Action: func(c *cli.Context) {
			logrus.Infof("Starting the API on %s", config.Server.Addr)

			if config.Server.Cert != "" && config.Server.Key != "" {
				logrus.Fatal(
					http.ListenAndServeTLS(
						config.Server.Addr,
						config.Server.Cert,
						config.Server.Key,
						router.Load(),
					),
				)
			} else {
				logrus.Fatal(
					http.ListenAndServe(
						config.Server.Addr,
						router.Load(),
					),
				)
			}
		},
	}
}
<commit_msg>Always print useful error for s3 connection<commit_after>package cmd

import (
	"fmt"
	"net/http"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/kleister/kleister-api/config"
	"github.com/kleister/kleister-api/router"
	"github.com/kleister/kleister-api/shared/s3client"
	"github.com/urfave/cli"
)

// Server provides the sub-command to start the API server.
func Server() cli.Command {
	return cli.Command{
		Name:  "server",
		Usage: "Start the Kleister API",
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:        "db-driver",
				Value:       "mysql",
				Usage:       "Database driver selection",
				EnvVar:      "KLEISTER_DB_DRIVER",
				Destination: &config.Database.Driver,
			},
			cli.StringFlag{
				Name:        "db-name",
				Value:       "kleister",
				Usage:       "Name for database connection",
				EnvVar:      "KLEISTER_DB_NAME",
				Destination: &config.Database.Name,
			},
			cli.StringFlag{
				Name:        "db-username",
				Value:       "root",
				Usage:       "Username for database connection",
				EnvVar:      "KLEISTER_DB_USERNAME",
				Destination: &config.Database.Username,
			},
			cli.StringFlag{
				Name:        "db-password",
				Value:       "root",
				Usage:       "Password for database connection",
				EnvVar:      "KLEISTER_DB_PASSWORD",
				Destination: &config.Database.Password,
			},
			cli.StringFlag{
				Name:        "db-host",
				Value:       "localhost:3306",
				Usage:       "Host for database connection",
				EnvVar:      "KLEISTER_DB_HOST",
				Destination: &config.Database.Host,
			},
			cli.StringFlag{
				Name:        "host",
				Value:       "http://localhost:8080",
				Usage:       "External access to server",
				EnvVar:      "KLEISTER_SERVER_HOST",
				Destination: &config.Server.Host,
			},
			cli.StringFlag{
				Name:        "addr",
				Value:       ":8080",
				Usage:       "Address to bind the server",
				EnvVar:      "KLEISTER_SERVER_ADDR",
				Destination: &config.Server.Addr,
			},
			cli.StringFlag{
				Name:        "cert",
				Value:       "",
				Usage:       "Path to SSL cert",
				EnvVar:      "KLEISTER_SERVER_CERT",
				Destination: &config.Server.Cert,
			},
			cli.StringFlag{
				Name:        "key",
				Value:       "",
				Usage:       "Path to SSL key",
				EnvVar:      "KLEISTER_SERVER_KEY",
				Destination: &config.Server.Key,
			},
			cli.StringFlag{
				Name:        "root",
				Value:       "/",
				Usage:       "Root folder of the app",
				EnvVar:      "KLEISTER_SERVER_ROOT",
				Destination: &config.Server.Root,
			},
			cli.StringFlag{
				Name:        "storage",
				Value:       "storage/",
				Usage:       "Folder for storing uploads",
				EnvVar:      "KLEISTER_SERVER_STORAGE",
				Destination: &config.Server.Storage,
			},
			cli.DurationFlag{
				Name:        "expire",
				Value:       time.Hour * 24,
				Usage:       "Session expire duration",
				EnvVar:      "KLEISTER_SESSION_EXPIRE",
				Destination: &config.Session.Expire,
			},
			cli.BoolFlag{
				Name:        "s3-enabled",
				Usage:       "Enable S3 uploads",
				EnvVar:      "KLEISTER_S3_ENABLED",
				Destination: &config.S3.Enabled,
			},
			cli.StringFlag{
				Name:        "s3-endpoint",
				Value:       "",
				Usage:       "S3 API endpoint",
				EnvVar:      "KLEISTER_S3_ENDPOINT",
				Destination: &config.S3.Endpoint,
			},
			cli.StringFlag{
				Name:        "s3-bucket",
				Value:       "kleister",
				Usage:       "S3 bucket name",
				EnvVar:      "KLEISTER_S3_BUCKET",
				Destination: &config.S3.Bucket,
			},
			cli.StringFlag{
				Name:        "s3-region",
				Value:       "us-east-1",
				Usage:       "S3 region name",
				EnvVar:      "KLEISTER_S3_REGION",
				Destination: &config.S3.Region,
			},
			cli.StringFlag{
				Name:        "s3-access",
				Value:       "",
				Usage:       "S3 public key",
				EnvVar:      "KLEISTER_S3_ACCESS_KEY",
				Destination: &config.S3.Access,
			},
			cli.StringFlag{
				Name:        "s3-secret",
				Value:       "",
				Usage:       "S3 secret key",
				EnvVar:      "KLEISTER_S3_SECRET_KEY",
				Destination: &config.S3.Secret,
			},
			cli.BoolFlag{
				Name:        "s3-path-style",
				Usage:       "S3 path style",
				EnvVar:      "KLEISTER_S3_PATH_STYLE",
				Destination: &config.S3.PathStyle,
			},
		},
		Before: func(c *cli.Context) error {
			if config.S3.Enabled {
				_, err := s3client.New().List()

				if err != nil {
					return fmt.Errorf("Failed to connect to S3. %s", err)
				}
			}

			return nil
		},
		Action: func(c *cli.Context) {
			logrus.Infof("Starting the API on %s", config.Server.Addr)

			if config.Server.Cert != "" && config.Server.Key != "" {
				logrus.Fatal(
					http.ListenAndServeTLS(
						config.Server.Addr,
						config.Server.Cert,
						config.Server.Key,
						router.Load(),
					),
				)
			} else {
				logrus.Fatal(
					http.ListenAndServe(
						config.Server.Addr,
						router.Load(),
					),
				)
			}
		},
	}
}
<|endoftext|>