<commit_before>package temperrcatcher

import (
	"fmt"
	"testing"
	"time"
)

var (
	ErrTemp  = ErrTemporary{fmt.Errorf("ErrTemp")}
	ErrSkip  = fmt.Errorf("ErrSkip")
	ErrOther = fmt.Errorf("ErrOther")
)

func testTec(t *testing.T, c TempErrCatcher, errs map[error]bool) {
	for e, expected := range errs {
		if c.IsTemporary(e) != expected {
			t.Error("expected %s to be %v", e, expected)
		}
	}
}

func TestNil(t *testing.T) {
	var c TempErrCatcher
	testTec(t, c, map[error]bool{
		ErrTemp:  true,
		ErrSkip:  false,
		ErrOther: false,
	})
}

func TestWait(t *testing.T) {
	var c TempErrCatcher
	worked := make(chan time.Duration, 3)
	c.Wait = func(t time.Duration) {
		worked <- t
	}
	testTec(t, c, map[error]bool{
		ErrTemp:  true,
		ErrSkip:  false,
		ErrOther: false,
	})

	// should've called it once
	select {
	case <-worked:
	default:
		t.Error("did not call our Wait func")
	}

	// should've called it ONLY once
	select {
	case <-worked:
		t.Error("called our Wait func more than once")
	default:
	}
}

func TestTemporary(t *testing.T) {
	var c TempErrCatcher
	testTec(t, c, map[error]bool{
		ErrTemp:  true,
		ErrSkip:  false,
		ErrOther: false,
	})
}

func TestDoubles(t *testing.T) {
	last := time.Now()
	diff := func() time.Duration {
		now := time.Now()
		diff := now.Sub(last)
		last = now
		return diff
	}

	testDiff := func(low, hi time.Duration) {
		d := diff()
		grace := time.Duration(time.Microsecond)
		if (d + grace) < low {
			t.Error("time difference is smaller than", low, d)
		}
		if (d - grace) > hi {
			t.Error("time difference is greater than", hi, d)
		}
	}

	var c TempErrCatcher
	testDiff(0, c.Start)
	c.IsTemporary(ErrTemp)
	testDiff(c.Start, 2*c.Start) // first time.
	c.IsTemporary(ErrTemp)
	testDiff(2*c.Start, 4*c.Start) // second time.
	c.IsTemporary(ErrTemp)
	testDiff(4*c.Start, 8*c.Start) // third time.
}

func TestDifferentStart(t *testing.T) {
	last := time.Now()
	diff := func() time.Duration {
		now := time.Now()
		diff := now.Sub(last)
		last = now
		return diff
	}

	testDiff := func(low, hi time.Duration) {
		d := diff()
		grace := time.Duration(time.Microsecond)
		if (d + grace) < low {
			t.Error("time difference is smaller than", low, d)
		}
		if (d - grace) > hi {
			t.Error("time difference is greater than", hi, d)
		}
	}

	var c TempErrCatcher
	f := time.Millisecond
	testDiff(0, f)
	c.IsTemporary(ErrTemp)
	testDiff(f, 2*f) // first time.
	c.IsTemporary(ErrTemp)
	testDiff(2*f, 4*f) // second time.
	c.IsTemporary(ErrTemp)
	testDiff(4*f, 8*f) // third time.

	c.Reset()
	c.Start = 10 * time.Millisecond
	f = c.Start
	testDiff(0, f)
	c.IsTemporary(ErrTemp)
	testDiff(f, 2*f) // first time.
	c.IsTemporary(ErrTemp)
	testDiff(2*f, 4*f) // second time.
	c.IsTemporary(ErrTemp)
	testDiff(4*f, 8*f) // third time.
}

func TestDifferentStreaks(t *testing.T) {
	var c TempErrCatcher
	// one streak
	c.IsTemporary(ErrTemp) // 1
	c.IsTemporary(ErrTemp) // 2
	c.IsTemporary(ErrTemp) // 4
	expect := 4 * time.Millisecond
	if c.delay != expect {
		t.Error("delay should be:", expect, c.delay)
	}

	<-time.After(c.delay * 10)

	// a different streak
	c.IsTemporary(ErrTemp) // 1
	c.IsTemporary(ErrTemp) // 2
	c.IsTemporary(ErrTemp) // 4
	if c.delay != expect {
		t.Error("delay should be:", expect, c.delay)
	}
}

func TestFunc(t *testing.T) {
	var c TempErrCatcher
	c.IsTemp = func(e error) bool {
		return e == ErrSkip
	}
	testTec(t, c, map[error]bool{
		ErrTemp:  false,
		ErrSkip:  true,
		ErrOther: false,
	})
}
<commit_msg>better grace<commit_after>package temperrcatcher

import (
	"fmt"
	"testing"
	"time"
)

var (
	ErrTemp  = ErrTemporary{fmt.Errorf("ErrTemp")}
	ErrSkip  = fmt.Errorf("ErrSkip")
	ErrOther = fmt.Errorf("ErrOther")
)

func testTec(t *testing.T, c TempErrCatcher, errs map[error]bool) {
	for e, expected := range errs {
		if c.IsTemporary(e) != expected {
			t.Error("expected %s to be %v", e, expected)
		}
	}
}

func TestNil(t *testing.T) {
	var c TempErrCatcher
	testTec(t, c, map[error]bool{
		ErrTemp:  true,
		ErrSkip:  false,
		ErrOther: false,
	})
}

func TestWait(t *testing.T) {
	var c TempErrCatcher
	worked := make(chan time.Duration, 3)
	c.Wait = func(t time.Duration) {
		worked <- t
	}
	testTec(t, c, map[error]bool{
		ErrTemp:  true,
		ErrSkip:  false,
		ErrOther: false,
	})

	// should've called it once
	select {
	case <-worked:
	default:
		t.Error("did not call our Wait func")
	}

	// should've called it ONLY once
	select {
	case <-worked:
		t.Error("called our Wait func more than once")
	default:
	}
}

func TestTemporary(t *testing.T) {
	var c TempErrCatcher
	testTec(t, c, map[error]bool{
		ErrTemp:  true,
		ErrSkip:  false,
		ErrOther: false,
	})
}

func TestDoubles(t *testing.T) {
	last := time.Now()
	diff := func() time.Duration {
		now := time.Now()
		diff := now.Sub(last)
		last = now
		return diff
	}

	testDiff := func(low, hi time.Duration) {
		d := diff()
		grace := time.Duration(50 * time.Microsecond)
		if (d + grace) < low {
			t.Error("time difference is smaller than", low, d)
		}
		if (d - grace) > hi {
			t.Error("time difference is greater than", hi, d)
		}
	}

	var c TempErrCatcher
	testDiff(0, c.Start)
	c.IsTemporary(ErrTemp)
	testDiff(c.Start, 2*c.Start) // first time.
	c.IsTemporary(ErrTemp)
	testDiff(2*c.Start, 4*c.Start) // second time.
	c.IsTemporary(ErrTemp)
	testDiff(4*c.Start, 8*c.Start) // third time.
}

func TestDifferentStart(t *testing.T) {
	last := time.Now()
	diff := func() time.Duration {
		now := time.Now()
		diff := now.Sub(last)
		last = now
		return diff
	}

	testDiff := func(low, hi time.Duration) {
		d := diff()
		grace := time.Duration(50 * time.Microsecond)
		if (d + grace) < low {
			t.Error("time difference is smaller than", low, d)
		}
		if (d - grace) > hi {
			t.Error("time difference is greater than", hi, d)
		}
	}

	var c TempErrCatcher
	f := time.Millisecond
	testDiff(0, f)
	c.IsTemporary(ErrTemp)
	testDiff(f, 2*f) // first time.
	c.IsTemporary(ErrTemp)
	testDiff(2*f, 4*f) // second time.
	c.IsTemporary(ErrTemp)
	testDiff(4*f, 8*f) // third time.

	c.Reset()
	c.Start = 10 * time.Millisecond
	f = c.Start
	testDiff(0, f)
	c.IsTemporary(ErrTemp)
	testDiff(f, 2*f) // first time.
	c.IsTemporary(ErrTemp)
	testDiff(2*f, 4*f) // second time.
	c.IsTemporary(ErrTemp)
	testDiff(4*f, 8*f) // third time.
}

func TestDifferentStreaks(t *testing.T) {
	var c TempErrCatcher
	// one streak
	c.IsTemporary(ErrTemp) // 1
	c.IsTemporary(ErrTemp) // 2
	c.IsTemporary(ErrTemp) // 4
	expect := 4 * time.Millisecond
	if c.delay != expect {
		t.Error("delay should be:", expect, c.delay)
	}

	<-time.After(c.delay * 10)

	// a different streak
	c.IsTemporary(ErrTemp) // 1
	c.IsTemporary(ErrTemp) // 2
	c.IsTemporary(ErrTemp) // 4
	if c.delay != expect {
		t.Error("delay should be:", expect, c.delay)
	}
}

func TestFunc(t *testing.T) {
	var c TempErrCatcher
	c.IsTemp = func(e error) bool {
		return e == ErrSkip
	}
	testTec(t, c, map[error]bool{
		ErrTemp:  false,
		ErrSkip:  true,
		ErrOther: false,
	})
}
<|endoftext|>


<commit_before>package userd_k8s

import (
	"context"
	"fmt"
	"sync"

	"github.com/pkg/errors"
	"google.golang.org/grpc"
	empty "google.golang.org/protobuf/types/known/emptypb"
	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/discovery"

	"github.com/datawire/ambassador/pkg/kates"
	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/rpc/v2/daemon"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/actions"
)

type nameMeta struct {
	Name string `json:"name"`
}

type objName struct {
	nameMeta `json:"metadata"`
}

type Callbacks struct {
	SetDNSSearchPath func(ctx context.Context, in *daemon.Paths, opts ...grpc.CallOption) (*empty.Empty, error)
}

// k8sCluster is a Kubernetes cluster reference
type Cluster struct {
	*Config
	mappedNamespaces []string

	// Main
	client    *kates.Client
	callbacks Callbacks

	lastNamespaces []string

	// Currently intercepted namespaces by remote intercepts
	interceptedNamespaces map[string]struct{}

	// Currently intercepted namespaces by local intercepts
	localInterceptedNamespaces map[string]struct{}

	accLock         sync.Mutex
	accWait         chan struct{}
	LocalIntercepts map[string]string

	// Current Namespace snapshot, get set by acc.Update().
	curSnapshot struct {
		Namespaces []*objName
	}
}

func (kc *Cluster) ActualNamespace(namespace string) string {
	if namespace == "" {
		namespace = kc.Namespace
	}
	if !kc.namespaceExists(namespace) {
		namespace = ""
	}
	return namespace
}

// check uses a non-caching DiscoveryClientConfig to retrieve the server version
func (kc *Cluster) check(c context.Context) error {
	// The discover client is using context.TODO() so the timeout specified in our
	// context has no effect.
	errCh := make(chan error)
	go func() {
		dc, err := discovery.NewDiscoveryClientForConfig(kc.config)
		if err != nil {
			errCh <- err
			return
		}
		info, err := dc.ServerVersion()
		if err != nil {
			errCh <- err
			return
		}
		dlog.Infof(c, "Server version %s", info.GitVersion)
		close(errCh)
	}()

	select {
	case <-c.Done():
	case err := <-errCh:
		if err == nil {
			return nil
		}
		if c.Err() == nil {
			return fmt.Errorf("initial cluster check failed: %w", client.RunError(err))
		}
	}
	return c.Err()
}

// kindNames returns the names of all objects of a specified Kind in a given Namespace
func (kc *Cluster) kindNames(c context.Context, kind, namespace string) ([]string, error) {
	var objNames []objName
	if err := kc.client.List(c, kates.Query{Kind: kind, Namespace: namespace}, &objNames); err != nil {
		return nil, err
	}
	names := make([]string, len(objNames))
	for i, n := range objNames {
		names[i] = n.Name
	}
	return names, nil
}

// DeploymentNames returns the names of all deployments found in the given Namespace
func (kc *Cluster) DeploymentNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "Deployment", namespace)
}

// ReplicaSetNames returns the names of all replica sets found in the given Namespace
func (kc *Cluster) ReplicaSetNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "ReplicaSet", namespace)
}

// StatefulSetNames returns the names of all replica sets found in the given Namespace
func (kc *Cluster) StatefulSetNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "StatefulSet", namespace)
}

