<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package github

import "fmt"

// Status defines a git commit on Github
type Status struct {
	State   string `json:"state"`
	Context string `json:"context"`
}

// Statuses defines the overall status of a git commit on Github
type Statuses struct {
	State    string   `json:"state"`
	Statuses []Status `json:"statuses"`
}

const (
	statusesListPath = "/repos/%s/%s/commits/%s/statuses"
	statusListPath   = "/repos/%s/%s/commits/%s/status"
)

// Statuses lists statuses for a git commit
func getStatuses(token, user, repo, sha string) ([]Status, error) {
	url, err := githubURL(githubAPIURL)
	if err != nil {
		return nil, err
	}
	url.Path = fmt.Sprintf(statusesListPath, user, repo, sha)
	var statuses []Status
	if err = Get(token, url.String(), &statuses); err != nil {
		return nil, err
	}
	return statuses, nil
}

// OverallStatus lists the overall status for a git commit.
// Instead of all the statuses, it gives an overall status
// if all have passed, plus a list of the most recent results
// for each context.
func overallStatus(token, user, repo, sha string) (Statuses, error) {
	url, err := githubURL(githubAPIURL)
	if err != nil {
		return Statuses{}, err
	}
	url.Path = fmt.Sprintf(statusListPath, user, repo, sha)
	var statuses Statuses
	if err = Get(token, url.String(), &statuses); err != nil {
		return Statuses{}, err
	}
	return statuses, nil
}
<commit_msg>get 100 statuses instead of 30 (#24)<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package github

import "fmt"

// Status defines a git commit on Github
type Status struct {
	State   string `json:"state"`
	Context string `json:"context"`
}

// Statuses defines the overall status of a git commit on Github
type Statuses struct {
	State    string   `json:"state"`
	Statuses []Status `json:"statuses"`
}

const (
	statusesListPath = "/repos/%s/%s/statuses/%s"
	statusListPath   = "/repos/%s/%s/commits/%s/status"
)

// Statuses lists statuses for a git commit
func getStatuses(token, user, repo, sha string) ([]Status, error) {
	url, err := githubURL(githubAPIURL)
	if err != nil {
		return nil, err
	}
	url.Path = fmt.Sprintf(statusesListPath, user, repo, sha)
	var statuses []Status
	if err = Get(token, url.String()+"?per_page=100", &statuses); err != nil {
		return nil, err
	}
	return statuses, nil
}

// OverallStatus lists the overall status for a git commit.
// Instead of all the statuses, it gives an overall status
// if all have passed, plus a list of the most recent results
// for each context.
func overallStatus(token, user, repo, sha string) (Statuses, error) {
	url, err := githubURL(githubAPIURL)
	if err != nil {
		return Statuses{}, err
	}
	url.Path = fmt.Sprintf(statusListPath, user, repo, sha)
	var statuses Statuses
	if err = Get(token, url.String(), &statuses); err != nil {
		return Statuses{}, err
	}
	return statuses, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	stdlog "log"
	"math/rand"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/apache/thrift/lib/go/thrift"
	stdprometheus "github.com/prometheus/client_golang/prometheus"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/examples/addsvc/pb"
	"github.com/go-kit/kit/examples/addsvc/server"
	thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/add"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/expvar"
	"github.com/go-kit/kit/metrics/prometheus"
	"github.com/go-kit/kit/tracing/zipkin"
	httptransport "github.com/go-kit/kit/transport/http"
)

func main() {
	// Flag domain. Note that gRPC transitively registers flags via its import
	// of glog. So, we define a new flag set, to keep those domains distinct.
	fs := flag.NewFlagSet("", flag.ExitOnError)
	var (
		debugAddr                    = fs.String("debug.addr", ":8000", "Address for HTTP debug/instrumentation server")
		httpAddr                     = fs.String("http.addr", ":8001", "Address for HTTP (JSON) server")
		grpcAddr                     = fs.String("grpc.addr", ":8002", "Address for gRPC server")
		netrpcAddr                   = fs.String("netrpc.addr", ":8003", "Address for net/rpc server")
		thriftAddr                   = fs.String("thrift.addr", ":8004", "Address for Thrift server")
		thriftProtocol               = fs.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
		thriftBufferSize             = fs.Int("thrift.buffer.size", 0, "0 for unbuffered")
		thriftFramed                 = fs.Bool("thrift.framed", false, "true to enable framing")
		zipkinHostPort               = fs.String("zipkin.host.port", "my.service.domain:12345", "Zipkin host:port")
		zipkinServiceName            = fs.String("zipkin.service.name", "addsvc", "Zipkin service name")
		zipkinCollectorAddr          = fs.String("zipkin.collector.addr", "", "Zipkin Scribe collector address (empty will log spans)")
		zipkinCollectorTimeout       = fs.Duration("zipkin.collector.timeout", time.Second, "Zipkin collector timeout")
		zipkinCollectorBatchSize     = fs.Int("zipkin.collector.batch.size", 100, "Zipkin collector batch size")
		zipkinCollectorBatchInterval = fs.Duration("zipkin.collector.batch.interval", time.Second, "Zipkin collector batch interval")
	)
	flag.Usage = fs.Usage // only show our flags
	if err := fs.Parse(os.Args[1:]); err != nil {
		fmt.Fprintf(os.Stderr, "%v", err)
		os.Exit(1)
	}

	// package log
	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC).With("caller", log.DefaultCaller)
		stdlog.SetFlags(0)                             // flags are handled by Go kit's logger
		stdlog.SetOutput(log.NewStdlibAdapter(logger)) // redirect anything using stdlib log to us
	}

	// package metrics
	var requestDuration metrics.TimeHistogram
	{
		requestDuration = metrics.NewTimeHistogram(time.Nanosecond, metrics.NewMultiHistogram(
			expvar.NewHistogram("request_duration_ns", 0, 5e9, 1, 50, 95, 99),
			prometheus.NewSummary(stdprometheus.SummaryOpts{
				Namespace: "myorg",
				Subsystem: "addsvc",
				Name:      "duration_ns",
				Help:      "Request duration in nanoseconds.",
			}, []string{"method"}),
		))
	}

	// package tracing
	var collector zipkin.Collector
	{
		zipkinLogger := log.NewContext(logger).With("component", "zipkin")
		collector = loggingCollector{zipkinLogger} // TODO(pb)
		if *zipkinCollectorAddr != "" {
			var err error
			if collector, err = zipkin.NewScribeCollector(
				*zipkinCollectorAddr,
				*zipkinCollectorTimeout,
				zipkin.ScribeBatchSize(*zipkinCollectorBatchSize),
				zipkin.ScribeBatchInterval(*zipkinCollectorBatchInterval),
				zipkin.ScribeLogger(zipkinLogger),
			); err != nil {
				zipkinLogger.Log("err", err)
				os.Exit(1)
			}
		}
	}

	// Business domain
	var svc server.AddService
	{
		svc = pureAddService{}
		svc = loggingMiddleware{svc, logger}
		svc = instrumentingMiddleware{svc, requestDuration}
	}

	// Mechanical stuff
	rand.Seed(time.Now().UnixNano())
	root := context.Background()
	errc := make(chan error)

	go func() {
		errc <- interrupt()
	}()

	// Debug/instrumentation
	go func() {
		transportLogger := log.NewContext(logger).With("transport", "debug")
		transportLogger.Log("addr", *debugAddr)
		errc <- http.ListenAndServe(*debugAddr, nil) // DefaultServeMux
	}()

	// Transport: HTTP/JSON
	go func() {
		var (
			transportLogger = log.NewContext(logger).With("transport", "HTTP/JSON")
			tracingLogger   = log.NewContext(transportLogger).With("component", "tracing")
			newSumSpan      = zipkin.MakeNewSpanFunc(*zipkinHostPort, *zipkinServiceName, "sum")
			newConcatSpan   = zipkin.MakeNewSpanFunc(*zipkinHostPort, *zipkinServiceName, "concat")
			traceSum        = zipkin.ToContext(newSumSpan, tracingLogger)
			traceConcat     = zipkin.ToContext(newConcatSpan, tracingLogger)
			mux             = http.NewServeMux()
			sum, concat     endpoint.Endpoint
		)

		sum = makeSumEndpoint(svc)
		sum = zipkin.AnnotateServer(newSumSpan, collector)(sum)
		mux.Handle("/sum", httptransport.NewServer(
			root,
			sum,
			server.DecodeSumRequest,
			server.EncodeSumResponse,
			httptransport.ServerBefore(traceSum),
			httptransport.ServerErrorLogger(transportLogger),
		))

		concat = makeConcatEndpoint(svc)
		concat = zipkin.AnnotateServer(newConcatSpan, collector)(concat)
		mux.Handle("/concat", httptransport.NewServer(
			root,
			concat,
			server.DecodeConcatRequest,
			server.EncodeConcatResponse,
			httptransport.ServerBefore(traceConcat),
			httptransport.ServerErrorLogger(transportLogger),
		))

		transportLogger.Log("addr", *httpAddr)
		errc <- http.ListenAndServe(*httpAddr, mux)
	}()

	// Transport: gRPC
	go func() {
		transportLogger := log.NewContext(logger).With("transport", "gRPC")
		ln, err := net.Listen("tcp", *grpcAddr)
		if err != nil {
			errc <- err
			return
		}
		s := grpc.NewServer() // uses its own, internal context
		pb.RegisterAddServer(s, grpcBinding{svc})
		transportLogger.Log("addr", *grpcAddr)
		errc <- s.Serve(ln)
	}()

	// Transport: net/rpc
	go func() {
		transportLogger := log.NewContext(logger).With("transport", "net/rpc")
		s := rpc.NewServer()
		if err := s.RegisterName("addsvc", netrpcBinding{svc}); err != nil {
			errc <- err
			return
		}
		s.HandleHTTP(rpc.DefaultRPCPath, rpc.DefaultDebugPath)
		transportLogger.Log("addr", *netrpcAddr)
		errc <- http.ListenAndServe(*netrpcAddr, s)
	}()

	// Transport: Thrift
	go func() {
		var protocolFactory thrift.TProtocolFactory
		switch *thriftProtocol {
		case "binary":
			protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
		case "compact":
			protocolFactory = thrift.NewTCompactProtocolFactory()
		case "json":
			protocolFactory = thrift.NewTJSONProtocolFactory()
		case "simplejson":
			protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
		default:
			errc <- fmt.Errorf("invalid Thrift protocol %q", *thriftProtocol)
			return
		}
		var transportFactory thrift.TTransportFactory
		if *thriftBufferSize > 0 {
			transportFactory = thrift.NewTBufferedTransportFactory(*thriftBufferSize)
		} else {
			transportFactory = thrift.NewTTransportFactory()
		}
		if *thriftFramed {
			transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
		}
		transport, err := thrift.NewTServerSocket(*thriftAddr)
		if err != nil {
			errc <- err
			return
		}
		transportLogger := log.NewContext(logger).With("transport", "net/rpc")
		transportLogger.Log("addr", *thriftAddr)
		errc <- thrift.NewTSimpleServer4(
			thriftadd.NewAddServiceProcessor(thriftBinding{svc}),
			transport,
			transportFactory,
			protocolFactory,
		).Serve()
	}()

	logger.Log("fatal", <-errc)
}

func interrupt() error {
	c := make(chan os.Signal)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	return fmt.Errorf("%s", <-c)
}

type loggingCollector struct{ log.Logger }

func (c loggingCollector) Collect(s *zipkin.Span) error {
	annotations := s.Encode().GetAnnotations()
	values := make([]string, len(annotations))
	for i, a := range annotations {
		values[i] = a.Value
	}
	c.Logger.Log(
		"trace_id", s.TraceID(),
		"span_id", s.SpanID(),
		"parent_span_id", s.ParentSpanID(),
		"annotations", strings.Join(values, " "),
	)
	return nil
}
<commit_msg>addsvc: new metrics.NewMultiHistogram signature<commit_after>package main

import (
	"flag"
	"fmt"
	stdlog "log"
	"math/rand"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/apache/thrift/lib/go/thrift"
	stdprometheus "github.com/prometheus/client_golang/prometheus"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/examples/addsvc/pb"
	"github.com/go-kit/kit/examples/addsvc/server"
	thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/add"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/expvar"
	"github.com/go-kit/kit/metrics/prometheus"
	"github.com/go-kit/kit/tracing/zipkin"
	httptransport "github.com/go-kit/kit/transport/http"
)

