<commit_before>/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package couchdb

import (
	"time"

	"github.com/hyperledger/fabric/common/metrics"
)

var (
	apiProcessingTimeOpts = metrics.HistogramOpts{
		Namespace:    "couchdb",
		Subsystem:    "",
		Name:         "processing_time",
		LabelNames:   []string{"database", "function_name", "result"},
		StatsdFormat: "%{#fqname}.%{database}.%{function_name}.%{result}",
	}
)

type stats struct {
	apiProcessingTime metrics.Histogram
}

func newStats(metricsProvider metrics.Provider) *stats {
	return &stats{
		apiProcessingTime: metricsProvider.NewHistogram(apiProcessingTimeOpts),
	}
}

func (s *stats) observeProcessingTime(startTime time.Time, dbName, functionName, result string) {
	s.apiProcessingTime.With(
		"database", dbName,
		"function_name", functionName,
		"result", result,
	).Observe(time.Since(startTime).Seconds())
}
<commit_msg>FAB-13193 - Added help message to couch db metric<commit_after>/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package couchdb

import (
	"time"

	"github.com/hyperledger/fabric/common/metrics"
)

var (
	apiProcessingTimeOpts = metrics.HistogramOpts{
		Namespace:    "couchdb",
		Subsystem:    "",
		Name:         "processing_time",
		Help:         "Time taken in seconds for the function to complete request to CouchDB",
		LabelNames:   []string{"database", "function_name", "result"},
		StatsdFormat: "%{#fqname}.%{database}.%{function_name}.%{result}",
	}
)

type stats struct {
	apiProcessingTime metrics.Histogram
}

func newStats(metricsProvider metrics.Provider) *stats {
	return &stats{
		apiProcessingTime: metricsProvider.NewHistogram(apiProcessingTimeOpts),
	}
}

func (s *stats) observeProcessingTime(startTime time.Time, dbName, functionName, result string) {
	s.apiProcessingTime.With(
		"database", dbName,
		"function_name", functionName,
		"result", result,
	).Observe(time.Since(startTime).Seconds())
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"net"
	"path/filepath"

	"github.com/containernetworking/plugins/pkg/ip"
	"github.com/containernetworking/plugins/pkg/ns"
	log "github.com/sirupsen/logrus"
)

// Host is a host instance
type Host struct {
	NodeType string
	Name     string
	vethName string
	IfName   string
	Sandbox  string
	IP       string
	MAC      string
}

// NewHost for creating a network namespace
func NewHost(name string, docker bool) (*Host, error) {
	h := new(Host)
	h.NodeType = "Host"
	h.Name = name

	// Create a network namespace
	targetNs, err := ns.NewNS()
	if err != nil {
		log.Fatal("failed to open netns: ", err)
		return nil, err
	}
	// log.Info("netns mouted into the host: ", targetNs.Path())
	log.Infof("Adding a host: %s, namespace: %s", h.Name, filepath.Base(targetNs.Path()))
	h.Sandbox = targetNs.Path()
	return h, nil
}

// NewContainer for creating a docker container
func NewContainer(name string, imageRef string) (*Host, error) {
	h := new(Host)
	h.NodeType = "Host"
	h.Name = name
	_, sandboxKey, err := ensureDocker(imageRef)
	if err != nil {
		log.Fatal("failed to start container: ", err)
		return nil, err
	}
	h.Sandbox = sandboxKey
	log.Info("netns mouted into the host: ", h.Sandbox)
	return h, nil
}