// PodNames returns the names of all replica sets found in the given Namespace
func (kc *Cluster) PodNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "Pod", namespace)
}

// FindDeployment returns a deployment with the given name in the given namespace or nil
// if no such deployment could be found.
func (kc *Cluster) FindDeployment(c context.Context, namespace, name string) (*kates.Deployment, error) {
	dep := &kates.Deployment{
		TypeMeta:   kates.TypeMeta{Kind: "Deployment"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, dep, dep); err != nil {
		return nil, err
	}
	return dep, nil
}

// FindStatefulSet returns a statefulSet with the given name in the given namespace or nil
// if no such statefulSet could be found.
func (kc *Cluster) FindStatefulSet(c context.Context, namespace, name string) (*kates.StatefulSet, error) {
	statefulSet := &kates.StatefulSet{
		TypeMeta:   kates.TypeMeta{Kind: "StatefulSet"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, statefulSet, statefulSet); err != nil {
		return nil, err
	}
	return statefulSet, nil
}

// FindReplicaSet returns a replica set with the given name in the given namespace or nil
// if no such replica set could be found.
func (kc *Cluster) FindReplicaSet(c context.Context, namespace, name string) (*kates.ReplicaSet, error) {
	rs := &kates.ReplicaSet{
		TypeMeta:   kates.TypeMeta{Kind: "ReplicaSet"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, rs, rs); err != nil {
		return nil, err
	}
	return rs, nil
}

// FindPod returns a pod with the given name in the given namespace or nil
// if no such replica set could be found.
func (kc *Cluster) FindPod(c context.Context, namespace, name string) (*kates.Pod, error) {
	pod := &kates.Pod{
		TypeMeta:   kates.TypeMeta{Kind: "Pod"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, pod, pod); err != nil {
		return nil, err
	}
	return pod, nil
}

// FindObjectKind returns a workload for the given name and namespace. We
// search in a specific order based on how we prefer workload objects:
// 1. Deployments
// 2. ReplicaSets
// 3. StatefulSets
// And return the kind as soon as we find one that matches
func (kc *Cluster) FindObjectKind(c context.Context, namespace, name string) (string, error) {
	depNames, err := kc.DeploymentNames(c, namespace)
	if err != nil {
		return "", err
	}
	for _, depName := range depNames {
		if depName == name {
			return "Deployment", nil
		}
	}

	// Since Deployments manage ReplicaSets, we only look for matching
	// ReplicaSets if no Deployment was found
	rsNames, err := kc.ReplicaSetNames(c, namespace)
	if err != nil {
		return "", err
	}
	for _, rsName := range rsNames {
		if rsName == name {
			return "ReplicaSet", nil
		}
	}

	// Like ReplicaSets, StatefulSets only manage pods so we check for
	// them next
	StatefulSetNames, err := kc.StatefulSetNames(c, namespace)
	if err != nil {
		return "", err
	}
	for _, statefulSetName := range StatefulSetNames {
		if statefulSetName == name {
			return "StatefulSet", nil
		}
	}
	return "", errors.New("No supported Object Kind Found")
}

// FindSvc finds a service with the given name in the given Namespace and returns
// either a copy of that service or nil if no such service could be found.
func (kc *Cluster) FindSvc(c context.Context, namespace, name string) (*kates.Service, error) {
	rs := &kates.Service{
		TypeMeta:   kates.TypeMeta{Kind: "Service"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, rs, rs); err != nil {
		return nil, err
	}
	return rs, nil
}

// findAllSvc finds services with the given service type in all namespaces of the cluster returns
// a slice containing a copy of those services.
func (kc *Cluster) findAllSvcByType(c context.Context, svcType v1.ServiceType) ([]*kates.Service, error) {
	// NOTE: This is expensive in terms of bandwidth on a large cluster. We currently only use this
	// to retrieve ingress info and that task could be moved to the traffic-manager instead.
	var svcs []*kates.Service
	if err := kc.client.List(c, kates.Query{Kind: "Service"}, &svcs); err != nil {
		return nil, err
	}
	var typedSvcs []*kates.Service
	for _, svc := range svcs {
		if svc.Spec.Type == svcType {
			typedSvcs = append(typedSvcs, svc)
			break
		}
	}
	return typedSvcs, nil
}

func (kc *Cluster) namespaceExists(namespace string) (exists bool) {
	kc.accLock.Lock()
	for _, n := range kc.lastNamespaces {
		if n == namespace {
			exists = true
			break
		}
	}
	kc.accLock.Unlock()
	return exists
}

func NewCluster(c context.Context, kubeFlags *Config, mappedNamespaces []string, callbacks Callbacks) (*Cluster, error) {
	// TODO: Add constructor to kates that takes an additional restConfig argument to prevent that kates recreates it.
	kc, err := kates.NewClientFromConfigFlags(kubeFlags.ConfigFlags)
	if err != nil {
		return nil, client.CheckTimeout(c, fmt.Errorf("k8s client create failed: %w", err))
	}

	ret := &Cluster{
		Config:           kubeFlags,
		mappedNamespaces: mappedNamespaces,
		client:           kc,
		callbacks:        callbacks,
		LocalIntercepts:  map[string]string{},
		accWait:          make(chan struct{}),
	}

	if err := ret.check(c); err != nil {
		return nil, err
	}

	dlog.Infof(c, "Context: %s", ret.Context)
	dlog.Infof(c, "Server: %s", ret.Server)

	return ret, nil
}

func (kc *Cluster) WaitUntilReady(ctx context.Context) error {
	select {
	case <-kc.accWait:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

func (kc *Cluster) GetClusterId(ctx context.Context) string {
	clusterID, _ := actions.GetClusterID(ctx, kc.client)
	return clusterID
}

func (kc *Cluster) Client() *kates.Client {
	return kc.client
}

func (kc *Cluster) GetManagerNamespace() string {
	return kc.kubeconfigExtension.Manager.Namespace
}
<commit_msg>Simplify FindObjectKind().<commit_after>package userd_k8s

import (
	"context"
	"fmt"
	"sync"

	"k8s.io/apimachinery/pkg/runtime/schema"

	"google.golang.org/grpc"
	empty "google.golang.org/protobuf/types/known/emptypb"
	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/discovery"

	"github.com/datawire/ambassador/pkg/kates"
	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/rpc/v2/daemon"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/actions"
)

type nameMeta struct {
	Name string `json:"name"`
}

type objName struct {
	nameMeta `json:"metadata"`
}

type Callbacks struct {
	SetDNSSearchPath func(ctx context.Context, in *daemon.Paths, opts ...grpc.CallOption) (*empty.Empty, error)
}

// k8sCluster is a Kubernetes cluster reference
type Cluster struct {
	*Config
	mappedNamespaces []string

	// Main
	client    *kates.Client
	callbacks Callbacks

	lastNamespaces []string

	// Currently intercepted namespaces by remote intercepts
	interceptedNamespaces map[string]struct{}

	// Currently intercepted namespaces by local intercepts
	localInterceptedNamespaces map[string]struct{}

	accLock         sync.Mutex
	accWait         chan struct{}
	LocalIntercepts map[string]string

	// Current Namespace snapshot, get set by acc.Update().
	curSnapshot struct {
		Namespaces []*objName
	}
}

func (kc *Cluster) ActualNamespace(namespace string) string {
	if namespace == "" {
		namespace = kc.Namespace
	}
	if !kc.namespaceExists(namespace) {
		namespace = ""
	}
	return namespace
}

// check uses a non-caching DiscoveryClientConfig to retrieve the server version
func (kc *Cluster) check(c context.Context) error {
	// The discover client is using context.TODO() so the timeout specified in our
	// context has no effect.
	errCh := make(chan error)
	go func() {
		dc, err := discovery.NewDiscoveryClientForConfig(kc.config)
		if err != nil {
			errCh <- err
			return
		}
		info, err := dc.ServerVersion()
		if err != nil {
			errCh <- err
			return
		}
		dlog.Infof(c, "Server version %s", info.GitVersion)
		close(errCh)
	}()

	select {
	case <-c.Done():
	case err := <-errCh:
		if err == nil {
			return nil
		}
		if c.Err() == nil {
			return fmt.Errorf("initial cluster check failed: %w", client.RunError(err))
		}
	}
	return c.Err()
}

// kindNames returns the names of all objects of a specified Kind in a given Namespace
func (kc *Cluster) kindNames(c context.Context, kind, namespace string) ([]string, error) {
	var objNames []objName
	if err := kc.client.List(c, kates.Query{Kind: kind, Namespace: namespace}, &objNames); err != nil {
		return nil, err
	}
	names := make([]string, len(objNames))
	for i, n := range objNames {
		names[i] = n.Name
	}
	return names, nil
}

// DeploymentNames returns the names of all deployments found in the given Namespace
func (kc *Cluster) DeploymentNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "Deployment", namespace)
}

// ReplicaSetNames returns the names of all replica sets found in the given Namespace
func (kc *Cluster) ReplicaSetNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "ReplicaSet", namespace)
}

// StatefulSetNames returns the names of all replica sets found in the given Namespace
func (kc *Cluster) StatefulSetNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "StatefulSet", namespace)
}

// PodNames returns the names of all replica sets found in the given Namespace
func (kc *Cluster) PodNames(c context.Context, namespace string) ([]string, error) {
	return kc.kindNames(c, "Pod", namespace)
}

// FindDeployment returns a deployment with the given name in the given namespace or nil
// if no such deployment could be found.
func (kc *Cluster) FindDeployment(c context.Context, namespace, name string) (*kates.Deployment, error) {
	dep := &kates.Deployment{
		TypeMeta:   kates.TypeMeta{Kind: "Deployment"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, dep, dep); err != nil {
		return nil, err
	}
	return dep, nil
}

// FindStatefulSet returns a statefulSet with the given name in the given namespace or nil
// if no such statefulSet could be found.
func (kc *Cluster) FindStatefulSet(c context.Context, namespace, name string) (*kates.StatefulSet, error) {
	statefulSet := &kates.StatefulSet{
		TypeMeta:   kates.TypeMeta{Kind: "StatefulSet"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, statefulSet, statefulSet); err != nil {
		return nil, err
	}
	return statefulSet, nil
}

// FindReplicaSet returns a replica set with the given name in the given namespace or nil
// if no such replica set could be found.
func (kc *Cluster) FindReplicaSet(c context.Context, namespace, name string) (*kates.ReplicaSet, error) {
	rs := &kates.ReplicaSet{
		TypeMeta:   kates.TypeMeta{Kind: "ReplicaSet"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, rs, rs); err != nil {
		return nil, err
	}
	return rs, nil
}

// FindPod returns a pod with the given name in the given namespace or nil
// if no such replica set could be found.
func (kc *Cluster) FindPod(c context.Context, namespace, name string) (*kates.Pod, error) {
	pod := &kates.Pod{
		TypeMeta:   kates.TypeMeta{Kind: "Pod"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, pod, pod); err != nil {
		return nil, err
	}
	return pod, nil
}

// FindObjectKind returns a workload for the given name and namespace. We
// search in a specific order based on how we prefer workload objects:
// 1. Deployments
// 2. ReplicaSets
// 3. StatefulSets
// And return the kind as soon as we find one that matches
func (kc *Cluster) FindObjectKind(c context.Context, namespace, name string) (string, error) {
	type workLoad struct {
		kind string
		obj  kates.Object
	}
	for _, wl := range []workLoad{{"Deployment", &kates.Deployment{}}, {"ReplicaSet", &kates.ReplicaSet{}}, {"StatefulSet", &kates.StatefulSet{}}} {
		wl.obj.(schema.ObjectKind).SetGroupVersionKind(schema.GroupVersionKind{Kind: wl.kind})
		wl.obj.SetName(name)
		wl.obj.SetNamespace(namespace)
		if err := kc.client.Get(c, wl.obj, nil); err != nil {
			if kates.IsNotFound(err) {
				continue
			}
			return "", err
		}
		return wl.kind, nil
	}
	return "", fmt.Errorf("no supported Object Kind Found for %s.%s", name, namespace)
}

// FindSvc finds a service with the given name in the given Namespace and returns
// either a copy of that service or nil if no such service could be found.
func (kc *Cluster) FindSvc(c context.Context, namespace, name string) (*kates.Service, error) {
	rs := &kates.Service{
		TypeMeta:   kates.TypeMeta{Kind: "Service"},
		ObjectMeta: kates.ObjectMeta{Name: name, Namespace: namespace},
	}
	if err := kc.client.Get(c, rs, rs); err != nil {
		return nil, err
	}
	return rs, nil
}

// findAllSvc finds services with the given service type in all namespaces of the cluster returns
// a slice containing a copy of those services.
func (kc *Cluster) findAllSvcByType(c context.Context, svcType v1.ServiceType) ([]*kates.Service, error) {
	// NOTE: This is expensive in terms of bandwidth on a large cluster. We currently only use this
	// to retrieve ingress info and that task could be moved to the traffic-manager instead.
	var svcs []*kates.Service
	if err := kc.client.List(c, kates.Query{Kind: "Service"}, &svcs); err != nil {
		return nil, err
	}
	var typedSvcs []*kates.Service
	for _, svc := range svcs {
		if svc.Spec.Type == svcType {
			typedSvcs = append(typedSvcs, svc)
			break
		}
	}
	return typedSvcs, nil
}

func (kc *Cluster) namespaceExists(namespace string) (exists bool) {
	kc.accLock.Lock()
	for _, n := range kc.lastNamespaces {
		if n == namespace {
			exists = true
			break
		}
	}
	kc.accLock.Unlock()
	return exists
}

func NewCluster(c context.Context, kubeFlags *Config, mappedNamespaces []string, callbacks Callbacks) (*Cluster, error) {
	// TODO: Add constructor to kates that takes an additional restConfig argument to prevent that kates recreates it.
	kc, err := kates.NewClientFromConfigFlags(kubeFlags.ConfigFlags)
	if err != nil {
		return nil, client.CheckTimeout(c, fmt.Errorf("k8s client create failed: %w", err))
	}

	ret := &Cluster{
		Config:           kubeFlags,
		mappedNamespaces: mappedNamespaces,
		client:           kc,
		callbacks:        callbacks,
		LocalIntercepts:  map[string]string{},
		accWait:          make(chan struct{}),
	}

	if err := ret.check(c); err != nil {
		return nil, err
	}

	dlog.Infof(c, "Context: %s", ret.Context)
	dlog.Infof(c, "Server: %s", ret.Server)

	return ret, nil
}

func (kc *Cluster) WaitUntilReady(ctx context.Context) error {
	select {
	case <-kc.accWait:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

func (kc *Cluster) GetClusterId(ctx context.Context) string {
	clusterID, _ := actions.GetClusterID(ctx, kc.client)
	return clusterID
}

func (kc *Cluster) Client() *kates.Client {
	return kc.client
}

func (kc *Cluster) GetManagerNamespace() string {
	return kc.kubeconfigExtension.Manager.Namespace
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"sync"

	log "github.com/Sirupsen/logrus"
)

var cmdInspect = &Command{
	Exec:        runInspect,
	UsageLine:   "inspect [OPTIONS] IDENTIFIER [IDENTIFIER...]",
	Description: "Return low-level information on a server, image, snapshot or bootscript",
	Help:        "Return low-level information on a server, image, snapshot or bootscript.",
	Examples: `
    $ scw inspect a-public-image
    $ scw inspect my-snapshot
    $ scw inspect my-image
    $ scw inspect my-server
    $ scw inspect my-volume
    $ scw inspect my-server | jq '.[0].public_ip.address'
    $ scw inspect $(scw inspect my-image | jq '.[0].root_volume.id')
`,
}

// ScalewayResolvedIdentifier represents a list of matching identifier for a specifier pattern
type ScalewayResolvedIdentifier struct {
	// Identifiers holds matching identifiers
	Identifiers []ScalewayIdentifier

	// Needle is the criteria used to lookup identifiers
	Needle string
}

func init() {
	cmdInspect.Flag.BoolVar(&inspectHelp, []string{"h", "-help"}, false, "Print usage")
}

// Flags
var inspectHelp bool // -h, --help flag

// resolveIdentifiers resolves needles provided by the user
func resolveIdentifiers(cmd *Command, needles []string, out chan ScalewayResolvedIdentifier) {
	// first attempt, only lookup from the cache
	var unresolved []string
	for _, needle := range needles {
		idents := cmd.API.Cache.LookUpIdentifiers(needle)
		if len(idents) == 0 {
			unresolved = append(unresolved, needle)
		} else {
			out <- ScalewayResolvedIdentifier{
				Identifiers: idents,
				Needle:      needle,
			}
		}
	}
	// fill the cache by fetching from the API and resolve missing identifiers
	if len(unresolved) > 0 {
		var wg sync.WaitGroup
		wg.Add(4)
		go func() {
			cmd.API.GetServers(true, 0)
			wg.Done()
		}()
		go func() {
			cmd.API.GetImages()
			wg.Done()
		}()
		go func() {
			cmd.API.GetSnapshots()
			wg.Done()
		}()
		go func() {
			cmd.API.GetBootscripts()
			wg.Done()
		}()
		wg.Wait()
		for _, needle := range unresolved {
			idents := cmd.API.Cache.LookUpIdentifiers(needle)
			out <- ScalewayResolvedIdentifier{
				Identifiers: idents,
				Needle:      needle,
			}
		}
	}
	close(out)
}

// inspectIdentifiers inspects identifiers concurrently
func inspectIdentifiers(cmd *Command, ci chan ScalewayResolvedIdentifier, cj chan interface{}) {
	var wg sync.WaitGroup
	for {
		idents, ok := <-ci
		if !ok {
			break
		}
		if len(idents.Identifiers) != 1 {
			if len(idents.Identifiers) == 0 {
				log.Errorf("Unable to resolve identifier %s", idents.Needle)
			} else {
				log.Errorf("Too many candidates for %s (%d)", idents.Needle, len(idents.Identifiers))
				for _, identifier := range idents.Identifiers {
					// FIXME: also print the name
					log.Infof("- %s", identifier.Identifier)
				}
			}
		} else {
			ident := idents.Identifiers[0]
			wg.Add(1)
			go func() {
				if ident.Type == IdentifierServer {
					server, err := cmd.API.GetServer(ident.Identifier)
					if err == nil {
						cj <- server
					}
				} else if ident.Type == IdentifierImage {
					image, err := cmd.API.GetImage(ident.Identifier)
					if err == nil {
						cj <- image
					}
				} else if ident.Type == IdentifierSnapshot {
					snap, err := cmd.API.GetSnapshot(ident.Identifier)
					if err == nil {
						cj <- snap
					}
				} else if ident.Type == IdentifierBootscript {
					bootscript, err := cmd.API.GetBootscript(ident.Identifier)
					if err == nil {
						cj <- bootscript
					}
				}
				wg.Done()
			}()
		}
	}
	wg.Wait()
	close(cj)
}

func runInspect(cmd *Command, args []string) {
	if inspectHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	res := "["
	nbInspected := 0
	ci := make(chan ScalewayResolvedIdentifier)
	cj := make(chan interface{})
	go resolveIdentifiers(cmd, args, ci)
	go inspectIdentifiers(cmd, ci, cj)
	for {
		data, open := <-cj
		if !open {
			break
		}
		dataB, err := json.MarshalIndent(data, "", "  ")
		if err == nil {
			if nbInspected != 0 {
				res += ",\n"
			}
			res += string(dataB)
			nbInspected++
		}
	}
	res += "]"

	fmt.Println(res)

	if len(args) != nbInspected {
		os.Exit(1)
	}
}
<commit_msg>Inspecting volumes (#49)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"os"
	"sync"

	log "github.com/Sirupsen/logrus"
)

var cmdInspect = &Command{
	Exec:        runInspect,
	UsageLine:   "inspect [OPTIONS] IDENTIFIER [IDENTIFIER...]",
	Description: "Return low-level information on a server, image, snapshot, volume or bootscript",
	Help:        "Return low-level information on a server, image, snapshot, volume or bootscript.",
	Examples: `
    $ scw inspect a-public-image
    $ scw inspect my-snapshot
    $ scw inspect my-volume
    $ scw inspect my-image
    $ scw inspect my-server
    $ scw inspect my-volume
    $ scw inspect my-server | jq '.[0].public_ip.address'
    $ scw inspect $(scw inspect my-image | jq '.[0].root_volume.id')
`,
}

// ScalewayResolvedIdentifier represents a list of matching identifier for a specifier pattern
type ScalewayResolvedIdentifier struct {
	// Identifiers holds matching identifiers
	Identifiers []ScalewayIdentifier

	// Needle is the criteria used to lookup identifiers
	Needle string
}

func init() {
	cmdInspect.Flag.BoolVar(&inspectHelp, []string{"h", "-help"}, false, "Print usage")
}

// Flags
var inspectHelp bool // -h, --help flag

// resolveIdentifiers resolves needles provided by the user
func resolveIdentifiers(cmd *Command, needles []string, out chan ScalewayResolvedIdentifier) {
	// first attempt, only lookup from the cache
	var unresolved []string
	for _, needle := range needles {
		idents := cmd.API.Cache.LookUpIdentifiers(needle)
		if len(idents) == 0 {
			unresolved = append(unresolved, needle)
		} else {
			out <- ScalewayResolvedIdentifier{
				Identifiers: idents,
				Needle:      needle,
			}
		}
	}
	// fill the cache by fetching from the API and resolve missing identifiers
	if len(unresolved) > 0 {
		var wg sync.WaitGroup
		wg.Add(4)
		go func() {
			cmd.API.GetServers(true, 0)
			wg.Done()
		}()
		go func() {
			cmd.API.GetImages()
			wg.Done()
		}()
		go func() {
			cmd.API.GetSnapshots()
			wg.Done()
		}()
		go func() {
			cmd.API.GetVolumes()
			wg.Done()
		}()
		go func() {
			cmd.API.GetBootscripts()
			wg.Done()
		}()
		wg.Wait()
		for _, needle := range unresolved {
			idents := cmd.API.Cache.LookUpIdentifiers(needle)
			out <- ScalewayResolvedIdentifier{
				Identifiers: idents,
				Needle:      needle,
			}
		}
	}
	close(out)
}

// inspectIdentifiers inspects identifiers concurrently
func inspectIdentifiers(cmd *Command, ci chan ScalewayResolvedIdentifier, cj chan interface{}) {
	var wg sync.WaitGroup
	for {
		idents, ok := <-ci
		if !ok {
			break
		}
		if len(idents.Identifiers) != 1 {
			if len(idents.Identifiers) == 0 {
				log.Errorf("Unable to resolve identifier %s", idents.Needle)
			} else {
				log.Errorf("Too many candidates for %s (%d)", idents.Needle, len(idents.Identifiers))
				for _, identifier := range idents.Identifiers {
					// FIXME: also print the name
					log.Infof("- %s", identifier.Identifier)
				}
			}
		} else {
			ident := idents.Identifiers[0]
			wg.Add(1)
			go func() {
				if ident.Type == IdentifierServer {
					server, err := cmd.API.GetServer(ident.Identifier)
					if err == nil {
						cj <- server
					}
				} else if ident.Type == IdentifierImage {
					image, err := cmd.API.GetImage(ident.Identifier)
					if err == nil {
						cj <- image
					}
				} else if ident.Type == IdentifierSnapshot {
					snap, err := cmd.API.GetSnapshot(ident.Identifier)
					if err == nil {
						cj <- snap
					}
				} else if ident.Type == IdentifierVolume {
					snap, err := cmd.API.GetVolume(ident.Identifier)
					if err == nil {
						cj <- snap
					}
				} else if ident.Type == IdentifierBootscript {
					bootscript, err := cmd.API.GetBootscript(ident.Identifier)
					if err == nil {
						cj <- bootscript
					}
				}
				wg.Done()
			}()
		}
	}
	wg.Wait()
	close(cj)
}

func runInspect(cmd *Command, args []string) {
	if inspectHelp {
		cmd.PrintUsage()
	}
	if len(args) < 1 {
		cmd.PrintShortUsage()
	}

	res := "["
	nbInspected := 0
	ci := make(chan ScalewayResolvedIdentifier)
	cj := make(chan interface{})
	go resolveIdentifiers(cmd, args, ci)
	go inspectIdentifiers(cmd, ci, cj)
	for {
		data, open := <-cj
		if !open {
			break
		}
		dataB, err := json.MarshalIndent(data, "", "  ")
		if err == nil {
			if nbInspected != 0 {
				res += ",\n"
			}
			res += string(dataB)
			nbInspected++
		}
	}
	res += "]"

	fmt.Println(res)

	if len(args) != nbInspected {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package gofast_test

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/fcgi"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/yookoala/gofast"
)

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

func newApp(network, address string, fn http.HandlerFunc) (l net.Listener, err error) {
	l, err = net.Listen(network, address)
	if err != nil {
		return
	}
	go fcgi.Serve(l, http.HandlerFunc(fn))
	return
}

func TestClient_NewRequest(t *testing.T) {

	t.Logf("default limit: %d", 65535)

	c, _ := gofast.SimpleClientFactory(
		func() (net.Conn, error) { return nil, nil }, // dummy conn factory
		0,
	)()

	for i := uint32(0); i <= 65535; i++ {
		r := gofast.NewRequest(c, nil)
		if want, have := uint16(i), r.ID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	}

	// test if client can allocate new request
	// when all request ids are already allocated
	newAlloc := make(chan uint16)
	go func(c gofast.Client, newAlloc chan<- uint16) {
		r := gofast.NewRequest(c, nil) // should be blocked before releaseID call
		newAlloc <- r.ID
	}(c, newAlloc)

	select {
	case reqID := <-newAlloc:
		t.Errorf("unexpected new allocation: %d", reqID)
	case <-time.After(time.Millisecond * 100):
		t.Log("blocks as expected")
	}

	// now, release a random ID
	released := uint16(rand.Int31n(65535))
	go func(c gofast.Client, released uint16) {
		c.ReleaseID(released)
	}(c, released)

	select {
	case reqID := <-newAlloc:
		if want, have := released, reqID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	case <-time.After(time.Millisecond * 100):
		t.Errorf("unexpected blocking")
	}
}

func TestClient_NewRequestWithLimit(t *testing.T) {

	limit := uint32(rand.Int31n(100) + 10)
	t.Logf("random limit: %d", limit)

	c, _ := gofast.SimpleClientFactory(
		func() (net.Conn, error) { return nil, nil }, // dummy conn factory
		limit,
	)()

	for i := uint32(0); i < limit; i++ {
		r := gofast.NewRequest(c, nil)
		if want, have := uint16(i), r.ID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	}

	// test if client can allocate new request
	// when all request ids are already allocated
	newAlloc := make(chan uint16)
	go func(c gofast.Client, newAlloc chan<- uint16) {
		r := gofast.NewRequest(c, nil) // should be blocked before releaseID call
		newAlloc <- r.ID
	}(c, newAlloc)

	select {
	case reqID := <-newAlloc:
		t.Errorf("unexpected new allocation: %d", reqID)
	case <-time.After(time.Millisecond * 100):
		t.Log("blocks as expected")
	}

	// now, release a random ID
	released := uint16(rand.Int31n(int32(limit)))
	go func(c gofast.Client, released uint16) {
		c.ReleaseID(released)
	}(c, released)

	select {
	case reqID := <-newAlloc:
		if want, have := released, reqID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	case <-time.After(time.Millisecond * 100):
		t.Errorf("unexpected blocking")
	}
}

func TestClient_canceled(t *testing.T) {

	// proxy implements Proxy interface
	type proxy struct {
		network string
		address string
	}

	NewRequest := func(c gofast.Client, r *http.Request) (req *gofast.Request) {
		var isHTTPS string
		if r.URL.Scheme == "https" || r.URL.Scheme == "wss" {
			isHTTPS = "on"
		}

		remoteAddr, remotePort, _ := net.SplitHostPort(r.RemoteAddr)
		_, serverPort, err := net.SplitHostPort(r.URL.Host)
		if err != nil {
			if r.URL.Scheme == "https" || r.URL.Scheme == "wss" {
				serverPort = "443"
			} else {
				serverPort = "80"
			}
		}

		req = gofast.NewRequest(c, r)
		req.Params["CONTENT_TYPE"] = r.Header.Get("Content-Type")
		req.Params["CONTENT_LENGTH"] = r.Header.Get("Content-Length")
		req.Params["HTTPS"] = isHTTPS
		req.Params["GATEWAY_INTERFACE"] = "CGI/1.1"
		req.Params["REMOTE_ADDR"] = remoteAddr
		req.Params["REMOTE_PORT"] = remotePort
		req.Params["SERVER_PORT"] = serverPort
		req.Params["SERVER_NAME"] = r.Host
		req.Params["SERVER_PROTOCOL"] = r.Proto
		req.Params["SERVER_SOFTWARE"] = "gofast"
		req.Params["REDIRECT_STATUS"] = "200"
		req.Params["REQUEST_METHOD"] = r.Method
		req.Params["REQUEST_URI"] = r.RequestURI
		req.Params["QUERY_STRING"] = r.URL.RawQuery
		return
	}

	// ServeHTTP implements http.Handler
	ServeHTTP := func(p *proxy, w http.ResponseWriter, r *http.Request) (errStr string) {
		c, err := gofast.SimpleClientFactory(
			gofast.SimpleConnFactory(p.network, p.address),
			0,
		)()
		if err != nil {
			http.Error(w, "failed to connect to FastCGI application", http.StatusBadGateway)
			log.Printf("web server: unable to connect to FastCGI application "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}
		innerCtx, cancel := context.WithCancel(r.Context())
		req := NewRequest(c, r.WithContext(innerCtx))

		// handle the result
		resp, err := c.Do(req)
		cancel() // cancel before reading
		if err != nil {
			http.Error(w, "failed to process request", http.StatusInternalServerError)
			log.Printf("web server: unable to process request "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}

		errBuffer := new(bytes.Buffer)
		resp.WriteTo(w, errBuffer)

		if errBuffer.Len() > 0 {
			errStr = errBuffer.String()
			log.Printf("web server: error stream from application process "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, errStr)
			return
		}

		return
	}

	// create temporary socket in the testing folder
	dir, err := os.Getwd()
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	sock := dir + "/client.test.sock"

	// create temporary fcgi application server
	// that listens to the socket
	fn := func(w http.ResponseWriter, r *http.Request) {
		t.Logf("accessing FastCGI process")
		fmt.Fprintf(w, "hello world")
	}
	l, err := newApp("unix", sock, fn)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	defer os.Remove(sock)
	defer l.Close()

	// deine a proxy that access the temp fcgi application server
	w := httptest.NewRecorder()

	// request the application server
	r, err := http.NewRequest("GET", "/add", nil)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}

	// test error
	p := &proxy{l.Addr().Network(), l.Addr().String()}
	if want, have := "gofast: timeout or canceled", ServeHTTP(p, w, r); want != have {
		t.Errorf("expected %#v, got %#v", want, have)
	}
}

func TestClient_StdErr(t *testing.T) {

	// proxy implements Proxy interface
	type proxy struct {
		network string
		address string
	}

	// ServeHTTP implements http.Handler
	ServeHTTP := func(p *proxy, w http.ResponseWriter, r *http.Request) (errStr string) {
		c, err := gofast.SimpleClientFactory(
			gofast.SimpleConnFactory(p.network, p.address),
			0,
		)()
		if err != nil {
			http.Error(w, "failed to connect to FastCGI application", http.StatusBadGateway)
			log.Printf("web server: unable to connect to FastCGI application "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}
		req := gofast.NewRequest(c, nil)

		// Some required paramters with invalid values
		req.Params["REQUEST_METHOD"] = ""
		req.Params["SERVER_PROTOCOL"] = ""

		// handle the result
		resp, err := c.Do(req)
		if err != nil {
			http.Error(w, "failed to process request", http.StatusInternalServerError)
			log.Printf("web server: unable to process request "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}
		errBuffer := new(bytes.Buffer)
		resp.WriteTo(w, errBuffer)

		if errBuffer.Len() > 0 {
			errStr = errBuffer.String()
			log.Printf("web server: error stream from application process "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, errStr)
			return
		}

		return
	}

	// create temporary socket in the testing folder
	dir, err := os.Getwd()
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	sock := dir + "/client.test.sock"

	// create temporary fcgi application server
	// that listens to the socket
	fn := func(w http.ResponseWriter, r *http.Request) {
		t.Logf("accessing FastCGI process")
		fmt.Fprintf(w, "hello world")
	}
	l, err := newApp("unix", sock, fn)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	defer os.Remove(sock)
	defer l.Close()

	// deine a proxy that access the temp fcgi application server
	w := httptest.NewRecorder()

	// request the application server
	r, err := http.NewRequest("GET", "/add", nil)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}

	p := &proxy{l.Addr().Network(), l.Addr().String()}
	if want, have := "cgi: no REQUEST_METHOD in environment", ServeHTTP(p, w, r); want != have {
		t.Errorf("expected %#v, got %#v", want, have)
	}

	// examine the result
	// FIXME: should show "internal server error"
	if want, have := "", w.Body.String(); want != have {
		t.Errorf("expected %#v, got %#v", want, have)
	}

}
<commit_msg>fix TestClient_canceled<commit_after>package gofast_test

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/fcgi"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/yookoala/gofast"
)

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

func newApp(network, address string, fn http.HandlerFunc) (l net.Listener, err error) {
	l, err = net.Listen(network, address)
	if err != nil {
		return
	}
	go fcgi.Serve(l, http.HandlerFunc(fn))
	return
}

func TestClient_NewRequest(t *testing.T) {

	t.Logf("default limit: %d", 65535)

	c, _ := gofast.SimpleClientFactory(
		func() (net.Conn, error) { return nil, nil }, // dummy conn factory
		0,
	)()

	for i := uint32(0); i <= 65535; i++ {
		r := gofast.NewRequest(c, nil)
		if want, have := uint16(i), r.ID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	}

	// test if client can allocate new request
	// when all request ids are already allocated
	newAlloc := make(chan uint16)
	go func(c gofast.Client, newAlloc chan<- uint16) {
		r := gofast.NewRequest(c, nil) // should be blocked before releaseID call
		newAlloc <- r.ID
	}(c, newAlloc)

	select {
	case reqID := <-newAlloc:
		t.Errorf("unexpected new allocation: %d", reqID)
	case <-time.After(time.Millisecond * 100):
		t.Log("blocks as expected")
	}

	// now, release a random ID
	released := uint16(rand.Int31n(65535))
	go func(c gofast.Client, released uint16) {
		c.ReleaseID(released)
	}(c, released)

	select {
	case reqID := <-newAlloc:
		if want, have := released, reqID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	case <-time.After(time.Millisecond * 100):
		t.Errorf("unexpected blocking")
	}
}

func TestClient_NewRequestWithLimit(t *testing.T) {

	limit := uint32(rand.Int31n(100) + 10)
	t.Logf("random limit: %d", limit)

	c, _ := gofast.SimpleClientFactory(
		func() (net.Conn, error) { return nil, nil }, // dummy conn factory
		limit,
	)()

	for i := uint32(0); i < limit; i++ {
		r := gofast.NewRequest(c, nil)
		if want, have := uint16(i), r.ID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	}

	// test if client can allocate new request
	// when all request ids are already allocated
	newAlloc := make(chan uint16)
	go func(c gofast.Client, newAlloc chan<- uint16) {
		r := gofast.NewRequest(c, nil) // should be blocked before releaseID call
		newAlloc <- r.ID
	}(c, newAlloc)

	select {
	case reqID := <-newAlloc:
		t.Errorf("unexpected new allocation: %d", reqID)
	case <-time.After(time.Millisecond * 100):
		t.Log("blocks as expected")
	}

	// now, release a random ID
	released := uint16(rand.Int31n(int32(limit)))
	go func(c gofast.Client, released uint16) {
		c.ReleaseID(released)
	}(c, released)

	select {
	case reqID := <-newAlloc:
		if want, have := released, reqID; want != have {
			t.Errorf("expected %d, got %d", want, have)
		}
	case <-time.After(time.Millisecond * 100):
		t.Errorf("unexpected blocking")
	}
}

func TestClient_canceled(t *testing.T) {

	// proxy implements Proxy interface
	type proxy struct {
		network string
		address string
	}

	NewRequest := func(c gofast.Client, r *http.Request) (req *gofast.Request) {
		var isHTTPS string
		if r.URL.Scheme == "https" || r.URL.Scheme == "wss" {
			isHTTPS = "on"
		}

		remoteAddr, remotePort, _ := net.SplitHostPort(r.RemoteAddr)
		_, serverPort, err := net.SplitHostPort(r.URL.Host)
		if err != nil {
			if r.URL.Scheme == "https" || r.URL.Scheme == "wss" {
				serverPort = "443"
			} else {
				serverPort = "80"
			}
		}

		req = gofast.NewRequest(c, r)
		req.Params["CONTENT_TYPE"] = r.Header.Get("Content-Type")
		req.Params["CONTENT_LENGTH"] = r.Header.Get("Content-Length")
		req.Params["HTTPS"] = isHTTPS
		req.Params["GATEWAY_INTERFACE"] = "CGI/1.1"
		req.Params["REMOTE_ADDR"] = remoteAddr
		req.Params["REMOTE_PORT"] = remotePort
		req.Params["SERVER_PORT"] = serverPort
		req.Params["SERVER_NAME"] = r.Host
		req.Params["SERVER_PROTOCOL"] = r.Proto
		req.Params["SERVER_SOFTWARE"] = "gofast"
		req.Params["REDIRECT_STATUS"] = "200"
		req.Params["REQUEST_METHOD"] = r.Method
		req.Params["REQUEST_URI"] = r.RequestURI
		req.Params["QUERY_STRING"] = r.URL.RawQuery
		return
	}

	// ServeHTTP implements http.Handler
	ServeHTTP := func(p *proxy, w http.ResponseWriter, r *http.Request) (errStr string) {
		c, err := gofast.SimpleClientFactory(
			gofast.SimpleConnFactory(p.network, p.address),
			0,
		)()
		if err != nil {
			http.Error(w, "failed to connect to FastCGI application", http.StatusBadGateway)
			log.Printf("web server: unable to connect to FastCGI application "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}
		innerCtx, cancel := context.WithCancel(r.Context())
		req := NewRequest(c, r.WithContext(innerCtx))

		// cancel before request
		cancel()
		select {
		case <-time.After(time.Millisecond):
			// artifically wait for some times
			// to let the cancel signal kick in
		}

		// handle the result
		resp, err := c.Do(req)
		if err != nil {
			http.Error(w, "failed to process request", http.StatusInternalServerError)
			log.Printf("web server: unable to process request "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}

		errBuffer := new(bytes.Buffer)
		resp.WriteTo(w, errBuffer)

		if errBuffer.Len() > 0 {
			errStr = errBuffer.String()
			log.Printf("web server: error stream from application process "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, errStr)
			return
		}

		return
	}

	// create temporary socket in the testing folder
	dir, err := os.Getwd()
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	sock := dir + "/client.test.sock"

	// create temporary fcgi application server
	// that listens to the socket
	fn := func(w http.ResponseWriter, r *http.Request) {
		t.Logf("accessing FastCGI process")
		fmt.Fprintf(w, "hello world")
	}
	l, err := newApp("unix", sock, fn)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	defer os.Remove(sock)
	defer l.Close()

	// deine a proxy that access the temp fcgi application server
	w := httptest.NewRecorder()

	// request the application server
	r, err := http.NewRequest("GET", "/add", nil)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}

	// test error
	p := &proxy{l.Addr().Network(), l.Addr().String()}
	if want, have := "gofast: timeout or canceled", ServeHTTP(p, w, r); want != have {
		t.Errorf("expected %#v, got %#v", want, have)
	}
}

func TestClient_StdErr(t *testing.T) {

	// proxy implements Proxy interface
	type proxy struct {
		network string
		address string
	}

	// ServeHTTP implements http.Handler
	ServeHTTP := func(p *proxy, w http.ResponseWriter, r *http.Request) (errStr string) {
		c, err := gofast.SimpleClientFactory(
			gofast.SimpleConnFactory(p.network, p.address),
			0,
		)()
		if err != nil {
			http.Error(w, "failed to connect to FastCGI application", http.StatusBadGateway)
			log.Printf("web server: unable to connect to FastCGI application "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}
		req := gofast.NewRequest(c, nil)

		// Some required paramters with invalid values
		req.Params["REQUEST_METHOD"] = ""
		req.Params["SERVER_PROTOCOL"] = ""

		// handle the result
		resp, err := c.Do(req)
		if err != nil {
			http.Error(w, "failed to process request", http.StatusInternalServerError)
			log.Printf("web server: unable to process request "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, err.Error())
			return
		}
		errBuffer := new(bytes.Buffer)
		resp.WriteTo(w, errBuffer)

		if errBuffer.Len() > 0 {
			errStr = errBuffer.String()
			log.Printf("web server: error stream from application process "+
				"(network=%#v, address=%#v, error=%#v)",
				p.network, p.address, errStr)
			return
		}

		return
	}

	// create temporary socket in the testing folder
	dir, err := os.Getwd()
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	sock := dir + "/client.test.sock"

	// create temporary fcgi application server
	// that listens to the socket
	fn := func(w http.ResponseWriter, r *http.Request) {
		t.Logf("accessing FastCGI process")
		fmt.Fprintf(w, "hello world")
	}
	l, err := newApp("unix", sock, fn)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}
	defer os.Remove(sock)
	defer l.Close()

	// deine a proxy that access the temp fcgi application server
	w := httptest.NewRecorder()

	// request the application server
	r, err := http.NewRequest("GET", "/add", nil)
	if err != nil {
		t.Errorf("unexpected error: %#v", err.Error())
	}

	p := &proxy{l.Addr().Network(), l.Addr().String()}
	if want, have := "cgi: no REQUEST_METHOD in environment", ServeHTTP(p, w, r); want != have {
		t.Errorf("expected %#v, got %#v", want, have)
	}

	// examine the result
	// FIXME: should show "internal server error"
	if want, have := "", w.Body.String(); want != have {
		t.Errorf("expected %#v, got %#v", want, have)
	}

}
<|endoftext|>


<commit_before>// Copyright 2014 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 main

import (
	"flag"
	"math"

	ui "github.com/gizak/termui"
)

const usage = `- :c to see the current CPU profile
- :h to see the current heap profile
- :f to regex filter to focus by symbol name
- :s to toggle cumulative sort of the results
- :r to refresh the current profile
- :p to paginate, e.g. :p=0 to view the first page
`

var (
	p      = flag.String("p", "cpu", "name of the profile: cpu or heap")
	target = flag.String("target", "http://localhost:6060", "the target process to profile; it has to enable pprof debug server")
)

var reports = make(map[string]*Report)

func init() {
	reports["profile"] = &Report{name: "profile", secs: 30}
	reports["heap"] = &Report{name: "heap"}
}

func main() {
	flag.Parse()

	if err := ui.Init(); err != nil {
		panic(err)
	}
	defer ui.Close()
	draw()
}

func draw() {
	sinps := (func() []float64 {
		n := 400
		ps := make([]float64, n)
		for i := range ps {
			ps[i] = 1 + math.Sin(float64(i)/5)
		}
		return ps
	})()
	data := (func() []int {
		ps := make([]int, len(sinps))
		for i, v := range sinps {
			ps[i] = int(100*v + 10)
		}
		return ps
	})()

	p := ui.NewPar(usage)
	p.Height = 8
	p.BorderLabel = " gom - visual runtime profiler "
	p.TextFgColor = ui.ColorWhite
	p.BorderFg = ui.ColorCyan

	prompt := ui.NewPar(` >_ `)
	prompt.Height = 2
	prompt.Border = false

	goroutines := ui.Sparkline{}
	goroutines.Title = "goroutines"
	goroutines.Height = 4
	goroutines.Data = data
	goroutines.LineColor = ui.ColorCyan

	threads := ui.Sparkline{}
	threads.Title = "threads"
	threads.Height = 4
	threads.Data = data
	threads.LineColor = ui.ColorCyan

	sp := ui.NewSparklines(goroutines, threads)
	sp.Height = 11
	sp.Border = false

	gs := make([]*ui.Gauge, 12)
	for i := range gs {
		gs[i] = ui.NewGauge()
		gs[i].LabelAlign = ui.AlignLeft
		gs[i].Height = 2
		gs[i].Border = false
		gs[i].Percent = 50 + i*20
		gs[i].PaddingBottom = 1
		gs[i].BarColor = ui.ColorRed
	}

	ls := ui.NewList()
	ls.Border = false
	ls.Items = []string{
		"0 0% 0% 0.01s 100% 00000000000105a7 runtime.notesleep",
		"",
		"[2] Downloading File 2",
		"",
		"[3] Uploading File 3",
	}
	ls.Height = 5

	ui.Handle("/sys/kbd/C-c", func(ui.Event) {
		ui.StopLoop()
	})
	ui.Body.AddRows(
		ui.NewRow(ui.NewCol(12, 0, p)),
		ui.NewRow(ui.NewCol(12, 0, prompt)),
		ui.NewRow(ui.NewCol(12, 0, sp)),
		ui.NewRow(
			ui.NewCol(4, 0, gs[0], gs[1], gs[2], gs[3], gs[4], gs[5]),
			ui.NewCol(8, 0, ls)),
	)
	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		ui.Body.Width = ui.TermWidth()
		ui.Body.Align()
		ui.Render(ui.Body)
	})
	ui.Body.Align()
	ui.Render(ui.Body)
	ui.Loop()
}

// func statsHandler(w http.ResponseWriter, r *http.Request) {
// 	url := fmt.Sprintf("%s/debug/pprofstats", *target)
// 	resp, err := http.Get(url)
// 	if err != nil {
// 		log.Print(err)
// 		w.WriteHeader(500)
// 		fmt.Fprintf(w, "%v", err)
// 		return
// 	}
// 	defer resp.Body.Close()
// 	all, err := ioutil.ReadAll(resp.Body)
// 	if err != nil {
// 		w.WriteHeader(500)
// 		fmt.Fprintf(w, "%v", err)
// 		return
// 	}
// 	if resp.StatusCode != http.StatusOK {
// 		w.WriteHeader(500)
// 		fmt.Fprintf(w, "%s", all)
// 		return
// 	}
// 	w.Header().Set("Content-Type", "application/json")
// 	fmt.Fprintf(w, "%s", all)
// }

// func profileHandler(w http.ResponseWriter, r *http.Request) {
// 	profile := r.FormValue("profile")
// 	filter := r.FormValue("filter")
// 	img, _ := strconv.ParseBool(r.FormValue("img"))
// 	cumsort, _ := strconv.ParseBool(r.FormValue("cumsort"))
// 	force, _ := strconv.ParseBool(r.FormValue("force"))

// 	rpt, ok := reports[profile]
// 	if !ok {
// 		w.WriteHeader(404)
// 		fmt.Fprintf(w, "Profile not found.")
// 		return
// 	}
// 	if !rpt.Inited() || force {
// 		if err := rpt.Fetch(0); err != nil {
// 			w.WriteHeader(500)
// 			fmt.Fprintf(w, "%v", err)
// 			return
// 		}
// 	}
// 	var re *regexp.Regexp
// 	var err error
// 	if filter != "" {
// 		re, err = regexp.Compile(filter)
// 	}
// 	if err != nil {
// 		w.WriteHeader(400)
// 		fmt.Fprintf(w, "%v", err)
// 		return
// 	}
// 	if img {
// 		w.Header().Set("Content-Type", "image/svg+xml")
// 		rpt.Draw(w, cumsort, re)
// 		return
// 	}
// 	w.Header().Set("Content-Type", "application/json")
// 	rpt.Filter(w, cumsort, re)
// }
<commit_msg>implement the prompt handler<commit_after>// Copyright 2014 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 main

import (
	"flag"
	"math"

	ui "github.com/gizak/termui"
)

var (
	promptMsg = ""
	target    = flag.String("target", "http://localhost:6060", "the target process to profile; it has to enable pprof debug server")

	prompt *ui.Par
)

var reports = make(map[string]*Report)

func init() {
	reports["profile"] = &Report{name: "profile", secs: 30}
	reports["heap"] = &Report{name: "heap"}
}

func main() {
	flag.Parse()

	if err := ui.Init(); err != nil {
		panic(err)
	}
	defer ui.Close()
	draw()
}

func draw() {
	sinps := (func() []float64 {
		n := 400
		ps := make([]float64, n)
		for i := range ps {
			ps[i] = 1 + math.Sin(float64(i)/5)
		}
		return ps
	})()
	data := (func() []int {
		ps := make([]int, len(sinps))
		for i, v := range sinps {
			ps[i] = int(100*v + 10)
		}
		return ps
	})()

	prompt = ui.NewPar(promptMsg)
	prompt.Height = 1
	prompt.Border = false

	help := ui.NewPar(`:c, :h for profiles; :f to filter; :0 to paginate`)
	help.Height = 1
	help.Border = false
	help.TextBgColor = ui.ColorBlue
	help.Bg = ui.ColorBlue
	help.TextFgColor = ui.ColorWhite

	goroutines := ui.Sparkline{}
	goroutines.Title = "goroutines"
	goroutines.Height = 4
	goroutines.Data = data
	goroutines.LineColor = ui.ColorCyan

	threads := ui.Sparkline{}
	threads.Title = "threads"
	threads.Height = 4
	threads.Data = data
	threads.LineColor = ui.ColorCyan

	sp := ui.NewSparklines(goroutines, threads)
	sp.Height = 11
	sp.Border = false

	gs := make([]*ui.Gauge, 10)
	for i := range gs {
		gs[i] = ui.NewGauge()
		gs[i].LabelAlign = ui.AlignLeft
		gs[i].Height = 2
		gs[i].Border = false
		gs[i].Percent = 100 - i*10
		gs[i].PaddingBottom = 1
		gs[i].BarColor = ui.ColorRed
	}

	ls := ui.NewList()
	ls.Border = false
	// ls.Items = []string{
	// 	" 0 0% 0% 0.01s 100% 00000000000105a7 runtime.notesleep",
	// 	"",
	// 	" [2] Downloading File 2",
	// 	"",
	// 	" [3] Uploading File 3",
	// 	"",
	// 	" [3] Uploading File 3",
	// 	"",
	// 	" [3] Uploading File 3",
	// }
	// ls.Height = 5

	ui.Handle("/sys/kbd", func(e ui.Event) {
		ev := e.Data.(ui.EvtKbd)
		switch ev.KeyStr {
		case ":":
			promptMsg = ":"
		case "C-8":
			if l := len(promptMsg); l != 0 {
				promptMsg = promptMsg[:l-1]
			}
		case "<enter>":
			// todo
			promptMsg = ""
		case "<escape>":
			promptMsg = ""
		default:
			promptMsg += ev.KeyStr
		}
		refresh()
	})
	ui.Handle("/sys/kbd/C-c", func(ui.Event) {
		ui.StopLoop()
	})
	ui.Body.AddRows(
		ui.NewRow(ui.NewCol(4, 0, prompt), ui.NewCol(8, 0, help)),
		ui.NewRow(ui.NewCol(12, 0, sp)),
		ui.NewRow(
			ui.NewCol(3, 0, gs[0], gs[1], gs[2], gs[3], gs[4], gs[5]),
			ui.NewCol(9, 0, ls)),
	)
	ui.Handle("1s", func(e ui.Event) {
		refresh()
	})
	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		refresh()
	})
	ui.Body.Align()
	ui.Render(ui.Body)
	ui.Loop()
}

func refresh() {
	prompt.Text = promptMsg
	ui.Body.Width = ui.TermWidth()
	ui.Body.Align()
	ui.Render(ui.Body)
}

// func statsHandler(w http.ResponseWriter, r *http.Request) {
// 	url := fmt.Sprintf("%s/debug/pprofstats", *target)
// 	resp, err := http.Get(url)
// 	if err != nil {
// 		log.Print(err)
// 		w.WriteHeader(500)
// 		fmt.Fprintf(w, "%v", err)
// 		return
// 	}
// 	defer resp.Body.Close()
// 	all, err := ioutil.ReadAll(resp.Body)
// 	if err != nil {
// 		w.WriteHeader(500)
// 		fmt.Fprintf(w, "%v", err)
// 		return
// 	}
// 	if resp.StatusCode != http.StatusOK {
// 		w.WriteHeader(500)
// 		fmt.Fprintf(w, "%s", all)
// 		return
// 	}
// 	w.Header().Set("Content-Type", "application/json")
// 	fmt.Fprintf(w, "%s", all)
// }

// func profileHandler(w http.ResponseWriter, r *http.Request) {
// 	profile := r.FormValue("profile")
// 	filter := r.FormValue("filter")
// 	img, _ := strconv.ParseBool(r.FormValue("img"))
// 	cumsort, _ := strconv.ParseBool(r.FormValue("cumsort"))
// 	force, _ := strconv.ParseBool(r.FormValue("force"))

// 	rpt, ok := reports[profile]
// 	if !ok {
// 		w.WriteHeader(404)
// 		fmt.Fprintf(w, "Profile not found.")
// 		return
// 	}
// 	if !rpt.Inited() || force {
// 		if err := rpt.Fetch(0); err != nil {
// 			w.WriteHeader(500)
// 			fmt.Fprintf(w, "%v", err)
// 			return
// 		}
// 	}
// 	var re *regexp.Regexp
// 	var err error
// 	if filter != "" {
// 		re, err = regexp.Compile(filter)
// 	}
// 	if err != nil {
// 		w.WriteHeader(400)
// 		fmt.Fprintf(w, "%v", err)
// 		return
// 	}
// 	if img {
// 		w.Header().Set("Content-Type", "image/svg+xml")
// 		rpt.Draw(w, cumsort, re)
// 		return
// 	}
// 	w.Header().Set("Content-Type", "application/json")
// 	rpt.Filter(w, cumsort, re)
// }
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"go/build"

	"github.com/constabulary/gb"
)

// Resolver resolves packages.
type Resolver interface {
	// Srcdirs returns []string{ "$PROJECT/src", "$PROJECT/vendor/src" }
	Srcdirs() []string

	// ResolvePackage resolves the import path to a *gb.Package
	ResolvePackage(path string) (*gb.Package, error)

	// ResolvePackagesWithTests is similar to ResolvePackages however
	// it also loads the test and external test packages of args into
	// the context.
	ResolvePackageWithTests(path string) (*gb.Package, error)
}

// ResolvePackages resolves import paths to packages.
func ResolvePackages(r Resolver, paths ...string) ([]*gb.Package, error) {
	var pkgs []*gb.Package
	for _, path := range paths {
		path = relImportPath(r.Srcdirs()[0], path)
		pkg, err := r.ResolvePackage(path)
		if err != nil {
			if _, ok := err.(*build.NoGoError); ok {
				gb.Debugf("skipping %q", path)
				continue
			}
			return pkgs, fmt.Errorf("failed to resolve import path %q: %v", path, err)
		}
		pkgs = append(pkgs, pkg)
	}
	return pkgs, nil
}

// ResolvePackagesWithTests is similar to ResolvePackages however
// it also loads the test and external test packages of args into
// the context.
func ResolvePackagesWithTests(r Resolver, paths ...string) ([]*gb.Package, error) {
	var pkgs []*gb.Package
	for _, path := range paths {
		path = relImportPath(r.Srcdirs()[0], path)
		pkg, err := r.ResolvePackageWithTests(path)
		if err != nil {
			if _, ok := err.(*build.NoGoError); ok {
				gb.Debugf("skipping %q", path)
				continue
			}
			return pkgs, fmt.Errorf("failed to resolve package %q: %v", path, err)
		}
		pkgs = append(pkgs, pkg)
	}
	return pkgs, nil
}
<commit_msg>Include error<commit_after>package cmd

import (
	"fmt"
	"go/build"

	"github.com/constabulary/gb"
)

// Resolver resolves packages.
type Resolver interface {
	// Srcdirs returns []string{ "$PROJECT/src", "$PROJECT/vendor/src" }
	Srcdirs() []string

	// ResolvePackage resolves the import path to a *gb.Package
	ResolvePackage(path string) (*gb.Package, error)

	// ResolvePackagesWithTests is similar to ResolvePackages however
	// it also loads the test and external test packages of args into
	// the context.
	ResolvePackageWithTests(path string) (*gb.Package, error)
}

// ResolvePackages resolves import paths to packages.
func ResolvePackages(r Resolver, paths ...string) ([]*gb.Package, error) {
	var pkgs []*gb.Package
	for _, path := range paths {
		path = relImportPath(r.Srcdirs()[0], path)
		pkg, err := r.ResolvePackage(path)
		if err != nil {
			if _, ok := err.(*build.NoGoError); ok {
				gb.Debugf("skipping %q because of error %q", path, err)
				continue
			}
			return pkgs, fmt.Errorf("failed to resolve import path %q: %v", path, err)
		}
		pkgs = append(pkgs, pkg)
	}
	return pkgs, nil
}

// ResolvePackagesWithTests is similar to ResolvePackages however
// it also loads the test and external test packages of args into
// the context.
func ResolvePackagesWithTests(r Resolver, paths ...string) ([]*gb.Package, error) {
	var pkgs []*gb.Package
	for _, path := range paths {
		path = relImportPath(r.Srcdirs()[0], path)
		pkg, err := r.ResolvePackageWithTests(path)
		if err != nil {
			if _, ok := err.(*build.NoGoError); ok {
				gb.Debugf("skipping %q", path)
				continue
			}
			return pkgs, fmt.Errorf("failed to resolve package %q: %v", path, err)
		}
		pkgs = append(pkgs, pkg)
	}
	return pkgs, nil
}
<|endoftext|>


<commit_before>package client

import (
	"bufio"
	"fmt"
	"regexp"
	"strconv"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kapi "k8s.io/kubernetes/pkg/api"
	kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"

	osclientcmd "github.com/openshift/origin/pkg/cmd/util/clientcmd"
	"github.com/openshift/origin/pkg/cmd/util/variable"
	"github.com/openshift/origin/pkg/diagnostics/types"
)

const (
	DiagnosticPodName = "DiagnosticPod"
)

// DiagnosticPod is a diagnostic that runs a diagnostic pod and relays the results.
type DiagnosticPod struct {
	KubeClient          kclientset.Interface
	Namespace           string
	Level               int
	Factory             *osclientcmd.Factory
	PreventModification bool
	ImageTemplate       variable.ImageTemplate
}

// Name is part of the Diagnostic interface and just returns name.
func (d *DiagnosticPod) Name() string {
	return DiagnosticPodName
}

// Description is part of the Diagnostic interface and provides a user-focused description of what the diagnostic does.
func (d *DiagnosticPod) Description() string {
	return "Create a pod to run diagnostics from the application standpoint"
}

// CanRun is part of the Diagnostic interface; it determines if the conditions are right to run this diagnostic.
func (d *DiagnosticPod) CanRun() (bool, error) {
	if d.PreventModification {
		return false, fmt.Errorf("running the diagnostic pod is an API change, which is prevented as you indicated")
	}
	return true, nil
}

// Check is part of the Diagnostic interface; it runs the actual diagnostic logic
func (d *DiagnosticPod) Check() types.DiagnosticResult {
	r := types.NewDiagnosticResult("DiagnosticPod")
	d.runDiagnosticPod(nil, r)
	return r
}

func (d *DiagnosticPod) runDiagnosticPod(service *kapi.Service, r types.DiagnosticResult) {
	loglevel := d.Level
	if loglevel > 2 {
		loglevel = 2 // need to show summary at least
	}
	imageName := d.ImageTemplate.ExpandOrDie("deployer")
	pod, err := d.KubeClient.Core().Pods(d.Namespace).Create(&kapi.Pod{
		ObjectMeta: metav1.ObjectMeta{GenerateName: "pod-diagnostic-test-"},
		Spec: kapi.PodSpec{
			RestartPolicy: kapi.RestartPolicyNever,
			Containers: []kapi.Container{
				{
					Name:    "pod-diagnostics",
					Image:   imageName,
					Command: []string{"openshift", "infra", "diagnostic-pod", "-l", strconv.Itoa(loglevel)},
				},
			},
		},
	})
	if err != nil {
		r.Error("DCli2001", err, fmt.Sprintf("Creating diagnostic pod with image %s failed. Error: (%[2]T) %[2]v", imageName, err))
		return
	}
	defer func() { // delete what we created, or notify that we couldn't
		zero := int64(0)
		delOpts := metav1.DeleteOptions{TypeMeta: pod.TypeMeta, GracePeriodSeconds: &zero}
		if err := d.KubeClient.Core().Pods(d.Namespace).Delete(pod.ObjectMeta.Name, &delOpts); err != nil {
			r.Error("DCl2002", err, fmt.Sprintf("Deleting diagnostic pod '%s' failed. Error: %s", pod.ObjectMeta.Name, fmt.Sprintf("(%T) %[1]s", err)))
		}
	}()
	pod, err = d.KubeClient.Core().Pods(d.Namespace).Get(pod.ObjectMeta.Name, metav1.GetOptions{}) // status is filled in post-create
	if err != nil {
		r.Error("DCli2003", err, fmt.Sprintf("Retrieving the diagnostic pod definition failed. Error: (%T) %[1]v", err))
		return
	}
	r.Debug("DCli2004", fmt.Sprintf("Created diagnostic pod named %v running image %s.", pod.ObjectMeta.Name, imageName))

	bytelim := int64(1024000)
	podLogsOpts := &kapi.PodLogOptions{
		TypeMeta:   pod.TypeMeta,
		Container:  "pod-diagnostics",
		Follow:     true,
		LimitBytes: &bytelim,
	}
	req, err := d.Factory.LogsForObject(pod, podLogsOpts)
	if err != nil {
		r.Error("DCli2005", err, fmt.Sprintf("The request for diagnostic pod logs failed unexpectedly. Error: (%T) %[1]v", err))
		return
	}

	// wait for pod to be started and logs available
	var scanner *bufio.Scanner
	var lastError error
	for times := 1; true; times++ {
		if times <= 25 {
			readCloser, err := req.Stream()
			if err != nil {
				lastError = err
				r.Debug("DCli2010", fmt.Sprintf("Could not get diagnostic pod logs (loop %d): (%T[2]) %[2]v", times, err))
				time.Sleep(time.Duration(times*100) * time.Millisecond)
				continue
			}
			defer readCloser.Close()
			// make sure we can actually get something from the stream before going on.
			// it seems the creation of docker logs can trail the container start a bit.
			lineScanner := bufio.NewScanner(readCloser)
			if lineScanner.Scan() {
				scanner = lineScanner
				break // success - drop down to reading the logs.
			}
			// no luck - try, try again
			lastError = fmt.Errorf("Diagnostics pod is ready but not its logs (loop %d). Retry.", times)
			r.Debug("DCli2010", lastError.Error())
			time.Sleep(time.Duration(times*100) * time.Millisecond)
			continue
		}
		// after 25 times trying:
		r.Warn("DCli2006", err, fmt.Sprintf("Timed out preparing diagnostic pod logs for streaming, so this diagnostic cannot run.\nIt is likely that the image '%s' was not pulled and running yet.\nLast error: (%T[2]) %[2]v", pod.Spec.Containers[0].Image, lastError))
		return
	}
	// then watch logs and wait until it exits
	podLogs, warnings, errors := "", 0, 0
	errorRegex := regexp.MustCompile(`^\[Note\]\s+Errors\s+seen:\s+(\d+)`)
	warnRegex := regexp.MustCompile(`^\[Note\]\s+Warnings\s+seen:\s+(\d+)`)
	// keep in mind one test line was already scanned, so scan after the loop runs once
	for scanned := true; scanned; scanned = scanner.Scan() {
		line := scanner.Text()
		podLogs += line + "\n"
		if matches := errorRegex.FindStringSubmatch(line); matches != nil {
			errors, _ = strconv.Atoi(matches[1])
		} else if matches := warnRegex.FindStringSubmatch(line); matches != nil {
			warnings, _ = strconv.Atoi(matches[1])
		}
	}
	if err := scanner.Err(); err != nil { // Scan terminated abnormally
		r.Error("DCli2009", err, fmt.Sprintf("Unexpected error reading diagnostic pod logs: (%T) %[1]v\nLogs are:\n%[2]s", err, podLogs))
	} else {
		if errors > 0 {
			r.Error("DCli2012", nil, "See the errors below in the output from the diagnostic pod:\n"+podLogs)
		} else if warnings > 0 {
			r.Warn("DCli2013", nil, "See the warnings below in the output from the diagnostic pod:\n"+podLogs)
		} else {
			r.Info("DCli2008", fmt.Sprintf("Output from the diagnostic pod (image %s):\n", imageName)+podLogs)
		}
	}
}
<commit_msg>DiagnosticPod: double timeout for pod start<commit_after>package client

import (
	"bufio"
	"fmt"
	"regexp"
	"strconv"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kapi "k8s.io/kubernetes/pkg/api"
	kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"

	osclientcmd "github.com/openshift/origin/pkg/cmd/util/clientcmd"
	"github.com/openshift/origin/pkg/cmd/util/variable"
	"github.com/openshift/origin/pkg/diagnostics/types"
)

const (
	DiagnosticPodName = "DiagnosticPod"
)

// DiagnosticPod is a diagnostic that runs a diagnostic pod and relays the results.
type DiagnosticPod struct {
	KubeClient          kclientset.Interface
	Namespace           string
	Level               int
	Factory             *osclientcmd.Factory
	PreventModification bool
	ImageTemplate       variable.ImageTemplate
}

// Name is part of the Diagnostic interface and just returns name.
func (d *DiagnosticPod) Name() string {
	return DiagnosticPodName
}

// Description is part of the Diagnostic interface and provides a user-focused description of what the diagnostic does.
func (d *DiagnosticPod) Description() string {
	return "Create a pod to run diagnostics from the application standpoint"
}

// CanRun is part of the Diagnostic interface; it determines if the conditions are right to run this diagnostic.
func (d *DiagnosticPod) CanRun() (bool, error) {
	if d.PreventModification {
		return false, fmt.Errorf("running the diagnostic pod is an API change, which is prevented as you indicated")
	}
	return true, nil
}

// Check is part of the Diagnostic interface; it runs the actual diagnostic logic
func (d *DiagnosticPod) Check() types.DiagnosticResult {
	r := types.NewDiagnosticResult("DiagnosticPod")
	d.runDiagnosticPod(nil, r)
	return r
}

func (d *DiagnosticPod) runDiagnosticPod(service *kapi.Service, r types.DiagnosticResult) {
	loglevel := d.Level
	if loglevel > 2 {
		loglevel = 2 // need to show summary at least
	}
	imageName := d.ImageTemplate.ExpandOrDie("deployer")
	pod, err := d.KubeClient.Core().Pods(d.Namespace).Create(&kapi.Pod{
		ObjectMeta: metav1.ObjectMeta{GenerateName: "pod-diagnostic-test-"},
		Spec: kapi.PodSpec{
			RestartPolicy: kapi.RestartPolicyNever,
			Containers: []kapi.Container{
				{
					Name:    "pod-diagnostics",
					Image:   imageName,
					Command: []string{"openshift", "infra", "diagnostic-pod", "-l", strconv.Itoa(loglevel)},
				},
			},
		},
	})
	if err != nil {
		r.Error("DCli2001", err, fmt.Sprintf("Creating diagnostic pod with image %s failed. Error: (%[2]T) %[2]v", imageName, err))
		return
	}
	defer func() { // delete what we created, or notify that we couldn't
		zero := int64(0)
		delOpts := metav1.DeleteOptions{TypeMeta: pod.TypeMeta, GracePeriodSeconds: &zero}
		if err := d.KubeClient.Core().Pods(d.Namespace).Delete(pod.ObjectMeta.Name, &delOpts); err != nil {
			r.Error("DCl2002", err, fmt.Sprintf("Deleting diagnostic pod '%s' failed. Error: %s", pod.ObjectMeta.Name, fmt.Sprintf("(%T) %[1]s", err)))
		}
	}()
	pod, err = d.KubeClient.Core().Pods(d.Namespace).Get(pod.ObjectMeta.Name, metav1.GetOptions{}) // status is filled in post-create
	if err != nil {
		r.Error("DCli2003", err, fmt.Sprintf("Retrieving the diagnostic pod definition failed. Error: (%T) %[1]v", err))
		return
	}
	r.Debug("DCli2004", fmt.Sprintf("Created diagnostic pod named %v running image %s.", pod.ObjectMeta.Name, imageName))

	bytelim := int64(1024000)
	podLogsOpts := &kapi.PodLogOptions{
		TypeMeta:   pod.TypeMeta,
		Container:  "pod-diagnostics",
		Follow:     true,
		LimitBytes: &bytelim,
	}
	req, err := d.Factory.LogsForObject(pod, podLogsOpts)
	if err != nil {
		r.Error("DCli2005", err, fmt.Sprintf("The request for diagnostic pod logs failed unexpectedly. Error: (%T) %[1]v", err))
		return
	}

	// wait for pod to be started and logs available
	var scanner *bufio.Scanner
	var lastError error
	for times := 1; true; times++ {
		if times <= 50 {
			readCloser, err := req.Stream()
			if err != nil {
				lastError = err
				r.Debug("DCli2010", fmt.Sprintf("Could not get diagnostic pod logs (loop %d): (%T[2]) %[2]v", times, err))
				time.Sleep(time.Duration(times*100) * time.Millisecond)
				continue
			}
			defer readCloser.Close()
			// make sure we can actually get something from the stream before going on.
			// it seems the creation of docker logs can trail the container start a bit.
			lineScanner := bufio.NewScanner(readCloser)
			if lineScanner.Scan() {
				scanner = lineScanner
				break // success - drop down to reading the logs.
			}
			// no luck - try, try again
			lastError = fmt.Errorf("Diagnostics pod is ready but not its logs (loop %d). Retry.", times)
			r.Debug("DCli2010", lastError.Error())
			time.Sleep(time.Duration(times*100) * time.Millisecond)
			continue
		}
		// tries exhausted
		r.Warn("DCli2006", err, fmt.Sprintf("Timed out preparing diagnostic pod logs for streaming, so this diagnostic cannot run.\nIt is likely that the image '%s' was not pulled and running yet.\nLast error: (%T[2]) %[2]v", pod.Spec.Containers[0].Image, lastError))
		return
	}
	// then watch logs and wait until it exits
	podLogs, warnings, errors := "", 0, 0
	errorRegex := regexp.MustCompile(`^\[Note\]\s+Errors\s+seen:\s+(\d+)`)
	warnRegex := regexp.MustCompile(`^\[Note\]\s+Warnings\s+seen:\s+(\d+)`)
	// keep in mind one test line was already scanned, so scan after the loop runs once
	for scanned := true; scanned; scanned = scanner.Scan() {
		line := scanner.Text()
		podLogs += line + "\n"
		if matches := errorRegex.FindStringSubmatch(line); matches != nil {
			errors, _ = strconv.Atoi(matches[1])
		} else if matches := warnRegex.FindStringSubmatch(line); matches != nil {
			warnings, _ = strconv.Atoi(matches[1])
		}
	}
	if err := scanner.Err(); err != nil { // Scan terminated abnormally
		r.Error("DCli2009", err, fmt.Sprintf("Unexpected error reading diagnostic pod logs: (%T) %[1]v\nLogs are:\n%[2]s", err, podLogs))
	} else {
		if errors > 0 {
			r.Error("DCli2012", nil, "See the errors below in the output from the diagnostic pod:\n"+podLogs)
		} else if warnings > 0 {
			r.Warn("DCli2013", nil, "See the warnings below in the output from the diagnostic pod:\n"+podLogs)
		} else {
			r.Info("DCli2008", fmt.Sprintf("Output from the diagnostic pod (image %s):\n", imageName)+podLogs)
		}
	}
}
<|endoftext|>


<commit_before>package atlas

import (
	"bytes"
	"fmt"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net/http"
	"testing"
)

func TestGetVersion(t *testing.T) {
	ver := GetVersion()
	assert.EqualValues(t, ourVersion, ver, "should be equal")
}

func TestGetPageNum(t *testing.T) {
	url := "https://foo.example.com/"

	n := getPageNum(url)
	if n != "" {
		t.Errorf("n=%s should be ''", n)
	}
	url = "https://foo.example.com/?page=42"
	n = getPageNum(url)
	if n != "42" {
		t.Errorf("n=%s should be 42", n)
	}
	url = "https://foo.example.com/?country=fr&page=43"
	n = getPageNum(url)
	if n != "43" {
		t.Errorf("n=%s should be 43", n)
	}
	url = "https://foo.example.com/?country=fr&page=666&bar=1"
	n = getPageNum(url)
	if n != "666" {
		t.Errorf("n=%s should be 666", n)
	}

	url = ""
	n = getPageNum(url)
	if n != "" {
		t.Errorf("n=%s should be ''", n)
	}
}

func TestClienthandleAPIResponsese(t *testing.T) {
	var (
		r http.Response
		b bytes.Buffer
	)

	client, err := NewClient()
	err = client.handleAPIResponsese(nil)
	assert.Error(t, err, "should be in error")

	r = http.Response{StatusCode: 0}
	err = client.handleAPIResponsese(&r)
	assert.NoError(t, err, "should be no error")

	r = http.Response{StatusCode: 200}
	err = client.handleAPIResponsese(&r)
	assert.NoError(t, err, "should be no error")

	var jsonErr = `error:{status: 501, code: 500, detail: "test"}`

	fmt.Fprintf(&b, "%v", jsonErr)
	r.StatusCode = 300
	r.Body = ioutil.NopCloser(&b)
	err = client.handleAPIResponsese(&r)
	assert.NoError(t, err, "should be in error")

	r.StatusCode = 500
	r.Body = ioutil.NopCloser(&b)
	err = client.handleAPIResponsese(&r)
	assert.Error(t, err, "should be in error")
}

func TestAddQueryParameters(t *testing.T) {
	p := AddQueryParameters("", map[string]string{})
	assert.Equal(t, "", p)
}

func TestAddQueryParameters_1(t *testing.T) {
	p := AddQueryParameters("", map[string]string{"": ""})
	assert.Equal(t, "?=", p)
}

func TestAddQueryParameters_2(t *testing.T) {
	p := AddQueryParameters("foo", map[string]string{"bar": "baz"})
	assert.Equal(t, "foo?bar=baz", p)
}
<commit_msg>Test for atlas.addAPIKey().<commit_after>package atlas

import (
	"bytes"
	"fmt"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"io/ioutil"
	"net/http"
	"testing"
)

func TestGetVersion(t *testing.T) {
	ver := GetVersion()
	assert.EqualValues(t, ourVersion, ver, "should be equal")
}

func TestGetPageNum(t *testing.T) {
	url := "https://foo.example.com/"

	n := getPageNum(url)
	if n != "" {
		t.Errorf("n=%s should be ''", n)
	}
	url = "https://foo.example.com/?page=42"
	n = getPageNum(url)
	if n != "42" {
		t.Errorf("n=%s should be 42", n)
	}
	url = "https://foo.example.com/?country=fr&page=43"
	n = getPageNum(url)
	if n != "43" {
		t.Errorf("n=%s should be 43", n)
	}
	url = "https://foo.example.com/?country=fr&page=666&bar=1"
	n = getPageNum(url)
	if n != "666" {
		t.Errorf("n=%s should be 666", n)
	}

	url = ""
	n = getPageNum(url)
	if n != "" {
		t.Errorf("n=%s should be ''", n)
	}
}

func TestClienthandleAPIResponsese(t *testing.T) {
	var (
		r http.Response
		b bytes.Buffer
	)

	client, err := NewClient()
	err = client.handleAPIResponsese(nil)
	assert.Error(t, err, "should be in error")

	r = http.Response{StatusCode: 0}
	err = client.handleAPIResponsese(&r)
	assert.NoError(t, err, "should be no error")

	r = http.Response{StatusCode: 200}
	err = client.handleAPIResponsese(&r)
	assert.NoError(t, err, "should be no error")

	var jsonErr = `error:{status: 501, code: 500, detail: "test"}`

	fmt.Fprintf(&b, "%v", jsonErr)
	r.StatusCode = 300
	r.Body = ioutil.NopCloser(&b)
	err = client.handleAPIResponsese(&r)
	assert.NoError(t, err, "should be in error")

	r.StatusCode = 500
	r.Body = ioutil.NopCloser(&b)
	err = client.handleAPIResponsese(&r)
	assert.Error(t, err, "should be in error")
}

func TestAddQueryParameters(t *testing.T) {
	p := AddQueryParameters("", map[string]string{})
	assert.Equal(t, "", p)
}

func TestAddQueryParameters_1(t *testing.T) {
	p := AddQueryParameters("", map[string]string{"": ""})
	assert.Equal(t, "?=", p)
}

func TestAddQueryParameters_2(t *testing.T) {
	p := AddQueryParameters("foo", map[string]string{"bar": "baz"})
	assert.Equal(t, "foo?bar=baz", p)
}

func TestClient_AddAPIKey(t *testing.T) {
	c, err := NewClient(Config{APIKey:"foo"})
	require.NoError(t, err)
	assert.NotNil(t, c)
	assert.NotEmpty(t, c)

	opts := map[string]string{}

	new := c.addAPIKey(opts)
	assert.NotEmpty(t, c.config.APIKey)
	assert.Equal(t, 1, len(new))
	assert.EqualValues(t, map[string]string{"key": "foo"}, new)
}
<|endoftext|>


<commit_before>package notifiers

import (
	"encoding/json"
	"fmt"
	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/setting"
	"time"
)

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type: "googlechat",
		Name: "Google Hangouts Chat",
		Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message " +
			"format (https://developers.google.com/hangouts/chat/reference/message-formats/).",
		Factory: NewGoogleChatNotifier,
		OptionsTemplate: `

Google Hangouts Chat settings

        Url
        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Google Hangouts Chat incoming webhook url"></input>

    `,
	})
}

func NewGoogleChatNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
	url := model.Settings.Get("url").MustString()
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
	}

	return &GoogleChatNotifier{
		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
		Url:          url,
		log:          log.New("alerting.notifier.googlechat"),
	}, nil
}

