<commit_before>package mutator

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	admission "k8s.io/api/admission/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	"github.com/datawire/ambassador/pkg/kates"
	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/v2/cmd/traffic/cmd/manager/managerutil"
	"github.com/telepresenceio/telepresence/v2/pkg/install"
)

var podResource = metav1.GroupVersionResource{Version: "v1", Group: "", Resource: "pods"}
var findMatchingService = install.FindMatchingService

func agentInjector(ctx context.Context, req *admission.AdmissionRequest) ([]patchOperation, error) {
	// This handler should only get called on Pod objects as per the MutatingWebhookConfiguration in the YAML file.
	// Pod objects are immutable, hence we only care about the CREATE event.
	// Applying patches to Pods instead of Deployments means we don't have side effects on
	// user-managed Deployments and Services. It also means we don't have to manage update flows
	// such as removing or updating the sidecar in Deployments objects... a new Pod just gets created instead!

	// If (for whatever reason) this handler is invoked on an object different than a Pod,
	// issue a log message and let the object request pass through.
	if req.Resource != podResource {
		dlog.Debugf(ctx, "expect resource to be %s, got %s; skipping", podResource, req.Resource)
		return nil, nil
	}

	// Parse the Pod object.
	raw := req.Object.Raw
	pod := corev1.Pod{}
	if _, _, err := universalDeserializer.Decode(raw, nil, &pod); err != nil {
		return nil, fmt.Errorf("could not deserialize pod object: %v", err)
	}

	podNamespace := pod.Namespace
	if podNamespace == "" {
		// It is very probable the pod was not yet assigned a namespace,
		// in which case we should use the AdmissionRequest namespace.
		podNamespace = req.Namespace
	}
	podName := pod.Name
	if podName == "" {
		// It is very probable the pod was not yet assigned a name,
		// in which case we should use the metadata generated name.
		podName = pod.ObjectMeta.GenerateName
	}

	// Validate traffic-agent injection preconditions.
	refPodName := fmt.Sprintf("%s.%s", podName, podNamespace)
	if podName == "" || podNamespace == "" {
		dlog.Debugf(ctx, "Unable to extract pod name and/or namespace (got %q); skipping", refPodName)
		return nil, nil
	}

	if pod.Annotations[install.InjectAnnotation] != "enabled" {
		dlog.Debugf(ctx, `The %s pod has not enabled %s container injection through %q annotation; skipping`,
			refPodName, install.AgentContainerName, install.InjectAnnotation)
		return nil, nil
	}

	for _, container := range pod.Spec.Containers {
		if container.Name == install.AgentContainerName {
			dlog.Infof(ctx, "The %s pod already has a %q container; skipping", refPodName, install.AgentContainerName)
			return nil, nil
		}
	}

	// Make the kates client available in the context
	// TODO: Use the kubernetes SharedInformerFactory instead
	client, err := kates.NewClient(kates.ClientConfig{})
	if err != nil {
		return nil, fmt.Errorf("failed to create new kates client: %w", err)
	}

	svc, err := findMatchingService(ctx, client, "", "", podNamespace, pod.Labels)
	if err != nil {
		dlog.Error(ctx, err)
		return nil, nil
	}

	// The ServicePortAnnotation is expected to contain a string that identifies the service port.
	portNameOrNumber := pod.Annotations[install.ServicePortAnnotation]
	servicePort, appContainer, containerPortIndex, err := install.FindMatchingPort(pod.Spec.Containers, portNameOrNumber, svc)
	if err != nil {
		dlog.Error(ctx, err)
		return nil, nil
	}

	env := managerutil.GetEnv(ctx)
	ports := appContainer.Ports
	for i := range ports {
		if ports[i].ContainerPort == env.AgentPort {
			dlog.Infof(ctx, "the %s pod container is exposing the same port (%d) as the %s sidecar; skipping",
				refPodName, env.AgentPort, install.AgentContainerName)
			return nil, nil
		}
	}

	if svc.Spec.ClusterIP == "None" {
		return nil, fmt.Errorf("intercepts of headless service: %s.%s won't work "+
			"see https://github.com/telepresenceio/telepresence/issues/1632",
			svc.Name, svc.Namespace)
	}

	if servicePort.TargetPort.Type == intstr.Int {
		return nil, fmt.Errorf("intercepts of service %s.%s won't work because it has an integer targetPort",
			svc.Name, svc.Namespace)
	}

	appPort := appContainer.Ports[containerPortIndex]

	// Create patch operations to add the traffic-agent sidecar
	dlog.Infof(ctx, "Injecting %s into pod %s", install.AgentContainerName, refPodName)

	var patches []patchOperation
	patches, err = addAgentContainer(ctx, svc, servicePort, appContainer, &appPort, podName, podNamespace, patches)
	if err != nil {
		return nil, err
	}
	patches = hidePorts(&pod, appContainer, servicePort.TargetPort.StrVal, patches)
	patches = addAgentVolume(patches)
	return patches, nil
}

func addAgentVolume(patches []patchOperation) []patchOperation {
	return append(patches, patchOperation{
		Op:    "add",
		Path:  "/spec/volumes/-",
		Value: install.AgentVolume(),
	})
}

// addAgentContainer creates a patch operation to add the traffic-agent container
func addAgentContainer(
	ctx context.Context,
	svc *corev1.Service,
	svcPort *corev1.ServicePort,
	appContainer *corev1.Container,
	appPort *corev1.ContainerPort,
	podName, namespace string,
	patches []patchOperation) ([]patchOperation, error) {
	env := managerutil.GetEnv(ctx)

	refPodName := podName + "." + namespace
	dlog.Debugf(ctx, "using service %q port %q when intercepting %s",
		svc.Name,
		func() string {
			if svcPort.Name != "" {
				return svcPort.Name
			}
			return strconv.Itoa(int(svcPort.Port))
		}(), refPodName)

	agentName := podName
	if strings.HasSuffix(agentName, "-") {
		// Transform a generated name "my-echo-697464c6c5-" into an agent service name "my-echo"
		tokens := strings.Split(podName, "-")
		agentName = strings.Join(tokens[:len(tokens)-2], "-")
	}

	proto := svcPort.Protocol
	if proto == "" {
		proto = appPort.Protocol
	}
	patches = append(patches, patchOperation{
		Op:   "add",
		Path: "/spec/containers/-",
		Value: install.AgentContainer(
			agentName,
			env.AgentRegistry+"/"+env.AgentImage,
			appContainer,
			corev1.ContainerPort{
				Name:          svcPort.TargetPort.StrVal,
				Protocol:      proto,
				ContainerPort: env.AgentPort,
			},
			int(appPort.ContainerPort),
			env.ManagerNamespace)})

	return patches, nil
}

// hidePorts  will replace the symbolic name of a container port with a generated name. It will perform
// the same replacement on all references to that port from the probes of the container
func hidePorts(pod *corev1.Pod, cn *corev1.Container, portName string, patches []patchOperation) []patchOperation {
	cns := pod.Spec.Containers
	var containerPath string
	for i := range cns {
		if &cns[i] == cn {
			containerPath = fmt.Sprintf("/spec/containers/%d", i)
			break
		}
	}

	hiddenPortName := install.HiddenPortName(portName, 0)
	hidePort := func(path string) {
		patches = append(patches, patchOperation{
			Op:    "replace",
			Path:  fmt.Sprintf("%s/%s", containerPath, path),
			Value: hiddenPortName,
		})
	}

	for i, p := range cn.Ports {
		if p.Name == portName {
			hidePort(fmt.Sprintf("ports/%d/name", i))
			break
		}
	}

	probes := []*corev1.Probe{cn.LivenessProbe, cn.ReadinessProbe, cn.StartupProbe}
	probeNames := []string{"livenessProbe/", "readinessProbe/", "startupProbe/"}

	for i, probe := range probes {
		if probe == nil {
			continue
		}
		if h := probe.HTTPGet; h != nil && h.Port.StrVal == portName {
			hidePort(probeNames[i] + "httpGet/port")
		}
		if t := probe.TCPSocket; t != nil && t.Port.StrVal == portName {
			hidePort(probeNames[i] + "tcpSocket/port")
		}
	}
	return patches
}
<commit_msg>Change ambassador/pkg to ambassador/v2/pkg in agent_injector.go<commit_after>package mutator

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	admission "k8s.io/api/admission/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	"github.com/datawire/ambassador/v2/pkg/kates"
	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/v2/cmd/traffic/cmd/manager/managerutil"
	"github.com/telepresenceio/telepresence/v2/pkg/install"
)

var podResource = metav1.GroupVersionResource{Version: "v1", Group: "", Resource: "pods"}
var findMatchingService = install.FindMatchingService

func agentInjector(ctx context.Context, req *admission.AdmissionRequest) ([]patchOperation, error) {
	// This handler should only get called on Pod objects as per the MutatingWebhookConfiguration in the YAML file.
	// Pod objects are immutable, hence we only care about the CREATE event.
	// Applying patches to Pods instead of Deployments means we don't have side effects on
	// user-managed Deployments and Services. It also means we don't have to manage update flows
	// such as removing or updating the sidecar in Deployments objects... a new Pod just gets created instead!

	// If (for whatever reason) this handler is invoked on an object different than a Pod,
	// issue a log message and let the object request pass through.
	if req.Resource != podResource {
		dlog.Debugf(ctx, "expect resource to be %s, got %s; skipping", podResource, req.Resource)
		return nil, nil
	}

	// Parse the Pod object.
	raw := req.Object.Raw
	pod := corev1.Pod{}
	if _, _, err := universalDeserializer.Decode(raw, nil, &pod); err != nil {
		return nil, fmt.Errorf("could not deserialize pod object: %v", err)
	}

	podNamespace := pod.Namespace
	if podNamespace == "" {
		// It is very probable the pod was not yet assigned a namespace,
		// in which case we should use the AdmissionRequest namespace.
		podNamespace = req.Namespace
	}
	podName := pod.Name
	if podName == "" {
		// It is very probable the pod was not yet assigned a name,
		// in which case we should use the metadata generated name.
		podName = pod.ObjectMeta.GenerateName
	}

	// Validate traffic-agent injection preconditions.
	refPodName := fmt.Sprintf("%s.%s", podName, podNamespace)
	if podName == "" || podNamespace == "" {
		dlog.Debugf(ctx, "Unable to extract pod name and/or namespace (got %q); skipping", refPodName)
		return nil, nil
	}

	if pod.Annotations[install.InjectAnnotation] != "enabled" {
		dlog.Debugf(ctx, `The %s pod has not enabled %s container injection through %q annotation; skipping`,
			refPodName, install.AgentContainerName, install.InjectAnnotation)
		return nil, nil
	}

	for _, container := range pod.Spec.Containers {
		if container.Name == install.AgentContainerName {
			dlog.Infof(ctx, "The %s pod already has a %q container; skipping", refPodName, install.AgentContainerName)
			return nil, nil
		}
	}

	// Make the kates client available in the context
	// TODO: Use the kubernetes SharedInformerFactory instead
	client, err := kates.NewClient(kates.ClientConfig{})
	if err != nil {
		return nil, fmt.Errorf("failed to create new kates client: %w", err)
	}

	svc, err := findMatchingService(ctx, client, "", "", podNamespace, pod.Labels)
	if err != nil {
		dlog.Error(ctx, err)
		return nil, nil
	}

	// The ServicePortAnnotation is expected to contain a string that identifies the service port.
	portNameOrNumber := pod.Annotations[install.ServicePortAnnotation]
	servicePort, appContainer, containerPortIndex, err := install.FindMatchingPort(pod.Spec.Containers, portNameOrNumber, svc)
	if err != nil {
		dlog.Error(ctx, err)
		return nil, nil
	}

	env := managerutil.GetEnv(ctx)
	ports := appContainer.Ports
	for i := range ports {
		if ports[i].ContainerPort == env.AgentPort {
			dlog.Infof(ctx, "the %s pod container is exposing the same port (%d) as the %s sidecar; skipping",
				refPodName, env.AgentPort, install.AgentContainerName)
			return nil, nil
		}
	}

	if svc.Spec.ClusterIP == "None" {
		return nil, fmt.Errorf("intercepts of headless service: %s.%s won't work "+
			"see https://github.com/telepresenceio/telepresence/issues/1632",
			svc.Name, svc.Namespace)
	}

	if servicePort.TargetPort.Type == intstr.Int {
		return nil, fmt.Errorf("intercepts of service %s.%s won't work because it has an integer targetPort",
			svc.Name, svc.Namespace)
	}

	appPort := appContainer.Ports[containerPortIndex]

	// Create patch operations to add the traffic-agent sidecar
	dlog.Infof(ctx, "Injecting %s into pod %s", install.AgentContainerName, refPodName)

	var patches []patchOperation
	patches, err = addAgentContainer(ctx, svc, servicePort, appContainer, &appPort, podName, podNamespace, patches)
	if err != nil {
		return nil, err
	}
	patches = hidePorts(&pod, appContainer, servicePort.TargetPort.StrVal, patches)
	patches = addAgentVolume(patches)
	return patches, nil
}