func (h *Host) setupVeth(ifName string, mtu int) (*Host, error) {
	// Get network namespace object
	netns, err := ns.GetNS(h.Sandbox)
	if err != nil {
		log.Fatal("failed to open netns: ", err)
	}
	defer netns.Close()

	// attach network namespace and setup veth pair
	err = netns.Do(func(hostNS ns.NetNS) error {
		// create the veth pair in the container and move host end into host netns
		hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS)
		if err != nil {
			return err
		}
		// Host interface name
		h.IfName = containerVeth.Name

		// h.mac = containerVeth.HardwareAddr.String()

		// Host veth name
		h.vethName = hostVeth.Name

		// ip link set lo up
		_, err = ifaceUp("lo")
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return h, nil
}

func (h *Host) setIfaceIP(address string) error {
	// Get network namespace object
	netns, err := ns.GetNS(h.Sandbox)
	if err != nil {
		log.Fatal("failed to open netns: ", err)
	}
	defer netns.Close()

	ipv4Addr, _, err := net.ParseCIDR(address)
	if err != nil {
		return err
	}
	h.IP = ipv4Addr.String()
	err = netns.Do(func(hostNS ns.NetNS) error {
		if err := setIP(h.IfName, address); err != nil {
			return err
		}
		// ip link set ifName up
		_, err := ifaceUp(h.IfName)
		if err != nil {
			return err
		}
		// get Host mac address
		hostIface, err := net.InterfaceByName(h.IfName)
		if err != nil {
			return err
		}
		h.MAC = hostIface.HardwareAddr.String()
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>fixed function not enough arguments<commit_after>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"net"
	"path/filepath"

	"github.com/containernetworking/plugins/pkg/ip"
	"github.com/containernetworking/plugins/pkg/ns"
	log "github.com/sirupsen/logrus"
)

// Host is a host instance
type Host struct {
	NodeType string
	Name     string
	vethName string
	IfName   string
	Sandbox  string
	IP       string
	MAC      string
}

// NewHost for creating a network namespace
func NewHost(name string) (*Host, error) {
	h := new(Host)
	h.NodeType = "Host"
	h.Name = name

	// Create a network namespace
	targetNs, err := ns.NewNS()
	if err != nil {
		log.Fatal("failed to open netns: ", err)
		return nil, err
	}
	// log.Info("netns mouted into the host: ", targetNs.Path())
	log.Infof("Adding a host: %s, namespace: %s", h.Name, filepath.Base(targetNs.Path()))
	h.Sandbox = targetNs.Path()
	return h, nil
}

// NewContainer for creating a docker container
func NewContainer(name string, imageRef string) (*Host, error) {
	h := new(Host)
	h.NodeType = "Host"
	h.Name = name
	_, sandboxKey, err := ensureDocker(imageRef)
	if err != nil {
		log.Fatal("failed to start container: ", err)
		return nil, err
	}
	h.Sandbox = sandboxKey
	log.Info("netns mouted into the host: ", h.Sandbox)
	return h, nil
}

func (h *Host) setupVeth(ifName string, mtu int) (*Host, error) {
	// Get network namespace object
	netns, err := ns.GetNS(h.Sandbox)
	if err != nil {
		log.Fatal("failed to open netns: ", err)
	}
	defer netns.Close()

	// attach network namespace and setup veth pair
	err = netns.Do(func(hostNS ns.NetNS) error {
		// create the veth pair in the container and move host end into host netns
		hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS)
		if err != nil {
			return err
		}
		// Host interface name
		h.IfName = containerVeth.Name

		// h.mac = containerVeth.HardwareAddr.String()

		// Host veth name
		h.vethName = hostVeth.Name

		// ip link set lo up
		_, err = ifaceUp("lo")
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return h, nil
}

func (h *Host) setIfaceIP(address string) error {
	// Get network namespace object
	netns, err := ns.GetNS(h.Sandbox)
	if err != nil {
		log.Fatal("failed to open netns: ", err)
	}
	defer netns.Close()

	ipv4Addr, _, err := net.ParseCIDR(address)
	if err != nil {
		return err
	}
	h.IP = ipv4Addr.String()
	err = netns.Do(func(hostNS ns.NetNS) error {
		if err := setIP(h.IfName, address); err != nil {
			return err
		}
		// ip link set ifName up
		_, err := ifaceUp(h.IfName)
		if err != nil {
			return err
		}
		// get Host mac address
		hostIface, err := net.InterfaceByName(h.IfName)
		if err != nil {
			return err
		}
		h.MAC = hostIface.HardwareAddr.String()
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package zabbix

import (
	"github.com/AlekSi/reflector"
)

type (
	AvailableType int
	StatusType    int
)

const (
	Available   AvailableType = 1
	Unavailable AvailableType = 2

	Monitored   StatusType = 0
	Unmonitored StatusType = 0
)

// https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/definitions
type Host struct {
	HostId    string        `json:"hostid,omitempty"`
	Host      string        `json:"host"`
	Available AvailableType `json:"available"`
	Error     string        `json:"error"`
	Name      string        `json:"name"`
	Status    StatusType    `json:"status"`

	// Fields below used only when creating hosts
	GroupIds   HostGroupIds   `json:"groups,omitempty"`
	Interfaces HostInterfaces `json:"interfaces,omitempty"`
}

type Hosts []Host

// Wrapper for host.get: https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/get
func (api *API) HostsGet(params Params) (res Hosts, err error) {
	if _, present := params["output"]; !present {
		params["output"] = "extend"
	}
	response, err := api.CallWithError("host.get", params)
	if err != nil {
		return
	}

	reflector.MapsToStructs2(response.Result.([]interface{}), &res, reflector.Strconv, "json")
	return
}

// Gets hosts by host group Ids.
func (api *API) HostsGetByHostGroupIds(ids []string) (res Hosts, err error) {
	return api.HostsGet(Params{"groupids": ids})
}

// Gets hosts by host groups.
func (api *API) HostsGetByHostGroups(hostGroups HostGroups) (res Hosts, err error) {
	ids := make([]string, len(hostGroups))
	for i, id := range hostGroups {
		ids[i] = id.GroupId
	}
	return api.HostsGetByHostGroupIds(ids)
}

// Gets host by Id only if there is exactly 1 matching host.
func (api *API) HostGetById(id string) (res *Host, err error) {
	hosts, err := api.HostsGet(Params{"hostids": id})
	if err != nil {
		return
	}

	if len(hosts) == 1 {
		res = &hosts[0]
	} else {
		e := ExpectedOneResult(len(hosts))
		err = &e
	}
	return
}

// Gets host by Host only if there is exactly 1 matching host.
func (api *API) HostGetByHost(host string) (res *Host, err error) {
	hosts, err := api.HostsGet(Params{"filter": map[string]string{"host": host}})
	if err != nil {
		return
	}

	if len(hosts) == 1 {
		res = &hosts[0]
	} else {
		e := ExpectedOneResult(len(hosts))
		err = &e
	}
	return
}

// Wrapper for host.create: https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/create
func (api *API) HostsCreate(hosts Hosts) (err error) {
	response, err := api.CallWithError("host.create", hosts)
	if err != nil {
		return
	}

	result := response.Result.(map[string]interface{})
	hostids := result["hostids"].([]interface{})
	for i, id := range hostids {
		hosts[i].HostId = id.(string)
	}
	return
}

// Wrapper for host.delete: https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/delete
// Cleans HostId in all hosts elements if call succeed.
func (api *API) HostsDelete(hosts Hosts) (err error) {
	ids := make([]string, len(hosts))
	for i, host := range hosts {
		ids[i] = host.HostId
	}

	err = api.HostsDeleteByIds(ids)
	if err == nil {
		for i := range hosts {
			hosts[i].HostId = ""
		}
	}
	return
}

// Wrapper for host.delete: https://www.zabbxix.com/documentation/2.0/manual/appendix/api/host/delete
func (api *API) HostsDeleteByIds(ids []string) (err error) {
	hostIds := make([]map[string]string, len(ids))
	for i, id := range ids {
		hostIds[i] = map[string]string{"hostid": id}
	}

	response, err := api.CallWithError("host.delete", hostIds)
	if err != nil {
		return
	}

	result := response.Result.(map[string]interface{})
	hostids := result["hostids"].([]interface{})
	if len(ids) != len(hostids) {
		err = &ExpectedMore{len(ids), len(hostids)}
	}
	return
}
<commit_msg>Fix Unmonitored constant.<commit_after>package zabbix

import (
	"github.com/AlekSi/reflector"
)

type (
	AvailableType int
	StatusType    int
)

const (
	Available   AvailableType = 1
	Unavailable AvailableType = 2

	Monitored   StatusType = 0
	Unmonitored StatusType = 1
)

// https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/definitions
type Host struct {
	HostId    string        `json:"hostid,omitempty"`
	Host      string        `json:"host"`
	Available AvailableType `json:"available"`
	Error     string        `json:"error"`
	Name      string        `json:"name"`
	Status    StatusType    `json:"status"`

	// Fields below used only when creating hosts
	GroupIds   HostGroupIds   `json:"groups,omitempty"`
	Interfaces HostInterfaces `json:"interfaces,omitempty"`
}

type Hosts []Host

// Wrapper for host.get: https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/get
func (api *API) HostsGet(params Params) (res Hosts, err error) {
	if _, present := params["output"]; !present {
		params["output"] = "extend"
	}
	response, err := api.CallWithError("host.get", params)
	if err != nil {
		return
	}

	reflector.MapsToStructs2(response.Result.([]interface{}), &res, reflector.Strconv, "json")
	return
}

// Gets hosts by host group Ids.
func (api *API) HostsGetByHostGroupIds(ids []string) (res Hosts, err error) {
	return api.HostsGet(Params{"groupids": ids})
}

// Gets hosts by host groups.
func (api *API) HostsGetByHostGroups(hostGroups HostGroups) (res Hosts, err error) {
	ids := make([]string, len(hostGroups))
	for i, id := range hostGroups {
		ids[i] = id.GroupId
	}
	return api.HostsGetByHostGroupIds(ids)
}

// Gets host by Id only if there is exactly 1 matching host.
func (api *API) HostGetById(id string) (res *Host, err error) {
	hosts, err := api.HostsGet(Params{"hostids": id})
	if err != nil {
		return
	}

	if len(hosts) == 1 {
		res = &hosts[0]
	} else {
		e := ExpectedOneResult(len(hosts))
		err = &e
	}
	return
}

// Gets host by Host only if there is exactly 1 matching host.
func (api *API) HostGetByHost(host string) (res *Host, err error) {
	hosts, err := api.HostsGet(Params{"filter": map[string]string{"host": host}})
	if err != nil {
		return
	}

	if len(hosts) == 1 {
		res = &hosts[0]
	} else {
		e := ExpectedOneResult(len(hosts))
		err = &e
	}
	return
}

// Wrapper for host.create: https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/create
func (api *API) HostsCreate(hosts Hosts) (err error) {
	response, err := api.CallWithError("host.create", hosts)
	if err != nil {
		return
	}

	result := response.Result.(map[string]interface{})
	hostids := result["hostids"].([]interface{})
	for i, id := range hostids {
		hosts[i].HostId = id.(string)
	}
	return
}

// Wrapper for host.delete: https://www.zabbix.com/documentation/2.0/manual/appendix/api/host/delete
// Cleans HostId in all hosts elements if call succeed.
func (api *API) HostsDelete(hosts Hosts) (err error) {
	ids := make([]string, len(hosts))
	for i, host := range hosts {
		ids[i] = host.HostId
	}

	err = api.HostsDeleteByIds(ids)
	if err == nil {
		for i := range hosts {
			hosts[i].HostId = ""
		}
	}
	return
}

// Wrapper for host.delete: https://www.zabbxix.com/documentation/2.0/manual/appendix/api/host/delete
func (api *API) HostsDeleteByIds(ids []string) (err error) {
	hostIds := make([]map[string]string, len(ids))
	for i, id := range ids {
		hostIds[i] = map[string]string{"hostid": id}
	}

	response, err := api.CallWithError("host.delete", hostIds)
	if err != nil {
		return
	}

	result := response.Result.(map[string]interface{})
	hostids := result["hostids"].([]interface{})
	if len(ids) != len(hostids) {
		err = &ExpectedMore{len(ids), len(hostids)}
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes 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 proxy

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"
	"sync/atomic"
	"time"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/httpstream"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/portforward"
	"k8s.io/client-go/transport/spdy"
	"k8s.io/klog/v2"
)

// Maximum number of forwarded connections. In practice we don't
// need more than one per sidecar and kubelet. Keeping this reasonably
// small ensures that we don't establish connections through the apiserver
// and the remote kernel which then aren't needed.
const maxConcurrentConnections = 10

// Listen creates a listener which returns new connections whenever someone connects
// to a socat or mock driver proxy instance running inside the given pod.
//
// socat must by started with "<listen>,fork TCP-LISTEN:<port>,reuseport"
// for this to work. "<listen>" can be anything that accepts connections,
// for example "UNIX-LISTEN:/csi/csi.sock". In this mode, socat will
// accept exactly one connection on the given port for each connection
// that socat itself accepted.
//
// Listening stops when the context is done or Close() is called.
func Listen(ctx context.Context, clientset kubernetes.Interface, restConfig *rest.Config, addr Addr) (net.Listener, error) {
	// We connect through port forwarding. Strictly
	// speaking this is overkill because we don't need a local
	// port. But this way we can reuse client-go/tools/portforward
	// instead of having to replicate handleConnection
	// in our own code.
	restClient := clientset.CoreV1().RESTClient()
	if restConfig.GroupVersion == nil {
		restConfig.GroupVersion = &schema.GroupVersion{}
	}
	if restConfig.NegotiatedSerializer == nil {
		restConfig.NegotiatedSerializer = scheme.Codecs
	}

	// The setup code around the actual portforward is from
	// https://github.com/kubernetes/kubernetes/blob/c652ffbe4a29143623a1aaec39f745575f7e43ad/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go
	req := restClient.Post().
		Resource("pods").
		Namespace(addr.Namespace).
		Name(addr.PodName).
		SubResource("portforward")
	transport, upgrader, err := spdy.RoundTripperFor(restConfig)
	if err != nil {
		return nil, fmt.Errorf("create round tripper: %v", err)
	}
	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL())

	prefix := fmt.Sprintf("port forwarding for %s", addr)
	ctx, cancel := context.WithCancel(ctx)
	l := &listener{
		connections: make(chan *connection),
		ctx:         ctx,
		cancel:      cancel,
		addr:        addr,
	}

	var connectionsCreated, connectionsClosed int32

	runForwarding := func() {
		klog.V(2).Infof("%s: starting connection polling", prefix)
		defer klog.V(2).Infof("%s: connection polling ended", prefix)

		// This delay determines how quickly we notice when someone has
		// connected inside the cluster. With socat, we cannot make this too small
		// because otherwise we get many rejected connections. With the mock
		// driver as proxy that doesn't happen as long as we don't
		// ask for too many concurrent connections because the mock driver
		// keeps the listening port open at all times and the Linux
		// kernel automatically accepts our connection requests.
		tryConnect := time.NewTicker(100 * time.Millisecond)
		defer tryConnect.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-tryConnect.C:
				currentClosed := atomic.LoadInt32(&connectionsClosed)
				openConnections := connectionsCreated - currentClosed
				if openConnections >= maxConcurrentConnections {
					break
				}

				klog.V(5).Infof("%s: trying to create a new connection #%d, %d open", prefix, connectionsCreated, openConnections)
				stream, err := dial(ctx, fmt.Sprintf("%s #%d", prefix, connectionsCreated), dialer, addr.Port)
				if err != nil {
					klog.Errorf("%s: no connection: %v", prefix, err)
					break
				}
				// Make the connection available to Accept below.
				klog.V(5).Infof("%s: created a new connection #%d", prefix, connectionsCreated)
				l.connections <- &connection{
					stream:  stream,
					addr:    addr,
					counter: connectionsCreated,
					closed:  &connectionsClosed,
				}
				connectionsCreated++
			}
		}
	}

	// Portforwarding and polling for connections run in the background.
	go func() {
		for {
			running := false
			pod, err := clientset.CoreV1().Pods(addr.Namespace).Get(ctx, addr.PodName, metav1.GetOptions{})
			if err != nil {
				klog.V(5).Infof("checking for container %q in pod %s/%s: %v", addr.ContainerName, addr.Namespace, addr.PodName, err)
			}
			for i, status := range pod.Status.ContainerStatuses {
				if pod.Spec.Containers[i].Name == addr.ContainerName &&
					status.State.Running != nil {
					running = true
					break
				}
			}

			if running {
				klog.V(2).Infof("container %q in pod %s/%s is running", addr.ContainerName, addr.Namespace, addr.PodName)
				runForwarding()
			}

			select {
			case <-ctx.Done():
				return
			// Sleep a bit before restarting. This is
			// where we potentially wait for the pod to
			// start.
			case <-time.After(1 * time.Second):
			}
		}
	}()

	return l, nil
}

// Addr contains all relevant parameters for a certain port in a pod.
// The container must be running before connections are attempted.
type Addr struct {
	Namespace, PodName, ContainerName string
	Port                              int
}

var _ net.Addr = Addr{}

func (a Addr) Network() string {
	return "port-forwarding"
}

func (a Addr) String() string {
	return fmt.Sprintf("%s/%s:%d", a.Namespace, a.PodName, a.Port)
}

type stream struct {
	httpstream.Stream
	streamConn httpstream.Connection
}

func dial(ctx context.Context, prefix string, dialer httpstream.Dialer, port int) (s *stream, finalErr error) {
	streamConn, _, err := dialer.Dial(portforward.PortForwardProtocolV1Name)
	if err != nil {
		return nil, fmt.Errorf("dialer failed: %v", err)
	}
	requestID := "1"
	defer func() {
		if finalErr != nil {
			streamConn.Close()
		}
	}()

	// create error stream
	headers := http.Header{}
	headers.Set(v1.StreamType, v1.StreamTypeError)
	headers.Set(v1.PortHeader, fmt.Sprintf("%d", port))
	headers.Set(v1.PortForwardRequestIDHeader, requestID)

	// We're not writing to this stream, just reading an error message from it.
	// This happens asynchronously.
	errorStream, err := streamConn.CreateStream(headers)
	if err != nil {
		return nil, fmt.Errorf("error creating error stream: %v", err)
	}
	errorStream.Close()
	go func() {
		message, err := io.ReadAll(errorStream)
		switch {
		case err != nil:
			klog.Errorf("%s: error reading from error stream: %v", prefix, err)
		case len(message) > 0:
			klog.Errorf("%s: an error occurred connecting to the remote port: %v", prefix, string(message))
		}
	}()

	// create data stream
	headers.Set(v1.StreamType, v1.StreamTypeData)
	dataStream, err := streamConn.CreateStream(headers)
	if err != nil {
		return nil, fmt.Errorf("error creating data stream: %v", err)
	}

	return &stream{
		Stream:     dataStream,
		streamConn: streamConn,
	}, nil
}

func (s *stream) Close() {
	s.Stream.Close()
	s.streamConn.Close()
}

type listener struct {
	addr        Addr
	connections chan *connection
	ctx         context.Context
	cancel      func()
}

var _ net.Listener = &listener{}

func (l *listener) Close() error {
	klog.V(5).Infof("forward listener for %s: closing", l.addr)
	l.cancel()
	return nil
}

func (l *listener) Accept() (net.Conn, error) {
	select {
	case <-l.ctx.Done():
		return nil, errors.New("listening was stopped")
	case c := <-l.connections:
		klog.V(5).Infof("forward listener for %s: got a new connection #%d", l.addr, c.counter)
		return c, nil
	}
}

type connection struct {
	stream  *stream
	addr    Addr
	counter int32
	closed  *int32
	mutex   sync.Mutex
}

var _ net.Conn = &connection{}

func (c *connection) LocalAddr() net.Addr {
	return c.addr
}

func (c *connection) RemoteAddr() net.Addr {
	return c.addr
}

func (c *connection) SetDeadline(t time.Time) error {
	return nil
}

func (c *connection) SetReadDeadline(t time.Time) error {
	return nil
}

func (c *connection) SetWriteDeadline(t time.Time) error {
	return nil
}

func (c *connection) Read(b []byte) (int, error) {
	n, err := c.stream.Read(b)
	if errors.Is(err, io.EOF) {
		klog.V(5).Infof("forward connection #%d for %s: remote side closed the stream", c.counter, c.addr)
	}
	return n, err
}

func (c *connection) Write(b []byte) (int, error) {
	n, err := c.stream.Write(b)
	if errors.Is(err, io.EOF) {
		klog.V(5).Infof("forward connection #%d for %s: remote side closed the stream", c.counter, c.addr)
	}
	return n, err
}

func (c *connection) Close() error {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if c.closed != nil {
		// Do the logging and book-keeping only once. The function itself may be called more than once.
		klog.V(5).Infof("forward connection #%d for %s: closing our side", c.counter, c.addr)
		atomic.AddInt32(c.closed, 1)
		c.closed = nil
	}
	c.stream.Close()
	return nil
}

func (l *listener) Addr() net.Addr {
	return l.addr
}
<commit_msg>e2e storage: close all pending streams when proxy listener gets closed<commit_after>/*
Copyright 2020 The Kubernetes 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 proxy

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"
	"time"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/httpstream"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/portforward"
	"k8s.io/client-go/transport/spdy"
	"k8s.io/klog/v2"
)

// Maximum number of forwarded connections. In practice we don't
// need more than one per sidecar and kubelet. Keeping this reasonably
// small ensures that we don't establish connections through the apiserver
// and the remote kernel which then aren't needed.
const maxConcurrentConnections = 10

// This delay determines how quickly we notice when someone has
// connected inside the cluster. With socat, we cannot make this too small
// because otherwise we get many rejected connections. With the mock
// driver as proxy that doesn't happen as long as we don't
// ask for too many concurrent connections because the mock driver
// keeps the listening port open at all times and the Linux
// kernel automatically accepts our connection requests.
const connectionPollInterval = 100 * time.Millisecond

// Listen creates a listener which returns new connections whenever someone connects
// to a socat or mock driver proxy instance running inside the given pod.
//
// socat must by started with "<listen>,fork TCP-LISTEN:<port>,reuseport"
// for this to work. "<listen>" can be anything that accepts connections,
// for example "UNIX-LISTEN:/csi/csi.sock". In this mode, socat will
// accept exactly one connection on the given port for each connection
// that socat itself accepted.
//
// Listening stops when the context is done or Close() is called.
func Listen(ctx context.Context, clientset kubernetes.Interface, restConfig *rest.Config, addr Addr) (net.Listener, error) {
	// We connect through port forwarding. Strictly
	// speaking this is overkill because we don't need a local
	// port. But this way we can reuse client-go/tools/portforward
	// instead of having to replicate handleConnection
	// in our own code.
	restClient := clientset.CoreV1().RESTClient()
	if restConfig.GroupVersion == nil {
		restConfig.GroupVersion = &schema.GroupVersion{}
	}
	if restConfig.NegotiatedSerializer == nil {
		restConfig.NegotiatedSerializer = scheme.Codecs
	}

	// The setup code around the actual portforward is from
	// https://github.com/kubernetes/kubernetes/blob/c652ffbe4a29143623a1aaec39f745575f7e43ad/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go
	req := restClient.Post().
		Resource("pods").
		Namespace(addr.Namespace).
		Name(addr.PodName).
		SubResource("portforward")
	transport, upgrader, err := spdy.RoundTripperFor(restConfig)
	if err != nil {
		return nil, fmt.Errorf("create round tripper: %v", err)
	}
	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL())

	prefix := fmt.Sprintf("port forwarding for %s", addr)
	ctx, cancel := context.WithCancel(ctx)
	l := &listener{
		ctx:    ctx,
		cancel: cancel,
		addr:   addr,
	}

	var connectionsCreated int

	runForwarding := func() {
		klog.V(2).Infof("%s: starting connection polling", prefix)
		defer klog.V(2).Infof("%s: connection polling ended", prefix)

		tryConnect := time.NewTicker(connectionPollInterval)
		defer tryConnect.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-tryConnect.C:
				func() {
					l.mutex.Lock()
					defer l.mutex.Unlock()

					for i, c := range l.connections {
						if c == nil {
							klog.V(5).Infof("%s: trying to create a new connection #%d", prefix, connectionsCreated)
							stream, err := dial(ctx, fmt.Sprintf("%s #%d", prefix, connectionsCreated), dialer, addr.Port)
							if err != nil {
								klog.Errorf("%s: no connection: %v", prefix, err)
								return
							}
							// Make the connection available to Accept below.
							klog.V(5).Infof("%s: created a new connection #%d", prefix, connectionsCreated)
							c := &connection{
								l:       l,
								stream:  stream,
								addr:    addr,
								counter: connectionsCreated,
							}
							l.connections[i] = c
							connectionsCreated++
							return
						}
					}
				}()
			}
		}
	}

	// Portforwarding and polling for connections run in the background.
	go func() {
		for {
			running := false
			pod, err := clientset.CoreV1().Pods(addr.Namespace).Get(ctx, addr.PodName, metav1.GetOptions{})
			if err != nil {
				klog.V(5).Infof("checking for container %q in pod %s/%s: %v", addr.ContainerName, addr.Namespace, addr.PodName, err)
			}
			for i, status := range pod.Status.ContainerStatuses {
				if pod.Spec.Containers[i].Name == addr.ContainerName &&
					status.State.Running != nil {
					running = true
					break
				}
			}

			if running {
				klog.V(2).Infof("container %q in pod %s/%s is running", addr.ContainerName, addr.Namespace, addr.PodName)
				runForwarding()
			}

			select {
			case <-ctx.Done():
				return
			// Sleep a bit before restarting. This is
			// where we potentially wait for the pod to
			// start.
			case <-time.After(1 * time.Second):
			}
		}
	}()

	return l, nil
}

// Addr contains all relevant parameters for a certain port in a pod.
// The container must be running before connections are attempted.
type Addr struct {
	Namespace, PodName, ContainerName string
	Port                              int
}

var _ net.Addr = Addr{}

func (a Addr) Network() string {
	return "port-forwarding"
}

func (a Addr) String() string {
	return fmt.Sprintf("%s/%s:%d", a.Namespace, a.PodName, a.Port)
}

type stream struct {
	httpstream.Stream
	streamConn httpstream.Connection
}

func dial(ctx context.Context, prefix string, dialer httpstream.Dialer, port int) (s *stream, finalErr error) {
	streamConn, _, err := dialer.Dial(portforward.PortForwardProtocolV1Name)
	if err != nil {
		return nil, fmt.Errorf("dialer failed: %v", err)
	}
	requestID := "1"
	defer func() {
		if finalErr != nil {
			streamConn.Close()
		}
	}()

	// create error stream
	headers := http.Header{}
	headers.Set(v1.StreamType, v1.StreamTypeError)
	headers.Set(v1.PortHeader, fmt.Sprintf("%d", port))
	headers.Set(v1.PortForwardRequestIDHeader, requestID)

	// We're not writing to this stream, just reading an error message from it.
	// This happens asynchronously.
	errorStream, err := streamConn.CreateStream(headers)
	if err != nil {
		return nil, fmt.Errorf("error creating error stream: %v", err)
	}
	errorStream.Close()
	go func() {
		message, err := io.ReadAll(errorStream)
		switch {
		case err != nil:
			klog.Errorf("%s: error reading from error stream: %v", prefix, err)
		case len(message) > 0:
			klog.Errorf("%s: an error occurred connecting to the remote port: %v", prefix, string(message))
		}
	}()

	// create data stream
	headers.Set(v1.StreamType, v1.StreamTypeData)
	dataStream, err := streamConn.CreateStream(headers)
	if err != nil {
		return nil, fmt.Errorf("error creating data stream: %v", err)
	}

	return &stream{
		Stream:     dataStream,
		streamConn: streamConn,
	}, nil
}

func (s *stream) Close() {
	s.Stream.Close()
	s.streamConn.Close()
}

type listener struct {
	addr   Addr
	ctx    context.Context
	cancel func()

	mutex       sync.Mutex
	connections [maxConcurrentConnections]*connection
}

var _ net.Listener = &listener{}

func (l *listener) Close() error {
	klog.V(5).Infof("forward listener for %s: closing", l.addr)
	l.cancel()

	l.mutex.Lock()
	defer l.mutex.Unlock()
	for _, c := range l.connections {
		if c != nil {
			c.stream.Close()
		}
	}

	return nil
}

func (l *listener) Accept() (net.Conn, error) {
	tryAccept := time.NewTicker(connectionPollInterval)
	defer tryAccept.Stop()
	for {
		select {
		case <-l.ctx.Done():
			return nil, errors.New("listening was stopped")
		case <-tryAccept.C:
			conn := func() net.Conn {
				l.mutex.Lock()
				defer l.mutex.Unlock()

				for _, c := range l.connections {
					if c != nil && !c.accepted {
						klog.V(5).Infof("forward listener for %s: got a new connection #%d", l.addr, c.counter)
						c.accepted = true
						return c
					}
				}
				return nil
			}()
			if conn != nil {
				return conn, nil
			}
		}
	}
}

type connection struct {
	l                *listener
	stream           *stream
	addr             Addr
	counter          int
	mutex            sync.Mutex
	accepted, closed bool
}

var _ net.Conn = &connection{}

func (c *connection) LocalAddr() net.Addr {
	return c.addr
}

func (c *connection) RemoteAddr() net.Addr {
	return c.addr
}

func (c *connection) SetDeadline(t time.Time) error {
	return nil
}

func (c *connection) SetReadDeadline(t time.Time) error {
	return nil
}

func (c *connection) SetWriteDeadline(t time.Time) error {
	return nil
}

func (c *connection) Read(b []byte) (int, error) {
	n, err := c.stream.Read(b)
	if errors.Is(err, io.EOF) {
		klog.V(5).Infof("forward connection #%d for %s: remote side closed the stream", c.counter, c.addr)
	}
	return n, err
}

func (c *connection) Write(b []byte) (int, error) {
	n, err := c.stream.Write(b)
	if errors.Is(err, io.EOF) {
		klog.V(5).Infof("forward connection #%d for %s: remote side closed the stream", c.counter, c.addr)
	}
	return n, err
}

func (c *connection) Close() error {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if !c.closed {
		// Do the logging and book-keeping only once. The function itself may be called more than once.
		klog.V(5).Infof("forward connection #%d for %s: closing our side", c.counter, c.addr)

		c.l.mutex.Lock()
		defer c.l.mutex.Unlock()
		for i, c2 := range c.l.connections {
			if c2 == c {
				c.l.connections[i] = nil
				break
			}
		}
	}
	c.stream.Close()

	return nil
}

func (l *listener) Addr() net.Addr {
	return l.addr
}
<|endoftext|>


<commit_before>package chef

import (
	"bytes"
	"crypto"
	"crypto/rsa"
	"errors"
	"fmt"
	"net/http"
	"path"
	"time"
)

// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"

// AuthConfig representing a client and a private key used for encryption
type AuthConfig struct {
	privateKey *rsa.PrivateKey
	clientName string
	cryptoHash crypto.Hash
}

// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request, cryptoHash crypto.Hash) error {
	// sanitize the path for the chef-server
	// chef-server doesn't support '//' in the Hash Path.
	var endpoint string
	if request.URL.Path != "" {
		endpoint = path.Clean(request.URL.Path)
		request.URL.Path = endpoint
	} else {
		endpoint = request.URL.Path
	}

	request.Header.Set("Method", request.Method)
	// Since we're not setting the encoded slice limit
	// we can safely call out [0]
	generatedHash := generateHash(endpoint, cryptoHash)
	if generatedHash == nil {
		return errors.New("Unsupported crypto hashing algorithm")
	}
	request.Header.Set("Hashed Path", base64BlockEncode(generatedHash, 0)[0])
	request.Header.Set("Accept", "application/json")
	request.Header.Set("X-Chef-Version", ChefVersion)
	request.Header.Set("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
	request.Header.Set("X-Ops-Userid", ac.clientName)

	var xOpsSignCrypto string
	switch cryptoHash {
	case crypto.MD5:
		xOpsSignCrypto = "algorithm=md5;"
	case crypto.SHA1:
		xOpsSignCrypto = "algorithm=sha1;"
	case crypto.SHA224:
		xOpsSignCrypto = "algorithm=sha224;"
	case crypto.SHA256:
		xOpsSignCrypto = "algorithm=sha256;"
	case crypto.SHA384:
		xOpsSignCrypto = "algorithm=sha384;"
	case crypto.SHA512:
		xOpsSignCrypto = "algorithm=sha512;"
	}

	request.Header.Set("X-Ops-Sign", xOpsSignCrypto+"version=1.0")
	request.Header.Set("X-Ops-Content-Hash", calcBodyHash(request, cryptoHash))

	// To validate the signature it seems to be very particular
	// Would like to use this loop to generate the content
	// But it causes validation to fail.. so we do it explicitly

	// var content string
	// for key, value := range request.Header {
	// 	content += fmt.Sprintf("%s:%s\n", key, request.Header.Get(key))
	// }
	var content string
	content += fmt.Sprintf("%s:%s\n", "Method", request.Header.Get("Method"))
	content += fmt.Sprintf("%s:%s\n", "Hashed Path", request.Header.Get("Hashed Path"))
	content += fmt.Sprintf("%s:%s\n", "Accept", request.Header.Get("Accept"))
	content += fmt.Sprintf("%s:%s\n", "X-Chef-Version", request.Header.Get("X-Chef-Version"))
	content += fmt.Sprintf("%s:%s\n", "X-Ops-Timestamp", request.Header.Get("X-Ops-Timestamp"))
	content += fmt.Sprintf("%s:%s\n", "X-Ops-Userid", request.Header.Get("X-Ops-Userid"))
	content += fmt.Sprintf("%s:%s\n", "X-Ops-Sign", request.Header.Get("X-Ops-Sign"))
	content += fmt.Sprintf("%s:%s\n", "X-Ops-Content-Hash", request.Header.Get("X-Ops-Content-Hash"))

	// generate signed string of headers
	// Since we've gone through additional validation steps above,
	// we shouldn't get an error at this point
	signature, _ := generateSignature(ac.privateKey, content, cryptoHash)

	// TODO: THIS IS CHEF PROTOCOL SPECIFIC
	// Signature is made up of n 60 length chunks
	base64sig := base64BlockEncode(signature, 60)

	// roll over the auth slice and add the apropriate header
	for index, value := range base64sig {
		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
	}

	return nil
}

// modified from goiardi calcBodyHash
func calcBodyHash(r *http.Request, cryptoHash crypto.Hash) string {
	var bodyStr string

	if r.Body == nil {
		bodyStr = ""
	} else {
		buf := new(bytes.Buffer)
		buf.ReadFrom(r.Body)
		bodyStr = buf.String()
	}

	// Since we're not setting the encoded slice limit
	// we can safely call out [0]
	chkHash := base64BlockEncode(generateHash(bodyStr, cryptoHash), 0)
	return chkHash[0]
}
<commit_msg>DRY out building content body for header sign<commit_after>package chef

import (
	"bytes"
	"crypto"
	"crypto/rsa"
	"errors"
	"fmt"
	"net/http"
	"path"
	"time"
)

// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"

// AuthConfig representing a client and a private key used for encryption
type AuthConfig struct {
	privateKey *rsa.PrivateKey
	clientName string
	cryptoHash crypto.Hash
}

// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request, cryptoHash crypto.Hash) error {
	// sanitize the path for the chef-server
	// chef-server doesn't support '//' in the Hash Path.
	var endpoint string
	if request.URL.Path != "" {
		endpoint = path.Clean(request.URL.Path)
		request.URL.Path = endpoint
	} else {
		endpoint = request.URL.Path
	}

	request.Header.Set("Method", request.Method)
	// Since we're not setting the encoded slice limit
	// we can safely call out [0]
	generatedHash := generateHash(endpoint, cryptoHash)
	if generatedHash == nil {
		return errors.New("Unsupported crypto hashing algorithm")
	}
	request.Header.Set("Hashed Path", base64BlockEncode(generatedHash, 0)[0])
	request.Header.Set("Accept", "application/json")
	request.Header.Set("X-Chef-Version", ChefVersion)
	request.Header.Set("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339))
	request.Header.Set("X-Ops-Userid", ac.clientName)

	var xOpsSignCrypto string
	switch cryptoHash {
	case crypto.MD5:
		xOpsSignCrypto = "algorithm=md5;"
	case crypto.SHA1:
		xOpsSignCrypto = "algorithm=sha1;"
	case crypto.SHA224:
		xOpsSignCrypto = "algorithm=sha224;"
	case crypto.SHA256:
		xOpsSignCrypto = "algorithm=sha256;"
	case crypto.SHA384:
		xOpsSignCrypto = "algorithm=sha384;"
	case crypto.SHA512:
		xOpsSignCrypto = "algorithm=sha512;"
	}

	request.Header.Set("X-Ops-Sign", xOpsSignCrypto+"version=1.0")
	request.Header.Set("X-Ops-Content-Hash", calcBodyHash(request, cryptoHash))

	// To validate the signature it seems to be very particular
	var content string
	for _, key := range []string{"Method", "Hashed Path", "Accept", "X-Chef-Version", "X-Ops-Timestamp", "X-Ops-Userid", "X-Ops-Sign", "X-Ops-Content-Hash"} {
		content += fmt.Sprintf("%s:%s\n", key, request.Header.Get(key))
	}

	// generate signed string of headers
	// Since we've gone through additional validation steps above,
	// we shouldn't get an error at this point
	signature, _ := generateSignature(ac.privateKey, content, cryptoHash)

	// TODO: THIS IS CHEF PROTOCOL SPECIFIC
	// Signature is made up of n 60 length chunks
	base64sig := base64BlockEncode(signature, 60)

	// roll over the auth slice and add the apropriate header
	for index, value := range base64sig {
		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
	}

	return nil
}

// modified from goiardi calcBodyHash
func calcBodyHash(r *http.Request, cryptoHash crypto.Hash) string {
	var bodyStr string

	if r.Body == nil {
		bodyStr = ""
	} else {
		buf := new(bytes.Buffer)
		buf.ReadFrom(r.Body)
		bodyStr = buf.String()
	}

	// Since we're not setting the encoded slice limit
	// we can safely call out [0]
	chkHash := base64BlockEncode(generateHash(bodyStr, cryptoHash), 0)
	return chkHash[0]
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"

	"go/build"
	"github.com/broady/mtgprice/mtgprice"
)

var client *mtgprice.Client

func main() {
	staticDir, err := staticDir()
	if err != nil {
		log.Fatalf("could not find static dir")
	}
	c, err := mtgprice.Open(mtgprice.Opts{
		Filename: "mtgprice.db",
		CardData: filepath.Join(staticDir, "AllCards.json"),
	})
	if err != nil {
		log.Fatalf("could not open db: %v", err)
	}
	client = c

	handleInterrupt()

	http.Handle("/", serveFile(filepath.Join(staticDir, "index.html")))
	http.Handle("/static", http.StripPrefix("static", http.FileServer(http.Dir(staticDir))))
	http.HandleFunc("/api/price", priceHandler)
	http.HandleFunc("/api/info", infoHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func staticDir() (string, error) {
	if fi, err := os.Stat("static"); err == nil && fi.IsDir() {
		return "static", nil
	}
	pkg, err := build.Import("github.com/broady/mtgprice", "", build.FindOnly)
	if err != nil {
		return "", err
	}
	return filepath.Join(pkg.Dir, "static"), nil
}

func priceHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("cardName")
	_, ok := client.CardInfo(name)
	if !ok {
		http.NotFound(w, r)
		return
	}
	p, err := client.PriceForCard(name)
	if err != nil {
		log.Printf("card err: %v", err)
		http.NotFound(w, r)
		return
	}
	json.NewEncoder(w).Encode(p)
}

func infoHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("cardName")
	i, err := client.RichInfo(name)
	if err != nil {
		log.Printf("card err (%s): %v", name, err)
		http.NotFound(w, r)
		return
	}
	if len(i.Names) != 0 {
		i.Name = strings.Join(i.Names, " // ")
		i.Names = []string{}
	}
	json.NewEncoder(w).Encode(i)
}

func handleInterrupt() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		log.Printf("shutting down...")
		if err := client.Close(); err != nil {
			log.Fatalf("could not clean up: %v", err)
		}
		os.Exit(1)
	}()
}

func serveFile(fn string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fn)
	})
}
<commit_msg>Add -addr flag<commit_after>package main

import (
	"encoding/json"
	"flag"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"

	"go/build"

	"github.com/broady/mtgprice/mtgprice"
)

var (
	client *mtgprice.Client
	addr   = flag.String("addr", ":8080", "address/port to listen on")
)

func main() {
	flag.Parse()

	staticDir, err := staticDir()
	if err != nil {
		log.Fatalf("could not find static dir")
	}
	c, err := mtgprice.Open(mtgprice.Opts{
		Filename: "mtgprice.db",
		CardData: filepath.Join(staticDir, "AllCards.json"),
	})
	if err != nil {
		log.Fatalf("could not open db: %v", err)
	}
	client = c

	handleInterrupt()

	http.Handle("/", serveFile(filepath.Join(staticDir, "index.html")))
	http.Handle("/static", http.StripPrefix("static", http.FileServer(http.Dir(staticDir))))
	http.HandleFunc("/api/price", priceHandler)
	http.HandleFunc("/api/info", infoHandler)
	log.Fatal(http.ListenAndServe(*addr, nil))
}

func staticDir() (string, error) {
	if fi, err := os.Stat("static"); err == nil && fi.IsDir() {
		return "static", nil
	}
	pkg, err := build.Import("github.com/broady/mtgprice", "", build.FindOnly)
	if err != nil {
		return "", err
	}
	return filepath.Join(pkg.Dir, "static"), nil
}

func priceHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("cardName")
	_, ok := client.CardInfo(name)
	if !ok {
		http.NotFound(w, r)
		return
	}
	p, err := client.PriceForCard(name)
	if err != nil {
		log.Printf("card err: %v", err)
		http.NotFound(w, r)
		return
	}
	json.NewEncoder(w).Encode(p)
}

func infoHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("cardName")
	i, err := client.RichInfo(name)
	if err != nil {
		log.Printf("card err (%s): %v", name, err)
		http.NotFound(w, r)
		return
	}
	if len(i.Names) != 0 {
		i.Name = strings.Join(i.Names, " // ")
		i.Names = []string{}
	}
	json.NewEncoder(w).Encode(i)
}

func handleInterrupt() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		log.Printf("shutting down...")
		if err := client.Close(); err != nil {
			log.Fatalf("could not clean up: %v", err)
		}
		os.Exit(1)
	}()
}

func serveFile(fn string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fn)
	})
}
<|endoftext|>


<commit_before>package chef

import (
	"bytes"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
)

// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"

// Body wraps io.Reader and adds methods for calculating hashes and detecting content
type Body struct {
	io.Reader
}

// AuthConfig representing a client and a private key used for encryption
//  This is embedded in the Client type
type AuthConfig struct {
	PrivateKey *rsa.PrivateKey
	ClientName string
}

// Client is vessel for public methods used against the chef-server
type Client struct {
	Auth    *AuthConfig
	BaseURL *url.URL
	client  *http.Client

	ACLs         *ACLService
	Clients      *ApiClientService
	Cookbooks    *CookbookService
	DataBags     *DataBagService
	Environments *EnvironmentService
	Nodes        *NodeService
	Principals   *PrincipalService
	Roles        *RoleService
	Sandboxes    *SandboxService
	Search       *SearchService
}

// Config contains the configuration options for a chef client. This is Used primarily in the NewClient() constructor in order to setup a proper client object
type Config struct {
	// This should be the user ID on the chef server
	Name string

	// This is the plain text private Key for the user
	Key string

	// BaseURL is the chef server URL used to connect too. Is using orgs you should include your org in the url
	BaseURL string

	// When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true
	SkipSSL bool

	// Time to wait in seconds before giving up on a request to the server
	Timeout time.Duration
}

/*
An ErrorResponse reports one or more errors caused by an API request.
Thanks to https://github.com/google/go-github
*/
type ErrorResponse struct {
	Response *http.Response // HTTP response that caused this error
}

// Buffer creates a  byte.Buffer copy from a io.Reader resets read on reader to 0,0
func (body *Body) Buffer() *bytes.Buffer {
	var b bytes.Buffer
	if body.Reader == nil {
		return &b
	}

	b.ReadFrom(body.Reader)
	_, err := body.Reader.(io.Seeker).Seek(0, 0)
	if err != nil {
		log.Fatal(err)
	}
	return &b
}

// Hash calculates the body content hash
func (body *Body) Hash() (h string) {
	b := body.Buffer()
	// empty buffs should return a empty string
	if b.Len() == 0 {
		h = HashStr("")
	}
	h = HashStr(b.String())
	return
}

// ContentType returns the content-type string of Body as detected by http.DetectContentType()
func (body *Body) ContentType() string {
	if json.Unmarshal(body.Buffer().Bytes(), &struct{}{}) == nil {
		return "application/json"
	}
	return http.DetectContentType(body.Buffer().Bytes())
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode)
}

// NewClient is the client generator used to instantiate a client for talking to a chef-server
// It is a simple constructor for the Client struct intended as a easy interface for issuing
// signed requests
func NewClient(cfg *Config) (*Client, error) {
	pk, err := PrivateKeyFromString([]byte(cfg.Key))
	if err != nil {
		return nil, err
	}

	baseUrl, _ := url.Parse(cfg.BaseURL)

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.SkipSSL},
	}

	c := &Client{
		Auth: &AuthConfig{
			PrivateKey: pk,
			ClientName: cfg.Name,
		},
		client: &http.Client{
			Transport: tr,
			Timeout:   cfg.Timeout * time.Second,
		},
		BaseURL: baseUrl,
	}
	c.ACLs = &ACLService{client: c}
	c.Clients = &ApiClientService{client: c}
	c.Cookbooks = &CookbookService{client: c}
	c.DataBags = &DataBagService{client: c}
	c.Environments = &EnvironmentService{client: c}
	c.Nodes = &NodeService{client: c}
	c.Principals = &PrincipalService{client: c}
	c.Roles = &RoleService{client: c}
	c.Sandboxes = &SandboxService{client: c}
	c.Search = &SearchService{client: c}
	return c, nil
}

// magicRequestDecoder performs a request on an endpoint, and decodes the response into the passed in Type
func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v interface{}) error {
	req, err := c.NewRequest(method, path, body)
	if err != nil {
		return err
	}

	debug("Request: %+v \n", req)
	res, err := c.Do(req, v)
	if res != nil {
		defer res.Body.Close()
	}
	debug("Response: %+v \n", res)
	if err != nil {
		return err
	}
	return err
}

// NewRequest returns a signed request  suitable for the chef server
func (c *Client) NewRequest(method string, requestUrl string, body io.Reader) (*http.Request, error) {
	relativeUrl, err := url.Parse(requestUrl)
	if err != nil {
		return nil, err
	}
	u := c.BaseURL.ResolveReference(relativeUrl)

	// NewRequest uses a new value object of body
	req, err := http.NewRequest(method, u.String(), body)
	if err != nil {
		return nil, err
	}

	// parse and encode Querystring Values
	values := req.URL.Query()
	req.URL.RawQuery = values.Encode()
	debug("Encoded url %+v", u)

	myBody := &Body{body}

	if body != nil {
		// Detect Content-type
		req.Header.Set("Content-Type", myBody.ContentType())
	}

	// Calculate the body hash
	req.Header.Set("X-Ops-Content-Hash", myBody.Hash())

	// don't have to check this works, signRequest only emits error when signing hash is not valid, and we baked that in
	c.Auth.SignRequest(req)
	return req, nil
}

// CheckResponse receives a pointer to a http.Response and generates an Error via unmarshalling
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}
	return errorResponse
}