func main() {
	// Flag domain. Note that gRPC transitively registers flags via its import
	// of glog. So, we define a new flag set, to keep those domains distinct.
	fs := flag.NewFlagSet("", flag.ExitOnError)
	var (
		debugAddr                    = fs.String("debug.addr", ":8000", "Address for HTTP debug/instrumentation server")
		httpAddr                     = fs.String("http.addr", ":8001", "Address for HTTP (JSON) server")
		grpcAddr                     = fs.String("grpc.addr", ":8002", "Address for gRPC server")
		netrpcAddr                   = fs.String("netrpc.addr", ":8003", "Address for net/rpc server")
		thriftAddr                   = fs.String("thrift.addr", ":8004", "Address for Thrift server")
		thriftProtocol               = fs.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
		thriftBufferSize             = fs.Int("thrift.buffer.size", 0, "0 for unbuffered")
		thriftFramed                 = fs.Bool("thrift.framed", false, "true to enable framing")
		zipkinHostPort               = fs.String("zipkin.host.port", "my.service.domain:12345", "Zipkin host:port")
		zipkinServiceName            = fs.String("zipkin.service.name", "addsvc", "Zipkin service name")
		zipkinCollectorAddr          = fs.String("zipkin.collector.addr", "", "Zipkin Scribe collector address (empty will log spans)")
		zipkinCollectorTimeout       = fs.Duration("zipkin.collector.timeout", time.Second, "Zipkin collector timeout")
		zipkinCollectorBatchSize     = fs.Int("zipkin.collector.batch.size", 100, "Zipkin collector batch size")
		zipkinCollectorBatchInterval = fs.Duration("zipkin.collector.batch.interval", time.Second, "Zipkin collector batch interval")
	)
	flag.Usage = fs.Usage // only show our flags
	if err := fs.Parse(os.Args[1:]); err != nil {
		fmt.Fprintf(os.Stderr, "%v", err)
		os.Exit(1)
	}

	// package log
	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC).With("caller", log.DefaultCaller)
		stdlog.SetFlags(0)                             // flags are handled by Go kit's logger
		stdlog.SetOutput(log.NewStdlibAdapter(logger)) // redirect anything using stdlib log to us
	}

	// package metrics
	var requestDuration metrics.TimeHistogram
	{
		requestDuration = metrics.NewTimeHistogram(time.Nanosecond, metrics.NewMultiHistogram(
			"request_duration_ns",
			expvar.NewHistogram("request_duration_ns", 0, 5e9, 1, 50, 95, 99),
			prometheus.NewSummary(stdprometheus.SummaryOpts{
				Namespace: "myorg",
				Subsystem: "addsvc",
				Name:      "duration_ns",
				Help:      "Request duration in nanoseconds.",
			}, []string{"method"}),
		))
	}

	// package tracing
	var collector zipkin.Collector
	{
		zipkinLogger := log.NewContext(logger).With("component", "zipkin")
		collector = loggingCollector{zipkinLogger} // TODO(pb)
		if *zipkinCollectorAddr != "" {
			var err error
			if collector, err = zipkin.NewScribeCollector(
				*zipkinCollectorAddr,
				*zipkinCollectorTimeout,
				zipkin.ScribeBatchSize(*zipkinCollectorBatchSize),
				zipkin.ScribeBatchInterval(*zipkinCollectorBatchInterval),
				zipkin.ScribeLogger(zipkinLogger),
			); err != nil {
				zipkinLogger.Log("err", err)
				os.Exit(1)
			}
		}
	}

	// Business domain
	var svc server.AddService
	{
		svc = pureAddService{}
		svc = loggingMiddleware{svc, logger}
		svc = instrumentingMiddleware{svc, requestDuration}
	}

	// Mechanical stuff
	rand.Seed(time.Now().UnixNano())
	root := context.Background()
	errc := make(chan error)

	go func() {
		errc <- interrupt()
	}()

	// Debug/instrumentation
	go func() {
		transportLogger := log.NewContext(logger).With("transport", "debug")
		transportLogger.Log("addr", *debugAddr)
		errc <- http.ListenAndServe(*debugAddr, nil) // DefaultServeMux
	}()

	// Transport: HTTP/JSON
	go func() {
		var (
			transportLogger = log.NewContext(logger).With("transport", "HTTP/JSON")
			tracingLogger   = log.NewContext(transportLogger).With("component", "tracing")
			newSumSpan      = zipkin.MakeNewSpanFunc(*zipkinHostPort, *zipkinServiceName, "sum")
			newConcatSpan   = zipkin.MakeNewSpanFunc(*zipkinHostPort, *zipkinServiceName, "concat")
			traceSum        = zipkin.ToContext(newSumSpan, tracingLogger)
			traceConcat     = zipkin.ToContext(newConcatSpan, tracingLogger)
			mux             = http.NewServeMux()
			sum, concat     endpoint.Endpoint
		)

		sum = makeSumEndpoint(svc)
		sum = zipkin.AnnotateServer(newSumSpan, collector)(sum)
		mux.Handle("/sum", httptransport.NewServer(
			root,
			sum,
			server.DecodeSumRequest,
			server.EncodeSumResponse,
			httptransport.ServerBefore(traceSum),
			httptransport.ServerErrorLogger(transportLogger),
		))

		concat = makeConcatEndpoint(svc)
		concat = zipkin.AnnotateServer(newConcatSpan, collector)(concat)
		mux.Handle("/concat", httptransport.NewServer(
			root,
			concat,
			server.DecodeConcatRequest,
			server.EncodeConcatResponse,
			httptransport.ServerBefore(traceConcat),
			httptransport.ServerErrorLogger(transportLogger),
		))

		transportLogger.Log("addr", *httpAddr)
		errc <- http.ListenAndServe(*httpAddr, mux)
	}()

	// Transport: gRPC
	go func() {
		transportLogger := log.NewContext(logger).With("transport", "gRPC")
		ln, err := net.Listen("tcp", *grpcAddr)
		if err != nil {
			errc <- err
			return
		}
		s := grpc.NewServer() // uses its own, internal context
		pb.RegisterAddServer(s, grpcBinding{svc})
		transportLogger.Log("addr", *grpcAddr)
		errc <- s.Serve(ln)
	}()

	// Transport: net/rpc
	go func() {
		transportLogger := log.NewContext(logger).With("transport", "net/rpc")
		s := rpc.NewServer()
		if err := s.RegisterName("addsvc", netrpcBinding{svc}); err != nil {
			errc <- err
			return
		}
		s.HandleHTTP(rpc.DefaultRPCPath, rpc.DefaultDebugPath)
		transportLogger.Log("addr", *netrpcAddr)
		errc <- http.ListenAndServe(*netrpcAddr, s)
	}()

	// Transport: Thrift
	go func() {
		var protocolFactory thrift.TProtocolFactory
		switch *thriftProtocol {
		case "binary":
			protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
		case "compact":
			protocolFactory = thrift.NewTCompactProtocolFactory()
		case "json":
			protocolFactory = thrift.NewTJSONProtocolFactory()
		case "simplejson":
			protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
		default:
			errc <- fmt.Errorf("invalid Thrift protocol %q", *thriftProtocol)
			return
		}
		var transportFactory thrift.TTransportFactory
		if *thriftBufferSize > 0 {
			transportFactory = thrift.NewTBufferedTransportFactory(*thriftBufferSize)
		} else {
			transportFactory = thrift.NewTTransportFactory()
		}
		if *thriftFramed {
			transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
		}
		transport, err := thrift.NewTServerSocket(*thriftAddr)
		if err != nil {
			errc <- err
			return
		}
		transportLogger := log.NewContext(logger).With("transport", "net/rpc")
		transportLogger.Log("addr", *thriftAddr)
		errc <- thrift.NewTSimpleServer4(
			thriftadd.NewAddServiceProcessor(thriftBinding{svc}),
			transport,
			transportFactory,
			protocolFactory,
		).Serve()
	}()

	logger.Log("fatal", <-errc)
}

func interrupt() error {
	c := make(chan os.Signal)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	return fmt.Errorf("%s", <-c)
}

type loggingCollector struct{ log.Logger }

func (c loggingCollector) Collect(s *zipkin.Span) error {
	annotations := s.Encode().GetAnnotations()
	values := make([]string, len(annotations))
	for i, a := range annotations {
		values[i] = a.Value
	}
	c.Logger.Log(
		"trace_id", s.TraceID(),
		"span_id", s.SpanID(),
		"parent_span_id", s.ParentSpanID(),
		"annotations", strings.Join(values, " "),
	)
	return nil
}
<|endoftext|>


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

package v3rpc

import (
	"context"
	"crypto/sha256"
	"io"

	"go.etcd.io/etcd/auth"
	"go.etcd.io/etcd/etcdserver"
	"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
	"go.etcd.io/etcd/mvcc"
	"go.etcd.io/etcd/mvcc/backend"
	"go.etcd.io/etcd/raft"
	"go.etcd.io/etcd/version"

	"go.uber.org/zap"
)

type KVGetter interface {
	KV() mvcc.ConsistentWatchableKV
}

type BackendGetter interface {
	Backend() backend.Backend
}

type Alarmer interface {
	// Alarms is implemented in Server interface located in etcdserver/server.go
	// It returns a list of alarms present in the AlarmStore
	Alarms() []*pb.AlarmMember
	Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
}

type LeaderTransferrer interface {
	MoveLeader(ctx context.Context, lead, target uint64) error
}

type AuthGetter interface {
	AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error)
	AuthStore() auth.AuthStore
}

type ClusterStatusGetter interface {
	IsLearner() bool
}

type maintenanceServer struct {
	lg  *zap.Logger
	rg  etcdserver.RaftStatusGetter
	kg  KVGetter
	bg  BackendGetter
	a   Alarmer
	lt  LeaderTransferrer
	hdr header
	cs  ClusterStatusGetter
}

func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
	srv := &maintenanceServer{lg: s.Cfg.Logger, rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s), cs: s}
	return &authMaintenanceServer{srv, s}
}

func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
	if ms.lg != nil {
		ms.lg.Info("starting defragment")
	} else {
		plog.Noticef("starting to defragment the storage backend...")
	}
	err := ms.bg.Backend().Defrag()
	if err != nil {
		if ms.lg != nil {
			ms.lg.Warn("failed to defragment", zap.Error(err))
		} else {
			plog.Errorf("failed to defragment the storage backend (%v)", err)
		}
		return nil, err
	}
	if ms.lg != nil {
		ms.lg.Info("finished defragment")
	} else {
		plog.Noticef("finished defragmenting the storage backend")
	}
	return &pb.DefragmentResponse{}, nil
}

func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance_SnapshotServer) error {
	snap := ms.bg.Backend().Snapshot()
	pr, pw := io.Pipe()

	defer pr.Close()

	go func() {
		snap.WriteTo(pw)
		if err := snap.Close(); err != nil {
			if ms.lg != nil {
				ms.lg.Warn("failed to close snapshot", zap.Error(err))
			} else {
				plog.Errorf("error closing snapshot (%v)", err)
			}
		}
		pw.Close()
	}()

	// send file data
	h := sha256.New()
	br := int64(0)
	buf := make([]byte, 32*1024)
	sz := snap.Size()
	for br < sz {
		n, err := io.ReadFull(pr, buf)
		if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
			return togRPCError(err)
		}
		br += int64(n)
		resp := &pb.SnapshotResponse{
			RemainingBytes: uint64(sz - br),
			Blob:           buf[:n],
		}
		if err = srv.Send(resp); err != nil {
			return togRPCError(err)
		}
		h.Write(buf[:n])
	}

	// send sha
	sha := h.Sum(nil)
	hresp := &pb.SnapshotResponse{RemainingBytes: 0, Blob: sha}
	if err := srv.Send(hresp); err != nil {
		return togRPCError(err)
	}

	return nil
}

func (ms *maintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
	h, rev, err := ms.kg.KV().Hash()
	if err != nil {
		return nil, togRPCError(err)
	}
	resp := &pb.HashResponse{Header: &pb.ResponseHeader{Revision: rev}, Hash: h}
	ms.hdr.fill(resp.Header)
	return resp, nil
}