type GoogleChatNotifier struct {
	NotifierBase
	Url    string
	method string
	log    log.Logger
}

/**
Structs used to build a custom Google Hangouts Chat message card.
See: https://developers.google.com/hangouts/chat/reference/message-formats/cards
*/
type outerStruct struct {
	Cards []card `json:"cards"`
}

type card struct {
	Header   header    `json:"header"`
	Sections []section `json:"sections"`
}

type header struct {
	Title string `json:"title"`
}

type section struct {
	Widgets []widget `json:"widgets"`
}

// "generic" widget used to add different types of widgets (buttonWidget, textParagraphWidget, imageWidget)
type widget interface {
}

type buttonWidget struct {
	Buttons []button `json:"buttons"`
}

type textParagraphWidget struct {
	Text text `json:"textParagraph"`
}

type text struct {
	Text string `json:"text"`
}

type imageWidget struct {
	Image image `json:"image"`
}

type image struct {
	ImageUrl string `json:"imageUrl"`
}

type button struct {
	TextButton textButton `json:"textButton"`
}

type textButton struct {
	Text    string  `json:"text"`
	OnClick onClick `json:"onClick"`
}

type onClick struct {
	OpenLink openLink `json:"openLink"`
}

type openLink struct {
	Url string `json:"url"`
}