// Do is used either internally via our magic request shite or a user may use it
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	// BUG(fujin) tightly coupled
	err = CheckResponse(res) // <--
	if err != nil {
		return res, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, res.Body)
		} else {
			err = json.NewDecoder(res.Body).Decode(v)
			if err != nil {
				return res, err
			}
		}
	}
	return res, nil
}

// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request) error {
	// sanitize the path for the chef-server
	// chef-server doesn't support '//' in the Hash Path.
	var endpoint string
	if request.URL.Path != "" {
		endpoint = path.Clean(request.URL.Path)
		request.URL.Path = endpoint
	} else {
		endpoint = request.URL.Path
	}

	vals := map[string]string{
		"Method":             request.Method,
		"Hashed Path":        HashStr(endpoint),
		"Accept":             "application/json",
		"X-Chef-Version":     ChefVersion,
		"X-Ops-Timestamp":    time.Now().UTC().Format(time.RFC3339),
		"X-Ops-UserId":       ac.ClientName,
		"X-Ops-Sign":         "algorithm=sha1;version=1.0",
		"X-Ops-Content-Hash": request.Header.Get("X-Ops-Content-Hash"),
	}

	for _, key := range []string{"Method", "Accept", "X-Chef-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign"} {
		request.Header.Set(key, vals[key])
	}

	// To validate the signature it seems to be very particular
	var content string
	for _, key := range []string{"Method", "Hashed Path", "X-Ops-Content-Hash", "X-Ops-Timestamp", "X-Ops-UserId"} {
		content += fmt.Sprintf("%s:%s\n", key, vals[key])
	}
	content = strings.TrimSuffix(content, "\n")
	// generate signed string of headers
	// Since we've gone through additional validation steps above,
	// we shouldn't get an error at this point
	signature, err := GenerateSignature(ac.PrivateKey, content)
	if err != nil {
		return err
	}

	// TODO: THIS IS CHEF PROTOCOL SPECIFIC
	// Signature is made up of n 60 length chunks
	base64sig := Base64BlockEncode(signature, 60)

	// roll over the auth slice and add the apropriate header
	for index, value := range base64sig {
		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
	}

	return nil
}