func (ms *maintenanceServer) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
	h, rev, compactRev, err := ms.kg.KV().HashByRev(r.Revision)
	if err != nil {
		return nil, togRPCError(err)
	}

	resp := &pb.HashKVResponse{Header: &pb.ResponseHeader{Revision: rev}, Hash: h, CompactRevision: compactRev}
	ms.hdr.fill(resp.Header)
	return resp, nil
}

func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) {
	return ms.a.Alarm(ctx, ar)
}

func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
	hdr := &pb.ResponseHeader{}
	ms.hdr.fill(hdr)
	resp := &pb.StatusResponse{
		Header:           hdr,
		Version:          version.Version,
		Leader:           uint64(ms.rg.Leader()),
		RaftIndex:        ms.rg.CommittedIndex(),
		RaftAppliedIndex: ms.rg.AppliedIndex(),
		RaftTerm:         ms.rg.Term(),
		DbSize:           ms.bg.Backend().Size(),
		DbSizeInUse:      ms.bg.Backend().SizeInUse(),
		IsLearner:        ms.cs.IsLearner(),
	}
	if resp.Leader == raft.None {
		resp.Errors = append(resp.Errors, etcdserver.ErrNoLeader.Error())
	}
	for _, a := range ms.a.Alarms() {
		resp.Errors = append(resp.Errors, a.String())
	}
	return resp, nil
}

func (ms *maintenanceServer) MoveLeader(ctx context.Context, tr *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
	if ms.rg.ID() != ms.rg.Leader() {
		return nil, rpctypes.ErrGRPCNotLeader
	}

	if err := ms.lt.MoveLeader(ctx, uint64(ms.rg.Leader()), tr.TargetID); err != nil {
		return nil, togRPCError(err)
	}
	return &pb.MoveLeaderResponse{}, nil
}

type authMaintenanceServer struct {
	*maintenanceServer
	ag AuthGetter
}

func (ams *authMaintenanceServer) isAuthenticated(ctx context.Context) error {
	authInfo, err := ams.ag.AuthInfoFromCtx(ctx)
	if err != nil {
		return err
	}

	return ams.ag.AuthStore().IsAdminPermitted(authInfo)
}

func (ams *authMaintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
	if err := ams.isAuthenticated(ctx); err != nil {
		return nil, err
	}

	return ams.maintenanceServer.Defragment(ctx, sr)
}

func (ams *authMaintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance_SnapshotServer) error {
	if err := ams.isAuthenticated(srv.Context()); err != nil {
		return err
	}

	return ams.maintenanceServer.Snapshot(sr, srv)
}

func (ams *authMaintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
	if err := ams.isAuthenticated(ctx); err != nil {
		return nil, err
	}

	return ams.maintenanceServer.Hash(ctx, r)
}

func (ams *authMaintenanceServer) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
	if err := ams.isAuthenticated(ctx); err != nil {
		return nil, err
	}
	return ams.maintenanceServer.HashKV(ctx, r)
}

func (ams *authMaintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
	return ams.maintenanceServer.Status(ctx, ar)
}

func (ams *authMaintenanceServer) MoveLeader(ctx context.Context, tr *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
	return ams.maintenanceServer.MoveLeader(ctx, tr)
}
<commit_msg>etcdserver: populate ResponseHeader in Alarm method (#11600)<commit_after>// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v3rpc

import (
	"context"
	"crypto/sha256"
	"io"

	"go.etcd.io/etcd/auth"
	"go.etcd.io/etcd/etcdserver"
	"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
	"go.etcd.io/etcd/mvcc"
	"go.etcd.io/etcd/mvcc/backend"
	"go.etcd.io/etcd/raft"
	"go.etcd.io/etcd/version"

	"go.uber.org/zap"
)

type KVGetter interface {
	KV() mvcc.ConsistentWatchableKV
}

type BackendGetter interface {
	Backend() backend.Backend
}

type Alarmer interface {
	// Alarms is implemented in Server interface located in etcdserver/server.go
	// It returns a list of alarms present in the AlarmStore
	Alarms() []*pb.AlarmMember
	Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
}

type LeaderTransferrer interface {
	MoveLeader(ctx context.Context, lead, target uint64) error
}

type AuthGetter interface {
	AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error)
	AuthStore() auth.AuthStore
}

type ClusterStatusGetter interface {
	IsLearner() bool
}

type maintenanceServer struct {
	lg  *zap.Logger
	rg  etcdserver.RaftStatusGetter
	kg  KVGetter
	bg  BackendGetter
	a   Alarmer
	lt  LeaderTransferrer
	hdr header
	cs  ClusterStatusGetter
}

func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
	srv := &maintenanceServer{lg: s.Cfg.Logger, rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s), cs: s}
	return &authMaintenanceServer{srv, s}
}

func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
	if ms.lg != nil {
		ms.lg.Info("starting defragment")
	} else {
		plog.Noticef("starting to defragment the storage backend...")
	}
	err := ms.bg.Backend().Defrag()
	if err != nil {
		if ms.lg != nil {
			ms.lg.Warn("failed to defragment", zap.Error(err))
		} else {
			plog.Errorf("failed to defragment the storage backend (%v)", err)
		}
		return nil, err
	}
	if ms.lg != nil {
		ms.lg.Info("finished defragment")
	} else {
		plog.Noticef("finished defragmenting the storage backend")
	}
	return &pb.DefragmentResponse{}, nil
}

func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance_SnapshotServer) error {
	snap := ms.bg.Backend().Snapshot()
	pr, pw := io.Pipe()

	defer pr.Close()

	go func() {
		snap.WriteTo(pw)
		if err := snap.Close(); err != nil {
			if ms.lg != nil {
				ms.lg.Warn("failed to close snapshot", zap.Error(err))
			} else {
				plog.Errorf("error closing snapshot (%v)", err)
			}
		}
		pw.Close()
	}()

	// send file data
	h := sha256.New()
	br := int64(0)
	buf := make([]byte, 32*1024)
	sz := snap.Size()
	for br < sz {
		n, err := io.ReadFull(pr, buf)
		if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
			return togRPCError(err)
		}
		br += int64(n)
		resp := &pb.SnapshotResponse{
			RemainingBytes: uint64(sz - br),
			Blob:           buf[:n],
		}
		if err = srv.Send(resp); err != nil {
			return togRPCError(err)
		}
		h.Write(buf[:n])
	}

	// send sha
	sha := h.Sum(nil)
	hresp := &pb.SnapshotResponse{RemainingBytes: 0, Blob: sha}
	if err := srv.Send(hresp); err != nil {
		return togRPCError(err)
	}

	return nil
}

func (ms *maintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
	h, rev, err := ms.kg.KV().Hash()
	if err != nil {
		return nil, togRPCError(err)
	}
	resp := &pb.HashResponse{Header: &pb.ResponseHeader{Revision: rev}, Hash: h}
	ms.hdr.fill(resp.Header)
	return resp, nil
}

func (ms *maintenanceServer) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
	h, rev, compactRev, err := ms.kg.KV().HashByRev(r.Revision)
	if err != nil {
		return nil, togRPCError(err)
	}

	resp := &pb.HashKVResponse{Header: &pb.ResponseHeader{Revision: rev}, Hash: h, CompactRevision: compactRev}
	ms.hdr.fill(resp.Header)
	return resp, nil
}

func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) {
	resp, err := ms.a.Alarm(ctx, ar)
	if err != nil {
		return nil, togRPCError(err)
	}
	if resp.Header == nil {
		resp.Header = &pb.ResponseHeader{}
	}
	ms.hdr.fill(resp.Header)
	return resp, nil
}

func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
	hdr := &pb.ResponseHeader{}
	ms.hdr.fill(hdr)
	resp := &pb.StatusResponse{
		Header:           hdr,
		Version:          version.Version,
		Leader:           uint64(ms.rg.Leader()),
		RaftIndex:        ms.rg.CommittedIndex(),
		RaftAppliedIndex: ms.rg.AppliedIndex(),
		RaftTerm:         ms.rg.Term(),
		DbSize:           ms.bg.Backend().Size(),
		DbSizeInUse:      ms.bg.Backend().SizeInUse(),
		IsLearner:        ms.cs.IsLearner(),
	}
	if resp.Leader == raft.None {
		resp.Errors = append(resp.Errors, etcdserver.ErrNoLeader.Error())
	}
	for _, a := range ms.a.Alarms() {
		resp.Errors = append(resp.Errors, a.String())
	}
	return resp, nil
}

func (ms *maintenanceServer) MoveLeader(ctx context.Context, tr *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
	if ms.rg.ID() != ms.rg.Leader() {
		return nil, rpctypes.ErrGRPCNotLeader
	}

	if err := ms.lt.MoveLeader(ctx, uint64(ms.rg.Leader()), tr.TargetID); err != nil {
		return nil, togRPCError(err)
	}
	return &pb.MoveLeaderResponse{}, nil
}

type authMaintenanceServer struct {
	*maintenanceServer
	ag AuthGetter
}

func (ams *authMaintenanceServer) isAuthenticated(ctx context.Context) error {
	authInfo, err := ams.ag.AuthInfoFromCtx(ctx)
	if err != nil {
		return err
	}

	return ams.ag.AuthStore().IsAdminPermitted(authInfo)
}

func (ams *authMaintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
	if err := ams.isAuthenticated(ctx); err != nil {
		return nil, err
	}

	return ams.maintenanceServer.Defragment(ctx, sr)
}

func (ams *authMaintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance_SnapshotServer) error {
	if err := ams.isAuthenticated(srv.Context()); err != nil {
		return err
	}

	return ams.maintenanceServer.Snapshot(sr, srv)
}

func (ams *authMaintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
	if err := ams.isAuthenticated(ctx); err != nil {
		return nil, err
	}

	return ams.maintenanceServer.Hash(ctx, r)
}

func (ams *authMaintenanceServer) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
	if err := ams.isAuthenticated(ctx); err != nil {
		return nil, err
	}
	return ams.maintenanceServer.HashKV(ctx, r)
}

func (ams *authMaintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
	return ams.maintenanceServer.Status(ctx, ar)
}

func (ams *authMaintenanceServer) MoveLeader(ctx context.Context, tr *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
	return ams.maintenanceServer.MoveLeader(ctx, tr)
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"time"

	"text/template"

	"github.com/SeerUK/tid/pkg/state"
	"github.com/SeerUK/tid/pkg/tracking"
	"github.com/eidolon/console"
	"github.com/eidolon/console/parameters"
	"github.com/olekukonko/tablewriter"
)

// ReportDateFmt is the date format for report date ranges.
const ReportDateFmt = "2006-01-02"

// reportOutputItem represents the formattable source of an item in the report command output.
type reportOutputItem struct {
	Entry   tracking.Entry
	Status  tracking.Status
	Running bool
}