func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
	this.log.Info("Executing Google Chat notification")

	headers := map[string]string{
		"Content-Type": "application/json; charset=UTF-8",
	}

	ruleUrl, err := evalContext.GetRuleUrl()
	if err != nil {
		this.log.Error("evalContext returned an invalid rule URL")
	}

	// add a text paragraph widget for the message
	widgets := []widget{
		textParagraphWidget{
			Text: text{
				Text: evalContext.Rule.Message,
			},
		},
	}

	// add a text paragraph widget for the fields
	var fields []textParagraphWidget
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields,
			textParagraphWidget{
				Text: text{
					Text: "" + evt.Metric + ": " + fmt.Sprint(evt.Value) + "",
				},
			},
		)
		if index > fieldLimitCount {
			break
		}
	}
	widgets = append(widgets, fields)

	// if an image exists, add it as an image widget
	if evalContext.ImagePublicUrl != "" {
		widgets = append(widgets, imageWidget{
			Image: image{
				ImageUrl: evalContext.ImagePublicUrl,
			},
		})
	} else {
		this.log.Info("Could not retrieve a public image URL.")
	}

	// add a button widget (link to Grafana)
	widgets = append(widgets, buttonWidget{
		Buttons: []button{
			{
				TextButton: textButton{
					Text: "OPEN IN GRAFANA",
					OnClick: onClick{
						OpenLink: openLink{
							Url: ruleUrl,
						},
					},
				},
			},
		},
	})

	// add text paragraph widget for the build version and timestamp
	widgets = append(widgets, textParagraphWidget{
		Text: text{
			Text: "Grafana v" + setting.BuildVersion + " | " + (time.Now()).Format(time.RFC822),
		},
	})

	// nest the required structs
	res1D := &outerStruct{
		Cards: []card{
			{
				Header: header{
					Title: evalContext.GetNotificationTitle(),
				},
				Sections: []section{
					{
						Widgets: widgets,
					},
				},
			},
		},
	}
	body, _ := json.Marshal(res1D)

	cmd := &m.SendWebhookSync{
		Url:        this.Url,
		HttpMethod: "POST",
		HttpHeader: headers,
		Body:       string(body),
	}

	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		this.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", this.Name)
		return err
	}

	return nil
}
<commit_msg>fixes merge error<commit_after>package notifiers

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/setting"
)

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type: "googlechat",
		Name: "Google Hangouts Chat",
		Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message " +
			"format (https://developers.google.com/hangouts/chat/reference/message-formats/).",
		Factory: NewGoogleChatNotifier,
		OptionsTemplate: `

Google Hangouts Chat settings

        Url
        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Google Hangouts Chat incoming webhook url"></input>

    `,
	})
}

func NewGoogleChatNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
	url := model.Settings.Get("url").MustString()
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
	}

	return &GoogleChatNotifier{
		NotifierBase: NewNotifierBase(model),
		Url:          url,
		log:          log.New("alerting.notifier.googlechat"),
	}, nil
}

type GoogleChatNotifier struct {
	NotifierBase
	Url    string
	method string
	log    log.Logger
}

/**
Structs used to build a custom Google Hangouts Chat message card.
See: https://developers.google.com/hangouts/chat/reference/message-formats/cards
*/
type outerStruct struct {
	Cards []card `json:"cards"`
}

type card struct {
	Header   header    `json:"header"`
	Sections []section `json:"sections"`
}

type header struct {
	Title string `json:"title"`
}

type section struct {
	Widgets []widget `json:"widgets"`
}

// "generic" widget used to add different types of widgets (buttonWidget, textParagraphWidget, imageWidget)
type widget interface {
}

type buttonWidget struct {
	Buttons []button `json:"buttons"`
}

type textParagraphWidget struct {
	Text text `json:"textParagraph"`
}

type text struct {
	Text string `json:"text"`
}

type imageWidget struct {
	Image image `json:"image"`
}

type image struct {
	ImageUrl string `json:"imageUrl"`
}

type button struct {
	TextButton textButton `json:"textButton"`
}