// PrivateKeyFromString parses an RSA private key from a string
func PrivateKeyFromString(key []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, fmt.Errorf("block size invalid for '%s'", string(key))
	}
	rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return rsaKey, nil
}
<commit_msg>Set default timeouts and any proxy env settings<commit_after>package chef

import (
	"bytes"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
)

// ChefVersion that we pretend to emulate
const ChefVersion = "11.12.0"

// Body wraps io.Reader and adds methods for calculating hashes and detecting content
type Body struct {
	io.Reader
}

// AuthConfig representing a client and a private key used for encryption
//  This is embedded in the Client type
type AuthConfig struct {
	PrivateKey *rsa.PrivateKey
	ClientName string
}

// Client is vessel for public methods used against the chef-server
type Client struct {
	Auth    *AuthConfig
	BaseURL *url.URL
	client  *http.Client

	ACLs         *ACLService
	Clients      *ApiClientService
	Cookbooks    *CookbookService
	DataBags     *DataBagService
	Environments *EnvironmentService
	Nodes        *NodeService
	Principals   *PrincipalService
	Roles        *RoleService
	Sandboxes    *SandboxService
	Search       *SearchService
}

// Config contains the configuration options for a chef client. This is Used primarily in the NewClient() constructor in order to setup a proper client object
type Config struct {
	// This should be the user ID on the chef server
	Name string

	// This is the plain text private Key for the user
	Key string

	// BaseURL is the chef server URL used to connect too. Is using orgs you should include your org in the url
	BaseURL string

	// When set to false (default) this will enable SSL Cert Verification. If you need to disable Cert Verification set to true
	SkipSSL bool

	// Time to wait in seconds before giving up on a request to the server
	Timeout time.Duration
}