// ReportCommand creates a command to view a timesheet report.
func ReportCommand(gateway tracking.Gateway) console.Command {
	var start time.Time
	var end time.Time
	var format string
	var noSummary bool

	configure := func(def *console.Definition) {
		def.AddOption(
			parameters.NewDateValue(&start),
			"-s, --start=START",
			"The start date of the report.",
		)

		def.AddOption(
			parameters.NewDateValue(&end),
			"-e, --end=END",
			"The end date of the report.",
		)

		def.AddOption(
			parameters.NewStringValue(&format),
			"-f, --format=FORMAT",
			"Format string, uses Go templates.",
		)

		def.AddOption(
			parameters.NewBoolValue(&noSummary),
			"--no-summary",
			"Hide the summary?",
		)
	}

	execute := func(input *console.Input, output *console.Output) error {
		hasStart := input.HasOption([]string{"s", "start"})
		hasEnd := input.HasOption([]string{"e", "end"})

		// We need to get the current date, this is a little hacky, but we need it without any time
		now, err := time.Parse(ReportDateFmt, time.Now().Format(ReportDateFmt))
		if err != nil {
			return err
		}

		if !hasStart {
			start = now
		}

		if !hasEnd {
			end = now
		}

		if start.After(end) {
			output.Println("report: The start date must be before the end date")
			return nil
		}

		keys := getDateRangeTimesheetKeys(start, end)
		sheets, err := getTimesheetsByKeys(gateway, keys)
		if err != nil {
			return err
		}

		status, err := gateway.FindOrCreateStatus()
		if err != nil {
			return err
		}

		var duration time.Duration
		var entries int

		err = forEachEntry(gateway, sheets, func(entry tracking.Entry) {
			if status.IsActive && status.Entry == entry.Hash {
				entry.UpdateDuration()
				gateway.PersistEntry(entry)
			}

			duration = duration + entry.Duration
			entries = entries + 1
		})

		if err != nil {
			return err
		}

		if entries == 0 {
			output.Println("report: No entries within the given time period")
			return nil
		}

		if !noSummary {
			if start.Equal(end) {
				format := "Report for %s.\n"
				output.Printf(format, end.Format(ReportDateFmt))
				output.Println()
			} else {
				format := "Report for %s to %s.\n"
				output.Printf(format, start.Format(ReportDateFmt), end.Format(ReportDateFmt))
				output.Println()
			}

			output.Printf("Total Duration: %s\n", duration)
			output.Printf("Entry Count: %d\n", entries)
			output.Println()
		}

		dateFormat := "3:04PM (2006-01-02)"

		if format != "" {
			// Write formatted output
			return forEachEntry(gateway, sheets, func(entry tracking.Entry) {
				out := reportOutputItem{}
				out.Entry = entry
				out.Status = status
				out.Running = status.IsActive && status.Entry == entry.Hash

				tmpl := template.Must(template.New("status").Parse(format))
				tmpl.Execute(output.Writer, out)

				// Always end with a new line...
				output.Println()
			})
		}

		// Write table
		table := tablewriter.NewWriter(output.Writer)
		table.SetAlignment(tablewriter.ALIGN_LEFT)

		table.SetHeader([]string{
			"Date",
			"Hash",
			"Created",
			"Updated",
			"Note",
			"Duration",
			"Running",
		})

		err = forEachEntry(gateway, sheets, func(entry tracking.Entry) {
			isRunning := status.IsActive && status.Entry == entry.Hash

			table.Append([]string{
				entry.Timesheet,
				entry.ShortHash(),
				entry.Created.Format(dateFormat),
				entry.Updated.Format(dateFormat),
				entry.Note,
				entry.Duration.String(),
				fmt.Sprintf("%t", isRunning),
			})
		})

		if err != nil {
			return err
		}

		table.SetAutoMergeCells(true)
		table.SetRowLine(true)
		table.Render()

		return nil
	}

	return console.Command{
		Name:        "report",
		Description: "Display a tabular timesheet report.",
		Configure:   configure,
		Execute:     execute,
	}
}

// forEachEntry runs the given function on each entry in each timesheet in the given array of
// timesheets. This uses the database.
func forEachEntry(gw tracking.Gateway, ss []tracking.Timesheet, fn func(tracking.Entry)) error {
	for _, sheet := range ss {
		for _, hash := range sheet.Entries {
			entry, err := gw.FindEntry(hash)
			if err != nil {
				return err
			}

			fn(entry)
		}
	}

	return nil
}

// getTimesheetsByKeys returns all of the timesheets that exist from an array of keys to try.
func getTimesheetsByKeys(gateway tracking.Gateway, keys []string) ([]tracking.Timesheet, error) {
	sheets := []tracking.Timesheet{}

	for _, key := range keys {
		sheet, err := gateway.FindTimesheet(key)
		if err != nil && err != state.ErrNilResult {
			return sheets, err
		}

		if err == state.ErrNilResult {
			continue
		}

		sheets = append(sheets, sheet)
	}

	return sheets, nil
}

// getDateRangeTimesheetKeys produces an array of keys to attempt to find timesheets within for a
// given start and end date range.
func getDateRangeTimesheetKeys(start time.Time, end time.Time) []string {
	keys := []string{}

	for current := start; !current.After(end); current = current.AddDate(0, 0, 1) {
		keys = append(keys, current.Format(tracking.KeyTimesheetDateFmt))
	}

	return keys
}
<commit_msg>Fixed CS issue.<commit_after>package cli

import (
	"fmt"
	"text/template"
	"time"

	"github.com/SeerUK/tid/pkg/state"
	"github.com/SeerUK/tid/pkg/tracking"
	"github.com/eidolon/console"
	"github.com/eidolon/console/parameters"
	"github.com/olekukonko/tablewriter"
)

// ReportDateFmt is the date format for report date ranges.
const ReportDateFmt = "2006-01-02"

// reportOutputItem represents the formattable source of an item in the report command output.
type reportOutputItem struct {
	Entry   tracking.Entry
	Status  tracking.Status
	Running bool
}

// ReportCommand creates a command to view a timesheet report.
func ReportCommand(gateway tracking.Gateway) console.Command {
	var start time.Time
	var end time.Time
	var format string
	var noSummary bool

	configure := func(def *console.Definition) {
		def.AddOption(
			parameters.NewDateValue(&start),
			"-s, --start=START",
			"The start date of the report.",
		)

		def.AddOption(
			parameters.NewDateValue(&end),
			"-e, --end=END",
			"The end date of the report.",
		)

		def.AddOption(
			parameters.NewStringValue(&format),
			"-f, --format=FORMAT",
			"Format string, uses Go templates.",
		)

		def.AddOption(
			parameters.NewBoolValue(&noSummary),
			"--no-summary",
			"Hide the summary?",
		)
	}

	execute := func(input *console.Input, output *console.Output) error {
		hasStart := input.HasOption([]string{"s", "start"})
		hasEnd := input.HasOption([]string{"e", "end"})

		// We need to get the current date, this is a little hacky, but we need it without any time
		now, err := time.Parse(ReportDateFmt, time.Now().Format(ReportDateFmt))
		if err != nil {
			return err
		}

		if !hasStart {
			start = now
		}

		if !hasEnd {
			end = now
		}

		if start.After(end) {
			output.Println("report: The start date must be before the end date")
			return nil
		}

		keys := getDateRangeTimesheetKeys(start, end)
		sheets, err := getTimesheetsByKeys(gateway, keys)
		if err != nil {
			return err
		}

		status, err := gateway.FindOrCreateStatus()
		if err != nil {
			return err
		}

		var duration time.Duration
		var entries int

		err = forEachEntry(gateway, sheets, func(entry tracking.Entry) {
			if status.IsActive && status.Entry == entry.Hash {
				entry.UpdateDuration()
				gateway.PersistEntry(entry)
			}

			duration = duration + entry.Duration
			entries = entries + 1
		})

		if err != nil {
			return err
		}

		if entries == 0 {
			output.Println("report: No entries within the given time period")
			return nil
		}

		if !noSummary {
			if start.Equal(end) {
				format := "Report for %s.\n"
				output.Printf(format, end.Format(ReportDateFmt))
				output.Println()
			} else {
				format := "Report for %s to %s.\n"
				output.Printf(format, start.Format(ReportDateFmt), end.Format(ReportDateFmt))
				output.Println()
			}

			output.Printf("Total Duration: %s\n", duration)
			output.Printf("Entry Count: %d\n", entries)
			output.Println()
		}

		dateFormat := "3:04PM (2006-01-02)"

		if format != "" {
			// Write formatted output
			return forEachEntry(gateway, sheets, func(entry tracking.Entry) {
				out := reportOutputItem{}
				out.Entry = entry
				out.Status = status
				out.Running = status.IsActive && status.Entry == entry.Hash

				tmpl := template.Must(template.New("status").Parse(format))
				tmpl.Execute(output.Writer, out)

				// Always end with a new line...
				output.Println()
			})
		}

		// Write table
		table := tablewriter.NewWriter(output.Writer)
		table.SetAlignment(tablewriter.ALIGN_LEFT)

		table.SetHeader([]string{
			"Date",
			"Hash",
			"Created",
			"Updated",
			"Note",
			"Duration",
			"Running",
		})

		err = forEachEntry(gateway, sheets, func(entry tracking.Entry) {
			isRunning := status.IsActive && status.Entry == entry.Hash

			table.Append([]string{
				entry.Timesheet,
				entry.ShortHash(),
				entry.Created.Format(dateFormat),
				entry.Updated.Format(dateFormat),
				entry.Note,
				entry.Duration.String(),
				fmt.Sprintf("%t", isRunning),
			})
		})

		if err != nil {
			return err
		}

		table.SetAutoMergeCells(true)
		table.SetRowLine(true)
		table.Render()

		return nil
	}

	return console.Command{
		Name:        "report",
		Description: "Display a tabular timesheet report.",
		Configure:   configure,
		Execute:     execute,
	}
}

// forEachEntry runs the given function on each entry in each timesheet in the given array of
// timesheets. This uses the database.
func forEachEntry(gw tracking.Gateway, ss []tracking.Timesheet, fn func(tracking.Entry)) error {
	for _, sheet := range ss {
		for _, hash := range sheet.Entries {
			entry, err := gw.FindEntry(hash)
			if err != nil {
				return err
			}

			fn(entry)
		}
	}

	return nil
}

// getTimesheetsByKeys returns all of the timesheets that exist from an array of keys to try.
func getTimesheetsByKeys(gateway tracking.Gateway, keys []string) ([]tracking.Timesheet, error) {
	sheets := []tracking.Timesheet{}

	for _, key := range keys {
		sheet, err := gateway.FindTimesheet(key)
		if err != nil && err != state.ErrNilResult {
			return sheets, err
		}

		if err == state.ErrNilResult {
			continue
		}

		sheets = append(sheets, sheet)
	}

	return sheets, nil
}

// getDateRangeTimesheetKeys produces an array of keys to attempt to find timesheets within for a
// given start and end date range.
func getDateRangeTimesheetKeys(start time.Time, end time.Time) []string {
	keys := []string{}

	for current := start; !current.After(end); current = current.AddDate(0, 0, 1) {
		keys = append(keys, current.Format(tracking.KeyTimesheetDateFmt))
	}

	return keys
}
<|endoftext|>


<commit_before>package daemon

import (
	"io"
	"syscall"
	"testing"

	"src.elv.sh/pkg/daemon/client"
	"src.elv.sh/pkg/daemon/daemondefs"
	"src.elv.sh/pkg/daemon/internal/api"
	. "src.elv.sh/pkg/prog/progtest"
	"src.elv.sh/pkg/store/storetest"
	"src.elv.sh/pkg/testutil"
)

func TestProgram_ServesClientRequests(t *testing.T) {
	testutil.Umask(t, 0)
	testutil.InTempDir(t)

	// Set up server.
	serverDone := make(chan struct{})
	go func() {
		exit, _, stderr := Run(Program, "elvish", "-daemon", "-sock", "sock", "-db", "db")
		if exit != 0 {
			t.Logf("daemon exited with %v; stderr:\n%v", exit, stderr)
		}
		close(serverDone)
	}()
	defer func() { <-serverDone }()

	// Set up client.
	client, err := client.Activate(io.Discard,
		&daemondefs.SpawnConfig{SockPath: "sock", DbPath: "db", RunDir: "."})
	if err != nil {
		t.Fatal("failed to activate client: ", err)
	}
	defer client.Close()

	// Test server state requests.
	gotVersion, err := client.Version()
	if gotVersion != api.Version || err != nil {
		t.Errorf(".Version() -> (%v, %v), want (%v, nil)", gotVersion, err, api.Version)
	}

	gotPid, err := client.Pid()
	wantPid := syscall.Getpid()
	if gotPid != wantPid || err != nil {
		t.Errorf(".Pid() -> (%v, %v), want (%v, nil)", gotPid, err, wantPid)
	}

	// Test store requests.
	storetest.TestCmd(t, client)
	storetest.TestDir(t, client)
	storetest.TestSharedVar(t, client)
}

func TestProgram_BadCLI(t *testing.T) {
	Test(t, Program,
		ThatElvish().
			ExitsWith(2).
			WritesStderr("internal error: no suitable subprogram\n"),

		ThatElvish("-daemon", "x").
			ExitsWith(2).
			WritesStderrContaining("arguments are not allowed with -daemon"),
	)
}
<commit_msg>pkg/daemon: Terminate test correctly if server did not come up.<commit_after>package daemon

import (
	"io"
	"syscall"
	"testing"

	"src.elv.sh/pkg/daemon/client"
	"src.elv.sh/pkg/daemon/daemondefs"
	"src.elv.sh/pkg/daemon/internal/api"
	. "src.elv.sh/pkg/prog/progtest"
	"src.elv.sh/pkg/store/storetest"
	"src.elv.sh/pkg/testutil"
)