func addAgentVolume(patches []patchOperation) []patchOperation {
	return append(patches, patchOperation{
		Op:    "add",
		Path:  "/spec/volumes/-",
		Value: install.AgentVolume(),
	})
}

// addAgentContainer creates a patch operation to add the traffic-agent container
func addAgentContainer(
	ctx context.Context,
	svc *corev1.Service,
	svcPort *corev1.ServicePort,
	appContainer *corev1.Container,
	appPort *corev1.ContainerPort,
	podName, namespace string,
	patches []patchOperation) ([]patchOperation, error) {
	env := managerutil.GetEnv(ctx)

	refPodName := podName + "." + namespace
	dlog.Debugf(ctx, "using service %q port %q when intercepting %s",
		svc.Name,
		func() string {
			if svcPort.Name != "" {
				return svcPort.Name
			}
			return strconv.Itoa(int(svcPort.Port))
		}(), refPodName)

	agentName := podName
	if strings.HasSuffix(agentName, "-") {
		// Transform a generated name "my-echo-697464c6c5-" into an agent service name "my-echo"
		tokens := strings.Split(podName, "-")
		agentName = strings.Join(tokens[:len(tokens)-2], "-")
	}

	proto := svcPort.Protocol
	if proto == "" {
		proto = appPort.Protocol
	}
	patches = append(patches, patchOperation{
		Op:   "add",
		Path: "/spec/containers/-",
		Value: install.AgentContainer(
			agentName,
			env.AgentRegistry+"/"+env.AgentImage,
			appContainer,
			corev1.ContainerPort{
				Name:          svcPort.TargetPort.StrVal,
				Protocol:      proto,
				ContainerPort: env.AgentPort,
			},
			int(appPort.ContainerPort),
			env.ManagerNamespace)})

	return patches, nil
}

// hidePorts  will replace the symbolic name of a container port with a generated name. It will perform
// the same replacement on all references to that port from the probes of the container
func hidePorts(pod *corev1.Pod, cn *corev1.Container, portName string, patches []patchOperation) []patchOperation {
	cns := pod.Spec.Containers
	var containerPath string
	for i := range cns {
		if &cns[i] == cn {
			containerPath = fmt.Sprintf("/spec/containers/%d", i)
			break
		}
	}

	hiddenPortName := install.HiddenPortName(portName, 0)
	hidePort := func(path string) {
		patches = append(patches, patchOperation{
			Op:    "replace",
			Path:  fmt.Sprintf("%s/%s", containerPath, path),
			Value: hiddenPortName,
		})
	}

	for i, p := range cn.Ports {
		if p.Name == portName {
			hidePort(fmt.Sprintf("ports/%d/name", i))
			break
		}
	}

	probes := []*corev1.Probe{cn.LivenessProbe, cn.ReadinessProbe, cn.StartupProbe}
	probeNames := []string{"livenessProbe/", "readinessProbe/", "startupProbe/"}

	for i, probe := range probes {
		if probe == nil {
			continue
		}
		if h := probe.HTTPGet; h != nil && h.Port.StrVal == portName {
			hidePort(probeNames[i] + "httpGet/port")
		}
		if t := probe.TCPSocket; t != nil && t.Port.StrVal == portName {
			hidePort(probeNames[i] + "tcpSocket/port")
		}
	}
	return patches
}
<|endoftext|>


<commit_before>// Copyright (C) 2017. See 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.

// +build openssl_static

package openssl

// #cgo linux windows pkg-config: --static libssl libcrypto
// #cgo linux CFLAGS: -Wno-deprecated-declarations
// #cgo darwin CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations
// #cgo darwin LDFLAGS: -w -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto
// #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN
import "C"
<commit_msg>remove unsupported build flags on go1.9.4+<commit_after>// Copyright (C) 2017. See 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.

// +build openssl_static

package openssl

// #cgo linux windows pkg-config: --static libssl libcrypto
// #cgo darwin CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include
// #cgo darwin LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto
// #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN
import "C"
<|endoftext|>


<commit_before>package lz4

import (
	"bufio"
	"hash"
	"io"

	"github.com/vova616/xxhash"
)

type bitReader struct {
	n    uint32
	bits uint
	err  error
	h    hash.Hash32

	r io.ByteReader
}

// newBitReader returns a new bitReader reading from r. If r is not
// already an io.ByteReader, it will be converted via a bufio.Reader.
func newBitReader(r io.Reader) bitReader {
	r, ok := r.(io.ByteReader)
	if !ok {
		r = bufio.NewReader(r)
	}
	return bitReader{
		r: r,
		h: xxhash.New(0),
	}
}

func (br *bitReader) ReadBits(bits uint) (uint32, error) {
	for bits > br.bits {
		b, err := br.r.ReadByte()
		br.h.Write([]byte{b})
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		if err != nil {
			br.err = err
			return 0, br.err
		}
		br.n <<= 8
		br.n |= uint32(b)
		br.bits += 8
	}
	n := (br.n >> (br.bits - bits)) & ((1 << bits) - 1)
	br.bits -= bits
	return n, nil
}

func (br *bitReader) ReadBit() (bool, error) {
	n, err := br.ReadBits(1)
	return n != 0, err
}

func (br *bitReader) Sum32() uint32 {
	return br.h.Sum32()
}
<commit_msg>fix bad change<commit_after>package lz4

import (
	"bufio"
	"hash"
	"io"

	"github.com/vova616/xxhash"
)

type bitReader struct {
	n    uint32
	bits uint
	err  error
	h    hash.Hash32

	r io.ByteReader
}

// newBitReader returns a new bitReader reading from r. If r is not
// already an io.ByteReader, it will be converted via a bufio.Reader.
func newBitReader(r io.Reader) bitReader {
	br, ok := r.(io.ByteReader)
	if !ok {
		br = bufio.NewReader(r)
	}
	return bitReader{
		r: br,
		h: xxhash.New(0),
	}
}

func (br *bitReader) ReadBits(bits uint) (uint32, error) {
	for bits > br.bits {
		b, err := br.r.ReadByte()
		br.h.Write([]byte{b})
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		if err != nil {
			br.err = err
			return 0, br.err
		}
		br.n <<= 8
		br.n |= uint32(b)
		br.bits += 8
	}
	n := (br.n >> (br.bits - bits)) & ((1 << bits) - 1)
	br.bits -= bits
	return n, nil
}

func (br *bitReader) ReadBit() (bool, error) {
	n, err := br.ReadBits(1)
	return n != 0, err
}

func (br *bitReader) Sum32() uint32 {
	return br.h.Sum32()
}
<|endoftext|>


<commit_before>package replybot

import (
	"github.com/kurrik/oauth1a"
	"github.com/kurrik/twittergo"
	"net/http"
	"net/url"
)

// Bot type
type Bot struct {
	client *twittergo.Client
}

// NewBot returns new bot
func NewBot(consumerKey string, consumerSecret string) *Bot {
	clientConfig := &oauth1a.ClientConfig{
		ConsumerKey:    consumerKey,
		ConsumerSecret: consumerSecret,
	}
	client := twittergo.NewClient(clientConfig, nil)
	return &Bot{
		client: client,
	}
}

// FollowersIDs returns follower's IDs
func (bot *Bot) FollowersIDs(userID string) ([]string, error) {
	var (
		ids    []string
		cursor string
	)
	for {
		query := url.Values{}
		query.Set("user_id", userID)
		query.Set("stringify_ids", "true")
		query.Set("count", "5000")
		if cursor != "" {
			query.Set("cursor", cursor)
		}
		req, err := http.NewRequest("GET", "/1.1/followers/ids.json?"+query.Encode(), nil)
		if err != nil {
			return nil, err
		}
		res, err := bot.client.SendRequest(req)
		if err != nil {
			return nil, err
		}

		results := &CursoredIDs{}
		if err := res.Parse(results); err != nil {
			return nil, err
		}
		ids = append(ids, results.IDs()...)

		if results.NextCursorStr() == "0" {
			break
		} else {
			cursor = results.NextCursorStr()
		}
	}
	return ids, nil
}
<commit_msg>shuffle ids<commit_after>package replybot

import (
	"github.com/kurrik/oauth1a"
	"github.com/kurrik/twittergo"
	"math/rand"
	"net/http"
	"net/url"
	"time"
)

// Bot type
type Bot struct {
	client *twittergo.Client
}