/*
An ErrorResponse reports one or more errors caused by an API request.
Thanks to https://github.com/google/go-github
*/
type ErrorResponse struct {
	Response *http.Response // HTTP response that caused this error
}

// Buffer creates a  byte.Buffer copy from a io.Reader resets read on reader to 0,0
func (body *Body) Buffer() *bytes.Buffer {
	var b bytes.Buffer
	if body.Reader == nil {
		return &b
	}

	b.ReadFrom(body.Reader)
	_, err := body.Reader.(io.Seeker).Seek(0, 0)
	if err != nil {
		log.Fatal(err)
	}
	return &b
}

// Hash calculates the body content hash
func (body *Body) Hash() (h string) {
	b := body.Buffer()
	// empty buffs should return a empty string
	if b.Len() == 0 {
		h = HashStr("")
	}
	h = HashStr(b.String())
	return
}

// ContentType returns the content-type string of Body as detected by http.DetectContentType()
func (body *Body) ContentType() string {
	if json.Unmarshal(body.Buffer().Bytes(), &struct{}{}) == nil {
		return "application/json"
	}
	return http.DetectContentType(body.Buffer().Bytes())
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode)
}

// NewClient is the client generator used to instantiate a client for talking to a chef-server
// It is a simple constructor for the Client struct intended as a easy interface for issuing
// signed requests
func NewClient(cfg *Config) (*Client, error) {
	pk, err := PrivateKeyFromString([]byte(cfg.Key))
	if err != nil {
		return nil, err
	}

	baseUrl, _ := url.Parse(cfg.BaseURL)

	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSClientConfig:     &tls.Config{InsecureSkipVerify: cfg.SkipSSL},
		TLSHandshakeTimeout: 10 * time.Second,
	}

	c := &Client{
		Auth: &AuthConfig{
			PrivateKey: pk,
			ClientName: cfg.Name,
		},
		client: &http.Client{
			Transport: tr,
			Timeout:   cfg.Timeout * time.Second,
		},
		BaseURL: baseUrl,
	}
	c.ACLs = &ACLService{client: c}
	c.Clients = &ApiClientService{client: c}
	c.Cookbooks = &CookbookService{client: c}
	c.DataBags = &DataBagService{client: c}
	c.Environments = &EnvironmentService{client: c}
	c.Nodes = &NodeService{client: c}
	c.Principals = &PrincipalService{client: c}
	c.Roles = &RoleService{client: c}
	c.Sandboxes = &SandboxService{client: c}
	c.Search = &SearchService{client: c}
	return c, nil
}