func TestProgram_ServesClientRequests(t *testing.T) {
	testutil.Umask(t, 0)
	testutil.InTempDir(t)

	// Set up server.
	serverDone := make(chan struct{})
	go func() {
		exit, _, stderr := Run(Program, "elvish", "-daemon", "-sock", "sock", "-db", "db")
		if exit != 0 {
			t.Logf("daemon exited with %v; stderr:\n%v", exit, stderr)
		}
		close(serverDone)
	}()
	defer func() { <-serverDone }()

	// Set up client.
	client, err := client.Activate(io.Discard,
		&daemondefs.SpawnConfig{SockPath: "sock", DbPath: "db", RunDir: "."})
	if err != nil {
		close(serverDone)
		t.Fatal("failed to activate client: ", err)
	}
	defer client.Close()

	// Test server state requests.
	gotVersion, err := client.Version()
	if gotVersion != api.Version || err != nil {
		t.Errorf(".Version() -> (%v, %v), want (%v, nil)", gotVersion, err, api.Version)
	}

	gotPid, err := client.Pid()
	wantPid := syscall.Getpid()
	if gotPid != wantPid || err != nil {
		t.Errorf(".Pid() -> (%v, %v), want (%v, nil)", gotPid, err, wantPid)
	}

	// Test store requests.
	storetest.TestCmd(t, client)
	storetest.TestDir(t, client)
	storetest.TestSharedVar(t, client)
}

func TestProgram_BadCLI(t *testing.T) {
	Test(t, Program,
		ThatElvish().
			ExitsWith(2).
			WritesStderr("internal error: no suitable subprogram\n"),

		ThatElvish("-daemon", "x").
			ExitsWith(2).
			WritesStderrContaining("arguments are not allowed with -daemon"),
	)
}
<|endoftext|>


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

package kube

import (
	"bytes"
	"context"
	"fmt"
	"net"
	"net/http"
	"os"

	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/portforward"
	"k8s.io/client-go/transport/spdy"
)

// PortForwarder manages the forwarding of a single port.
type PortForwarder interface {
	// Run this forwarder.
	Start() error

	// Address returns the local forwarded address. Only valid while the forwarder is running.
	Address() string

	// Close this forwarder and release an resources.
	Close()

	// Block until connection closed (e.g. control-C interrupt)
	WaitForStop()
}

var _ PortForwarder = &forwarder{}

type forwarder struct {
	forwarder *portforward.PortForwarder
	stopCh    chan struct{}
	readyCh   <-chan struct{}
	address   string
	output    *bytes.Buffer
}

func (f *forwarder) Start() error {
	errCh := make(chan error, 1)
	go func() {
		errCh <- f.forwarder.ForwardPorts()
	}()

	select {
	case err := <-errCh:
		return fmt.Errorf("failure running port forward process: %v", err)
	case <-f.readyCh:
		// The forwarder is now ready.
		return nil
	}
}

func (f *forwarder) Address() string {
	return f.address
}

func (f *forwarder) Close() {
	close(f.stopCh)
	// Closing the stop channel should close anything
	// opened by f.forwarder.ForwardPorts()
}

func (f *forwarder) WaitForStop() {
	<-f.stopCh
}

func newPortForwarder(restConfig *rest.Config, podName, ns, localAddress string, localPort, podPort int) (PortForwarder, error) {
	restClient, err := rest.RESTClientFor(restConfig)
	if err != nil {
		return nil, err
	}

	req := restClient.Post().Resource("pods").Namespace(ns).Name(podName).SubResource("portforward")
	serverURL := req.URL()

	roundTripper, upgrader, err := roundTripperFor(restConfig)
	if err != nil {
		return nil, fmt.Errorf("failure creating roundtripper: %v", err)
	}

	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, serverURL)

	stopCh := make(chan struct{})
	readyCh := make(chan struct{})
	output := new(bytes.Buffer)
	if localAddress == "" {
		localAddress = defaultLocalAddress
	}
	if localPort == 0 {
		localPort, err = availablePort(localAddress)
		if err != nil {
			return nil, fmt.Errorf("failure allocating port: %v", err)
		}
	}
	fw, err := portforward.NewOnAddresses(dialer,
		[]string{localAddress},
		[]string{fmt.Sprintf("%d:%d", localPort, podPort)},
		stopCh,
		readyCh,
		output,
		os.Stderr)
	if err != nil {
		return nil, fmt.Errorf("failed establishing port-forward: %v", err)
	}

	// Run the same check as k8s.io/kubectl/pkg/cmd/portforward/portforward.go
	// so that we will fail early if there is a problem contacting API server.
	podGet := restClient.Get().Resource("pods").Namespace(ns).Name(podName)
	obj, err := podGet.Do(context.TODO()).Get()
	if err != nil {
		return nil, fmt.Errorf("failed retrieving pod: %v", err)
	}
	pod, ok := obj.(*v1.Pod)
	if !ok {
		return nil, fmt.Errorf("failed getting pod: %v", err)
	}
	if pod.Status.Phase != v1.PodRunning {
		return nil, fmt.Errorf("pod is not running. Status=%v", pod.Status.Phase)
	}

	return &forwarder{
		forwarder: fw,
		stopCh:    stopCh,
		readyCh:   readyCh,
		output:    output,
		address:   fmt.Sprintf("%s:%d", defaultLocalAddress, localPort),
	}, nil
}

func availablePort(localAddr string) (int, error) {
	addr, err := net.ResolveTCPAddr("tcp", localAddr+":0")
	if err != nil {
		return 0, err
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return 0, err
	}
	port := l.Addr().(*net.TCPAddr).Port
	return port, l.Close()
}
<commit_msg>Fix dash command bind local addres always localhost (#30263)<commit_after>// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kube

import (
	"bytes"
	"context"
	"fmt"
	"net"
	"net/http"
	"os"

	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/portforward"
	"k8s.io/client-go/transport/spdy"
)

// PortForwarder manages the forwarding of a single port.
type PortForwarder interface {
	// Run this forwarder.
	Start() error

	// Address returns the local forwarded address. Only valid while the forwarder is running.
	Address() string

	// Close this forwarder and release an resources.
	Close()

	// Block until connection closed (e.g. control-C interrupt)
	WaitForStop()
}

var _ PortForwarder = &forwarder{}

type forwarder struct {
	forwarder *portforward.PortForwarder
	stopCh    chan struct{}
	readyCh   <-chan struct{}
	address   string
	output    *bytes.Buffer
}

func (f *forwarder) Start() error {
	errCh := make(chan error, 1)
	go func() {
		errCh <- f.forwarder.ForwardPorts()
	}()

	select {
	case err := <-errCh:
		return fmt.Errorf("failure running port forward process: %v", err)
	case <-f.readyCh:
		// The forwarder is now ready.
		return nil
	}
}

func (f *forwarder) Address() string {
	return f.address
}

func (f *forwarder) Close() {
	close(f.stopCh)
	// Closing the stop channel should close anything
	// opened by f.forwarder.ForwardPorts()
}

func (f *forwarder) WaitForStop() {
	<-f.stopCh
}

func newPortForwarder(restConfig *rest.Config, podName, ns, localAddress string, localPort, podPort int) (PortForwarder, error) {
	restClient, err := rest.RESTClientFor(restConfig)
	if err != nil {
		return nil, err
	}

	req := restClient.Post().Resource("pods").Namespace(ns).Name(podName).SubResource("portforward")
	serverURL := req.URL()

	roundTripper, upgrader, err := roundTripperFor(restConfig)
	if err != nil {
		return nil, fmt.Errorf("failure creating roundtripper: %v", err)
	}

	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, serverURL)

	stopCh := make(chan struct{})
	readyCh := make(chan struct{})
	output := new(bytes.Buffer)
	if localAddress == "" {
		localAddress = defaultLocalAddress
	}
	if localPort == 0 {
		localPort, err = availablePort(localAddress)
		if err != nil {
			return nil, fmt.Errorf("failure allocating port: %v", err)
		}
	}
	fw, err := portforward.NewOnAddresses(dialer,
		[]string{localAddress},
		[]string{fmt.Sprintf("%d:%d", localPort, podPort)},
		stopCh,
		readyCh,
		output,
		os.Stderr)
	if err != nil {
		return nil, fmt.Errorf("failed establishing port-forward: %v", err)
	}

	// Run the same check as k8s.io/kubectl/pkg/cmd/portforward/portforward.go
	// so that we will fail early if there is a problem contacting API server.
	podGet := restClient.Get().Resource("pods").Namespace(ns).Name(podName)
	obj, err := podGet.Do(context.TODO()).Get()
	if err != nil {
		return nil, fmt.Errorf("failed retrieving pod: %v", err)
	}
	pod, ok := obj.(*v1.Pod)
	if !ok {
		return nil, fmt.Errorf("failed getting pod: %v", err)
	}
	if pod.Status.Phase != v1.PodRunning {
		return nil, fmt.Errorf("pod is not running. Status=%v", pod.Status.Phase)
	}

	return &forwarder{
		forwarder: fw,
		stopCh:    stopCh,
		readyCh:   readyCh,
		output:    output,
		address:   fmt.Sprintf("%s:%d", localAddress, localPort),
	}, nil
}

func availablePort(localAddr string) (int, error) {
	addr, err := net.ResolveTCPAddr("tcp", localAddr+":0")
	if err != nil {
		return 0, err
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return 0, err
	}
	port := l.Addr().(*net.TCPAddr).Port
	return port, l.Close()
}
<|endoftext|>


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

package metrics

import (
	"github.com/prometheus/client_golang/prometheus"
)

type CounterVec interface {
	WithLabelValues(lvls ...string) prometheus.Counter
	GetMetricWithLabelValues(lvs ...string) (prometheus.Counter, error)
	With(labels prometheus.Labels) prometheus.Counter
	prometheus.Collector
}

type GaugeVec interface {
	WithLabelValues(lvls ...string) prometheus.Gauge
	prometheus.Collector
}
<commit_msg>pkg/metrics: add no-op implementations for disabled metrics<commit_after>// Copyright 2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metrics

import (
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
)

type CounterVec interface {
	WithLabelValues(lvls ...string) prometheus.Counter
	GetMetricWithLabelValues(lvs ...string) (prometheus.Counter, error)
	With(labels prometheus.Labels) prometheus.Counter
	prometheus.Collector
}

type GaugeVec interface {
	WithLabelValues(lvls ...string) prometheus.Gauge
	prometheus.Collector
}

var (
	NoOpMetric    prometheus.Metric    = &metric{}
	NoOpCollector prometheus.Collector = &collector{}

	NoOpCounter     prometheus.Counter     = &counter{NoOpMetric, NoOpCollector}
	NoOpCounterVec  CounterVec             = &counterVec{NoOpCollector}
	NoOpObserver    prometheus.Observer    = &observer{}
	NoOpObserverVec prometheus.ObserverVec = &observerVec{NoOpCollector}
	NoOpGauge       prometheus.Gauge       = &gauge{NoOpMetric, NoOpCollector}
	NoOpGaugeVec    GaugeVec               = &gaugeVec{NoOpCollector}
)

// Metric

type metric struct{}

// *WARNING*: Desc returns nil so do not register this metric into prometheus
// default register.
func (m *metric) Desc() *prometheus.Desc  { return nil }
func (m *metric) Write(*dto.Metric) error { return nil }

// Collector

type collector struct{}

func (c *collector) Describe(chan<- *prometheus.Desc) {}
func (c *collector) Collect(chan<- prometheus.Metric) {}

// Counter

type counter struct {
	prometheus.Metric
	prometheus.Collector
}

func (cv *counter) Add(float64) {}
func (cv *counter) Inc()        {}

// CounterVec

type counterVec struct{ prometheus.Collector }

func (cv *counterVec) WithLabelValues(lvls ...string) prometheus.Counter { return NoOpCounter }

func (cv *counterVec) GetMetricWithLabelValues(lvs ...string) (prometheus.Counter, error) {
	return NoOpCounter, nil
}

func (cv *counterVec) With(labels prometheus.Labels) prometheus.Counter { return NoOpCounter }

// Observer

type observer struct{}

func (o *observer) Observe(float64) {}

// ObserverVec

type observerVec struct {
	prometheus.Collector
}