type textButton struct {
	Text    string  `json:"text"`
	OnClick onClick `json:"onClick"`
}

type onClick struct {
	OpenLink openLink `json:"openLink"`
}

type openLink struct {
	Url string `json:"url"`
}

func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
	this.log.Info("Executing Google Chat notification")

	headers := map[string]string{
		"Content-Type": "application/json; charset=UTF-8",
	}

	ruleUrl, err := evalContext.GetRuleUrl()
	if err != nil {
		this.log.Error("evalContext returned an invalid rule URL")
	}

	// add a text paragraph widget for the message
	widgets := []widget{
		textParagraphWidget{
			Text: text{
				Text: evalContext.Rule.Message,
			},
		},
	}

	// add a text paragraph widget for the fields
	var fields []textParagraphWidget
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields,
			textParagraphWidget{
				Text: text{
					Text: "" + evt.Metric + ": " + fmt.Sprint(evt.Value) + "",
				},
			},
		)
		if index > fieldLimitCount {
			break
		}
	}
	widgets = append(widgets, fields)

	// if an image exists, add it as an image widget
	if evalContext.ImagePublicUrl != "" {
		widgets = append(widgets, imageWidget{
			Image: image{
				ImageUrl: evalContext.ImagePublicUrl,
			},
		})
	} else {
		this.log.Info("Could not retrieve a public image URL.")
	}

	// add a button widget (link to Grafana)
	widgets = append(widgets, buttonWidget{
		Buttons: []button{
			{
				TextButton: textButton{
					Text: "OPEN IN GRAFANA",
					OnClick: onClick{
						OpenLink: openLink{
							Url: ruleUrl,
						},
					},
				},
			},
		},
	})

	// add text paragraph widget for the build version and timestamp
	widgets = append(widgets, textParagraphWidget{
		Text: text{
			Text: "Grafana v" + setting.BuildVersion + " | " + (time.Now()).Format(time.RFC822),
		},
	})

	// nest the required structs
	res1D := &outerStruct{
		Cards: []card{
			{
				Header: header{
					Title: evalContext.GetNotificationTitle(),
				},
				Sections: []section{
					{
						Widgets: widgets,
					},
				},
			},
		},
	}
	body, _ := json.Marshal(res1D)

	cmd := &m.SendWebhookSync{
		Url:        this.Url,
		HttpMethod: "POST",
		HttpHeader: headers,
		Body:       string(body),
	}

	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		this.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", this.Name)
		return err
	}

	return nil
}
<|endoftext|>