// magicRequestDecoder performs a request on an endpoint, and decodes the response into the passed in Type
func (c *Client) magicRequestDecoder(method, path string, body io.Reader, v interface{}) error {
	req, err := c.NewRequest(method, path, body)
	if err != nil {
		return err
	}

	debug("Request: %+v \n", req)
	res, err := c.Do(req, v)
	if res != nil {
		defer res.Body.Close()
	}
	debug("Response: %+v \n", res)
	if err != nil {
		return err
	}
	return err
}

// NewRequest returns a signed request  suitable for the chef server
func (c *Client) NewRequest(method string, requestUrl string, body io.Reader) (*http.Request, error) {
	relativeUrl, err := url.Parse(requestUrl)
	if err != nil {
		return nil, err
	}
	u := c.BaseURL.ResolveReference(relativeUrl)

	// NewRequest uses a new value object of body
	req, err := http.NewRequest(method, u.String(), body)
	if err != nil {
		return nil, err
	}

	// parse and encode Querystring Values
	values := req.URL.Query()
	req.URL.RawQuery = values.Encode()
	debug("Encoded url %+v", u)

	myBody := &Body{body}

	if body != nil {
		// Detect Content-type
		req.Header.Set("Content-Type", myBody.ContentType())
	}

	// Calculate the body hash
	req.Header.Set("X-Ops-Content-Hash", myBody.Hash())

	// don't have to check this works, signRequest only emits error when signing hash is not valid, and we baked that in
	c.Auth.SignRequest(req)
	return req, nil
}

// CheckResponse receives a pointer to a http.Response and generates an Error via unmarshalling
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}
	return errorResponse
}

// Do is used either internally via our magic request shite or a user may use it
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	// BUG(fujin) tightly coupled
	err = CheckResponse(res) // <--
	if err != nil {
		return res, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, res.Body)
		} else {
			err = json.NewDecoder(res.Body).Decode(v)
			if err != nil {
				return res, err
			}
		}
	}
	return res, nil
}

// SignRequest modifies headers of an http.Request
func (ac AuthConfig) SignRequest(request *http.Request) error {
	// sanitize the path for the chef-server
	// chef-server doesn't support '//' in the Hash Path.
	var endpoint string
	if request.URL.Path != "" {
		endpoint = path.Clean(request.URL.Path)
		request.URL.Path = endpoint
	} else {
		endpoint = request.URL.Path
	}

	vals := map[string]string{
		"Method":             request.Method,
		"Hashed Path":        HashStr(endpoint),
		"Accept":             "application/json",
		"X-Chef-Version":     ChefVersion,
		"X-Ops-Timestamp":    time.Now().UTC().Format(time.RFC3339),
		"X-Ops-UserId":       ac.ClientName,
		"X-Ops-Sign":         "algorithm=sha1;version=1.0",
		"X-Ops-Content-Hash": request.Header.Get("X-Ops-Content-Hash"),
	}

	for _, key := range []string{"Method", "Accept", "X-Chef-Version", "X-Ops-Timestamp", "X-Ops-UserId", "X-Ops-Sign"} {
		request.Header.Set(key, vals[key])
	}

	// To validate the signature it seems to be very particular
	var content string
	for _, key := range []string{"Method", "Hashed Path", "X-Ops-Content-Hash", "X-Ops-Timestamp", "X-Ops-UserId"} {
		content += fmt.Sprintf("%s:%s\n", key, vals[key])
	}
	content = strings.TrimSuffix(content, "\n")
	// generate signed string of headers
	// Since we've gone through additional validation steps above,
	// we shouldn't get an error at this point
	signature, err := GenerateSignature(ac.PrivateKey, content)
	if err != nil {
		return err
	}

	// TODO: THIS IS CHEF PROTOCOL SPECIFIC
	// Signature is made up of n 60 length chunks
	base64sig := Base64BlockEncode(signature, 60)

	// roll over the auth slice and add the apropriate header
	for index, value := range base64sig {
		request.Header.Set(fmt.Sprintf("X-Ops-Authorization-%d", index+1), string(value))
	}

	return nil
}

// PrivateKeyFromString parses an RSA private key from a string
func PrivateKeyFromString(key []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, fmt.Errorf("block size invalid for '%s'", string(key))
	}
	rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return rsaKey, nil
}
<|endoftext|>


<commit_before>package bufio

import (
	"errors"
	"fmt"
	"github.com/alexandre-normand/glukit/app/glukitio"
	"github.com/alexandre-normand/glukit/app/model"
	"log"
)

type BufferedGlucoseReadBatchWriter struct {
	buf []model.DayOfGlucoseReads
	n   int
	wr  glukitio.GlucoseReadBatchWriter
	err error
}

// NewGlucoseReadWriterSize returns a new Writer whose Buffer has the specified size.
func NewGlucoseReadWriterSize(wr glukitio.GlucoseReadBatchWriter, size int) *BufferedGlucoseReadBatchWriter {
	// Is it already a Writer?
	b, ok := wr.(*BufferedGlucoseReadBatchWriter)
	if ok && len(b.buf) >= size {
		log.Printf("Already a writer")
		return b
	}

	if size <= 0 {
		size = defaultBufSize
	}
	w := new(BufferedGlucoseReadBatchWriter)
	w.buf = make([]model.DayOfGlucoseReads, size)

	log.Printf("Creating empty Buffer [%v] at address [%p]", w.buf[:w.n], &w.buf)
	w.wr = wr

	return w
}

// WriteGlucose writes a single model.DayOfGlucoseReads
func (b *BufferedGlucoseReadBatchWriter) WriteGlucoseReadBatch(p []model.GlucoseRead) (nn int, err error) {
	log.Printf("Before building of batch, current buffer is:\n%v\n", b.buf[:b.n])
	readsCopy := make([]model.GlucoseRead, len(p))
	if copied := copy(readsCopy, p); copied != len(p) {
		return 0, errors.New(fmt.Sprintf("Failed to create copy of glucose read batch to buffer write, copied [%d] elements but expected [%d]", copied, len(p)))
	}

	dayOfGlucoseReads := []model.DayOfGlucoseReads{model.DayOfGlucoseReads{readsCopy}}

	log.Printf("Creating batch [%p] of reads with %d elements for data: [%v]", &dayOfGlucoseReads, len(p), dayOfGlucoseReads)
	n, err := b.WriteGlucoseReadBatches(dayOfGlucoseReads)
	if err != nil {
		return n * len(p), err
	}

	return len(p), nil
}

// WriteGlucoseReadBatches writes the contents of p into the Buffer.
// It returns the number of batches written.
// If nn < len(p), it also returns an error explaining
// why the write is short.
func (b *BufferedGlucoseReadBatchWriter) WriteGlucoseReadBatches(p []model.DayOfGlucoseReads) (nn int, err error) {
	log.Printf("Before write, current Buffer is:\n%v\n", b.buf[:b.n])
	log.Printf("Buffer has %d space available with this batch being of length %d", b.Available(), len(p))
	for len(p) > b.Available() && b.err == nil {
		var n int
		log.Printf("Inner copying data at position %d: %v into\n\n-------\n%v", b.n, p, b.buf[:b.n])
		n = copy(b.buf[b.n:], p)
		log.Printf("Inner copied %d items", n)
		b.n += n
		log.Printf("Buffer is [%v]", b.buf[:b.n])
		b.Flush()
		nn += n
		p = p[n:]
	}
	if b.err != nil {
		return nn, b.err
	}
	//log.Printf("Copying data at position %d: %v into\n\n-------\n%v", b.n, p, b.Buf[:b.n])
	log.Printf("Doing the copy starting at index %d, Buffer is [%v]", b.n, b.buf[:b.n])
	n := copy(b.buf[b.n:], p)
	log.Printf("Buffer is [%v]", b.buf[:b.n])
	b.n += n
	nn += n

	log.Printf("Copied %d items from source (%p) to Buffer (%p), current Buffer is:\n%v\n", n, &p, &b.buf, b.buf[:b.n])
	log.Printf("Buffer is [%v]", b.buf[:b.n])

	return nn, nil
}