func (ov *observerVec) GetMetricWith(prometheus.Labels) (prometheus.Observer, error) {
	return NoOpObserver, nil
}
func (ov *observerVec) GetMetricWithLabelValues(lvs ...string) (prometheus.Observer, error) {
	return NoOpObserver, nil
}

func (ov *observerVec) With(prometheus.Labels) prometheus.Observer    { return NoOpObserver }
func (ov *observerVec) WithLabelValues(...string) prometheus.Observer { return NoOpObserver }

func (ov *observerVec) CurryWith(prometheus.Labels) (prometheus.ObserverVec, error) {
	return NoOpObserverVec, nil
}
func (ov *observerVec) MustCurryWith(prometheus.Labels) prometheus.ObserverVec {
	return NoOpObserverVec
}

// Gauge

type gauge struct {
	prometheus.Metric
	prometheus.Collector
}

func (g *gauge) Set(float64)       {}
func (g *gauge) Inc()              {}
func (g *gauge) Dec()              {}
func (g *gauge) Add(float64)       {}
func (g *gauge) Sub(float64)       {}
func (g *gauge) SetToCurrentTime() {}

// GaugeVec

type gaugeVec struct {
	prometheus.Collector
}

func (gv *gaugeVec) WithLabelValues(lvls ...string) prometheus.Gauge {
	return NoOpGauge
}
<|endoftext|>


<commit_before>package provisioning

import (
	log "github.com/Sirupsen/logrus"
	"os/exec"
	"github.com/intelsdi-x/swan/pkg/isolation"
	"time"
	"syscall"
	"bytes"
)

// LocalTask implements Task interface.
type LocalTask struct{
	pid isolation.TaskPID
	statusCh chan Status
	status Status
	terminated bool
}

// NewLocalTask returns a LocalTask instance.
func NewLocalTask(pid isolation.TaskPID, statusCh chan Status) *LocalTask {
	t := &LocalTask{
		pid,
		statusCh,
		Status{},
		false,
	}
	return t
}

func (task *LocalTask) completeTask(status Status) {
	task.terminated = true
	task.status = status
	task.statusCh = nil
}

// Stop terminates the local task.
func (task *LocalTask) Stop() {
	if (task.terminated) {
		panic("Task is not running.")
	}

	log.Debug("Sending SIGTERM to PID ", -task.pid)
	err := syscall.Kill(-int(task.pid), syscall.SIGTERM)
	if (err != nil) {
		panic(err)
	}

	s := <-task.statusCh
	task.completeTask(s)
}

// Status gets status of the local task.
func (task LocalTask) Status() Status {
	if (!task.terminated) {
		return Status{code: RunningCode}
	}

	return task.status
}

// Wait blocks until process is terminated or timeout appeared.
// Returns true after timeout exceeds.
func (task *LocalTask) Wait(timeoutMs int) bool {
	if (task.terminated) {
		return false
	}

	if (timeoutMs == 0) {
		s := <-task.statusCh
		task.completeTask(s)

	} else {
		timeoutDuration := time.Duration(timeoutMs) * time.Millisecond

		select {
		case s := <-task.statusCh:
			task.completeTask(s)
		case <-time.After(timeoutDuration):
			return true
		}
	}

	return false
}

// Local provisioning is responsible for providing the execution environment
// on local machine via exec.Command. It also needed to setup given isolation
// using Isolation Manager.
// It runs command as current user.
type Local struct{
	isolations []isolation.ProcessIsolation

	// It is important to set additional PGID for parent process and his children
	// to have ability to kill all the children processes.
	setPGID bool
}

// NewLocal returns a Local instance.
func NewLocal(isolations []isolation.ProcessIsolation) Local {
	l := Local{
		isolations: isolations,
		setPGID: true,
	}
	return l
}

// Run runs the command given as input.
// Returned Task pointer is able to stop & monitor the provisioned process.
func (l Local) Run(command string) (Task) {
	statusCh := make(chan Status)

	taskPidCh := make(chan isolation.TaskPID)

	// Run task in local locally.
	go func() {
		log.Debug("Starting ", command)

		cmd := exec.Command("sh", "-c", command)

		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: l.setPGID}

		// Setting Buffer as io.Writer for Command output.
		var stdout bytes.Buffer
		var stderr bytes.Buffer
		cmd.Stdout = &stdout
		cmd.Stderr = &stderr

		err := cmd.Start()

		if (err != nil) {
			panic(err)
		}

		log.Debug("Started with pid ", cmd.Process.Pid)

		// Report the process id.
		taskPidCh <- isolation.TaskPID(cmd.Process.Pid)

		// Wait for task completion.
		cmd.Wait()

		log.Debug(
		  "Ended ", command,
		  " with output: ", stdout.String(),
		  " with err output: ", stderr.String(),
		  " with status code: ",
		  (cmd.ProcessState.Sys().(syscall.WaitStatus)).ExitStatus())

		statusCh <- Status{
			(cmd.ProcessState.Sys().(syscall.WaitStatus)).ExitStatus(),
			stdout.String(),
			stderr.String(),
		}
	}()

	// Get PID.
	taskPid := <-taskPidCh

	// Perform rest of the isolation synchronously.
	for _, isolation := range l.isolations {
		isolation.Isolate(taskPid)
	}

	t := NewLocalTask(taskPid, statusCh)

	return t
}
<commit_msg>Refactored run function to use go routine only in the last step.<commit_after>package provisioning

import (
	log "github.com/Sirupsen/logrus"
	"os/exec"
	"github.com/intelsdi-x/swan/pkg/isolation"
	"time"
	"syscall"
	"bytes"
)

// LocalTask implements Task interface.
type LocalTask struct{
	pid isolation.TaskPID
	statusCh chan Status
	status Status
	terminated bool
}

// NewLocalTask returns a LocalTask instance.
func NewLocalTask(pid isolation.TaskPID, statusCh chan Status) *LocalTask {
	t := &LocalTask{
		pid,
		statusCh,
		Status{},
		false,
	}
	return t
}

func (task *LocalTask) completeTask(status Status) {
	task.terminated = true
	task.status = status
	task.statusCh = nil
}

// Stop terminates the local task.
func (task *LocalTask) Stop() {
	if (task.terminated) {
		panic("Task is not running.")
	}

	log.Debug("Sending SIGTERM to PID ", -task.pid)
	err := syscall.Kill(-int(task.pid), syscall.SIGTERM)
	if (err != nil) {
		panic(err)
	}

	s := <-task.statusCh
	task.completeTask(s)
}

// Status gets status of the local task.
func (task LocalTask) Status() Status {
	if (!task.terminated) {
		return Status{code: RunningCode}
	}

	return task.status
}

// Wait blocks until process is terminated or timeout appeared.
// Returns true after timeout exceeds.
func (task *LocalTask) Wait(timeoutMs int) bool {
	if (task.terminated) {
		return false
	}

	if (timeoutMs == 0) {
		s := <-task.statusCh
		task.completeTask(s)

	} else {
		timeoutDuration := time.Duration(timeoutMs) * time.Millisecond

		select {
		case s := <-task.statusCh:
			task.completeTask(s)
		case <-time.After(timeoutDuration):
			return true
		}
	}

	return false
}

// Local provisioning is responsible for providing the execution environment
// on local machine via exec.Command. It also needed to setup given isolation
// using Isolation Manager.
// It runs command as current user.
type Local struct{
	isolations []isolation.ProcessIsolation

	// It is important to set additional PGID for parent process and his children
	// to have ability to kill all the children processes.
	setPGID bool
}

// NewLocal returns a Local instance.
func NewLocal(isolations []isolation.ProcessIsolation) Local {
	l := Local{
		isolations: isolations,
		setPGID: true,
	}
	return l
}

// Run runs the command given as input.
// Returned Task pointer is able to stop & monitor the provisioned process.
func (l Local) Run(command string) (Task) {
	statusCh := make(chan Status)

	log.Debug("Starting ", command)

	cmd := exec.Command("sh", "-c", command)

	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: l.setPGID}

	// Setting Buffer as io.Writer for Command output.
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Start()

	if (err != nil) {
		panic(err)
	}

	log.Debug("Started with pid ", cmd.Process.Pid)

	// Wait for local task in goroutine.
	go func() {
		// Wait for task completion.
		cmd.Wait()

		log.Debug(
		  "Ended ", command,
		  " with output: ", stdout.String(),
		  " with err output: ", stderr.String(),
		  " with status code: ",
		  (cmd.ProcessState.Sys().(syscall.WaitStatus)).ExitStatus())

		statusCh <- Status{
			(cmd.ProcessState.Sys().(syscall.WaitStatus)).ExitStatus(),
			stdout.String(),
			stderr.String(),
		}
	}()

	taskPid := isolation.TaskPID(cmd.Process.Pid)

	// Perform rest of the isolation synchronously.
	for _, isolation := range l.isolations {
		isolation.Isolate(taskPid)
	}

	t := NewLocalTask(taskPid, statusCh)

	return t
}
<|endoftext|>


<commit_before>/*
Copyright 2012 The Camlistore Authors.

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

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

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

// Package sqlkv implements the sorted.KeyValue interface using an *sql.DB.
package sqlkv

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"regexp"
	"strings"
	"sync"

	"camlistore.org/pkg/leak"
	"camlistore.org/pkg/sorted"
)

// KeyValue implements the sorted.KeyValue interface using an *sql.DB.
type KeyValue struct {
	DB *sql.DB

	// SetFunc is an optional func to use when REPLACE INTO does not exist
	SetFunc      func(*sql.DB, string, string) error
	BatchSetFunc func(*sql.Tx, string, string) error

	// PlaceHolderFunc optionally replaces ? placeholders with the right ones for the rdbms
	// in use
	PlaceHolderFunc func(string) string

	// Serial determines whether a Go-level mutex protects DB from
	// concurrent access.  This isn't perfect and exists just for
	// SQLite, whose driver likes to return "the database is
	// locked" (camlistore.org/issue/114), so this keeps some
	// pressure off. But we still trust SQLite to deal with
	// concurrency in most cases.
	Serial bool

	// TablePrefix optionally provides a prefix for SQL table
	// names. This is typically "dbname.", ending in a period.
	TablePrefix string

	mu sync.Mutex // the mutex used, if Serial is set
}

func (kv *KeyValue) sql(v string) string {
	// TODO(bradfitz): all this string manipulation is redundant at runtime.
	// We should do it once at the beginning and keep the strings around.
	if f := kv.PlaceHolderFunc; f != nil {
		v = f(v)
	}
	return strings.Replace(v, "/*TPRE*/", kv.TablePrefix, -1)
}

type batchTx struct {
	tx  *sql.Tx
	err error // sticky

	// SetFunc is an optional func to use when REPLACE INTO does not exist
	SetFunc func(*sql.Tx, string, string) error

	// PlaceHolderFunc optionally replaces ? placeholders with the right ones for the rdbms
	// in use
	PlaceHolderFunc func(string) string
}

func (b *batchTx) sql(v string) string {
	if f := b.PlaceHolderFunc; f != nil {
		return f(v)
	}
	return v
}

func (b *batchTx) Set(key, value string) {
	if b.err != nil {
		return
	}
	if b.SetFunc != nil {
		b.err = b.SetFunc(b.tx, key, value)
		return
	}
	_, b.err = b.tx.Exec(b.sql("REPLACE INTO /*TPRE*/rows (k, v) VALUES (?, ?)"), key, value)
}

func (b *batchTx) Delete(key string) {
	if b.err != nil {
		return
	}
	_, b.err = b.tx.Exec(b.sql("DELETE FROM /*TPRE*/rows WHERE k=?"), key)
}

func (kv *KeyValue) BeginBatch() sorted.BatchMutation {
	if kv.Serial {
		kv.mu.Lock()
	}
	tx, err := kv.DB.Begin()
	return &batchTx{
		tx:              tx,
		err:             err,
		SetFunc:         kv.BatchSetFunc,
		PlaceHolderFunc: kv.PlaceHolderFunc,
	}
}

func (kv *KeyValue) CommitBatch(b sorted.BatchMutation) error {
	if kv.Serial {
		defer kv.mu.Unlock()
	}
	bt, ok := b.(*batchTx)
	if !ok {
		return fmt.Errorf("wrong BatchMutation type %T", b)
	}
	if bt.err != nil {
		return bt.err
	}
	return bt.tx.Commit()
}