// NewBot returns new bot
func NewBot(consumerKey string, consumerSecret string) *Bot {
	clientConfig := &oauth1a.ClientConfig{
		ConsumerKey:    consumerKey,
		ConsumerSecret: consumerSecret,
	}
	client := twittergo.NewClient(clientConfig, nil)
	return &Bot{
		client: client,
	}
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

// FollowersIDs returns follower's IDs
func (bot *Bot) FollowersIDs(userID string) ([]string, error) {
	var (
		ids    []string
		cursor string
	)
	for {
		query := url.Values{}
		query.Set("user_id", userID)
		query.Set("stringify_ids", "true")
		query.Set("count", "5000")
		if cursor != "" {
			query.Set("cursor", cursor)
		}
		req, err := http.NewRequest("GET", "/1.1/followers/ids.json?"+query.Encode(), nil)
		if err != nil {
			return nil, err
		}
		res, err := bot.client.SendRequest(req)
		if err != nil {
			return nil, err
		}

		results := &CursoredIDs{}
		if err := res.Parse(results); err != nil {
			return nil, err
		}
		ids = append(ids, results.IDs()...)

		if results.NextCursorStr() == "0" {
			break
		} else {
			cursor = results.NextCursorStr()
		}
	}
	for i := range ids {
		j := rand.Intn(i + 1)
		ids[i], ids[j] = ids[j], ids[i]
	}
	return ids, nil
}
<|endoftext|>


<commit_before>package play

import (
	"container/list"
	"strings"
	"sync"
	"time"

	"github.com/pkg/errors"
)

var (
	// DefaultPlaybackStartTimeout is the default amount of time to wait for a playback to start before declaring that the playback has failed.
	DefaultPlaybackStartTimeout = 2 * time.Second

	// DefaultMaxPlaybackTime is the default maximum amount of time any playback is allowed to run.  If this time is exeeded, the playback will be cancelled.
	DefaultMaxPlaybackTime = 10 * time.Minute

	// DefaultFirstDigitTimeout is the default amount of time to wait, after the playback for all audio completes, for the first digit to be received.
	DefaultFirstDigitTimeout = 4 * time.Second

	// DefaultInterDigitTimeout is the maximum time to wait for additional
	// digits after the first is received.
	DefaultInterDigitTimeout = 3 * time.Second

	// DefaultOverallDigitTimeout is the default maximum time to wait for a
	// response, after the playback for all audio is complete, regardless of the
	// number of received digits or pattern matching.
	DefaultOverallDigitTimeout = 3 * time.Minute

	// DigitBufferSize is the number of digits stored in the received-digit
	// event buffer before further digit events are ignored.  NOTE that digits
	// overflowing this buffer are still stored in the digits received buffer.
	// This only affects the digit _signaling_ buffer.
	DigitBufferSize = 20
)

// Result describes the result of a playback operation
type Result struct {
	mu sync.Mutex

	// Duration indicates how long the playback execution took, from start to finish
	Duration time.Duration

	// DTMF records any DTMF which was received by the playback, as modified by any match functions
	DTMF string

	// Error indicates any error encountered which caused the termination of the playback
	Error error

	// MatchResult indicates the final result of any applied match function for DTMF digits which were received
	MatchResult MatchResult

	// Status indicates the resulting status of the playback, why it was stopped
	Status Status
}

// Status indicates the final status of a playback, be it individual of an entire sequence.  This Status indicates the reason the playback stopped.
type Status int

const (
	// InProgress indicates that the audio is currently playing or is staged to play
	InProgress Status = iota

	// Cancelled indicates that the audio was cancelled.  This cancellation could be due
	// to anything from the control context being closed or a DTMF Match being found
	Cancelled

	// Failed indicates that the audio playback failed.  This indicates that one
	// or more of the audio playbacks failed to be played.  This could be due to
	// a system, network, or Asterisk error, but it could also be due to an
	// invalid audio URI.  Check the returned error for more details.
	Failed

	// Finished indicates that the playback completed playing all bound audio
	// URIs in full.  Note that for a prompt-style execution, this also means
	// that no DTMF was matched to the match function.
	Finished

	// Hangup indicates that the audio playback was interrupted due to a hangup.
	Hangup

	// Timeout indicates that audio playback timed out.  It is not known whether this was due to a failure in the playback, a network loss, or some other problem.
	Timeout
)

// MatchResult indicates the status of a match for the received DTMF of a playback
type MatchResult int

const (
	// Incomplete indicates that there are not enough digits to determine a match
	Incomplete MatchResult = iota

	// Complete indicates that a match was found and the current DTMF pattern is complete
	Complete

	// Invalid indicates that a match cannot befound from the current DTMF received set
	Invalid
)

type uriList struct {
	list    *list.List
	current *list.Element
	mu      sync.Mutex
}

func (u *uriList) Empty() bool {
	if u == nil || u.list == nil || u.list.Len() == 0 {
		return true
	}
	return false
}

func (u *uriList) Add(uri string) {
	u.mu.Lock()
	defer u.mu.Unlock()

	if u.list == nil {
		u.list = list.New()
	}

	u.list.PushBack(uri)

	if u.current == nil {
		u.current = u.list.Front()
	}
}

func (u *uriList) First() string {
	if u.list == nil {
		return ""
	}

	u.mu.Lock()
	defer u.mu.Unlock()

	u.current = u.list.Front()
	return u.val()
}

func (u *uriList) Next() string {
	if u.list == nil {
		return ""
	}

	u.mu.Lock()
	defer u.mu.Unlock()

	if u.current == nil {
		return ""
	}

	u.current = u.current.Next()
	return u.val()
}

func (u *uriList) val() string {
	if u.current == nil {
		return ""
	}

	ret, ok := u.current.Value.(string)
	if !ok {
		return ""
	}
	return ret
}

// Options represent the various playback options which can modify the operation of a Playback.
type Options struct {
	// uriList is the list of audio URIs to play
	uriList *uriList

	// playbackStartTimeout defines the amount of time to wait for a playback to
	// start before declaring it failed.
	//
	// This value is important because ARI does NOT report playback failures in
	// any usable way.
	//
	// If not specified, the default is DefaultPlaybackStartTimeout
	playbackStartTimeout time.Duration

	// maxPlaybackTime is the maximum amount of time to wait for a playback
	// session to complete, everything included.  The playback will be
	// terminated if this time is exceeded.
	maxPlaybackTime time.Duration

	// firstDigitTimeout is the maximum length of time to wait
	// after the prompt sequence ends for the user to enter
	// a response.
	//
	// If not specified, the default is DefaultFirstDigitTimeout.
	firstDigitTimeout time.Duration

	// interDigitTimeout is the maximum length of time to wait
	// for an additional digit after a digit is received.
	//
	// If not specified, the default is DefaultInterDigitTimeout.
	interDigitTimeout time.Duration

	// overallDigitTimeout is the maximum length of time to wait
	// for a response regardless of digits received after the completion
	// of all audio playbacks.
	// If not specified, the default is DefaultOverallTimeout.
	overallDigitTimeout time.Duration

	// matchFunc is an optional function which, if supplied, returns
	// a string and an int.
	//
	// The string is allows the MatchFunc to return a different number
	// to be used as `result.Data`.  This is commonly used for prompts
	// which look for a terminator.  In such a practice, the terminator
	// would be stripped from the match and this argument would be populated
	// with the result.  Otherwise, the original string should be returned.
	// NOTE: Whatever is returned here will become `result.Data`.
	//
	// The int parameter indicates the result of the match, and it should
	// be one of:
	//  Incomplete (0) : insufficient digits to determine match.
	//  Complete (1) : A match was found.
	//  Invalid (2) : A match could not be found, given the digits received.
	// If this function returns a non-zero int, then the prompt will be stopped.
	// If not specified MatchAny will be used.
	matchFunc func(string) (string, MatchResult)

	// maxReplays is the maximum number of times the audio sequence will be
	// replayed if there is no response.  By default, the audio sequence is
	// played only once.
	maxReplays int
}

// NewDefaultOptions returns a set of options which represent reasonable defaults for most simple playbacks.
func NewDefaultOptions() *Options {
	return &Options{
		playbackStartTimeout: DefaultPlaybackStartTimeout,
		maxPlaybackTime:      DefaultMaxPlaybackTime,
		uriList:              new(uriList),
	}
}

// ApplyOptions applies a set of OptionFuncs to the Playback
func (o *Options) ApplyOptions(opts ...OptionFunc) (err error) {
	for _, f := range opts {
		err = f(o)
		if err != nil {
			return errors.Wrap(err, "failed to apply option")
		}
	}
	return nil
}

// NewPromptOptions returns a set of options which represent reasonable defaults for most prompt playbacks.  It will terminate when any single DTMF digit is received.
func NewPromptOptions() *Options {
	opts := NewDefaultOptions()

	opts.firstDigitTimeout = DefaultFirstDigitTimeout
	opts.interDigitTimeout = DefaultInterDigitTimeout
	opts.overallDigitTimeout = DefaultOverallDigitTimeout

	MatchAny()(opts) // nolint  No error is possible with MatchAny

	return opts
}

// OptionFunc defines an interface for functions which can modify a play session's Options
type OptionFunc func(*Options) error

// URI adds a set of audio URIs to a playback
func URI(uri ...string) OptionFunc {
	return func(o *Options) error {
		if o.uriList == nil {
			o.uriList = new(uriList)
		}

		for _, u := range uri {
			if u != "" {
				o.uriList.Add(u)
			}
		}
		return nil
	}
}

// PlaybackStartTimeout overrides the default playback start timeout
func PlaybackStartTimeout(timeout time.Duration) OptionFunc {
	return func(o *Options) error {
		o.playbackStartTimeout = timeout
		return nil
	}
}

// DigitTimeouts sets the digit timeouts.  Passing a negative value to any of these indicates that the default value (shown in parentheses below) should be used.
//
//  - First digit timeout (4 sec):  The time (after the stop of the audio) to wait for the first digit to be received
//
//  - Inter digit timeout (3 sec):  The time (after receiving a digit) to wait for the _next_ digit to be received
//
//  - Overall digit timeout (3 min):  The maximum amount of time to wait (after the stop of the audio) for digits to be received, regardless of the digit frequency
//
func DigitTimeouts(first, inter, overall time.Duration) OptionFunc {
	return func(o *Options) error {
		if first >= 0 {
			o.firstDigitTimeout = first
		}
		if inter >= 0 {
			o.interDigitTimeout = inter
		}
		if overall >= 0 {
			o.overallDigitTimeout = overall
		}
		return nil
	}
}

// Replays sets the number of replays of the audio sequence before exiting
func Replays(count int) OptionFunc {
	return func(o *Options) error {
		o.maxReplays = count
		return nil
	}
}

// MatchAny indicates that the playback should be considered Matched and terminated if
// any DTMF digit is received during the playback or post-playback time.
func MatchAny() OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if len(pat) > 0 {
				return pat, Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchHash indicates that the playback should be considered Matched and terminated if it contains a hash (#).  The hash (and any subsequent digits) is removed from the final result.
func MatchHash() OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if strings.Contains(pat, "#") {
				return strings.Split(pat, "#")[0], Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchTerminator indicates that the playback shoiuld be considered Matched and terminated if it contains the provided Terminator string.  The terminator (and any subsequent digits) is removed from the final result.
func MatchTerminator(terminator string) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if strings.Contains(pat, terminator) {
				return strings.Split(pat, terminator)[0], Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchLen indicates that the playback should be considered Matched and terminated if the given number of DTMF digits are receieved.
func MatchLen(length int) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if len(pat) >= length {
				return pat, Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchLenOrTerminator indicates that the playback should be considered Matched and terminated if the given number of DTMF digits are receieved or if the given terminator is received.  If the terminator is present, it and any subsequent digits will be removed from the final result.
func MatchLenOrTerminator(length int, terminator string) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if len(pat) >= length {
				return pat, Complete
			}
			if strings.Contains(pat, terminator) {
				return strings.Split(pat, terminator)[0], Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchFunc uses the provided match function to determine when the playback should be terminated based on DTMF input.
func MatchFunc(f func(string) (string, MatchResult)) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = f
		return nil
	}
}
<commit_msg>ext/play: add NoExitOnDTMF OptionFunc<commit_after>package play

import (
	"container/list"
	"strings"
	"sync"
	"time"

	"github.com/pkg/errors"
)

var (
	// DefaultPlaybackStartTimeout is the default amount of time to wait for a playback to start before declaring that the playback has failed.
	DefaultPlaybackStartTimeout = 2 * time.Second

	// DefaultMaxPlaybackTime is the default maximum amount of time any playback is allowed to run.  If this time is exeeded, the playback will be cancelled.
	DefaultMaxPlaybackTime = 10 * time.Minute

	// DefaultFirstDigitTimeout is the default amount of time to wait, after the playback for all audio completes, for the first digit to be received.
	DefaultFirstDigitTimeout = 4 * time.Second

	// DefaultInterDigitTimeout is the maximum time to wait for additional
	// digits after the first is received.
	DefaultInterDigitTimeout = 3 * time.Second

	// DefaultOverallDigitTimeout is the default maximum time to wait for a
	// response, after the playback for all audio is complete, regardless of the
	// number of received digits or pattern matching.
	DefaultOverallDigitTimeout = 3 * time.Minute

	// DigitBufferSize is the number of digits stored in the received-digit
	// event buffer before further digit events are ignored.  NOTE that digits
	// overflowing this buffer are still stored in the digits received buffer.
	// This only affects the digit _signaling_ buffer.
	DigitBufferSize = 20
)

// Result describes the result of a playback operation
type Result struct {
	mu sync.Mutex

	// Duration indicates how long the playback execution took, from start to finish
	Duration time.Duration

	// DTMF records any DTMF which was received by the playback, as modified by any match functions
	DTMF string

	// Error indicates any error encountered which caused the termination of the playback
	Error error

	// MatchResult indicates the final result of any applied match function for DTMF digits which were received
	MatchResult MatchResult

	// Status indicates the resulting status of the playback, why it was stopped
	Status Status
}

// Status indicates the final status of a playback, be it individual of an entire sequence.  This Status indicates the reason the playback stopped.
type Status int

const (
	// InProgress indicates that the audio is currently playing or is staged to play
	InProgress Status = iota

	// Cancelled indicates that the audio was cancelled.  This cancellation could be due
	// to anything from the control context being closed or a DTMF Match being found
	Cancelled

	// Failed indicates that the audio playback failed.  This indicates that one
	// or more of the audio playbacks failed to be played.  This could be due to
	// a system, network, or Asterisk error, but it could also be due to an
	// invalid audio URI.  Check the returned error for more details.
	Failed

	// Finished indicates that the playback completed playing all bound audio
	// URIs in full.  Note that for a prompt-style execution, this also means
	// that no DTMF was matched to the match function.
	Finished

	// Hangup indicates that the audio playback was interrupted due to a hangup.
	Hangup

	// Timeout indicates that audio playback timed out.  It is not known whether this was due to a failure in the playback, a network loss, or some other problem.
	Timeout
)

// MatchResult indicates the status of a match for the received DTMF of a playback
type MatchResult int

const (
	// Incomplete indicates that there are not enough digits to determine a match
	Incomplete MatchResult = iota

	// Complete indicates that a match was found and the current DTMF pattern is complete
	Complete

	// Invalid indicates that a match cannot befound from the current DTMF received set
	Invalid
)

type uriList struct {
	list    *list.List
	current *list.Element
	mu      sync.Mutex
}

func (u *uriList) Empty() bool {
	if u == nil || u.list == nil || u.list.Len() == 0 {
		return true
	}
	return false
}

func (u *uriList) Add(uri string) {
	u.mu.Lock()
	defer u.mu.Unlock()

	if u.list == nil {
		u.list = list.New()
	}

	u.list.PushBack(uri)

	if u.current == nil {
		u.current = u.list.Front()
	}
}

func (u *uriList) First() string {
	if u.list == nil {
		return ""
	}

	u.mu.Lock()
	defer u.mu.Unlock()

	u.current = u.list.Front()
	return u.val()
}

func (u *uriList) Next() string {
	if u.list == nil {
		return ""
	}

	u.mu.Lock()
	defer u.mu.Unlock()

	if u.current == nil {
		return ""
	}

	u.current = u.current.Next()
	return u.val()
}

func (u *uriList) val() string {
	if u.current == nil {
		return ""
	}

	ret, ok := u.current.Value.(string)
	if !ok {
		return ""
	}
	return ret
}

// Options represent the various playback options which can modify the operation of a Playback.
type Options struct {
	// uriList is the list of audio URIs to play
	uriList *uriList

	// playbackStartTimeout defines the amount of time to wait for a playback to
	// start before declaring it failed.
	//
	// This value is important because ARI does NOT report playback failures in
	// any usable way.
	//
	// If not specified, the default is DefaultPlaybackStartTimeout
	playbackStartTimeout time.Duration

	// maxPlaybackTime is the maximum amount of time to wait for a playback
	// session to complete, everything included.  The playback will be
	// terminated if this time is exceeded.
	maxPlaybackTime time.Duration

	// firstDigitTimeout is the maximum length of time to wait
	// after the prompt sequence ends for the user to enter
	// a response.
	//
	// If not specified, the default is DefaultFirstDigitTimeout.
	firstDigitTimeout time.Duration

	// interDigitTimeout is the maximum length of time to wait
	// for an additional digit after a digit is received.
	//
	// If not specified, the default is DefaultInterDigitTimeout.
	interDigitTimeout time.Duration

	// overallDigitTimeout is the maximum length of time to wait
	// for a response regardless of digits received after the completion
	// of all audio playbacks.
	// If not specified, the default is DefaultOverallTimeout.
	overallDigitTimeout time.Duration

	// matchFunc is an optional function which, if supplied, returns
	// a string and an int.
	//
	// The string is allows the MatchFunc to return a different number
	// to be used as `result.Data`.  This is commonly used for prompts
	// which look for a terminator.  In such a practice, the terminator
	// would be stripped from the match and this argument would be populated
	// with the result.  Otherwise, the original string should be returned.
	// NOTE: Whatever is returned here will become `result.Data`.
	//
	// The int parameter indicates the result of the match, and it should
	// be one of:
	//  Incomplete (0) : insufficient digits to determine match.
	//  Complete (1) : A match was found.
	//  Invalid (2) : A match could not be found, given the digits received.
	// If this function returns a non-zero int, then the prompt will be stopped.
	// If not specified MatchAny will be used.
	matchFunc func(string) (string, MatchResult)

	// maxReplays is the maximum number of times the audio sequence will be
	// replayed if there is no response.  By default, the audio sequence is
	// played only once.
	maxReplays int
}

// NewDefaultOptions returns a set of options which represent reasonable defaults for most simple playbacks.
func NewDefaultOptions() *Options {
	opts := &Options{
		playbackStartTimeout: DefaultPlaybackStartTimeout,
		maxPlaybackTime:      DefaultMaxPlaybackTime,
		uriList:              new(uriList),
	}

	MatchAny()(opts) // nolint  No error is possible with MatchAny

	return opts
}

// ApplyOptions applies a set of OptionFuncs to the Playback
func (o *Options) ApplyOptions(opts ...OptionFunc) (err error) {
	for _, f := range opts {
		err = f(o)
		if err != nil {
			return errors.Wrap(err, "failed to apply option")
		}
	}
	return nil
}

// NewPromptOptions returns a set of options which represent reasonable defaults for most prompt playbacks.  It will terminate when any single DTMF digit is received.
func NewPromptOptions() *Options {
	opts := NewDefaultOptions()

	opts.firstDigitTimeout = DefaultFirstDigitTimeout
	opts.interDigitTimeout = DefaultInterDigitTimeout
	opts.overallDigitTimeout = DefaultOverallDigitTimeout

	return opts
}

// OptionFunc defines an interface for functions which can modify a play session's Options
type OptionFunc func(*Options) error

// NoExitOnDTMF disables exiting the playback when DTMF is received
func NoExitOnDTMF() OptionFunc {
	return func(o *Options) error {
		o.matchFunc = nil
		return nil
	}
}

// URI adds a set of audio URIs to a playback
func URI(uri ...string) OptionFunc {
	return func(o *Options) error {
		if o.uriList == nil {
			o.uriList = new(uriList)
		}

		for _, u := range uri {
			if u != "" {
				o.uriList.Add(u)
			}
		}
		return nil
	}
}

// PlaybackStartTimeout overrides the default playback start timeout
func PlaybackStartTimeout(timeout time.Duration) OptionFunc {
	return func(o *Options) error {
		o.playbackStartTimeout = timeout
		return nil
	}
}

// DigitTimeouts sets the digit timeouts.  Passing a negative value to any of these indicates that the default value (shown in parentheses below) should be used.
//
//  - First digit timeout (4 sec):  The time (after the stop of the audio) to wait for the first digit to be received
//
//  - Inter digit timeout (3 sec):  The time (after receiving a digit) to wait for the _next_ digit to be received
//
//  - Overall digit timeout (3 min):  The maximum amount of time to wait (after the stop of the audio) for digits to be received, regardless of the digit frequency
//
func DigitTimeouts(first, inter, overall time.Duration) OptionFunc {
	return func(o *Options) error {
		if first >= 0 {
			o.firstDigitTimeout = first
		}
		if inter >= 0 {
			o.interDigitTimeout = inter
		}
		if overall >= 0 {
			o.overallDigitTimeout = overall
		}
		return nil
	}
}

// Replays sets the number of replays of the audio sequence before exiting
func Replays(count int) OptionFunc {
	return func(o *Options) error {
		o.maxReplays = count
		return nil
	}
}

// MatchAny indicates that the playback should be considered Matched and terminated if
// any DTMF digit is received during the playback or post-playback time.
func MatchAny() OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if len(pat) > 0 {
				return pat, Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchHash indicates that the playback should be considered Matched and terminated if it contains a hash (#).  The hash (and any subsequent digits) is removed from the final result.
func MatchHash() OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if strings.Contains(pat, "#") {
				return strings.Split(pat, "#")[0], Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchTerminator indicates that the playback shoiuld be considered Matched and terminated if it contains the provided Terminator string.  The terminator (and any subsequent digits) is removed from the final result.
func MatchTerminator(terminator string) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if strings.Contains(pat, terminator) {
				return strings.Split(pat, terminator)[0], Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchLen indicates that the playback should be considered Matched and terminated if the given number of DTMF digits are receieved.
func MatchLen(length int) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if len(pat) >= length {
				return pat, Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchLenOrTerminator indicates that the playback should be considered Matched and terminated if the given number of DTMF digits are receieved or if the given terminator is received.  If the terminator is present, it and any subsequent digits will be removed from the final result.
func MatchLenOrTerminator(length int, terminator string) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = func(pat string) (string, MatchResult) {
			if len(pat) >= length {
				return pat, Complete
			}
			if strings.Contains(pat, terminator) {
				return strings.Split(pat, terminator)[0], Complete
			}
			return pat, Incomplete
		}
		return nil
	}
}

// MatchFunc uses the provided match function to determine when the playback should be terminated based on DTMF input.
func MatchFunc(f func(string) (string, MatchResult)) OptionFunc {
	return func(o *Options) error {
		o.matchFunc = f
		return nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com

package trello

const boardsTemplate = `

	{{if gt (len .Boards) 0}}

Boards

There are {{len .Boards}} boards, {{.ListTotal}} since lists, {{.CardTotal}} cards and {{len .MemberBoardAssign}} members {{.Since}}.

				{{range $b := .Boards}}

									{{$b.Board.Name}}
									{{$b.Board.OrgName}}

								{{range $act, $tot := $b.ActionSummary}}
									{{$tot}} {{$act}}{{if ne 1 $tot}}s{{end}},
								{{end}}
								{{if gt (len $b.Archived) 0}}
									{{len $b.Archived}} {{if eq 1 (len $b.Archived)}}card {{else}} cards {{end}}archived
								{{else}}
									no cards archived
								{{end}}

				{{end}}

	{{end}}

`
<commit_msg>Fix Trello section wording<commit_after>// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com

package trello

const boardsTemplate = `

	{{if gt (len .Boards) 0}}

Boards

There are {{len .Boards}} boards, {{.ListTotal}} lists, {{.CardTotal}} cards and {{len .MemberBoardAssign}} members. Activity since {{.Since}}

				{{range $b := .Boards}}

									{{$b.Board.Name}}
									{{$b.Board.OrgName}}

								{{range $act, $tot := $b.ActionSummary}}
									{{$tot}} {{$act}}{{if ne 1 $tot}}s{{end}},
								{{end}}
								{{if gt (len $b.Archived) 0}}
									{{len $b.Archived}} {{if eq 1 (len $b.Archived)}}card {{else}} cards {{end}}archived
								{{else}}
									no cards archived
								{{end}}

				{{end}}

	{{end}}

`
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. 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 gcscaching

import (
	"time"

	"github.com/jacobsa/gcloud/gcs"
)

// A cache mapping from name to most recent known record for the object of that
// name. External synchronization must be provided.
type StatCache interface {
	// Insert an entry for the given object record. The entry will not replace
	// any entry with a newer generation number, or any entry with an equivalent
	// generation number but newer metadata generation number, and will not be
	// available after the supplied expiration time.
	Insert(o *gcs.Object, expiration time.Time)

	// Erase the entry for the given object name, if any.
	Erase(name string)

	// Return the current entry for the given name, or nil if none. Use the
	// supplied time to decide whether entries have expired.
	LookUp(name string, now time.Time) (o *gcs.Object)

	// Panic if any internal invariants have been violated. The careful user can
	// arrange to call this at crucial moments.
	CheckInvariants()
}

// Create a new stat cache that holds the given number of entries, which must
// be positive.
func NewStatCache(capacity int) (sc StatCache) {
	panic("TODO")
}
<commit_msg>NewStatCache<commit_after>// Copyright 2015 Google Inc. 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 gcscaching

import (
	"time"

	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/util/lrucache"
)

// A cache mapping from name to most recent known record for the object of that
// name. External synchronization must be provided.
type StatCache interface {
	// Insert an entry for the given object record. The entry will not replace
	// any entry with a newer generation number, or any entry with an equivalent
	// generation number but newer metadata generation number, and will not be
	// available after the supplied expiration time.
	Insert(o *gcs.Object, expiration time.Time)

	// Erase the entry for the given object name, if any.
	Erase(name string)

	// Return the current entry for the given name, or nil if none. Use the
	// supplied time to decide whether entries have expired.
	LookUp(name string, now time.Time) (o *gcs.Object)

	// Panic if any internal invariants have been violated. The careful user can
	// arrange to call this at crucial moments.
	CheckInvariants()
}

// Create a new stat cache that holds the given number of entries, which must
// be positive.
func NewStatCache(capacity int) (sc StatCache) {
	sc = &statCache{
		c: lrucache.New(capacity),
	}

	return
}

type statCache struct {
	c lrucache.Cache
}

func (sc *statCache) Insert(o *gcs.Object, expiration time.Time) {
	panic("TODO")
}

func (sc *statCache) Erase(name string) {
	panic("TODO")
}

func (sc *statCache) LookUp(name string, now time.Time) (o *gcs.Object) {
	panic("TODO")
}

func (sc *statCache) CheckInvariants() {
	panic("TODO")
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 estimator

import (
	"fmt"
	"sort"
	"time"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/autoscaler/cluster-autoscaler/simulator"
	klog "k8s.io/klog/v2"
	schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
)

// podInfo contains Pod and score that corresponds to how important it is to handle the pod first.
type podInfo struct {
	score float64
	pod   *apiv1.Pod
}

// BinpackingNodeEstimator estimates the number of needed nodes to handle the given amount of pods.
type BinpackingNodeEstimator struct {
	predicateChecker simulator.PredicateChecker
	clusterSnapshot  simulator.ClusterSnapshot
}

// NewBinpackingNodeEstimator builds a new BinpackingNodeEstimator.
func NewBinpackingNodeEstimator(
	predicateChecker simulator.PredicateChecker,
	clusterSnapshot simulator.ClusterSnapshot) *BinpackingNodeEstimator {
	return &BinpackingNodeEstimator{
		predicateChecker: predicateChecker,
		clusterSnapshot:  clusterSnapshot,
	}
}

// Estimate implements First Fit Decreasing bin-packing approximation algorithm.
// See https://en.wikipedia.org/wiki/Bin_packing_problem for more details.
// While it is a multi-dimensional bin packing (cpu, mem, ports) in most cases the main dimension
// will be cpu thus the estimated overprovisioning of 11/9 * optimal + 6/9 should be
// still be maintained.
// It is assumed that all pods from the given list can fit to nodeTemplate.
// Returns the number of nodes needed to accommodate all pods from the list.
func (estimator *BinpackingNodeEstimator) Estimate(
	pods []*apiv1.Pod,
	nodeTemplate *schedulerframework.NodeInfo) int {
	podInfos := calculatePodScore(pods, nodeTemplate)
	sort.Slice(podInfos, func(i, j int) bool { return podInfos[i].score > podInfos[j].score })

	newNodeNames := make([]string, 0)

	if err := estimator.clusterSnapshot.Fork(); err != nil {
		klog.Errorf("Error while calling ClusterSnapshot.Fork; %v", err)
		return 0
	}
	defer func() {
		if err := estimator.clusterSnapshot.Revert(); err != nil {
			klog.Fatalf("Error while calling ClusterSnapshot.Revert; %v", err)
		}
	}()

	newNodeNameTimestamp := time.Now()
	newNodeNameIndex := 0

	for _, podInfo := range podInfos {
		found := false
		for _, nodeName := range newNodeNames {
			if err := estimator.predicateChecker.CheckPredicates(estimator.clusterSnapshot, podInfo.pod, nodeName); err == nil {
				found = true
				if err := estimator.clusterSnapshot.AddPod(podInfo.pod, nodeName); err != nil {
					klog.Errorf("Error adding pod %v.%v to node %v in ClusterSnapshot; %v", podInfo.pod.Namespace, podInfo.pod.Name, nodeName, err)
					return 0
				}
				break
			}
		}
		if !found {
			// Add new node
			newNodeName, err := estimator.addNewNodeToSnapshot(nodeTemplate, newNodeNameTimestamp, newNodeNameIndex)
			if err != nil {
				klog.Errorf("Error while adding new node for template to ClusterSnapshot; %v", err)
				return 0
			}
			newNodeNameIndex++
			// And schedule pod to it
			if err := estimator.clusterSnapshot.AddPod(podInfo.pod, newNodeName); err != nil {
				klog.Errorf("Error adding pod %v.%v to node %v in ClusterSnapshot; %v", podInfo.pod.Namespace, podInfo.pod.Name, newNodeName, err)
				return 0
			}
			newNodeNames = append(newNodeNames, newNodeName)
		}
	}
	return len(newNodeNames)
}

func (estimator *BinpackingNodeEstimator) addNewNodeToSnapshot(
	template *schedulerframework.NodeInfo,
	nameTimestamp time.Time,
	nameIndex int) (string, error) {

	newNode := template.Node().DeepCopy()
	newNode.Name = fmt.Sprintf("%s-%d-%d", newNode.Name, nameTimestamp.Unix(), nameIndex)
	if newNode.Labels == nil {
		newNode.Labels = make(map[string]string)
	}
	newNode.Labels["kubernetes.io/hostname"] = newNode.Name
	var pods []*apiv1.Pod
	for _, podInfo := range template.Pods {
		pods = append(pods, podInfo.Pod)
	}
	if err := estimator.clusterSnapshot.AddNodeWithPods(newNode, pods); err != nil {
		return "", err
	}
	return newNode.Name, nil
}

// Calculates score for all pods and returns podInfo structure.
// Score is defined as cpu_sum/node_capacity + mem_sum/node_capacity.
// Pods that have bigger requirements should be processed first, thus have higher scores.
func calculatePodScore(pods []*apiv1.Pod, nodeTemplate *schedulerframework.NodeInfo) []*podInfo {
	podInfos := make([]*podInfo, 0, len(pods))

	for _, pod := range pods {
		cpuSum := resource.Quantity{}
		memorySum := resource.Quantity{}

		for _, container := range pod.Spec.Containers {
			if request, ok := container.Resources.Requests[apiv1.ResourceCPU]; ok {
				cpuSum.Add(request)
			}
			if request, ok := container.Resources.Requests[apiv1.ResourceMemory]; ok {
				memorySum.Add(request)
			}
		}
		score := float64(0)
		if cpuAllocatable, ok := nodeTemplate.Node().Status.Allocatable[apiv1.ResourceCPU]; ok && cpuAllocatable.MilliValue() > 0 {
			score += float64(cpuSum.MilliValue()) / float64(cpuAllocatable.MilliValue())
		}
		if memAllocatable, ok := nodeTemplate.Node().Status.Allocatable[apiv1.ResourceMemory]; ok && memAllocatable.Value() > 0 {
			score += float64(memorySum.Value()) / float64(memAllocatable.Value())
		}

		podInfos = append(podInfos, &podInfo{
			score: score,
			pod:   pod,
		})
	}
	return podInfos
}
<commit_msg>Use FitsAnyNode in binpacking<commit_after>/*
Copyright 2016 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 estimator

import (
	"fmt"
	"sort"
	"time"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/autoscaler/cluster-autoscaler/simulator"
	klog "k8s.io/klog/v2"
	schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
)

// podInfo contains Pod and score that corresponds to how important it is to handle the pod first.
type podInfo struct {
	score float64
	pod   *apiv1.Pod
}

// BinpackingNodeEstimator estimates the number of needed nodes to handle the given amount of pods.
type BinpackingNodeEstimator struct {
	predicateChecker simulator.PredicateChecker
	clusterSnapshot  simulator.ClusterSnapshot
}

// NewBinpackingNodeEstimator builds a new BinpackingNodeEstimator.
func NewBinpackingNodeEstimator(
	predicateChecker simulator.PredicateChecker,
	clusterSnapshot simulator.ClusterSnapshot) *BinpackingNodeEstimator {
	return &BinpackingNodeEstimator{
		predicateChecker: predicateChecker,
		clusterSnapshot:  clusterSnapshot,
	}
}

// Estimate implements First Fit Decreasing bin-packing approximation algorithm.
// See https://en.wikipedia.org/wiki/Bin_packing_problem for more details.
// While it is a multi-dimensional bin packing (cpu, mem, ports) in most cases the main dimension
// will be cpu thus the estimated overprovisioning of 11/9 * optimal + 6/9 should be
// still be maintained.
// It is assumed that all pods from the given list can fit to nodeTemplate.
// Returns the number of nodes needed to accommodate all pods from the list.
func (estimator *BinpackingNodeEstimator) Estimate(
	pods []*apiv1.Pod,
	nodeTemplate *schedulerframework.NodeInfo) int {
	podInfos := calculatePodScore(pods, nodeTemplate)
	sort.Slice(podInfos, func(i, j int) bool { return podInfos[i].score > podInfos[j].score })

	newNodeNames := make(map[string]bool)

	if err := estimator.clusterSnapshot.Fork(); err != nil {
		klog.Errorf("Error while calling ClusterSnapshot.Fork; %v", err)
		return 0
	}
	defer func() {
		if err := estimator.clusterSnapshot.Revert(); err != nil {
			klog.Fatalf("Error while calling ClusterSnapshot.Revert; %v", err)
		}
	}()

	newNodeNameTimestamp := time.Now()
	newNodeNameIndex := 0

	for _, podInfo := range podInfos {
		found := false

		nodeName, err := estimator.predicateChecker.FitsAnyNodeMatching(estimator.clusterSnapshot, podInfo.pod, func(nodeInfo *schedulerframework.NodeInfo) bool {
			return newNodeNames[nodeInfo.Node().Name]
		})
		if err == nil {
			found = true
			if err := estimator.clusterSnapshot.AddPod(podInfo.pod, nodeName); err != nil {
				klog.Errorf("Error adding pod %v.%v to node %v in ClusterSnapshot; %v", podInfo.pod.Namespace, podInfo.pod.Name, nodeName, err)
				return 0
			}
		}

		if !found {
			// Add new node
			newNodeName, err := estimator.addNewNodeToSnapshot(nodeTemplate, newNodeNameTimestamp, newNodeNameIndex)
			if err != nil {
				klog.Errorf("Error while adding new node for template to ClusterSnapshot; %v", err)
				return 0
			}
			newNodeNameIndex++
			// And schedule pod to it
			if err := estimator.clusterSnapshot.AddPod(podInfo.pod, newNodeName); err != nil {
				klog.Errorf("Error adding pod %v.%v to node %v in ClusterSnapshot; %v", podInfo.pod.Namespace, podInfo.pod.Name, newNodeName, err)
				return 0
			}
			newNodeNames[newNodeName] = true
		}
	}
	return len(newNodeNames)
}

func (estimator *BinpackingNodeEstimator) addNewNodeToSnapshot(
	template *schedulerframework.NodeInfo,
	nameTimestamp time.Time,
	nameIndex int) (string, error) {

	newNode := template.Node().DeepCopy()
	newNode.Name = fmt.Sprintf("%s-%d-%d", newNode.Name, nameTimestamp.Unix(), nameIndex)
	if newNode.Labels == nil {
		newNode.Labels = make(map[string]string)
	}
	newNode.Labels["kubernetes.io/hostname"] = newNode.Name
	var pods []*apiv1.Pod
	for _, podInfo := range template.Pods {
		pods = append(pods, podInfo.Pod)
	}
	if err := estimator.clusterSnapshot.AddNodeWithPods(newNode, pods); err != nil {
		return "", err
	}
	return newNode.Name, nil
}

// Calculates score for all pods and returns podInfo structure.
// Score is defined as cpu_sum/node_capacity + mem_sum/node_capacity.
// Pods that have bigger requirements should be processed first, thus have higher scores.
func calculatePodScore(pods []*apiv1.Pod, nodeTemplate *schedulerframework.NodeInfo) []*podInfo {
	podInfos := make([]*podInfo, 0, len(pods))

	for _, pod := range pods {
		cpuSum := resource.Quantity{}
		memorySum := resource.Quantity{}

		for _, container := range pod.Spec.Containers {
			if request, ok := container.Resources.Requests[apiv1.ResourceCPU]; ok {
				cpuSum.Add(request)
			}
			if request, ok := container.Resources.Requests[apiv1.ResourceMemory]; ok {
				memorySum.Add(request)
			}
		}
		score := float64(0)
		if cpuAllocatable, ok := nodeTemplate.Node().Status.Allocatable[apiv1.ResourceCPU]; ok && cpuAllocatable.MilliValue() > 0 {
			score += float64(cpuSum.MilliValue()) / float64(cpuAllocatable.MilliValue())
		}
		if memAllocatable, ok := nodeTemplate.Node().Status.Allocatable[apiv1.ResourceMemory]; ok && memAllocatable.Value() > 0 {
			score += float64(memorySum.Value()) / float64(memAllocatable.Value())
		}

		podInfos = append(podInfos, &podInfo{
			score: score,
			pod:   pod,
		})
	}
	return podInfos
}
<|endoftext|>


<commit_before>package terraform

import (
	"fmt"
)

// NodeApplyableResource represents a resource that is "applyable":
// it is ready to be applied and is represented by a diff.
type NodeApplyableResource struct {
	*NodeAbstractResource
}

// GraphNodeCreator
func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
	return n.NodeAbstractResource.Addr
}

// GraphNodeEvalable
func (n *NodeApplyableResource) EvalTree() EvalNode {
	addr := n.NodeAbstractResource.Addr

	// stateId is the ID to put into the state
	stateId := addr.stateId()
	if addr.Index > -1 {
		stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
	}

	// Build the instance info. More of this will be populated during eval
	info := &InstanceInfo{
		Id:   stateId,
		Type: addr.Type,
	}

	// Build the resource for eval
	resource := &Resource{
		Name:       addr.Name,
		Type:       addr.Type,
		CountIndex: addr.Index,
	}
	if resource.CountIndex < 0 {
		resource.CountIndex = 0
	}

	// Determine the dependencies for the state. We use some older
	// code for this that we've used for a long time.
	var stateDeps []string
	{
		oldN := &graphNodeExpandedResource{Resource: n.Config}
		stateDeps = oldN.StateDependencies()
	}

	// Declare a bunch of variables that are used for state during
	// evaluation. Most of this are written to by-address below.
	var provider ResourceProvider
	var diff, diffApply *InstanceDiff
	var state *InstanceState
	var resourceConfig *ResourceConfig
	var err error
	var createNew bool
	var createBeforeDestroyEnabled bool

	return &EvalSequence{
		Nodes: []EvalNode{
			// Build the instance info
			&EvalInstanceInfo{
				Info: info,
			},

			// Get the saved diff for apply
			&EvalReadDiff{
				Name: stateId,
				Diff: &diffApply,
			},

			// We don't want to do any destroys
			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					if diffApply == nil {
						return true, EvalEarlyExitError{}
					}

					if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
						return true, EvalEarlyExitError{}
					}

					diffApply.SetDestroy(false)
					return true, nil
				},
				Then: EvalNoop{},
			},

			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					destroy := false
					if diffApply != nil {
						destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
					}

					createBeforeDestroyEnabled =
						n.Config.Lifecycle.CreateBeforeDestroy &&
							destroy

					return createBeforeDestroyEnabled, nil
				},
				Then: &EvalDeposeState{
					Name: stateId,
				},
			},

			&EvalInterpolate{
				Config:   n.Config.RawConfig.Copy(),
				Resource: resource,
				Output:   &resourceConfig,
			},
			&EvalGetProvider{
				Name:   n.ProvidedBy()[0],
				Output: &provider,
			},
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},
			// Re-run validation to catch any errors we missed, e.g. type
			// mismatches on computed values.
			&EvalValidateResource{
				Provider:       &provider,
				Config:         &resourceConfig,
				ResourceName:   n.Config.Name,
				ResourceType:   n.Config.Type,
				ResourceMode:   n.Config.Mode,
				IgnoreWarnings: true,
			},
			&EvalDiff{
				Info:       info,
				Config:     &resourceConfig,
				Resource:   n.Config,
				Provider:   &provider,
				Diff:       &diffApply,
				State:      &state,
				OutputDiff: &diffApply,
			},

			// Get the saved diff
			&EvalReadDiff{
				Name: stateId,
				Diff: &diff,
			},

			// Compare the diffs
			&EvalCompareDiff{
				Info: info,
				One:  &diff,
				Two:  &diffApply,
			},

			&EvalGetProvider{
				Name:   n.ProvidedBy()[0],
				Output: &provider,
			},
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},
			&EvalApply{
				Info:      info,
				State:     &state,
				Diff:      &diffApply,
				Provider:  &provider,
				Output:    &state,
				Error:     &err,
				CreateNew: &createNew,
			},
			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.Config.Provider,
				Dependencies: stateDeps,
				State:        &state,
			},
			&EvalApplyProvisioners{
				Info:           info,
				State:          &state,
				Resource:       n.Config,
				InterpResource: resource,
				CreateNew:      &createNew,
				Error:          &err,
			},
			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					return createBeforeDestroyEnabled && err != nil, nil
				},
				Then: &EvalUndeposeState{
					Name:  stateId,
					State: &state,
				},
				Else: &EvalWriteState{
					Name:         stateId,
					ResourceType: n.Config.Type,
					Provider:     n.Config.Provider,
					Dependencies: stateDeps,
					State:        &state,
				},
			},

			// We clear the diff out here so that future nodes
			// don't see a diff that is already complete. There
			// is no longer a diff!
			&EvalWriteDiff{
				Name: stateId,
				Diff: nil,
			},

			&EvalApplyPost{
				Info:  info,
				State: &state,
				Error: &err,
			},
			&EvalUpdateStateHook{},
		},
	}
}
<commit_msg>terraform: new apply resource node supports data sources<commit_after>package terraform

import (
	"fmt"

	"github.com/hashicorp/terraform/config"
)

// NodeApplyableResource represents a resource that is "applyable":
// it is ready to be applied and is represented by a diff.
type NodeApplyableResource struct {
	*NodeAbstractResource
}

// GraphNodeCreator
func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
	return n.NodeAbstractResource.Addr
}

// GraphNodeEvalable
func (n *NodeApplyableResource) EvalTree() EvalNode {
	addr := n.NodeAbstractResource.Addr

	// stateId is the ID to put into the state
	stateId := addr.stateId()
	if addr.Index > -1 {
		stateId = fmt.Sprintf("%s.%d", stateId, addr.Index)
	}

	// Build the instance info. More of this will be populated during eval
	info := &InstanceInfo{
		Id:   stateId,
		Type: addr.Type,
	}

	// Build the resource for eval
	resource := &Resource{
		Name:       addr.Name,
		Type:       addr.Type,
		CountIndex: addr.Index,
	}
	if resource.CountIndex < 0 {
		resource.CountIndex = 0
	}

	// Determine the dependencies for the state. We use some older
	// code for this that we've used for a long time.
	var stateDeps []string
	{
		oldN := &graphNodeExpandedResource{Resource: n.Config}
		stateDeps = oldN.StateDependencies()
	}

	// Eval info is different depending on what kind of resource this is
	switch n.Config.Mode {
	case config.ManagedResourceMode:
		return n.evalTreeManagedResource(
			stateId, info, resource, stateDeps,
		)
	case config.DataResourceMode:
		return n.evalTreeDataResource(
			stateId, info, resource, stateDeps)
	default:
		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
	}
}

func (n *NodeApplyableResource) evalTreeDataResource(
	stateId string, info *InstanceInfo,
	resource *Resource, stateDeps []string) EvalNode {
	var provider ResourceProvider
	var config *ResourceConfig
	var diff *InstanceDiff
	var state *InstanceState

	return &EvalSequence{
		Nodes: []EvalNode{
			// Get the saved diff for apply
			&EvalReadDiff{
				Name: stateId,
				Diff: &diff,
			},

			// Stop here if we don't actually have a diff
			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					if diff == nil {
						return true, EvalEarlyExitError{}
					}

					if diff.GetAttributesLen() == 0 {
						return true, EvalEarlyExitError{}
					}

					return true, nil
				},
				Then: EvalNoop{},
			},

			// We need to re-interpolate the config here, rather than
			// just using the diff's values directly, because we've
			// potentially learned more variable values during the
			// apply pass that weren't known when the diff was produced.
			&EvalInterpolate{
				Config:   n.Config.RawConfig.Copy(),
				Resource: resource,
				Output:   &config,
			},

			&EvalGetProvider{
				Name:   n.ProvidedBy()[0],
				Output: &provider,
			},

			// Make a new diff with our newly-interpolated config.
			&EvalReadDataDiff{
				Info:     info,
				Config:   &config,
				Previous: &diff,
				Provider: &provider,
				Output:   &diff,
			},

			&EvalReadDataApply{
				Info:     info,
				Diff:     &diff,
				Provider: &provider,
				Output:   &state,
			},

			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.Config.Provider,
				Dependencies: stateDeps,
				State:        &state,
			},

			// Clear the diff now that we've applied it, so
			// later nodes won't see a diff that's now a no-op.
			&EvalWriteDiff{
				Name: stateId,
				Diff: nil,
			},

			&EvalUpdateStateHook{},
		},
	}
}

func (n *NodeApplyableResource) evalTreeManagedResource(
	stateId string, info *InstanceInfo,
	resource *Resource, stateDeps []string) EvalNode {
	// Declare a bunch of variables that are used for state during
	// evaluation. Most of this are written to by-address below.
	var provider ResourceProvider
	var diff, diffApply *InstanceDiff
	var state *InstanceState
	var resourceConfig *ResourceConfig
	var err error
	var createNew bool
	var createBeforeDestroyEnabled bool

	return &EvalSequence{
		Nodes: []EvalNode{
			// Build the instance info
			&EvalInstanceInfo{
				Info: info,
			},

			// Get the saved diff for apply
			&EvalReadDiff{
				Name: stateId,
				Diff: &diffApply,
			},

			// We don't want to do any destroys
			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					if diffApply == nil {
						return true, EvalEarlyExitError{}
					}

					if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
						return true, EvalEarlyExitError{}
					}

					diffApply.SetDestroy(false)
					return true, nil
				},
				Then: EvalNoop{},
			},

			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					destroy := false
					if diffApply != nil {
						destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
					}

					createBeforeDestroyEnabled =
						n.Config.Lifecycle.CreateBeforeDestroy &&
							destroy

					return createBeforeDestroyEnabled, nil
				},
				Then: &EvalDeposeState{
					Name: stateId,
				},
			},

			&EvalInterpolate{
				Config:   n.Config.RawConfig.Copy(),
				Resource: resource,
				Output:   &resourceConfig,
			},
			&EvalGetProvider{
				Name:   n.ProvidedBy()[0],
				Output: &provider,
			},
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},
			// Re-run validation to catch any errors we missed, e.g. type
			// mismatches on computed values.
			&EvalValidateResource{
				Provider:       &provider,
				Config:         &resourceConfig,
				ResourceName:   n.Config.Name,
				ResourceType:   n.Config.Type,
				ResourceMode:   n.Config.Mode,
				IgnoreWarnings: true,
			},
			&EvalDiff{
				Info:       info,
				Config:     &resourceConfig,
				Resource:   n.Config,
				Provider:   &provider,
				Diff:       &diffApply,
				State:      &state,
				OutputDiff: &diffApply,
			},

			// Get the saved diff
			&EvalReadDiff{
				Name: stateId,
				Diff: &diff,
			},

			// Compare the diffs
			&EvalCompareDiff{
				Info: info,
				One:  &diff,
				Two:  &diffApply,
			},

			&EvalGetProvider{
				Name:   n.ProvidedBy()[0],
				Output: &provider,
			},
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},
			&EvalApply{
				Info:      info,
				State:     &state,
				Diff:      &diffApply,
				Provider:  &provider,
				Output:    &state,
				Error:     &err,
				CreateNew: &createNew,
			},
			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.Config.Provider,
				Dependencies: stateDeps,
				State:        &state,
			},
			&EvalApplyProvisioners{
				Info:           info,
				State:          &state,
				Resource:       n.Config,
				InterpResource: resource,
				CreateNew:      &createNew,
				Error:          &err,
			},
			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					return createBeforeDestroyEnabled && err != nil, nil
				},
				Then: &EvalUndeposeState{
					Name:  stateId,
					State: &state,
				},
				Else: &EvalWriteState{
					Name:         stateId,
					ResourceType: n.Config.Type,
					Provider:     n.Config.Provider,
					Dependencies: stateDeps,
					State:        &state,
				},
			},

			// We clear the diff out here so that future nodes
			// don't see a diff that is already complete. There
			// is no longer a diff!
			&EvalWriteDiff{
				Name: stateId,
				Diff: nil,
			},

			&EvalApplyPost{
				Info:  info,
				State: &state,
				Error: &err,
			},
			&EvalUpdateStateHook{},
		},
	}
}
<|endoftext|>


<commit_before>package cache

import (
	"bytes"
	"encoding/gob"
	"net/url"

	"github.com/nanopack/shaman/config"

	consul "github.com/hashicorp/consul/api"
	shaman "github.com/nanopack/shaman/core/common"
)

const prefix = "domains:"

type consulDb struct {
	db *consul.Client
}

func addPrefix(in string) string {
	return prefix + in
}

func (client *consulDb) initialize() error {
	u, err := url.Parse(config.L2Connect)
	if err != nil {
		return err
	}

	consulConfig := consul.DefaultNonPooledConfig()
	consulConfig.Address = u.Host
	consulConfig.Scheme = u.Scheme
	consulC, err := consul.NewClient(consulConfig)
	if err != nil {
		return err
	}
	client.db = consulC
	return nil
}

func (client consulDb) addRecord(resource shaman.Resource) error {
	return client.updateRecord(resource.Domain, resource)
}

func (client consulDb) getRecord(domain string) (*shaman.Resource, error) {
	kvHandler := client.db.KV()
	kvPair, _, err := kvHandler.Get(addPrefix(domain), nil)
	if err != nil {
		return nil, err
	}
	if kvPair == nil {
		return nil, errNoRecordError
	}
	var result shaman.Resource
	err = gob.NewDecoder(bytes.NewReader(kvPair.Value)).Decode(&result)
	if err != nil {
		return nil, err
	}

	return &result, nil
}

func (client consulDb) updateRecord(domain string, resource shaman.Resource) error {
	kvHandler := client.db.KV()
	var buf bytes.Buffer
	err := gob.NewEncoder(&buf).Encode(&resource)
	if err != nil {
		return err
	}

	_, err = kvHandler.Put(&consul.KVPair{
		Key:   addPrefix(domain),
		Value: buf.Bytes(),
	}, nil)
	if err != nil {
		return err
	}

	return nil
}

func (client consulDb) deleteRecord(domain string) error {
	kvHandler := client.db.KV()
	_, err := kvHandler.Delete(domain, nil)
	if err != nil {
		return err
	}

	return nil
}

func (client consulDb) resetRecords(resources []shaman.Resource) error {
	kvHandler := client.db.KV()
	_, err := kvHandler.DeleteTree("domains", nil)
	if err != nil {
		return err
	}

	return nil
}

func (client consulDb) listRecords() ([]shaman.Resource, error) {
	kvHandler := client.db.KV()
	kvPairs, _, err := kvHandler.List(prefix, nil)
	if err != nil {
		return nil, err
	}

	result := []shaman.Resource{}
	for _, kvPair := range kvPairs {
		var resource shaman.Resource
		err := gob.NewDecoder(bytes.NewReader(kvPair.Value)).Decode(&resource)
		if err != nil {
			return nil, err
		}
		result = append(result, resource)
	}

	return result, nil
}
<commit_msg>reset records now adds records too<commit_after>package cache

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"net/url"

	"github.com/nanopack/shaman/config"

	consul "github.com/hashicorp/consul/api"
	shaman "github.com/nanopack/shaman/core/common"
)

const prefix = "domains:"

type consulDb struct {
	db *consul.Client
}

func addPrefix(in string) string {
	return prefix + in
}

func (client *consulDb) initialize() error {
	u, err := url.Parse(config.L2Connect)
	if err != nil {
		return err
	}

	consulConfig := consul.DefaultNonPooledConfig()
	consulConfig.Address = u.Host
	consulConfig.Scheme = u.Scheme
	consulC, err := consul.NewClient(consulConfig)
	if err != nil {
		return err
	}
	client.db = consulC
	return nil
}

func (client consulDb) addRecord(resource shaman.Resource) error {
	return client.updateRecord(resource.Domain, resource)
}

func (client consulDb) getRecord(domain string) (*shaman.Resource, error) {
	kvHandler := client.db.KV()
	kvPair, _, err := kvHandler.Get(addPrefix(domain), nil)
	if err != nil {
		return nil, err
	}
	if kvPair == nil {
		return nil, errNoRecordError
	}
	var result shaman.Resource
	err = gob.NewDecoder(bytes.NewReader(kvPair.Value)).Decode(&result)
	if err != nil {
		return nil, err
	}

	return &result, nil
}

func (client consulDb) updateRecord(domain string, resource shaman.Resource) error {
	kvHandler := client.db.KV()
	var buf bytes.Buffer
	err := gob.NewEncoder(&buf).Encode(&resource)
	if err != nil {
		return err
	}

	_, err = kvHandler.Put(&consul.KVPair{
		Key:   addPrefix(domain),
		Value: buf.Bytes(),
	}, nil)
	if err != nil {
		return err
	}

	return nil
}

func (client consulDb) deleteRecord(domain string) error {
	kvHandler := client.db.KV()
	_, err := kvHandler.Delete(domain, nil)
	if err != nil {
		return err
	}

	return nil
}

func (client consulDb) resetRecords(resources []shaman.Resource) error {
	kvHandler := client.db.KV()
	_, err := kvHandler.DeleteTree(prefix, nil)
	if err != nil {
		return err
	}

	for i := range resources {
		err = client.addRecord(resources[i]) // prevents duplicates
		if err != nil {
			return fmt.Errorf("Failed to save records - %v", err)
		}
	}
	return nil
}

func (client consulDb) listRecords() ([]shaman.Resource, error) {
	kvHandler := client.db.KV()
	kvPairs, _, err := kvHandler.List(prefix, nil)
	if err != nil {
		return nil, err
	}

	result := []shaman.Resource{}
	for _, kvPair := range kvPairs {
		var resource shaman.Resource
		err := gob.NewDecoder(bytes.NewReader(kvPair.Value)).Decode(&resource)
		if err != nil {
			return nil, err
		}
		result = append(result, resource)
	}

	return result, nil
}
<|endoftext|>


<commit_before>package decoders

import (
	"errors"
	"log"
	"regexp"
	"strconv"
	"time"

	"github.com/seadsystem/Backend/DB/landingzone/constants"
)

type SeadPacket struct {
	Type      byte
	Location  byte
	Timestamp time.Time
	Period    time.Duration
	Count     uint
	Data      []uint16
	Serial    int
}

var headerRegex *regexp.Regexp
var InvalidHeader = errors.New("Invalid header.")
var InvalidPacket = errors.New("Invalid packet.")
var InvalidTime = errors.New("Invalid time.")

// init sets up stuff we need with proper error handling. If it isn't complicated or doesn't need error handling, it can probably just be assigned directly.
func init() {
	var err error
	headerRegex, err = regexp.Compile(constants.HEADER_REGEX)
	if err != nil {
		log.Panic("Regex compile error:", err)
	}
}

// DecodeHeader verifies that the header is in the correct format and extracts the serial number
func DecodeHeader(packet []byte) (serial int, offset time.Time, err error) {
	headerStrings := headerRegex.FindSubmatch(packet)

	if headerStrings == nil || len(headerStrings) != 3 {
		err = InvalidHeader
		return
	}

	log.Printf("Header serial string: %s\n", string(headerStrings[1]))

	var duration time.Duration
	duration, err = AsciiTimeToDuration(headerStrings[2])
	if err != nil {
		return
	}

	offset = time.Now().Add(-1 * duration)

	serial, err = strconv.Atoi(string(headerStrings[1]))
	return
}

// DecodePacket extracts the data sent from sensor
func DecodePacket(buffer []byte, offset time.Time) (packet SeadPacket, err error) {
	for i := 0; i < len(buffer); {
		datatype := buffer[i]
		i++

		// Switch on the type of data sent in the packet
		switch {
		case datatype == 'T':
			// Type
			packet.Type = buffer[i]
			i++
		case datatype == 'l':
			// Location
			packet.Location = buffer[i]
			i++
		case datatype == 't':
			// Timestamp
			var on_time time.Duration
			on_time, err = AsciiTimeToDuration(buffer[i : i+14])
			packet.Timestamp = offset.Add(on_time)
			i += 14
		case datatype == 'P':
			// Period separator
			packet.Period, err = AsciiTimeToDuration(buffer[i : i+14])
			i += 14
		case datatype == 'C':
			// Count
			packet.Count = Binary2uint(buffer[i : i+2])
			i += 2
		case datatype == 'D':
			// Data
			// if count isn't set, return error
			if packet.Count == 0 {
				err = InvalidPacket
			} else {
				count := int(packet.Count)
				bytes := count * 2
				data := buffer[i : i+bytes]
				packet.Data = make([]uint16, count)
				for i := 0; i < bytes; i += 2 {
					packet.Data[i/2] = Binary2uint16(data[i : i+2])
				}
				i += bytes
			}
		case datatype == 'S':
			// Serial
			packet.Serial, err = strconv.Atoi(string(buffer[i : i+6]))
			i += 6
		case datatype == 'X':
			return
		default:
			err = InvalidPacket
		}

		if err != nil {
			return
		}
	}
	err = InvalidPacket
	return
}

// Every checks if every byte in a slice meets some criteria
func Every(data []byte, check func(byte) bool) bool {
	for _, element := range data {
		if !check(element) {
			return false
		}
	}
	return true
}

// Binary2uint converts a byte array containing binary data into an int
func Binary2uint(data []byte) (total uint) {
	for index, element := range data {
		total += uint(element) << uint(index*8)
	}
	return
}

// Binary2uint16 converts a 2 byte array containing binary data into an uint16
func Binary2uint16(data []byte) (total uint16) {
	return uint16(data[0]) + uint16(data[1])<<uint16(8)
}

func AsciiTimeToDuration(ascii_time []byte) (duration time.Duration, err error) {
	// Check time string format
	if len(ascii_time) != 16 {
		err = InvalidTime
	}
	_, err = strconv.Atoi(string(ascii_time))
	if err != nil {
		return
	}

	// Do the conversion now that we know it should work
	var ptr int = 0

	days, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	duration += time.Hour * time.Duration(24*days)
	hours, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Hour * time.Duration(hours)
	minutes, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Minute * time.Duration(minutes)
	seconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Second * time.Duration(seconds)
	milliseconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	duration += time.Millisecond * time.Duration(milliseconds)
	clock, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Millisecond * time.Duration(clock) / 12
	return
}
<commit_msg>Removed unnessecary function that was causing problems.<commit_after>package decoders

import (
	"errors"
	"log"
	"regexp"
	"strconv"
	"time"

	"github.com/seadsystem/Backend/DB/landingzone/constants"
)

type SeadPacket struct {
	Type      byte
	Location  byte
	Timestamp time.Time
	Period    time.Duration
	Count     uint
	Data      []uint16
	Serial    int
}

var headerRegex *regexp.Regexp
var InvalidHeader = errors.New("Invalid header.")
var InvalidPacket = errors.New("Invalid packet.")
var InvalidTime = errors.New("Invalid time.")

// init sets up stuff we need with proper error handling. If it isn't complicated or doesn't need error handling, it can probably just be assigned directly.
func init() {
	var err error
	headerRegex, err = regexp.Compile(constants.HEADER_REGEX)
	if err != nil {
		log.Panic("Regex compile error:", err)
	}
}

// DecodeHeader verifies that the header is in the correct format and extracts the serial number
func DecodeHeader(packet []byte) (serial int, offset time.Time, err error) {
	headerStrings := headerRegex.FindSubmatch(packet)

	if headerStrings == nil || len(headerStrings) != 3 {
		err = InvalidHeader
		return
	}

	log.Printf("Header serial string: %s\n", string(headerStrings[1]))

	var duration time.Duration
	duration, err = AsciiTimeToDuration(headerStrings[2])
	if err != nil {
		return
	}

	offset = time.Now().Add(-1 * duration)

	serial, err = strconv.Atoi(string(headerStrings[1]))
	return
}

// DecodePacket extracts the data sent from sensor
func DecodePacket(buffer []byte, offset time.Time) (packet SeadPacket, err error) {
	for i := 0; i < len(buffer); {
		datatype := buffer[i]
		i++

		// Switch on the type of data sent in the packet
		switch {
		case datatype == 'T':
			// Type
			packet.Type = buffer[i]
			i++
		case datatype == 'l':
			// Location
			packet.Location = buffer[i]
			i++
		case datatype == 't':
			// Timestamp
			var on_time time.Duration
			on_time, err = AsciiTimeToDuration(buffer[i : i+14])
			packet.Timestamp = offset.Add(on_time)
			i += 14
		case datatype == 'P':
			// Period separator
			packet.Period, err = AsciiTimeToDuration(buffer[i : i+14])
			i += 14
		case datatype == 'C':
			// Count
			packet.Count = Binary2uint(buffer[i : i+2])
			i += 2
		case datatype == 'D':
			// Data
			// if count isn't set, return error
			if packet.Count == 0 {
				err = InvalidPacket
			} else {
				count := int(packet.Count)
				bytes := count * 2
				data := buffer[i : i+bytes]
				packet.Data = make([]uint16, count)
				for i := 0; i < bytes; i += 2 {
					packet.Data[i/2] = Binary2uint(data[i : i+2])
				}
				i += bytes
			}
		case datatype == 'S':
			// Serial
			packet.Serial, err = strconv.Atoi(string(buffer[i : i+6]))
			i += 6
		case datatype == 'X':
			return
		default:
			err = InvalidPacket
		}

		if err != nil {
			return
		}
	}
	err = InvalidPacket
	return
}

// Every checks if every byte in a slice meets some criteria
func Every(data []byte, check func(byte) bool) bool {
	for _, element := range data {
		if !check(element) {
			return false
		}
	}
	return true
}

// Binary2uint converts a byte array containing binary data into an int
func Binary2uint(data []byte) (total uint) {
	for index, element := range data {
		total += uint(element) << uint(index*8)
	}
	return
}

func AsciiTimeToDuration(ascii_time []byte) (duration time.Duration, err error) {
	// Check time string format
	if len(ascii_time) != 16 {
		err = InvalidTime
	}
	_, err = strconv.Atoi(string(ascii_time))
	if err != nil {
		return
	}

	// Do the conversion now that we know it should work
	var ptr int = 0

	days, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	duration += time.Hour * time.Duration(24*days)
	hours, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Hour * time.Duration(hours)
	minutes, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Minute * time.Duration(minutes)
	seconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Second * time.Duration(seconds)
	milliseconds, err := strconv.Atoi(string(ascii_time[ptr : ptr+3]))
	if err != nil {
		return
	}
	ptr += 3
	duration += time.Millisecond * time.Duration(milliseconds)
	clock, err := strconv.Atoi(string(ascii_time[ptr : ptr+2]))
	if err != nil {
		return
	}
	ptr += 2
	duration += time.Millisecond * time.Duration(clock) / 12
	return
}
<|endoftext|>


<commit_before>package cba

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"syscall"
	"testing"

	"github.com/hanwen/go-fuse/splice"
)

type netTestCase struct {
	tester              *testing.T
	tmp                 string
	server, clientStore *Store
	sockS, sockC        io.ReadWriteCloser
	client              *Client
	startSplices        int
}

// TODO - cut & paste.
func unixSocketpair() (l *os.File, r *os.File, err error) {
	fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)

	if err != nil {
		return nil, nil, os.NewSyscallError("socketpair",
			err.(syscall.Errno))
	}
	l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
	r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
	return
}

func (tc *netTestCase) Clean() {
	tc.sockS.Close()
	tc.sockC.Close()
	os.RemoveAll(tc.tmp)
	if tc.startSplices != splice.Used() {
		tc.tester.Fatalf("Splice leak before %d after %d",
			tc.startSplices, splice.Used())
	}
}

func newNetTestCase(t *testing.T) *netTestCase {
	me := &netTestCase{}
	me.tester = t
	me.startSplices = splice.Used()
	me.tmp, _ = ioutil.TempDir("", "term-cba")

	optS := StoreOptions{
		Dir: me.tmp + "/server",
	}
	me.server = NewStore(&optS)

	optC := optS
	optC.Dir = me.tmp + "/client"
	me.clientStore = NewStore(&optC)
	var err error
	me.sockS, me.sockC, err = unixSocketpair()
	if err != nil {
		t.Fatalf("unixSocketpair: %v", err)
	}

	go me.server.ServeConn(me.sockS)
	me.client = me.clientStore.NewClient(me.sockC)
	return me
}

func TestNet(t *testing.T) {
	tc := newNetTestCase(t)
	defer tc.Clean()

	b := bytes.NewBufferString("hello")
	l := b.Len()
	hash := tc.server.SaveStream(b, int64(l))

	different := hash[1:] + "x"
	if success, err := tc.client.Fetch(different, 1024); success || err != nil {
		t.Errorf("non-existent fetch should return false without error: %v %v", success, err)
	}

	if success, err := tc.client.Fetch(hash, 1024); !success || err != nil {
		t.Fatalf("unexpected error: Fetch: %v,%v", success, err)
	}

	if !tc.clientStore.Has(hash) {
		t.Errorf("after fetch, the hash should be there")
	}

	tc.sockC.Close()
	if success, err := tc.client.Fetch(different, 1024); success || err == nil {
		t.Errorf("after close, fetch should return error: succ=%v", success)
	}
}

func TestNetLargeFile(t *testing.T) {
	b := make([]byte, 257*1024)
	for i, _ := range b {
		b[i] = byte(i)
	}

	tc := newNetTestCase(t)
	defer tc.Clean()

	hash := tc.server.Save(b)

	tc.client.Fetch(hash, int64(len(b)))
	if !tc.clientStore.Has(hash) {
		t.Errorf("after fetch, the hash should be there")
	}
}
<commit_msg>cba: Use net.Pipe() in test.<commit_after>package cba

import (
	"bytes"
	"io"
	"io/ioutil"
	"net"
	"os"
	"testing"

	"github.com/hanwen/go-fuse/splice"
)

type netTestCase struct {
	tester              *testing.T
	tmp                 string
	server, clientStore *Store
	sockS, sockC        io.ReadWriteCloser
	client              *Client
	startSplices        int
}

func (tc *netTestCase) Clean() {
	tc.sockS.Close()
	tc.sockC.Close()
	os.RemoveAll(tc.tmp)
	if tc.startSplices != splice.Used() {
		tc.tester.Fatalf("Splice leak before %d after %d",
			tc.startSplices, splice.Used())
	}
}

func newNetTestCase(t *testing.T) *netTestCase {
	me := &netTestCase{}
	me.tester = t
	me.startSplices = splice.Used()
	me.tmp, _ = ioutil.TempDir("", "term-cba")

	optS := StoreOptions{
		Dir: me.tmp + "/server",
	}
	me.server = NewStore(&optS)

	optC := optS
	optC.Dir = me.tmp + "/client"
	me.clientStore = NewStore(&optC)
	var err error
	me.sockS, me.sockC = net.Pipe()
	if err != nil {
		t.Fatalf("unixSocketpair: %v", err)
	}

	go me.server.ServeConn(me.sockS)
	me.client = me.clientStore.NewClient(me.sockC)
	return me
}

func TestNet(t *testing.T) {
	tc := newNetTestCase(t)
	defer tc.Clean()

	b := bytes.NewBufferString("hello")
	l := b.Len()
	hash := tc.server.SaveStream(b, int64(l))

	different := hash[1:] + "x"
	if success, err := tc.client.Fetch(different, 1024); success || err != nil {
		t.Errorf("non-existent fetch should return false without error: %v %v", success, err)
	}

	if success, err := tc.client.Fetch(hash, 1024); !success || err != nil {
		t.Fatalf("unexpected error: Fetch: %v,%v", success, err)
	}

	if !tc.clientStore.Has(hash) {
		t.Errorf("after fetch, the hash should be there")
	}

	tc.sockC.Close()
	if success, err := tc.client.Fetch(different, 1024); success || err == nil {
		t.Errorf("after close, fetch should return error: succ=%v", success)
	}
}

func TestNetLargeFile(t *testing.T) {
	b := make([]byte, 257*1024)
	for i, _ := range b {
		b[i] = byte(i)
	}

	tc := newNetTestCase(t)
	defer tc.Clean()

	hash := tc.server.Save(b)

	tc.client.Fetch(hash, int64(len(b)))
	if !tc.clientStore.Has(hash) {
		t.Errorf("after fetch, the hash should be there")
	}
}
<|endoftext|>