// Flush writes any Buffered data to the underlying glukitio.Writer.
func (b *BufferedGlucoseReadBatchWriter) Flush() error {
	log.Printf("In flush.. with n=%d", b.n)
	if b.err != nil {
		return b.err
	}
	if b.n == 0 {
		return nil
	}

	log.Printf("Calling physical persistence with b=%d and data: %v", b.n, b.buf[:b.n])
	n, err := b.wr.WriteGlucoseReadBatches(b.buf[:b.n])
	log.Printf("Buffer is [%v]", b.buf[:b.n])
	if n < b.n && err == nil {
		err = glukitio.ErrShortWrite
	}
	if err != nil {
		if n > 0 && n < b.n {
			copy(b.buf[0:b.n-n], b.buf[n:b.n])
			log.Printf("Buffer is [%v]", b.buf[:b.n])
		}
		b.n -= n
		b.err = err
		return err
	}
	b.n = 0
	return nil
}

// Available returns how many bytes are unused in the Buffer.
func (b *BufferedGlucoseReadBatchWriter) Available() int {
	return len(b.buf) - b.n
}

// Buffered returns the number of bytes that have been written into the current Buffer.
func (b *BufferedGlucoseReadBatchWriter) Buffered() int {
	return b.n
}
<commit_msg>Remove Extra Logging From Glucose Buffered IO.<commit_after>package bufio

import (
	"errors"
	"fmt"
	"github.com/alexandre-normand/glukit/app/glukitio"
	"github.com/alexandre-normand/glukit/app/model"
)

type BufferedGlucoseReadBatchWriter struct {
	buf []model.DayOfGlucoseReads
	n   int
	wr  glukitio.GlucoseReadBatchWriter
	err error
}

// NewGlucoseReadWriterSize returns a new Writer whose Buffer has the specified size.
func NewGlucoseReadWriterSize(wr glukitio.GlucoseReadBatchWriter, size int) *BufferedGlucoseReadBatchWriter {
	// Is it already a Writer?
	b, ok := wr.(*BufferedGlucoseReadBatchWriter)
	if ok && len(b.buf) >= size {
		return b
	}

	if size <= 0 {
		size = defaultBufSize
	}
	w := new(BufferedGlucoseReadBatchWriter)
	w.buf = make([]model.DayOfGlucoseReads, size)

	w.wr = wr

	return w
}

// WriteGlucose writes a single model.DayOfGlucoseReads
func (b *BufferedGlucoseReadBatchWriter) WriteGlucoseReadBatch(p []model.GlucoseRead) (nn int, err error) {
	readsCopy := make([]model.GlucoseRead, len(p))
	if copied := copy(readsCopy, p); copied != len(p) {
		return 0, errors.New(fmt.Sprintf("Failed to create copy of glucose read batch to buffer write, copied [%d] elements but expected [%d]", copied, len(p)))
	}

	dayOfGlucoseReads := []model.DayOfGlucoseReads{model.DayOfGlucoseReads{readsCopy}}

	n, err := b.WriteGlucoseReadBatches(dayOfGlucoseReads)
	if err != nil {
		return n * len(p), err
	}

	return len(p), nil
}

// WriteGlucoseReadBatches writes the contents of p into the Buffer.
// It returns the number of batches written.
// If nn < len(p), it also returns an error explaining
// why the write is short.
func (b *BufferedGlucoseReadBatchWriter) WriteGlucoseReadBatches(p []model.DayOfGlucoseReads) (nn int, err error) {
	for len(p) > b.Available() && b.err == nil {
		var n int
		n = copy(b.buf[b.n:], p)
		b.n += n

		b.Flush()
		nn += n
		p = p[n:]
	}
	if b.err != nil {
		return nn, b.err
	}

	n := copy(b.buf[b.n:], p)
	b.n += n
	nn += n

	return nn, nil
}

// Flush writes any Buffered data to the underlying glukitio.Writer.
func (b *BufferedGlucoseReadBatchWriter) Flush() error {
	if b.err != nil {
		return b.err
	}
	if b.n == 0 {
		return nil
	}

	n, err := b.wr.WriteGlucoseReadBatches(b.buf[:b.n])

	if n < b.n && err == nil {
		err = glukitio.ErrShortWrite
	}
	if err != nil {
		if n > 0 && n < b.n {
			copy(b.buf[0:b.n-n], b.buf[n:b.n])
		}
		b.n -= n
		b.err = err
		return err
	}
	b.n = 0
	return nil
}

// Available returns how many bytes are unused in the Buffer.
func (b *BufferedGlucoseReadBatchWriter) Available() int {
	return len(b.buf) - b.n
}

// Buffered returns the number of bytes that have been written into the current Buffer.
func (b *BufferedGlucoseReadBatchWriter) Buffered() int {
	return b.n
}
<|endoftext|>


<commit_before>package gossm

import (
	"fmt"
	"html/template"
	"net/http"
	"time"
)

func calculateServerUptime(statusAtTime []*statusAtTime) string {
	if len(statusAtTime) == 0 {
		return "unknown"
	}

	var sum float64

	for _, val := range statusAtTime {
		var i float64
		if val.Status {
			i = 1
		} else {
			i = 0
		}
		sum += i
	}

	return fmt.Sprintf("%.2f", sum/float64(len(statusAtTime))*100)
}

func lastStatus(statusAtTime []*statusAtTime) string {
	if len(statusAtTime) == 0 {
		return "Not yet checked"
	}
	lastChecked := statusAtTime[len(statusAtTime)-1]
	difference := time.Since(lastChecked.Time)
	status := "OK"
	if !lastChecked.Status {
		status = "ERR"
	}
	return fmt.Sprintf("%s, %.0f seconds ago", status, difference.Seconds())
}

func RunHttp(address string, monitor *Monitor) {
	funcMap := template.FuncMap{
		"calculateServerUptime": calculateServerUptime,
		"lastStatus":            lastStatus,
	}

	t := template.Must(template.New("main").Funcs(funcMap).Parse(`<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<title>GOSSM - Dashboard</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">

Dashboard

			{{ range $server, $statusAtTime := .}}

{{ $server.Name }}

{{ $server }}
tested {{ len $statusAtTime }} times
{{ $statusAtTime | lastStatus }}

						{{ $statusAtTime | calculateServerUptime }}%

			{{ end }}

`))

	http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		t.Execute(rw, monitor.serverStatusData.GetServerStatus())
	})

	http.ListenAndServe(address, nil)
}
<commit_msg>Removed unnecessary javascript<commit_after>package gossm

import (
	"fmt"
	"html/template"
	"net/http"
	"time"
)

func calculateServerUptime(statusAtTime []*statusAtTime) string {
	if len(statusAtTime) == 0 {
		return "unknown"
	}

	var sum float64

	for _, val := range statusAtTime {
		var i float64
		if val.Status {
			i = 1
		} else {
			i = 0
		}
		sum += i
	}

	return fmt.Sprintf("%.2f", sum/float64(len(statusAtTime))*100)
}

func lastStatus(statusAtTime []*statusAtTime) string {
	if len(statusAtTime) == 0 {
		return "Not yet checked"
	}
	lastChecked := statusAtTime[len(statusAtTime)-1]
	difference := time.Since(lastChecked.Time)
	status := "OK"
	if !lastChecked.Status {
		status = "ERR"
	}
	return fmt.Sprintf("%s, %.0f seconds ago", status, difference.Seconds())
}

func RunHttp(address string, monitor *Monitor) {
	funcMap := template.FuncMap{
		"calculateServerUptime": calculateServerUptime,
		"lastStatus":            lastStatus,
	}

	t := template.Must(template.New("main").Funcs(funcMap).Parse(`<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<title>GOSSM - Dashboard</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">

Dashboard

			{{ range $server, $statusAtTime := .}}

{{ $server.Name }}

{{ $server }}
tested {{ len $statusAtTime }} times
{{ $statusAtTime | lastStatus }}

						{{ $statusAtTime | calculateServerUptime }}%

			{{ end }}

`))

	http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		t.Execute(rw, monitor.serverStatusData.GetServerStatus())
	})

	http.ListenAndServe(address, nil)
}
<|endoftext|>


<commit_before>// Copyright 2012 The AEGo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package user

import (
	"net/http"
)

var config = map[string]string{
	"login_url": "/-/auth/login",
}

// LoginRequired is a wrapper for http.HandleFunc. If the requesting
// User is not logged in, they will be redirect to the login page.
func LoginRequired(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if id, _ := CurrentUserID(r); id == "" {
			http.Redirect(w, r, config["login_url"], http.StatusForbidden)
		}
		fn(w, r)
	}
}

// AdminRequired is a wrapper for http.HandleFuc. If the requesting
// User is *not* an admin, they will redirect to the login page.
func AdminRequired(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !CurrentUserHasRole(w, r, "admin") {
			http.Redirect(w, r, config["login_url"], http.StatusForbidden)
		}
		fn(w, r)
	}
}
<commit_msg>Moved LoginURL to var; Redirect with Status Found - Status Forbidden does not issue the redirect<commit_after>// Copyright 2012 The GAEGo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package user

import (
	"net/http"
)

var LoginURL = "/login"

// LoginRequired is a wrapper for http.HandleFunc. If the requesting
// User is not logged in, they will be redirect to the login page.
func LoginRequired(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if id, _ := CurrentUserID(r); id == "" {
			http.Redirect(w, r, LoginURL, http.StatusFound)
			return
		}
		fn(w, r)
	}
}

// AdminRequired is a wrapper for http.HandleFuc. If the requesting
// User is *not* an admin, they will redirect to the login page.
func AdminRequired(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !CurrentUserHasRole(w, r, "admin") {
			http.Redirect(w, r, LoginURL, http.StatusFound)
		}
		fn(w, r)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/kardianos/osext"
	"golang.org/x/sys/windows/svc"
)

const dataDirectory = `Google/Cloud Operations/Ops Agent`
const serviceName = "Google Cloud Ops Agent"

func main() {
	if ok, err := svc.IsWindowsService(); ok && err == nil {
		if err := run(serviceName); err != nil {
			log.Fatal(err)
		}
	} else if err != nil {
		log.Fatalf("failed to talk to service control manager: %v", err)
	} else {
		if err := install(); err != nil {
			log.Fatal(err)
		}
		log.Printf("installed services")
	}
}

var services []struct {
	name    string
	exepath string
	args    []string
}

func init() {
	if err := initServices(); err != nil {
		log.Fatal(err)
	}
}