func (kv *KeyValue) Get(key string) (value string, err error) {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	err = kv.DB.QueryRow(kv.sql("SELECT v FROM /*TPRE*/rows WHERE k=?"), key).Scan(&value)
	if err == sql.ErrNoRows {
		err = sorted.ErrNotFound
	}
	return
}

func (kv *KeyValue) Set(key, value string) error {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	if kv.SetFunc != nil {
		return kv.SetFunc(kv.DB, key, value)
	}
	_, err := kv.DB.Exec(kv.sql("REPLACE INTO /*TPRE*/rows (k, v) VALUES (?, ?)"), key, value)
	return err
}

func (kv *KeyValue) Delete(key string) error {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	_, err := kv.DB.Exec(kv.sql("DELETE FROM /*TPRE*/rows WHERE k=?"), key)
	return err
}

func (kv *KeyValue) Wipe() error {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	_, err := kv.DB.Exec(kv.sql("DELETE FROM /*TPRE*/rows"))
	return err
}

func (kv *KeyValue) Close() error { return kv.DB.Close() }

func (kv *KeyValue) Find(start, end string) sorted.Iterator {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	var rows *sql.Rows
	var err error
	if end == "" {
		rows, err = kv.DB.Query(kv.sql("SELECT k, v FROM /*TPRE*/rows WHERE k >= ? ORDER BY k "), start)
	} else {
		rows, err = kv.DB.Query(kv.sql("SELECT k, v FROM /*TPRE*/rows WHERE k >= ? AND k < ? ORDER BY k "), start, end)
	}
	if err != nil {
		log.Printf("unexpected query error: %v", err)
		return &iter{err: err}
	}

	it := &iter{
		kv:         kv,
		rows:       rows,
		closeCheck: leak.NewChecker(),
	}
	return it
}

var wordThenPunct = regexp.MustCompile(`^\w+\W$`)

// iter is a iterator over sorted key/value pairs in rows.
type iter struct {
	kv  *KeyValue
	end string // optional end bound
	err error  // accumulated error, returned at Close

	closeCheck *leak.Checker

	rows *sql.Rows // if non-nil, the rows we're reading from

	key        sql.RawBytes
	val        sql.RawBytes
	skey, sval *string // if non-nil, it's been stringified
}

var errClosed = errors.New("sqlkv: Iterator already closed")

func (t *iter) KeyBytes() []byte { return t.key }
func (t *iter) Key() string {
	if t.skey != nil {
		return *t.skey
	}
	str := string(t.key)
	t.skey = &str
	return str
}

func (t *iter) ValueBytes() []byte { return t.val }
func (t *iter) Value() string {
	if t.sval != nil {
		return *t.sval
	}
	str := string(t.val)
	t.sval = &str
	return str
}

func (t *iter) Close() error {
	t.closeCheck.Close()
	if t.rows != nil {
		t.rows.Close()
		t.rows = nil
	}
	err := t.err
	t.err = errClosed
	return err
}

func (t *iter) Next() bool {
	if t.err != nil {
		return false
	}
	t.skey, t.sval = nil, nil
	if !t.rows.Next() {
		return false
	}
	t.err = t.rows.Scan(&t.key, &t.val)
	if t.err != nil {
		log.Printf("unexpected Scan error: %v", t.err)
		return false
	}
	return true
}
<commit_msg>sqlkv: remove one of the sql expansion funcs. make batch use KeyValue's<commit_after>/*
Copyright 2012 The Camlistore Authors.

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

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

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

// Package sqlkv implements the sorted.KeyValue interface using an *sql.DB.
package sqlkv

import (
	"database/sql"
	"errors"
	"fmt"
	"log"
	"regexp"
	"strings"
	"sync"

	"camlistore.org/pkg/leak"
	"camlistore.org/pkg/sorted"
)

// KeyValue implements the sorted.KeyValue interface using an *sql.DB.
type KeyValue struct {
	DB *sql.DB

	// SetFunc is an optional func to use when REPLACE INTO does not exist
	SetFunc      func(*sql.DB, string, string) error
	BatchSetFunc func(*sql.Tx, string, string) error

	// PlaceHolderFunc optionally replaces ? placeholders with the right ones for the rdbms
	// in use
	PlaceHolderFunc func(string) string

	// Serial determines whether a Go-level mutex protects DB from
	// concurrent access.  This isn't perfect and exists just for
	// SQLite, whose driver likes to return "the database is
	// locked" (camlistore.org/issue/114), so this keeps some
	// pressure off. But we still trust SQLite to deal with
	// concurrency in most cases.
	Serial bool

	// TablePrefix optionally provides a prefix for SQL table
	// names. This is typically "dbname.", ending in a period.
	TablePrefix string

	mu sync.Mutex // the mutex used, if Serial is set
}

func (kv *KeyValue) sql(v string) string {
	// TODO(bradfitz): all this string manipulation is redundant at runtime.
	// We should do it once at the beginning and keep the strings around.
	if f := kv.PlaceHolderFunc; f != nil {
		v = f(v)
	}
	return strings.Replace(v, "/*TPRE*/", kv.TablePrefix, -1)
}

type batchTx struct {
	tx  *sql.Tx
	err error // sticky
	kv  *KeyValue
}

func (b *batchTx) Set(key, value string) {
	if b.err != nil {
		return
	}
	if b.kv.BatchSetFunc != nil {
		b.err = b.kv.BatchSetFunc(b.tx, key, value)
		return
	}
	_, b.err = b.tx.Exec(b.kv.sql("REPLACE INTO /*TPRE*/rows (k, v) VALUES (?, ?)"), key, value)
}

func (b *batchTx) Delete(key string) {
	if b.err != nil {
		return
	}
	_, b.err = b.tx.Exec(b.kv.sql("DELETE FROM /*TPRE*/rows WHERE k=?"), key)
}

func (kv *KeyValue) BeginBatch() sorted.BatchMutation {
	if kv.Serial {
		kv.mu.Lock()
	}
	tx, err := kv.DB.Begin()
	if err != nil {
		log.Printf("SQL BEGIN BATCH: %v", err)
	}
	return &batchTx{
		tx:  tx,
		err: err,
		kv:  kv,
	}
}

func (kv *KeyValue) CommitBatch(b sorted.BatchMutation) error {
	if kv.Serial {
		defer kv.mu.Unlock()
	}
	bt, ok := b.(*batchTx)
	if !ok {
		return fmt.Errorf("wrong BatchMutation type %T", b)
	}
	if bt.err != nil {
		return bt.err
	}
	return bt.tx.Commit()
}

func (kv *KeyValue) Get(key string) (value string, err error) {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	err = kv.DB.QueryRow(kv.sql("SELECT v FROM /*TPRE*/rows WHERE k=?"), key).Scan(&value)
	if err == sql.ErrNoRows {
		err = sorted.ErrNotFound
	}
	return
}

func (kv *KeyValue) Set(key, value string) error {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	if kv.SetFunc != nil {
		return kv.SetFunc(kv.DB, key, value)
	}
	_, err := kv.DB.Exec(kv.sql("REPLACE INTO /*TPRE*/rows (k, v) VALUES (?, ?)"), key, value)
	return err
}

func (kv *KeyValue) Delete(key string) error {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	_, err := kv.DB.Exec(kv.sql("DELETE FROM /*TPRE*/rows WHERE k=?"), key)
	return err
}

func (kv *KeyValue) Wipe() error {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	_, err := kv.DB.Exec(kv.sql("DELETE FROM /*TPRE*/rows"))
	return err
}

func (kv *KeyValue) Close() error { return kv.DB.Close() }

func (kv *KeyValue) Find(start, end string) sorted.Iterator {
	if kv.Serial {
		kv.mu.Lock()
		defer kv.mu.Unlock()
	}
	var rows *sql.Rows
	var err error
	if end == "" {
		rows, err = kv.DB.Query(kv.sql("SELECT k, v FROM /*TPRE*/rows WHERE k >= ? ORDER BY k "), start)
	} else {
		rows, err = kv.DB.Query(kv.sql("SELECT k, v FROM /*TPRE*/rows WHERE k >= ? AND k < ? ORDER BY k "), start, end)
	}
	if err != nil {
		log.Printf("unexpected query error: %v", err)
		return &iter{err: err}
	}

	it := &iter{
		kv:         kv,
		rows:       rows,
		closeCheck: leak.NewChecker(),
	}
	return it
}

var wordThenPunct = regexp.MustCompile(`^\w+\W$`)

// iter is a iterator over sorted key/value pairs in rows.
type iter struct {
	kv  *KeyValue
	end string // optional end bound
	err error  // accumulated error, returned at Close

	closeCheck *leak.Checker

	rows *sql.Rows // if non-nil, the rows we're reading from

	key        sql.RawBytes
	val        sql.RawBytes
	skey, sval *string // if non-nil, it's been stringified
}

var errClosed = errors.New("sqlkv: Iterator already closed")

func (t *iter) KeyBytes() []byte { return t.key }
func (t *iter) Key() string {
	if t.skey != nil {
		return *t.skey
	}
	str := string(t.key)
	t.skey = &str
	return str
}

func (t *iter) ValueBytes() []byte { return t.val }
func (t *iter) Value() string {
	if t.sval != nil {
		return *t.sval
	}
	str := string(t.val)
	t.sval = &str
	return str
}

func (t *iter) Close() error {
	t.closeCheck.Close()
	if t.rows != nil {
		t.rows.Close()
		t.rows = nil
	}
	err := t.err
	t.err = errClosed
	return err
}