func initServices() error {
	// Identify relevant paths
	self, err := osext.Executable()
	if err != nil {
		return fmt.Errorf("could not determine own path: %w", err)
	}
	base, err := osext.ExecutableFolder()
	if err != nil {
		return fmt.Errorf("could not determine binary path: %w", err)
	}
	configOutDir := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, "generated_configs")
	if err := os.MkdirAll(configOutDir, 0644); err != nil {
		return err
	}
	fluentbitStoragePath := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, `fluentbit\buffers`)
	if err := os.MkdirAll(fluentbitStoragePath, 0644); err != nil {
		return err
	}
	logDirectory := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, "log")
	if err := os.MkdirAll(logDirectory, 0644); err != nil {
		return err
	}
	// TODO: Write meaningful descriptions for these services
	services = []struct {
		name    string
		exepath string
		args    []string
	}{
		{
			serviceName,
			self,
			[]string{"-in", filepath.Join(base, "../config/config.yaml"), "-out", configOutDir},
		},
		{
			fmt.Sprintf("%s - Metrics Agent", serviceName),
			filepath.Join(base, "google-cloud-metrics-agent_windows_amd64.exe"),
			[]string{
				"--add-instance-id=false",
				"--config=" + filepath.Join(configOutDir, `otel\otel.yaml`),
			},
		},
		{
			// TODO: fluent-bit hardcodes a service name of "fluent-bit"; do we need to match that?
			fmt.Sprintf("%s - Logging Agent", serviceName),
			filepath.Join(base, "fluent-bit.exe"),
			[]string{
				"-c", filepath.Join(configOutDir, `fluentbit\fluent_bit_main.conf`),
				"-R", filepath.Join(configOutDir, `fluentbit\fluent_bit_parser.conf`),
				"--storage_path", fluentbitStoragePath,
				"--log_file", filepath.Join(logDirectory, "logging-module.log"),
			},
		},
	}
	return nil
}
<commit_msg>Use the same path for storage-path that the config generator bakes into the binary<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/kardianos/osext"
	"golang.org/x/sys/windows/svc"
)

const dataDirectory = `Google/Cloud Operations/Ops Agent`
const serviceName = "Google Cloud Ops Agent"

func main() {
	if ok, err := svc.IsWindowsService(); ok && err == nil {
		if err := run(serviceName); err != nil {
			log.Fatal(err)
		}
	} else if err != nil {
		log.Fatalf("failed to talk to service control manager: %v", err)
	} else {
		if err := install(); err != nil {
			log.Fatal(err)
		}
		log.Printf("installed services")
	}
}

var services []struct {
	name    string
	exepath string
	args    []string
}

func init() {
	if err := initServices(); err != nil {
		log.Fatal(err)
	}
}

func initServices() error {
	// Identify relevant paths
	self, err := osext.Executable()
	if err != nil {
		return fmt.Errorf("could not determine own path: %w", err)
	}
	base, err := osext.ExecutableFolder()
	if err != nil {
		return fmt.Errorf("could not determine binary path: %w", err)
	}
	configOutDir := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, "generated_configs")
	if err := os.MkdirAll(configOutDir, 0644); err != nil {
		return err
	}
	fluentbitStoragePath := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, `run\buffers`)
	if err := os.MkdirAll(fluentbitStoragePath, 0644); err != nil {
		return err
	}
	logDirectory := filepath.Join(os.Getenv("PROGRAMDATA"), dataDirectory, "log")
	if err := os.MkdirAll(logDirectory, 0644); err != nil {
		return err
	}
	// TODO: Write meaningful descriptions for these services
	services = []struct {
		name    string
		exepath string
		args    []string
	}{
		{
			serviceName,
			self,
			[]string{"-in", filepath.Join(base, "../config/config.yaml"), "-out", configOutDir},
		},
		{
			fmt.Sprintf("%s - Metrics Agent", serviceName),
			filepath.Join(base, "google-cloud-metrics-agent_windows_amd64.exe"),
			[]string{
				"--add-instance-id=false",
				"--config=" + filepath.Join(configOutDir, `otel\otel.yaml`),
			},
		},
		{
			// TODO: fluent-bit hardcodes a service name of "fluent-bit"; do we need to match that?
			fmt.Sprintf("%s - Logging Agent", serviceName),
			filepath.Join(base, "fluent-bit.exe"),
			[]string{
				"-c", filepath.Join(configOutDir, `fluentbit\fluent_bit_main.conf`),
				"-R", filepath.Join(configOutDir, `fluentbit\fluent_bit_parser.conf`),
				"--storage_path", fluentbitStoragePath,
				"--log_file", filepath.Join(logDirectory, "logging-module.log"),
			},
		},
	}
	return nil
}
<|endoftext|>


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

package cmd

import (
	"context"
	"log"
	"path/filepath"
	"strings"

	"github.com/apigee/registry/cmd/registry/core"
	"github.com/apigee/registry/connection"
	"github.com/googleapis/gnostic/metrics/vocabulary"
	"github.com/spf13/cobra"
)

func init() {
	vocabularyCmd.AddCommand(vocabularyUniqueCmd)
	vocabularyUniqueCmd.Flags().String("output_id", "", "id of property to store output.")
}

// vocabularyUniqueCmd represents the vocabulary unique command
var vocabularyUniqueCmd = &cobra.Command{
	Use:   "unique",
	Short: "Compute the unique subsets of each member of specified vocabularies.",
	Long:  "Compute the unique subsets of each member of specified vocabularies.",
	Args:  cobra.MinimumNArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		var err error
		flagset := cmd.LocalFlags()
		outputPropertyID, err := flagset.GetString("output_id")
		if err != nil {
			log.Fatalf("%s", err.Error())
		}
		if strings.Contains(outputPropertyID, "/") {
			log.Fatal("output_id must specify a property id (final segment only) and not a full name.")
		}
		ctx := context.TODO()
		client, err := connection.NewClient(ctx)
		if err != nil {
			log.Fatalf("%s", err.Error())
		}
		names, inputs := collectInputs(ctx, client, args, vocabularyFilter)
		output := vocabulary.FilterCommon(inputs)
		if outputPropertyID != "" {
			for i, unique := range output.Vocabularies {
				outputPropertyName := filepath.Dir(names[i]) + "/" + outputPropertyID
				setVocabularyToProperty(ctx, client, unique, outputPropertyName)
			}
		} else {
			core.PrintMessage(output)
		}
	},
}
<commit_msg>add registry vocabulary versions command<commit_after>// Copyright 2020 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"context"
	"log"
	"path/filepath"
	"strings"

	"github.com/apigee/registry/cmd/registry/core"
	"github.com/apigee/registry/connection"
	"github.com/googleapis/gnostic/metrics/vocabulary"
	"github.com/spf13/cobra"
)

func init() {
	vocabularyCmd.AddCommand(vocabularyUniqueCmd)
	vocabularyUniqueCmd.Flags().String("output_id", "vocabulary-unique", "id of property to store output.")
}

// vocabularyUniqueCmd represents the vocabulary unique command
var vocabularyUniqueCmd = &cobra.Command{
	Use:   "unique",
	Short: "Compute the unique subsets of each member of specified vocabularies.",
	Long:  "Compute the unique subsets of each member of specified vocabularies.",
	Args:  cobra.MinimumNArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		var err error
		flagset := cmd.LocalFlags()
		outputPropertyID, err := flagset.GetString("output_id")
		if err != nil {
			log.Fatalf("%s", err.Error())
		}
		if strings.Contains(outputPropertyID, "/") {
			log.Fatal("output_id must specify a property id (final segment only) and not a full name.")
		}
		ctx := context.TODO()
		client, err := connection.NewClient(ctx)
		if err != nil {
			log.Fatalf("%s", err.Error())
		}
		names, inputs := collectInputs(ctx, client, args, vocabularyFilter)
		output := vocabulary.FilterCommon(inputs)
		if outputPropertyID != "" {
			for i, unique := range output.Vocabularies {
				outputPropertyName := filepath.Dir(names[i]) + "/" + outputPropertyID
				setVocabularyToProperty(ctx, client, unique, outputPropertyName)
			}
		} else {
			core.PrintMessage(output)
		}
	},
}
<|endoftext|>


<commit_before>package gat

import (
	"testing"
	"time"

	libgit "github.com/libgit2/git2go"
	. "github.com/smartystreets/goconvey/convey"

	"polydawn.net/danggit/lib/testutil"
)

func TestCommit(t *testing.T) {
	Convey("Given a local git repo", t, testutil.WithTmpdir(func() {
		repo, err := libgit.InitRepository("repo", true)
		maybePanic(err)

		Convey("and given some commits and branches", func() {
			// shove in file content
			blobOid, err := repo.CreateBlobFromBuffer([]byte("hello, world!\n"))
			maybePanic(err)

			// assemble tree
			treeBuilder, err := repo.TreeBuilder()
			maybePanic(err)
			defer treeBuilder.Free()
			treeBuilder.Insert("thefile", blobOid, libgit.FilemodeBlob)
			treeOid, err := treeBuilder.Write()
			maybePanic(err)
			// immediately look it up again because silly api
			tree, err := repo.LookupTree(treeOid)
			maybePanic(err)

			// assemble commit
			author := &libgit.Signature{
				Name:  "author",
				Email: "email@domain.wow",
				When:  time.Date(2009, 10, 14, 12, 0, 0, 0, time.UTC),
			}
			commitOid, err := repo.CreateCommit(
				"",
				author,
				author,
				"log message",
				tree,
			)
			maybePanic(err)
			// immediately look it up again because silly api
			commit, err := repo.LookupCommit(commitOid)
			maybePanic(err)

			// create branch
			branch, err := repo.CreateBranch("branchname", commit, false)
			maybePanic(err)
			branch.Free()

			Convey("execgit believe our work", func() {
				So(
					execgit.Bake("ls-remote", "repo").Output(),
					ShouldEqual,
					"5409e1f57cf0ffe7a542e78a1c69ae715f2d2abc   refs/heads/branchname\n",
				)
			})
		})
	}))
}
<commit_msg>dev needs ci badly<commit_after>package gat

import (
	"testing"
	"time"

	libgit "github.com/libgit2/git2go"
	. "github.com/smartystreets/goconvey/convey"

	"polydawn.net/danggit/lib/testutil"
)

func TestCommit(t *testing.T) {
	Convey("Given a local git repo", t, testutil.WithTmpdir(func() {
		repo, err := libgit.InitRepository("repo", true)
		maybePanic(err)

		Convey("and given some commits and branches", func() {
			// shove in file content
			blobOid, err := repo.CreateBlobFromBuffer([]byte("hello, world!\n"))
			maybePanic(err)

			// assemble tree
			treeBuilder, err := repo.TreeBuilder()
			maybePanic(err)
			defer treeBuilder.Free()
			treeBuilder.Insert("thefile", blobOid, libgit.FilemodeBlob)
			treeOid, err := treeBuilder.Write()
			maybePanic(err)
			// immediately look it up again because silly api
			tree, err := repo.LookupTree(treeOid)
			maybePanic(err)

			// assemble commit
			author := &libgit.Signature{
				Name:  "author",
				Email: "email@domain.wow",
				When:  time.Date(2009, 10, 14, 12, 0, 0, 0, time.UTC),
			}
			commitOid, err := repo.CreateCommit(
				"",
				author,
				author,
				"log message",
				tree,
			)
			maybePanic(err)
			// immediately look it up again because silly api
			commit, err := repo.LookupCommit(commitOid)
			maybePanic(err)

			// create branch
			branch, err := repo.CreateBranch("branchname", commit, false)
			maybePanic(err)
			branch.Free()

			Convey("execgit believe our work", func() {
				So(
					execgit.Bake("ls-remote", "repo").Output(),
					ShouldResemble,
					"5409e1f57cf0ffe7a542e78a1c69ae715f2d2abc\trefs/heads/branchname\n",
				)
			})
		})
	}))
}
<|endoftext|>