func (t *iter) Next() bool {
	if t.err != nil {
		return false
	}
	t.skey, t.sval = nil, nil
	if !t.rows.Next() {
		return false
	}
	t.err = t.rows.Scan(&t.key, &t.val)
	if t.err != nil {
		log.Printf("unexpected Scan error: %v", t.err)
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package vfs

import (
	"encoding/hex"
	"encoding/json"
	"sync"
	"time"

	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/go-redis/redis"
)

// A DownloadStore is essentially an object to store Archives & Files by keys
type DownloadStore interface {
	AddFile(domain, filePath string) (string, error)
	AddArchive(domain string, archive *Archive) (string, error)
	GetFile(domain, key string) (string, error)
	GetArchive(domain, key string) (*Archive, error)
}

// downloadStoreTTL is the time an Archive stay alive
const downloadStoreTTL = 1 * time.Hour

// downloadStoreCleanInterval is the time interval between each download
// cleanup.
const downloadStoreCleanInterval = 1 * time.Hour

var globalStoreMu sync.Mutex
var globalMemStore *memStore
var globalRedisStore *redisStore

type memRef struct {
	val interface{}
	exp time.Time
}

func storeCleaner() {
	for range time.Tick(downloadStoreCleanInterval) {
		globalMemStore.clean()
	}
}

// GetStore returns the DownloadStore.
func GetStore() DownloadStore {
	globalStoreMu.Lock()
	defer globalStoreMu.Unlock()
	if globalRedisStore != nil {
		return globalRedisStore
	}
	if globalMemStore != nil {
		return globalMemStore
	}
	opts := config.CacheOptions()
	if opts == nil {
		globalMemStore = &memStore{vals: make(map[string]*memRef)}
		go storeCleaner()
		return globalMemStore
	}
	redisClient := redis.NewClient(opts)
	return &redisStore{redisClient}
}

type memStore struct {
	mu   sync.Mutex
	vals map[string]*memRef
}

func (s *memStore) clean() {
	now := time.Now()
	for k, v := range s.vals {
		if now.After(v.exp) {
			delete(s.vals, k)
		}
	}
}

func (s *memStore) AddFile(domain, filePath string) (string, error) {
	key := makeSecret()
	s.mu.Lock()
	defer s.mu.Unlock()
	s.vals[domain+":"+key] = &memRef{
		val: filePath,
		exp: time.Now().Add(downloadStoreTTL),
	}
	return key, nil
}

func (s *memStore) AddArchive(domain string, archive *Archive) (string, error) {
	key := makeSecret()
	s.mu.Lock()
	defer s.mu.Unlock()
	s.vals[domain+":"+key] = &memRef{
		val: archive,
		exp: time.Now().Add(downloadStoreTTL),
	}
	return key, nil
}

func (s *memStore) GetFile(domain, key string) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	key = domain + ":" + key
	ref, ok := s.vals[key]
	if !ok {
		return "", nil
	}
	if time.Now().After(ref.exp) {
		delete(s.vals, key)
		return "", nil
	}
	f, ok := ref.val.(string)
	if !ok {
		return "", nil
	}
	return f, nil
}

func (s *memStore) GetArchive(domain, key string) (*Archive, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	key = domain + ":" + key
	ref, ok := s.vals[key]
	if !ok {
		return nil, nil
	}
	if time.Now().After(ref.exp) {
		delete(s.vals, key)
		return nil, nil
	}
	a, ok := ref.val.(*Archive)
	if !ok {
		return nil, nil
	}
	return a, nil
}

type redisStore struct {
	c *redis.Client
}

func (s *redisStore) AddFile(domain, filePath string) (string, error) {
	key := makeSecret()
	if err := s.c.Set(domain+":"+key, filePath, downloadStoreTTL).Err(); err != nil {
		return "", err
	}
	return key, nil
}

func (s *redisStore) AddArchive(domain string, archive *Archive) (string, error) {
	v, err := json.Marshal(archive)
	if err != nil {
		return "", err
	}
	key := makeSecret()
	if err = s.c.Set(domain+":"+key, v, downloadStoreTTL).Err(); err != nil {
		return "", err
	}
	return key, nil
}

func (s *redisStore) GetFile(domain, key string) (string, error) {
	f, err := s.c.Get(domain + ":" + key).Result()
	if err == redis.Nil {
		return "", nil
	}
	if err != nil {
		return "", err
	}
	return f, nil
}

func (s *redisStore) GetArchive(domain, key string) (*Archive, error) {
	b, err := s.c.Get(domain + ":" + key).Bytes()
	if err == redis.Nil {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	arch := &Archive{}
	if err = json.Unmarshal(b, &arch); err != nil {
		return nil, err
	}
	return arch, nil
}

func makeSecret() string {
	return hex.EncodeToString(crypto.GenerateRandomBytes(8))
}
<commit_msg>Use one global variable<commit_after>package vfs

import (
	"encoding/hex"
	"encoding/json"
	"sync"
	"time"

	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/go-redis/redis"
)

// A DownloadStore is essentially an object to store Archives & Files by keys
type DownloadStore interface {
	AddFile(domain, filePath string) (string, error)
	AddArchive(domain string, archive *Archive) (string, error)
	GetFile(domain, key string) (string, error)
	GetArchive(domain, key string) (*Archive, error)
}

// downloadStoreTTL is the time an Archive stay alive
const downloadStoreTTL = 1 * time.Hour

// downloadStoreCleanInterval is the time interval between each download
// cleanup.
const downloadStoreCleanInterval = 1 * time.Hour

var globalStoreMu sync.Mutex
var globalStore DownloadStore

type memRef struct {
	val interface{}
	exp time.Time
}

func storeCleaner() {
	for range time.Tick(downloadStoreCleanInterval) {
		globalStore.(*memStore).clean()
	}
}

// GetStore returns the DownloadStore.
func GetStore() DownloadStore {
	globalStoreMu.Lock()
	defer globalStoreMu.Unlock()
	if globalStore != nil {
		return globalStore
	}
	opts := config.CacheOptions()
	if opts == nil {
		globalStore = &memStore{vals: make(map[string]*memRef)}
		go storeCleaner()
	} else {
		globalStore = &redisStore{redis.NewClient(opts)}
	}
	return globalStore
}

type memStore struct {
	mu   sync.Mutex
	vals map[string]*memRef
}

func (s *memStore) clean() {
	now := time.Now()
	for k, v := range s.vals {
		if now.After(v.exp) {
			delete(s.vals, k)
		}
	}
}

func (s *memStore) AddFile(domain, filePath string) (string, error) {
	key := makeSecret()
	s.mu.Lock()
	defer s.mu.Unlock()
	s.vals[domain+":"+key] = &memRef{
		val: filePath,
		exp: time.Now().Add(downloadStoreTTL),
	}
	return key, nil
}

func (s *memStore) AddArchive(domain string, archive *Archive) (string, error) {
	key := makeSecret()
	s.mu.Lock()
	defer s.mu.Unlock()
	s.vals[domain+":"+key] = &memRef{
		val: archive,
		exp: time.Now().Add(downloadStoreTTL),
	}
	return key, nil
}

func (s *memStore) GetFile(domain, key string) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	key = domain + ":" + key
	ref, ok := s.vals[key]
	if !ok {
		return "", nil
	}
	if time.Now().After(ref.exp) {
		delete(s.vals, key)
		return "", nil
	}
	f, ok := ref.val.(string)
	if !ok {
		return "", nil
	}
	return f, nil
}

func (s *memStore) GetArchive(domain, key string) (*Archive, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	key = domain + ":" + key
	ref, ok := s.vals[key]
	if !ok {
		return nil, nil
	}
	if time.Now().After(ref.exp) {
		delete(s.vals, key)
		return nil, nil
	}
	a, ok := ref.val.(*Archive)
	if !ok {
		return nil, nil
	}
	return a, nil
}

type redisStore struct {
	c *redis.Client
}

func (s *redisStore) AddFile(domain, filePath string) (string, error) {
	key := makeSecret()
	if err := s.c.Set(domain+":"+key, filePath, downloadStoreTTL).Err(); err != nil {
		return "", err
	}
	return key, nil
}

func (s *redisStore) AddArchive(domain string, archive *Archive) (string, error) {
	v, err := json.Marshal(archive)
	if err != nil {
		return "", err
	}
	key := makeSecret()
	if err = s.c.Set(domain+":"+key, v, downloadStoreTTL).Err(); err != nil {
		return "", err
	}
	return key, nil
}

func (s *redisStore) GetFile(domain, key string) (string, error) {
	f, err := s.c.Get(domain + ":" + key).Result()
	if err == redis.Nil {
		return "", nil
	}
	if err != nil {
		return "", err
	}
	return f, nil
}

func (s *redisStore) GetArchive(domain, key string) (*Archive, error) {
	b, err := s.c.Get(domain + ":" + key).Bytes()
	if err == redis.Nil {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	arch := &Archive{}
	if err = json.Unmarshal(b, &arch); err != nil {
		return nil, err
	}
	return arch, nil
}

func makeSecret() string {
	return hex.EncodeToString(crypto.GenerateRandomBytes(8))
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"log"

	"google.golang.org/api/compute/v1"
)

const FINGERPRINT_RETRIES = 10
const FINGERPRINT_FAIL = "Invalid fingerprint."

// Since the google compute API uses optimistic locking, there is a chance
// we need to resubmit our updated metadata. To do this, you need to provide
// an update function that attempts to submit your metadata
func MetadataRetryWrapper(update func() error) error {
	attempt := 0
	for attempt < FINGERPRINT_RETRIES {
		err := update()
		if err != nil && err.Error() == FINGERPRINT_FAIL {
			attempt++
		} else {
			return err
		}
	}

	return fmt.Errorf("Failed to update metadata after %d retries", attempt)
}

// Update the metadata (serverMD) according to the provided diff (oldMDMap v
// newMDMap).
func MetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]interface{}, serverMD *compute.Metadata) {
	curMDMap := make(map[string]string)
	// Load metadata on server into map
	for _, kv := range serverMD.Items {
		// If the server state has a key that we had in our old
		// state, but not in our new state, we should delete it
		_, okOld := oldMDMap[kv.Key]
		_, okNew := newMDMap[kv.Key]
		if okOld && !okNew {
			continue
		} else {
			curMDMap[kv.Key] = *kv.Value
		}
	}

	// Insert new metadata into existing metadata (overwriting when needed)
	for key, val := range newMDMap {
		curMDMap[key] = val.(string)
	}

	// Reformat old metadata into a list
	serverMD.Items = nil
	for key, val := range curMDMap {
		v := val
		serverMD.Items = append(serverMD.Items, &compute.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}
}

// Format metadata from the server data format -> schema data format
func MetadataFormatSchema(curMDMap map[string]interface{}, md *compute.Metadata) map[string]interface{} {
	newMD := make(map[string]interface{})

	for _, kv := range md.Items {
		if _, ok := curMDMap[kv.Key]; ok {
			newMD[kv.Key] = *kv.Value
		}
	}

	return newMD
}

// flattenComputeMetadata transforms a list of MetadataItems (as returned via the GCP client) into a simple map from key
// to value.
func flattenComputeMetadata(metadata []*compute.MetadataItems) map[string]string {
	m := map[string]string{}

	for _, item := range metadata {
		// check for duplicates
		if item.Value == nil {
			continue
		}
		if val, ok := m[item.Key]; ok {
			// warn loudly!
			log.Printf("[WARN] Key '%s' already has value '%s' when flattening - ignoring incoming value '%s'",
				item.Key,
				val,
				*item.Value)
		}
		m[item.Key] = *item.Value
	}

	return m
}

// expandComputeMetadata transforms a map representing computing metadata into a list of compute.MetadataItems suitable
// for the GCP client.
func expandComputeMetadata(m map[string]string) []*compute.MetadataItems {
	metadata := make([]*compute.MetadataItems, len(m))

	idx := 0
	for key, value := range m {
		metadata[idx] = &compute.MetadataItems{Key: key, Value: &value}
		idx++
	}

	return metadata
}
<commit_msg>Fix bug where range variable is improperly dereferenced (#217)<commit_after>package google

import (
	"fmt"
	"log"

	"google.golang.org/api/compute/v1"
)

const FINGERPRINT_RETRIES = 10
const FINGERPRINT_FAIL = "Invalid fingerprint."

// Since the google compute API uses optimistic locking, there is a chance
// we need to resubmit our updated metadata. To do this, you need to provide
// an update function that attempts to submit your metadata
func MetadataRetryWrapper(update func() error) error {
	attempt := 0
	for attempt < FINGERPRINT_RETRIES {
		err := update()
		if err != nil && err.Error() == FINGERPRINT_FAIL {
			attempt++
		} else {
			return err
		}
	}

	return fmt.Errorf("Failed to update metadata after %d retries", attempt)
}

// Update the metadata (serverMD) according to the provided diff (oldMDMap v
// newMDMap).
func MetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]interface{}, serverMD *compute.Metadata) {
	curMDMap := make(map[string]string)
	// Load metadata on server into map
	for _, kv := range serverMD.Items {
		// If the server state has a key that we had in our old
		// state, but not in our new state, we should delete it
		_, okOld := oldMDMap[kv.Key]
		_, okNew := newMDMap[kv.Key]
		if okOld && !okNew {
			continue
		} else {
			curMDMap[kv.Key] = *kv.Value
		}
	}

	// Insert new metadata into existing metadata (overwriting when needed)
	for key, val := range newMDMap {
		curMDMap[key] = val.(string)
	}

	// Reformat old metadata into a list
	serverMD.Items = nil
	for key, val := range curMDMap {
		v := val
		serverMD.Items = append(serverMD.Items, &compute.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}
}

// Format metadata from the server data format -> schema data format
func MetadataFormatSchema(curMDMap map[string]interface{}, md *compute.Metadata) map[string]interface{} {
	newMD := make(map[string]interface{})

	for _, kv := range md.Items {
		if _, ok := curMDMap[kv.Key]; ok {
			newMD[kv.Key] = *kv.Value
		}
	}

	return newMD
}

// flattenComputeMetadata transforms a list of MetadataItems (as returned via the GCP client) into a simple map from key
// to value.
func flattenComputeMetadata(metadata []*compute.MetadataItems) map[string]string {
	m := map[string]string{}

	for _, item := range metadata {
		// check for duplicates
		if item.Value == nil {
			continue
		}
		if val, ok := m[item.Key]; ok {
			// warn loudly!
			log.Printf("[WARN] Key '%s' already has value '%s' when flattening - ignoring incoming value '%s'",
				item.Key,
				val,
				*item.Value)
		}
		m[item.Key] = *item.Value
	}

	return m
}

// expandComputeMetadata transforms a map representing computing metadata into a list of compute.MetadataItems suitable
// for the GCP client.
func expandComputeMetadata(m map[string]string) []*compute.MetadataItems {
	metadata := make([]*compute.MetadataItems, len(m))

	idx := 0
	for key, value := range m {
		// Make a copy of value as we need a ptr type; if we directly use 'value' then all items will reference the same
		// memory address
		vtmp := value
		metadata[idx] = &compute.MetadataItems{Key: key, Value: &vtmp}
		idx++
	}

	return metadata
}
<|endoftext|>