<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package status

import (
	"fmt"
	"net"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/pkg/errors"

	pool "gopkg.in/go-playground/pool.v3"
	apiv1 "k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/leaderelection"
	"k8s.io/client-go/tools/leaderelection/resourcelock"
	"k8s.io/client-go/tools/record"
	"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"

	"k8s.io/ingress-nginx/internal/k8s"
	"k8s.io/ingress-nginx/internal/task"
)

const (
	updateInterval = 60 * time.Second
)

// Sync ...
type Sync interface {
	Run()
	Shutdown()
}

type ingressLister interface {
	// ListIngresses returns the list of Ingresses
	ListIngresses() []*extensions.Ingress
}

// Config ...
type Config struct {
	Client clientset.Interface

	PublishService string

	PublishStatusAddress string

	ElectionID string

	UpdateStatusOnShutdown bool

	UseNodeInternalIP bool

	IngressLister ingressLister

	DefaultIngressClass string
	IngressClass        string
}

// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
// in all the defined rules. To simplify the process leader election is used so the update
// is executed only in one node (Ingress controllers can be scaled to more than one)
// If the controller is running with the flag --publish-service (with a valid service)
// the IP address behind the service is used, if it is running with the flag
// --publish-status-address, the address specified in the flag is used, if neither of the
// two flags are set, the source is the IP/s of the node/s
type statusSync struct {
	Config
	// pod contains runtime information about this pod
	pod *k8s.PodInfo

	elector *leaderelection.LeaderElector
	// workqueue used to keep in sync the status IP/s
	// in the Ingress rules
	syncQueue *task.Queue
}

// Run starts the loop to keep the status in sync
func (s statusSync) Run() {
	s.elector.Run()
}

// Shutdown stop the sync. In case the instance is the leader it will remove the current IP
// if there is no other instances running.
func (s statusSync) Shutdown() {
	go s.syncQueue.Shutdown()
	// remove IP from Ingress
	if !s.elector.IsLeader() {
		return
	}

	if !s.UpdateStatusOnShutdown {
		glog.Warningf("skipping update of status of Ingress rules")
		return
	}

	glog.Infof("updating status of Ingress rules (remove)")

	addrs, err := s.runningAddresses()
	if err != nil {
		glog.Errorf("error obtaining running IPs: %v", addrs)
		return
	}

	if len(addrs) > 1 {
		// leave the job to the next leader
		glog.Infof("leaving status update for next leader (%v)", len(addrs))
		return
	}

	if s.isRunningMultiplePods() {
		glog.V(2).Infof("skipping Ingress status update (multiple pods running - another one will be elected as master)")
		return
	}

	glog.Infof("removing address from ingress status (%v)", addrs)
	s.updateStatus([]apiv1.LoadBalancerIngress{})
}

func (s *statusSync) sync(key interface{}) error {
	if s.syncQueue.IsShuttingDown() {
		glog.V(2).Infof("skipping Ingress status update (shutting down in progress)")
		return nil
	}

	addrs, err := s.runningAddresses()
	if err != nil {
		return err
	}
	s.updateStatus(sliceToStatus(addrs))

	return nil
}

func (s statusSync) keyfunc(input interface{}) (interface{}, error) {
	return input, nil
}

// NewStatusSyncer returns a new Sync instance
func NewStatusSyncer(config Config) Sync {
	pod, err := k8s.GetPodDetails(config.Client)
	if err != nil {
		glog.Fatalf("unexpected error obtaining pod information: %v", err)
	}

	st := statusSync{
		pod: pod,

		Config: config,
	}
	st.syncQueue = task.NewCustomTaskQueue(st.sync, st.keyfunc)

	// we need to use the defined ingress class to allow multiple leaders
	// in order to update information about ingress status
	electionID := fmt.Sprintf("%v-%v", config.ElectionID, config.DefaultIngressClass)
	if config.IngressClass != "" {
		electionID = fmt.Sprintf("%v-%v", config.ElectionID, config.IngressClass)
	}

	callbacks := leaderelection.LeaderCallbacks{
		OnStartedLeading: func(stop <-chan struct{}) {
			glog.V(2).Infof("I am the new status update leader")
			go st.syncQueue.Run(time.Second, stop)
			// when this instance is the leader we need to enqueue
			// an item to trigger the update of the Ingress status.
			wait.PollUntil(updateInterval, func() (bool, error) {
				st.syncQueue.EnqueueTask(task.GetDummyObject("sync status"))
				return false, nil
			}, stop)
		},
		OnStoppedLeading: func() {
			glog.V(2).Infof("I am not status update leader anymore")
		},
		OnNewLeader: func(identity string) {
			glog.Infof("new leader elected: %v", identity)
		},
	}

	broadcaster := record.NewBroadcaster()
	hostname, _ := os.Hostname()

	recorder := broadcaster.NewRecorder(scheme.Scheme, apiv1.EventSource{
		Component: "ingress-leader-elector",
		Host:      hostname,
	})

	lock := resourcelock.ConfigMapLock{
		ConfigMapMeta: metav1.ObjectMeta{Namespace: pod.Namespace, Name: electionID},
		Client:        config.Client.CoreV1(),
		LockConfig: resourcelock.ResourceLockConfig{
			Identity:      pod.Name,
			EventRecorder: recorder,
		},
	}

	ttl := 30 * time.Second
	le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
		Lock:          &lock,
		LeaseDuration: ttl,
		RenewDeadline: ttl / 2,
		RetryPeriod:   ttl / 4,
		Callbacks:     callbacks,
	})

	if err != nil {
		glog.Fatalf("unexpected error starting leader election: %v", err)
	}

	st.elector = le
	return st
}

// runningAddresses returns a list of IP addresses and/or FQDN where the
// ingress controller is currently running
func (s *statusSync) runningAddresses() ([]string, error) {
	addrs := []string{}

	if s.PublishService != "" {
		ns, name, _ := k8s.ParseNameNS(s.PublishService)
		svc, err := s.Client.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
		if err != nil {
			return nil, err
		}

		if svc.Spec.Type == apiv1.ServiceTypeExternalName {
			addrs = append(addrs, svc.Spec.ExternalName)
			return addrs, nil
		}

		for _, ip := range svc.Status.LoadBalancer.Ingress {
			if ip.IP == "" {
				addrs = append(addrs, ip.Hostname)
			} else {
				addrs = append(addrs, ip.IP)
			}
		}

		addrs = append(addrs, svc.Spec.ExternalIPs...)
		return addrs, nil
	}

	if s.PublishStatusAddress != "" {
		addrs = append(addrs, s.PublishStatusAddress)
		return addrs, nil
	}

	// get information about all the pods running the ingress controller
	pods, err := s.Client.CoreV1().Pods(s.pod.Namespace).List(metav1.ListOptions{
		LabelSelector: labels.SelectorFromSet(s.pod.Labels).String(),
	})
	if err != nil {
		return nil, err
	}

	for _, pod := range pods.Items {
		// only Running pods are valid
		if pod.Status.Phase != apiv1.PodRunning {
			continue
		}

		name := k8s.GetNodeIPOrName(s.Client, pod.Spec.NodeName, s.UseNodeInternalIP)
		if !sliceutils.StringInSlice(name, addrs) {
			addrs = append(addrs, name)
		}
	}

	return addrs, nil
}

func (s *statusSync) isRunningMultiplePods() bool {
	pods, err := s.Client.CoreV1().Pods(s.pod.Namespace).List(metav1.ListOptions{
		LabelSelector: labels.SelectorFromSet(s.pod.Labels).String(),
	})
	if err != nil {
		return false
	}

	return len(pods.Items) > 1
}

// sliceToStatus converts a slice of IP and/or hostnames to LoadBalancerIngress
func sliceToStatus(endpoints []string) []apiv1.LoadBalancerIngress {
	lbi := []apiv1.LoadBalancerIngress{}
	for _, ep := range endpoints {
		if net.ParseIP(ep) == nil {
			lbi = append(lbi, apiv1.LoadBalancerIngress{Hostname: ep})
		} else {
			lbi = append(lbi, apiv1.LoadBalancerIngress{IP: ep})
		}
	}

	sort.SliceStable(lbi, func(a, b int) bool {
		return lbi[a].IP < lbi[b].IP
	})

	return lbi
}

// updateStatus changes the status information of Ingress rules
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
	ings := s.IngressLister.ListIngresses()

	p := pool.NewLimited(10)
	defer p.Close()

	batch := p.Batch()

	for _, ing := range ings {
		batch.Queue(runUpdate(ing, newIngressPoint, s.Client))
	}

	batch.QueueComplete()
	batch.WaitAll()
}

func runUpdate(ing *extensions.Ingress, status []apiv1.LoadBalancerIngress,
	client clientset.Interface) pool.WorkFunc {
	return func(wu pool.WorkUnit) (interface{}, error) {
		if wu.IsCancelled() {
			return nil, nil
		}

		sort.SliceStable(status, lessLoadBalancerIngress(status))

		curIPs := ing.Status.LoadBalancer.Ingress
		sort.SliceStable(curIPs, lessLoadBalancerIngress(curIPs))

		if ingressSliceEqual(status, curIPs) {
			glog.V(3).Infof("skipping update of Ingress %v/%v (no change)", ing.Namespace, ing.Name)
			return true, nil
		}

		ingClient := client.ExtensionsV1beta1().Ingresses(ing.Namespace)

		currIng, err := ingClient.Get(ing.Name, metav1.GetOptions{})
		if err != nil {
			return nil, errors.Wrap(err, fmt.Sprintf("unexpected error searching Ingress %v/%v", ing.Namespace, ing.Name))
		}

		glog.Infof("updating Ingress %v/%v status to %v", currIng.Namespace, currIng.Name, status)
		currIng.Status.LoadBalancer.Ingress = status
		_, err = ingClient.UpdateStatus(currIng)
		if err != nil {
			glog.Warningf("error updating ingress rule: %v", err)
		}

		return true, nil
	}
}

func lessLoadBalancerIngress(addrs []apiv1.LoadBalancerIngress) func(int, int) bool {
	return func(a, b int) bool {
		switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) {
		case -1:
			return true
		case 1:
			return false
		}
		return addrs[a].IP < addrs[b].IP
	}
}

func ingressSliceEqual(lhs, rhs []apiv1.LoadBalancerIngress) bool {
	if len(lhs) != len(rhs) {
		return false
	}

	for i := range lhs {
		if lhs[i].IP != rhs[i].IP {
			return false
		}
		if lhs[i].Hostname != rhs[i].Hostname {
			return false
		}
	}
	return true
}
<commit_msg>Don't sort IngressStatus from each Goroutine(update for each ingress)<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package status

import (
	"fmt"
	"net"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/pkg/errors"

	pool "gopkg.in/go-playground/pool.v3"
	apiv1 "k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/leaderelection"
	"k8s.io/client-go/tools/leaderelection/resourcelock"
	"k8s.io/client-go/tools/record"
	"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"

	"k8s.io/ingress-nginx/internal/k8s"
	"k8s.io/ingress-nginx/internal/task"
)

const (
	updateInterval = 60 * time.Second
)

// Sync ...
type Sync interface {
	Run()
	Shutdown()
}

type ingressLister interface {
	// ListIngresses returns the list of Ingresses
	ListIngresses() []*extensions.Ingress
}

// Config ...
type Config struct {
	Client clientset.Interface

	PublishService string

	PublishStatusAddress string

	ElectionID string

	UpdateStatusOnShutdown bool

	UseNodeInternalIP bool

	IngressLister ingressLister

	DefaultIngressClass string
	IngressClass        string
}

// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
// in all the defined rules. To simplify the process leader election is used so the update
// is executed only in one node (Ingress controllers can be scaled to more than one)
// If the controller is running with the flag --publish-service (with a valid service)
// the IP address behind the service is used, if it is running with the flag
// --publish-status-address, the address specified in the flag is used, if neither of the
// two flags are set, the source is the IP/s of the node/s
type statusSync struct {
	Config
	// pod contains runtime information about this pod
	pod *k8s.PodInfo

	elector *leaderelection.LeaderElector
	// workqueue used to keep in sync the status IP/s
	// in the Ingress rules
	syncQueue *task.Queue
}

// Run starts the loop to keep the status in sync
func (s statusSync) Run() {
	s.elector.Run()
}

// Shutdown stop the sync. In case the instance is the leader it will remove the current IP
// if there is no other instances running.
func (s statusSync) Shutdown() {
	go s.syncQueue.Shutdown()
	// remove IP from Ingress
	if !s.elector.IsLeader() {
		return
	}

	if !s.UpdateStatusOnShutdown {
		glog.Warningf("skipping update of status of Ingress rules")
		return
	}

	glog.Infof("updating status of Ingress rules (remove)")

	addrs, err := s.runningAddresses()
	if err != nil {
		glog.Errorf("error obtaining running IPs: %v", addrs)
		return
	}

	if len(addrs) > 1 {
		// leave the job to the next leader
		glog.Infof("leaving status update for next leader (%v)", len(addrs))
		return
	}

	if s.isRunningMultiplePods() {
		glog.V(2).Infof("skipping Ingress status update (multiple pods running - another one will be elected as master)")
		return
	}

	glog.Infof("removing address from ingress status (%v)", addrs)
	s.updateStatus([]apiv1.LoadBalancerIngress{})
}

func (s *statusSync) sync(key interface{}) error {
	if s.syncQueue.IsShuttingDown() {
		glog.V(2).Infof("skipping Ingress status update (shutting down in progress)")
		return nil
	}

	addrs, err := s.runningAddresses()
	if err != nil {
		return err
	}
	s.updateStatus(sliceToStatus(addrs))

	return nil
}

func (s statusSync) keyfunc(input interface{}) (interface{}, error) {
	return input, nil
}

// NewStatusSyncer returns a new Sync instance
func NewStatusSyncer(config Config) Sync {
	pod, err := k8s.GetPodDetails(config.Client)
	if err != nil {
		glog.Fatalf("unexpected error obtaining pod information: %v", err)
	}

	st := statusSync{
		pod: pod,

		Config: config,
	}
	st.syncQueue = task.NewCustomTaskQueue(st.sync, st.keyfunc)

	// we need to use the defined ingress class to allow multiple leaders
	// in order to update information about ingress status
	electionID := fmt.Sprintf("%v-%v", config.ElectionID, config.DefaultIngressClass)
	if config.IngressClass != "" {
		electionID = fmt.Sprintf("%v-%v", config.ElectionID, config.IngressClass)
	}

	callbacks := leaderelection.LeaderCallbacks{
		OnStartedLeading: func(stop <-chan struct{}) {
			glog.V(2).Infof("I am the new status update leader")
			go st.syncQueue.Run(time.Second, stop)
			// when this instance is the leader we need to enqueue
			// an item to trigger the update of the Ingress status.
			wait.PollUntil(updateInterval, func() (bool, error) {
				st.syncQueue.EnqueueTask(task.GetDummyObject("sync status"))
				return false, nil
			}, stop)
		},
		OnStoppedLeading: func() {
			glog.V(2).Infof("I am not status update leader anymore")
		},
		OnNewLeader: func(identity string) {
			glog.Infof("new leader elected: %v", identity)
		},
	}

	broadcaster := record.NewBroadcaster()
	hostname, _ := os.Hostname()

	recorder := broadcaster.NewRecorder(scheme.Scheme, apiv1.EventSource{
		Component: "ingress-leader-elector",
		Host:      hostname,
	})

	lock := resourcelock.ConfigMapLock{
		ConfigMapMeta: metav1.ObjectMeta{Namespace: pod.Namespace, Name: electionID},
		Client:        config.Client.CoreV1(),
		LockConfig: resourcelock.ResourceLockConfig{
			Identity:      pod.Name,
			EventRecorder: recorder,
		},
	}

	ttl := 30 * time.Second
	le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
		Lock:          &lock,
		LeaseDuration: ttl,
		RenewDeadline: ttl / 2,
		RetryPeriod:   ttl / 4,
		Callbacks:     callbacks,
	})

	if err != nil {
		glog.Fatalf("unexpected error starting leader election: %v", err)
	}

	st.elector = le
	return st
}

// runningAddresses returns a list of IP addresses and/or FQDN where the
// ingress controller is currently running
func (s *statusSync) runningAddresses() ([]string, error) {
	addrs := []string{}

	if s.PublishService != "" {
		ns, name, _ := k8s.ParseNameNS(s.PublishService)
		svc, err := s.Client.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
		if err != nil {
			return nil, err
		}

		if svc.Spec.Type == apiv1.ServiceTypeExternalName {
			addrs = append(addrs, svc.Spec.ExternalName)
			return addrs, nil
		}

		for _, ip := range svc.Status.LoadBalancer.Ingress {
			if ip.IP == "" {
				addrs = append(addrs, ip.Hostname)
			} else {
				addrs = append(addrs, ip.IP)
			}
		}

		addrs = append(addrs, svc.Spec.ExternalIPs...)
		return addrs, nil
	}

	if s.PublishStatusAddress != "" {
		addrs = append(addrs, s.PublishStatusAddress)
		return addrs, nil
	}

	// get information about all the pods running the ingress controller
	pods, err := s.Client.CoreV1().Pods(s.pod.Namespace).List(metav1.ListOptions{
		LabelSelector: labels.SelectorFromSet(s.pod.Labels).String(),
	})
	if err != nil {
		return nil, err
	}

	for _, pod := range pods.Items {
		// only Running pods are valid
		if pod.Status.Phase != apiv1.PodRunning {
			continue
		}

		name := k8s.GetNodeIPOrName(s.Client, pod.Spec.NodeName, s.UseNodeInternalIP)
		if !sliceutils.StringInSlice(name, addrs) {
			addrs = append(addrs, name)
		}
	}

	return addrs, nil
}

func (s *statusSync) isRunningMultiplePods() bool {
	pods, err := s.Client.CoreV1().Pods(s.pod.Namespace).List(metav1.ListOptions{
		LabelSelector: labels.SelectorFromSet(s.pod.Labels).String(),
	})
	if err != nil {
		return false
	}

	return len(pods.Items) > 1
}

// sliceToStatus converts a slice of IP and/or hostnames to LoadBalancerIngress
func sliceToStatus(endpoints []string) []apiv1.LoadBalancerIngress {
	lbi := []apiv1.LoadBalancerIngress{}
	for _, ep := range endpoints {
		if net.ParseIP(ep) == nil {
			lbi = append(lbi, apiv1.LoadBalancerIngress{Hostname: ep})
		} else {
			lbi = append(lbi, apiv1.LoadBalancerIngress{IP: ep})
		}
	}

	sort.SliceStable(lbi, func(a, b int) bool {
		return lbi[a].IP < lbi[b].IP
	})

	return lbi
}

// updateStatus changes the status information of Ingress rules
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
	ings := s.IngressLister.ListIngresses()

	p := pool.NewLimited(10)
	defer p.Close()

	batch := p.Batch()
	sort.SliceStable(newIngressPoint, lessLoadBalancerIngress(newIngressPoint))

	for _, ing := range ings {
		batch.Queue(runUpdate(ing, newIngressPoint, s.Client))
	}

	batch.QueueComplete()
	batch.WaitAll()
}

func runUpdate(ing *extensions.Ingress, status []apiv1.LoadBalancerIngress,
	client clientset.Interface) pool.WorkFunc {
	return func(wu pool.WorkUnit) (interface{}, error) {
		if wu.IsCancelled() {
			return nil, nil
		}

		curIPs := ing.Status.LoadBalancer.Ingress
		sort.SliceStable(curIPs, lessLoadBalancerIngress(curIPs))

		if ingressSliceEqual(status, curIPs) {
			glog.V(3).Infof("skipping update of Ingress %v/%v (no change)", ing.Namespace, ing.Name)
			return true, nil
		}

		ingClient := client.ExtensionsV1beta1().Ingresses(ing.Namespace)

		currIng, err := ingClient.Get(ing.Name, metav1.GetOptions{})
		if err != nil {
			return nil, errors.Wrap(err, fmt.Sprintf("unexpected error searching Ingress %v/%v", ing.Namespace, ing.Name))
		}

		glog.Infof("updating Ingress %v/%v status to %v", currIng.Namespace, currIng.Name, status)
		currIng.Status.LoadBalancer.Ingress = status
		_, err = ingClient.UpdateStatus(currIng)
		if err != nil {
			glog.Warningf("error updating ingress rule: %v", err)
		}

		return true, nil
	}
}

func lessLoadBalancerIngress(addrs []apiv1.LoadBalancerIngress) func(int, int) bool {
	return func(a, b int) bool {
		switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) {
		case -1:
			return true
		case 1:
			return false
		}
		return addrs[a].IP < addrs[b].IP
	}
}

func ingressSliceEqual(lhs, rhs []apiv1.LoadBalancerIngress) bool {
	if len(lhs) != len(rhs) {
		return false
	}

	for i := range lhs {
		if lhs[i].IP != rhs[i].IP {
			return false
		}
		if lhs[i].Hostname != rhs[i].Hostname {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package edgectl

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/datawire/ambassador/pkg/supervisor"
)

func (d *Daemon) interceptMessage() string {
	switch {
	case d.cluster == nil:
		return "Not connected (use 'edgectl connect' to connect to your cluster)"
	case d.trafficMgr == nil:
		return "Intercept unavailable: no traffic manager"
	case !d.trafficMgr.IsOkay():
		return "Connecting to traffic manager..."
	default:
		return ""
	}
}

// InterceptInfo tracks one intercept operation
type InterceptInfo struct {
	Name       string // Name of the intercept (user/logging)
	Namespace  string // Name in which to create the Intercept mapping
	Deployment string // Name of the deployment being intercepted
	Prefix     string // Prefix to intercept (default /)
	Patterns   map[string]string
	TargetHost string
	TargetPort int
}

// path returns the URL path for this intercept
func (ii *InterceptInfo) path() string {
	return fmt.Sprintf("intercept/%s/%s", ii.Namespace, ii.Deployment)
}

// PreviewURL returns the Service Preview URL for this intercept if it is
// configured appropriately, or the empty string otherwise.
func (ii *InterceptInfo) PreviewURL(hostname string) (url string) {
	if len(ii.Patterns) != 1 {
		return
	}

	for header, token := range ii.Patterns {
		if strings.ToLower(header) != "x-service-preview" {
			return
		}
		url = fmt.Sprintf("https://%s/.ambassador/service-preview/%s/", hostname, token)
	}

	return
}

// Acquire an intercept from the traffic manager
func (ii *InterceptInfo) Acquire(_ *supervisor.Process, tm *TrafficManager) (int, error) {
	reqPatterns := make([]map[string]string, 0, len(ii.Patterns))
	for header, regex := range ii.Patterns {
		pattern := map[string]string{"name": header, "regex_match": regex}
		reqPatterns = append(reqPatterns, pattern)
	}
	request := map[string]interface{}{
		"name":     ii.Name,
		"patterns": reqPatterns,
	}
	reqData, err := json.Marshal(request)
	if err != nil {
		return 0, err
	}

	result, code, err := tm.request("POST", ii.path(), reqData)
	if err != nil {
		return 0, errors.Wrap(err, "acquire intercept")
	}
	if code == 404 {
		return 0, fmt.Errorf("deployment %q is not known to the traffic manager", ii.Deployment)
	}
	if !(200 <= code && code <= 299) {
		return 0, fmt.Errorf("acquire intercept: %s: %s", http.StatusText(code), result)
	}
	port, err := strconv.Atoi(result)
	if err != nil {
		return 0, errors.Wrapf(err, "bad port number from traffic manager: %q", result)
	}
	return port, nil
}

// Retain the given intercept. This likely needs to be called every
// five seconds or so.
func (ii *InterceptInfo) Retain(_ *supervisor.Process, tm *TrafficManager, port int) error {
	data := []byte(fmt.Sprintf("{\"port\": %d}", port))
	result, code, err := tm.request("POST", ii.path(), data)
	if err != nil {
		return errors.Wrap(err, "retain intercept")
	}
	if !(200 <= code && code <= 299) {
		return fmt.Errorf("retain intercept: %s: %s", http.StatusText(code), result)
	}
	return nil
}

// Release the given intercept.
func (ii *InterceptInfo) Release(_ *supervisor.Process, tm *TrafficManager, port int) error {
	data := []byte(fmt.Sprintf("%d", port))
	result, code, err := tm.request("DELETE", ii.path(), data)
	if err != nil {
		return errors.Wrap(err, "release intercept")
	}
	if !(200 <= code && code <= 299) {
		return fmt.Errorf("release intercept: %s: %s", http.StatusText(code), result)
	}
	return nil
}

// ListIntercepts lists active intercepts
func (d *Daemon) ListIntercepts(_ *supervisor.Process, out *Emitter) error {
	msg := d.interceptMessage()
	if msg != "" {
		out.Println(msg)
		out.Send("intercept", msg)
		return nil
	}
	var previewURL string
	for idx, cept := range d.intercepts {
		ii := cept.ii
		url := ii.PreviewURL("$EDGE")
		out.Printf("%4d. %s\n", idx+1, ii.Name)
		if url != "" {
			previewURL = url
			out.Println("      (preview URL available)")
		}
		out.Send(fmt.Sprintf("local_intercept.%d", idx+1), ii.Name)
		key := "local_intercept." + ii.Name
		out.Printf("      Intercepting requests to %s when\n", ii.Deployment)
		out.Send(key, ii.Deployment)
		for k, v := range ii.Patterns {
			out.Printf("      - %s: %s\n", k, v)
			out.Send(key+"."+k, v)
		}
		out.Printf("      and redirecting them to %s:%d\n", ii.TargetHost, ii.TargetPort)
		out.Send(key+".host", ii.TargetHost)
		out.Send(key+".port", ii.TargetPort)
	}
	if previewURL != "" {
		out.Println("Share a preview of your changes with anyone by visiting\n  ", previewURL)
	}
	if len(d.intercepts) == 0 {
		out.Println("No intercepts")
	}
	return nil
}

// AddIntercept adds one intercept
func (d *Daemon) AddIntercept(p *supervisor.Process, out *Emitter, ii *InterceptInfo) error {
	msg := d.interceptMessage()
	if msg != "" {
		out.Println(msg)
		out.Send("intercept", msg)
		return nil
	}

	for _, cept := range d.intercepts {
		if cept.ii.Name == ii.Name {
			out.Printf("Intercept with name %q already exists\n", ii.Name)
			out.Send("failed", "intercept name exists")
			out.SendExit(1)
			return nil
		}
	}

	// Do we already have a namespace?
	if ii.Namespace == "" {
		// Nope. See if we have an interceptable that matches the name.

		matches := make([]InterceptInfo, 0)
		for _, deployment := range d.trafficMgr.interceptables {
			fields := strings.SplitN(deployment, "/", 2)

			appName := fields[0]
			appNamespace := d.cluster.namespace

			if len(fields) > 1 {
				appNamespace = fields[0]
				appName = fields[1]
			}

			if ii.Deployment == appName {
				// Abuse InterceptInfo rather than defining a new tuple type.
				matches = append(matches, InterceptInfo{"", appNamespace, appName, "", nil, "", 0})
			}
		}

		switch len(matches) {
		case 0:
			out.Printf("No interceptable deployment matching %s found\n", ii.Deployment)
			out.Send("failed", "no interceptable deployment matches")
			out.SendExit(1)
			return nil

		case 1:
			// Good to go.
			ii.Namespace = matches[0].Namespace
			out.Printf("Using deployment %s in namespace %s\n", ii.Deployment, ii.Namespace)

		default:
			out.Printf("Found more than one possible match:\n")

			for idx, match := range matches {
				out.Printf("%4d: %s in namespace %s\n", idx+1, match.Deployment, match.Namespace)
			}
			out.Send("failed", "multiple interceptable deployment matched")
			out.SendExit(1)
			return nil
		}
	}

	cept, err := MakeIntercept(p, out, d.trafficMgr, d.cluster, ii)
	if err != nil {
		out.Printf("Failed to establish intercept: %s\n", err)
		out.Send("failed", err.Error())
		out.SendExit(1)
		return nil
	}
	d.intercepts = append(d.intercepts, cept)
	out.Printf("Added intercept %q\n", ii.Name)
	return nil
}

// RemoveIntercept removes one intercept by name
func (d *Daemon) RemoveIntercept(p *supervisor.Process, out *Emitter, name string) error {
	msg := d.interceptMessage()
	for idx, cept := range d.intercepts {
		if cept.ii.Name == name {
			d.intercepts = append(d.intercepts[:idx], d.intercepts[idx+1:]...)

			out.Printf("Removed intercept %q\n", name)

			if err := cept.Close(); err != nil {
				out.Printf("Error while removing intercept: %v\n", err)
				out.Send("failed", err.Error())
				out.SendExit(1)
			}
			return nil
		}
	}
	if msg != "" {
		out.Println(msg)
		out.Send("intercept", msg)
		return nil
	}
	out.Printf("Intercept named %q not found\n", name)
	out.Send("failed", "not found")
	out.SendExit(1)
	return nil
}

// ClearIntercepts removes all intercepts
func (d *Daemon) ClearIntercepts(p *supervisor.Process) error {
	for _, cept := range d.intercepts {
		if err := cept.Close(); err != nil {
			p.Logf("Closing intercept %q: %v", cept.ii.Name, err)
		}
	}
	d.intercepts = d.intercepts[:0]
	return nil
}

// Intercept is a Resource handle that represents a live intercept
type Intercept struct {
	ii            *InterceptInfo
	tm            *TrafficManager
	cluster       *KCluster
	port          int
	crc           Resource
	mappingExists bool
	ResourceBase
}

// removeMapping drops an Intercept's mapping if needed (and possible).
func (cept *Intercept) removeMapping(p *supervisor.Process) error {
	var err error
	err = nil

	if cept.mappingExists {
		p.Logf("%v: Deleting mapping in namespace %v", cept.ii.Name, cept.ii.Namespace)
		delete := cept.cluster.GetKubectlCmd(p, "delete", "-n", cept.ii.Namespace, "mapping", fmt.Sprintf("%s-mapping", cept.ii.Name))
		err = delete.Run()
		p.Logf("%v: Deleted mapping in namespace %v", cept.ii.Name, cept.ii.Namespace)
	}

	if err != nil {
		return errors.Wrap(err, "Intercept: mapping could not be deleted")
	}

	return nil
}

type mappingMetadata struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

type mappingSpec struct {
	AmbassadorID []string          `json:"ambassador_id"`
	Prefix       string            `json:"prefix"`
	Service      string            `json:"service"`
	RegexHeaders map[string]string `json:"regex_headers"`
}

type interceptMapping struct {
	APIVersion string          `json:"apiVersion"`
	Kind       string          `json:"kind"`
	Metadata   mappingMetadata `json:"metadata"`
	Spec       mappingSpec     `json:"spec"`
}

// MakeIntercept acquires an intercept and returns a Resource handle
// for it
func MakeIntercept(p *supervisor.Process, out *Emitter, tm *TrafficManager, cluster *KCluster, ii *InterceptInfo) (*Intercept, error) {
	port, err := ii.Acquire(p, tm)
	if err != nil {
		return nil, err
	}

	cept := &Intercept{ii: ii, tm: tm, cluster: cluster, port: port}
	cept.mappingExists = false
	cept.doCheck = cept.check
	cept.doQuit = cept.quit
	cept.setup(p.Supervisor(), ii.Name)

	p.Logf("%s: Intercepting via port %v, using namespace %v", ii.Name, port, ii.Namespace)

	mapping := interceptMapping{
		APIVersion: "getambassador.io/v2",
		Kind:       "Mapping",
		Metadata: mappingMetadata{
			Name:      fmt.Sprintf("%s-mapping", ii.Name),
			Namespace: ii.Namespace,
		},
		Spec: mappingSpec{
			AmbassadorID: []string{fmt.Sprintf("intercept-%s", ii.Deployment)},
			Prefix:       ii.Prefix,
			Service:      fmt.Sprintf("telepresence-proxy.%s:%d", tm.namespace, port),
			RegexHeaders: ii.Patterns,
		},
	}

	manifest, err := json.MarshalIndent(&mapping, "", "  ")
	if err != nil {
		_ = cept.Close()
		return nil, errors.Wrap(err, "Intercept: mapping could not be constructed")
	}

	out.Printf("%s: applying intercept mapping in namespace %s\n", ii.Name, ii.Namespace)

	apply := cluster.GetKubectlCmdNoNamespace(p, "apply", "-f", "-")
	apply.Stdin = strings.NewReader(string(manifest))
	err = apply.Run()

	if err != nil {
		p.Logf("%v: Intercept could not apply mapping: %v", ii.Name, err)
		_ = cept.Close()
		return nil, errors.Wrap(err, "Intercept: kubectl apply")
	}

	cept.mappingExists = true

	sshCmd := []string{
		"ssh", "-C", "-N", "telepresence@localhost",
		"-oConnectTimeout=10", "-oExitOnForwardFailure=yes",
		"-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null",
		"-p", strconv.Itoa(tm.sshPort),
		"-R", fmt.Sprintf("%d:%s:%d", cept.port, ii.TargetHost, ii.TargetPort),
	}

	p.Logf("%s: starting SSH tunnel", ii.Name)
	out.Printf("%s: starting SSH tunnel\n", ii.Name)

	ssh, err := CheckedRetryingCommand(p, ii.Name+"-ssh", sshCmd, nil, nil, 5*time.Second)
	if err != nil {
		_ = cept.Close()
		return nil, err
	}

	cept.crc = ssh

	return cept, nil
}

func (cept *Intercept) check(p *supervisor.Process) error {
	return cept.ii.Retain(p, cept.tm, cept.port)
}

func (cept *Intercept) quit(p *supervisor.Process) error {
	cept.done = true

	p.Logf("cept.Quit removing %v", cept.ii.Name)

	if err := cept.removeMapping(p); err != nil {
		p.Logf("cept.Quit failed to remove %v: %+v", cept.ii.Name, err)
	} else {
		p.Logf("cept.Quit removed %v", cept.ii.Name)
	}

	if cept.crc != nil {
		_ = cept.crc.Close()
	}

	p.Logf("cept.Quit releasing %v", cept.ii.Name)

	if err := cept.ii.Release(p, cept.tm, cept.port); err != nil {
		p.Log(err)
	}

	p.Logf("cept.Quit released %v", cept.ii.Name)

	return nil
}
<commit_msg>displaying api error for intercept commands<commit_after>package edgectl

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/datawire/ambassador/pkg/supervisor"
)

func (d *Daemon) interceptMessage() string {
	switch {
	case d.cluster == nil:
		return "Not connected (use 'edgectl connect' to connect to your cluster)"
	case d.trafficMgr == nil:
		return "Intercept unavailable: no traffic manager"
	case !d.trafficMgr.IsOkay():
		if d.trafficMgr.apiErr != nil {
			return d.trafficMgr.apiErr.Error()
		} else {
			return "Connecting to traffic manager..."
		}
	default:
		return ""
	}
}

// InterceptInfo tracks one intercept operation
type InterceptInfo struct {
	Name       string // Name of the intercept (user/logging)
	Namespace  string // Name in which to create the Intercept mapping
	Deployment string // Name of the deployment being intercepted
	Prefix     string // Prefix to intercept (default /)
	Patterns   map[string]string
	TargetHost string
	TargetPort int
}

// path returns the URL path for this intercept
func (ii *InterceptInfo) path() string {
	return fmt.Sprintf("intercept/%s/%s", ii.Namespace, ii.Deployment)
}

// PreviewURL returns the Service Preview URL for this intercept if it is
// configured appropriately, or the empty string otherwise.
func (ii *InterceptInfo) PreviewURL(hostname string) (url string) {
	if len(ii.Patterns) != 1 {
		return
	}

	for header, token := range ii.Patterns {
		if strings.ToLower(header) != "x-service-preview" {
			return
		}
		url = fmt.Sprintf("https://%s/.ambassador/service-preview/%s/", hostname, token)
	}

	return
}

// Acquire an intercept from the traffic manager
func (ii *InterceptInfo) Acquire(_ *supervisor.Process, tm *TrafficManager) (int, error) {
	reqPatterns := make([]map[string]string, 0, len(ii.Patterns))
	for header, regex := range ii.Patterns {
		pattern := map[string]string{"name": header, "regex_match": regex}
		reqPatterns = append(reqPatterns, pattern)
	}
	request := map[string]interface{}{
		"name":     ii.Name,
		"patterns": reqPatterns,
	}
	reqData, err := json.Marshal(request)
	if err != nil {
		return 0, err
	}

	result, code, err := tm.request("POST", ii.path(), reqData)
	if err != nil {
		return 0, errors.Wrap(err, "acquire intercept")
	}
	if code == 404 {
		return 0, fmt.Errorf("deployment %q is not known to the traffic manager", ii.Deployment)
	}
	if !(200 <= code && code <= 299) {
		return 0, fmt.Errorf("acquire intercept: %s: %s", http.StatusText(code), result)
	}
	port, err := strconv.Atoi(result)
	if err != nil {
		return 0, errors.Wrapf(err, "bad port number from traffic manager: %q", result)
	}
	return port, nil
}

// Retain the given intercept. This likely needs to be called every
// five seconds or so.
func (ii *InterceptInfo) Retain(_ *supervisor.Process, tm *TrafficManager, port int) error {
	data := []byte(fmt.Sprintf("{\"port\": %d}", port))
	result, code, err := tm.request("POST", ii.path(), data)
	if err != nil {
		return errors.Wrap(err, "retain intercept")
	}
	if !(200 <= code && code <= 299) {
		return fmt.Errorf("retain intercept: %s: %s", http.StatusText(code), result)
	}
	return nil
}

// Release the given intercept.
func (ii *InterceptInfo) Release(_ *supervisor.Process, tm *TrafficManager, port int) error {
	data := []byte(fmt.Sprintf("%d", port))
	result, code, err := tm.request("DELETE", ii.path(), data)
	if err != nil {
		return errors.Wrap(err, "release intercept")
	}
	if !(200 <= code && code <= 299) {
		return fmt.Errorf("release intercept: %s: %s", http.StatusText(code), result)
	}
	return nil
}

// ListIntercepts lists active intercepts
func (d *Daemon) ListIntercepts(_ *supervisor.Process, out *Emitter) error {
	msg := d.interceptMessage()
	if msg != "" {
		out.Println(msg)
		out.Send("intercept", msg)
		return nil
	}
	var previewURL string
	for idx, cept := range d.intercepts {
		ii := cept.ii
		url := ii.PreviewURL("$EDGE")
		out.Printf("%4d. %s\n", idx+1, ii.Name)
		if url != "" {
			previewURL = url
			out.Println("      (preview URL available)")
		}
		out.Send(fmt.Sprintf("local_intercept.%d", idx+1), ii.Name)
		key := "local_intercept." + ii.Name
		out.Printf("      Intercepting requests to %s when\n", ii.Deployment)
		out.Send(key, ii.Deployment)
		for k, v := range ii.Patterns {
			out.Printf("      - %s: %s\n", k, v)
			out.Send(key+"."+k, v)
		}
		out.Printf("      and redirecting them to %s:%d\n", ii.TargetHost, ii.TargetPort)
		out.Send(key+".host", ii.TargetHost)
		out.Send(key+".port", ii.TargetPort)
	}
	if previewURL != "" {
		out.Println("Share a preview of your changes with anyone by visiting\n  ", previewURL)
	}
	if len(d.intercepts) == 0 {
		out.Println("No intercepts")
	}
	return nil
}

// AddIntercept adds one intercept
func (d *Daemon) AddIntercept(p *supervisor.Process, out *Emitter, ii *InterceptInfo) error {
	msg := d.interceptMessage()
	if msg != "" {
		out.Println(msg)
		out.Send("intercept", msg)
		return nil
	}

	for _, cept := range d.intercepts {
		if cept.ii.Name == ii.Name {
			out.Printf("Intercept with name %q already exists\n", ii.Name)
			out.Send("failed", "intercept name exists")
			out.SendExit(1)
			return nil
		}
	}

	// Do we already have a namespace?
	if ii.Namespace == "" {
		// Nope. See if we have an interceptable that matches the name.

		matches := make([]InterceptInfo, 0)
		for _, deployment := range d.trafficMgr.interceptables {
			fields := strings.SplitN(deployment, "/", 2)

			appName := fields[0]
			appNamespace := d.cluster.namespace

			if len(fields) > 1 {
				appNamespace = fields[0]
				appName = fields[1]
			}

			if ii.Deployment == appName {
				// Abuse InterceptInfo rather than defining a new tuple type.
				matches = append(matches, InterceptInfo{"", appNamespace, appName, "", nil, "", 0})
			}
		}

		switch len(matches) {
		case 0:
			out.Printf("No interceptable deployment matching %s found\n", ii.Deployment)
			out.Send("failed", "no interceptable deployment matches")
			out.SendExit(1)
			return nil

		case 1:
			// Good to go.
			ii.Namespace = matches[0].Namespace
			out.Printf("Using deployment %s in namespace %s\n", ii.Deployment, ii.Namespace)

		default:
			out.Printf("Found more than one possible match:\n")

			for idx, match := range matches {
				out.Printf("%4d: %s in namespace %s\n", idx+1, match.Deployment, match.Namespace)
			}
			out.Send("failed", "multiple interceptable deployment matched")
			out.SendExit(1)
			return nil
		}
	}

	cept, err := MakeIntercept(p, out, d.trafficMgr, d.cluster, ii)
	if err != nil {
		out.Printf("Failed to establish intercept: %s\n", err)
		out.Send("failed", err.Error())
		out.SendExit(1)
		return nil
	}
	d.intercepts = append(d.intercepts, cept)
	out.Printf("Added intercept %q\n", ii.Name)
	return nil
}

// RemoveIntercept removes one intercept by name
func (d *Daemon) RemoveIntercept(p *supervisor.Process, out *Emitter, name string) error {
	msg := d.interceptMessage()
	for idx, cept := range d.intercepts {
		if cept.ii.Name == name {
			d.intercepts = append(d.intercepts[:idx], d.intercepts[idx+1:]...)

			out.Printf("Removed intercept %q\n", name)

			if err := cept.Close(); err != nil {
				out.Printf("Error while removing intercept: %v\n", err)
				out.Send("failed", err.Error())
				out.SendExit(1)
			}
			return nil
		}
	}
	if msg != "" {
		out.Println(msg)
		out.Send("intercept", msg)
		return nil
	}
	out.Printf("Intercept named %q not found\n", name)
	out.Send("failed", "not found")
	out.SendExit(1)
	return nil
}

// ClearIntercepts removes all intercepts
func (d *Daemon) ClearIntercepts(p *supervisor.Process) error {
	for _, cept := range d.intercepts {
		if err := cept.Close(); err != nil {
			p.Logf("Closing intercept %q: %v", cept.ii.Name, err)
		}
	}
	d.intercepts = d.intercepts[:0]
	return nil
}

// Intercept is a Resource handle that represents a live intercept
type Intercept struct {
	ii            *InterceptInfo
	tm            *TrafficManager
	cluster       *KCluster
	port          int
	crc           Resource
	mappingExists bool
	ResourceBase
}

// removeMapping drops an Intercept's mapping if needed (and possible).
func (cept *Intercept) removeMapping(p *supervisor.Process) error {
	var err error
	err = nil

	if cept.mappingExists {
		p.Logf("%v: Deleting mapping in namespace %v", cept.ii.Name, cept.ii.Namespace)
		delete := cept.cluster.GetKubectlCmd(p, "delete", "-n", cept.ii.Namespace, "mapping", fmt.Sprintf("%s-mapping", cept.ii.Name))
		err = delete.Run()
		p.Logf("%v: Deleted mapping in namespace %v", cept.ii.Name, cept.ii.Namespace)
	}

	if err != nil {
		return errors.Wrap(err, "Intercept: mapping could not be deleted")
	}

	return nil
}

type mappingMetadata struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

type mappingSpec struct {
	AmbassadorID []string          `json:"ambassador_id"`
	Prefix       string            `json:"prefix"`
	Service      string            `json:"service"`
	RegexHeaders map[string]string `json:"regex_headers"`
}

type interceptMapping struct {
	APIVersion string          `json:"apiVersion"`
	Kind       string          `json:"kind"`
	Metadata   mappingMetadata `json:"metadata"`
	Spec       mappingSpec     `json:"spec"`
}

// MakeIntercept acquires an intercept and returns a Resource handle
// for it
func MakeIntercept(p *supervisor.Process, out *Emitter, tm *TrafficManager, cluster *KCluster, ii *InterceptInfo) (*Intercept, error) {
	port, err := ii.Acquire(p, tm)
	if err != nil {
		return nil, err
	}

	cept := &Intercept{ii: ii, tm: tm, cluster: cluster, port: port}
	cept.mappingExists = false
	cept.doCheck = cept.check
	cept.doQuit = cept.quit
	cept.setup(p.Supervisor(), ii.Name)

	p.Logf("%s: Intercepting via port %v, using namespace %v", ii.Name, port, ii.Namespace)

	mapping := interceptMapping{
		APIVersion: "getambassador.io/v2",
		Kind:       "Mapping",
		Metadata: mappingMetadata{
			Name:      fmt.Sprintf("%s-mapping", ii.Name),
			Namespace: ii.Namespace,
		},
		Spec: mappingSpec{
			AmbassadorID: []string{fmt.Sprintf("intercept-%s", ii.Deployment)},
			Prefix:       ii.Prefix,
			Service:      fmt.Sprintf("telepresence-proxy.%s:%d", tm.namespace, port),
			RegexHeaders: ii.Patterns,
		},
	}

	manifest, err := json.MarshalIndent(&mapping, "", "  ")
	if err != nil {
		_ = cept.Close()
		return nil, errors.Wrap(err, "Intercept: mapping could not be constructed")
	}

	out.Printf("%s: applying intercept mapping in namespace %s\n", ii.Name, ii.Namespace)

	apply := cluster.GetKubectlCmdNoNamespace(p, "apply", "-f", "-")
	apply.Stdin = strings.NewReader(string(manifest))
	err = apply.Run()

	if err != nil {
		p.Logf("%v: Intercept could not apply mapping: %v", ii.Name, err)
		_ = cept.Close()
		return nil, errors.Wrap(err, "Intercept: kubectl apply")
	}

	cept.mappingExists = true

	sshCmd := []string{
		"ssh", "-C", "-N", "telepresence@localhost",
		"-oConnectTimeout=10", "-oExitOnForwardFailure=yes",
		"-oStrictHostKeyChecking=no", "-oUserKnownHostsFile=/dev/null",
		"-p", strconv.Itoa(tm.sshPort),
		"-R", fmt.Sprintf("%d:%s:%d", cept.port, ii.TargetHost, ii.TargetPort),
	}

	p.Logf("%s: starting SSH tunnel", ii.Name)
	out.Printf("%s: starting SSH tunnel\n", ii.Name)

	ssh, err := CheckedRetryingCommand(p, ii.Name+"-ssh", sshCmd, nil, nil, 5*time.Second)
	if err != nil {
		_ = cept.Close()
		return nil, err
	}

	cept.crc = ssh

	return cept, nil
}

func (cept *Intercept) check(p *supervisor.Process) error {
	return cept.ii.Retain(p, cept.tm, cept.port)
}

func (cept *Intercept) quit(p *supervisor.Process) error {
	cept.done = true

	p.Logf("cept.Quit removing %v", cept.ii.Name)

	if err := cept.removeMapping(p); err != nil {
		p.Logf("cept.Quit failed to remove %v: %+v", cept.ii.Name, err)
	} else {
		p.Logf("cept.Quit removed %v", cept.ii.Name)
	}

	if cept.crc != nil {
		_ = cept.crc.Close()
	}

	p.Logf("cept.Quit releasing %v", cept.ii.Name)

	if err := cept.ii.Release(p, cept.tm, cept.port); err != nil {
		p.Log(err)
	}

	p.Logf("cept.Quit released %v", cept.ii.Name)

	return nil
}
<|endoftext|>


<commit_before>package web

import (
	"errors"
	"net/http"
	"regexp"
	"strings"

	"github.com/micro/go-micro/v2/api/resolver"
	res "github.com/micro/go-micro/v2/api/resolver"
	"github.com/micro/go-micro/v2/registry"
	"github.com/micro/go-micro/v2/router"
	// "github.com/micro/go-micro/v2/selector"
)

var re = regexp.MustCompile("^[a-zA-Z0-9]+([a-zA-Z0-9-]*[a-zA-Z0-9]*)?$")

type Resolver struct {
	// Options
	Options resolver.Options
	// selector to choose from a pool of nodes
	// Selector selector.Selector
	// router to lookup routes
	Router router.Router
}

func (r *Resolver) String() string {
	return "web/resolver"
}

// Resolve replaces the values of Host, Path, Scheme to calla backend service
// It accounts for subdomains for service names based on namespace
func (r *Resolver) Resolve(req *http.Request, opts ...res.ResolveOption) (*res.Endpoint, error) {
	// parse the options
	options := resolver.NewResolveOptions(opts...)

	parts := strings.Split(req.URL.Path, "/")
	if len(parts) < 2 {
		return nil, errors.New("unknown service")
	}

	if !re.MatchString(parts[1]) {
		return nil, res.ErrInvalidPath
	}

	// lookup the routes for the service
	query := []router.QueryOption{
		router.QueryService(r.Options.ServicePrefix + "." + parts[1]),
		router.QueryNetwork(options.Domain),
	}
	routes, err := r.Router.Lookup(query...)
	if err != nil {
		return nil, err
	}

	// select the route to use
	// todo: update to use selector once go-micro has updated the interface
	// route, err := r.Selector.Select(routes...)
	// if err != nil {
	// 	return nil, err
	// }
	route := routes[0]

	// we're done
	return &res.Endpoint{
		Name:   parts[1],
		Method: req.Method,
		Host:   route.Address,
		Path:   "/" + strings.Join(parts[2:], "/"),
		Domain: registry.DefaultDomain,
	}, nil
}
<commit_msg>internal/resolve/web: transform router not found error<commit_after>package web

import (
	"errors"
	"net/http"
	"regexp"
	"strings"

	"github.com/micro/go-micro/v2/api/resolver"
	res "github.com/micro/go-micro/v2/api/resolver"
	"github.com/micro/go-micro/v2/registry"
	"github.com/micro/go-micro/v2/router"
	// "github.com/micro/go-micro/v2/selector"
)

var re = regexp.MustCompile("^[a-zA-Z0-9]+([a-zA-Z0-9-]*[a-zA-Z0-9]*)?$")

type Resolver struct {
	// Options
	Options resolver.Options
	// selector to choose from a pool of nodes
	// Selector selector.Selector
	// router to lookup routes
	Router router.Router
}

func (r *Resolver) String() string {
	return "web/resolver"
}

// Resolve replaces the values of Host, Path, Scheme to calla backend service
// It accounts for subdomains for service names based on namespace
func (r *Resolver) Resolve(req *http.Request, opts ...res.ResolveOption) (*res.Endpoint, error) {
	// parse the options
	options := resolver.NewResolveOptions(opts...)

	parts := strings.Split(req.URL.Path, "/")
	if len(parts) < 2 {
		return nil, errors.New("unknown service")
	}

	if !re.MatchString(parts[1]) {
		return nil, res.ErrInvalidPath
	}

	// lookup the routes for the service
	query := []router.QueryOption{
		router.QueryService(r.Options.ServicePrefix + "." + parts[1]),
		router.QueryNetwork(options.Domain),
	}

	routes, err := r.Router.Lookup(query...)
	if err == router.ErrRouteNotFound {
		return nil, res.ErrNotFound
	} else if err != nil {
		return nil, err
	} else if len(routes) == 0 {
		return nil, res.ErrNotFound
	}

	// select the route to use
	// todo: update to use selector once go-micro has updated the interface
	// route, err := r.Selector.Select(routes...)
	// if err != nil {
	// 	return nil, err
	// }
	route := routes[0]

	// we're done
	return &res.Endpoint{
		Name:   parts[1],
		Method: req.Method,
		Host:   route.Address,
		Path:   "/" + strings.Join(parts[2:], "/"),
		Domain: registry.DefaultDomain,
	}, nil
}
<|endoftext|>


<commit_before>package locker

import "time"

// Lock service client
type Client struct {
	Store Store
}

// Get the value of a lock, returns LockNotFound if the lock doesn't exist
func (c Client) Get(service string) (string, error) {
	return c.Store.Get(service)
}

// Lock a key and set its value
func (c Client) Lock(service, host string, stateChanges chan<- StateChange, quit <-chan bool) {
	lastState := StateChange{}
	tick := time.Tick(time.Second * 3)

	for {
		select {
		case <-quit:
			return
		case <-tick:
			state := c.updateNode(service, host)

			if stateChanges != nil && lastState.State != state.State {
				stateChanges <- state
			}

			lastState = state
		}
	}
}

// Update the lock node in the cluster.
func (c Client) updateNode(service, value string) StateChange {
	if err := c.Store.AcquireOrFreshenLock(service, value); err != nil {
		if _, ok := err.(LockDenied); ok {
			return StateChange{Lost, time.Now(), nil}
		}

		// no idea what just happened, just return the error
		return StateChange{Error, time.Now(), err}
	}

	return StateChange{Won, time.Now(), nil}
}

// A backing store for the lock service. Needs to be able to support
// querying and an atomic compare-and-swap.
type Store interface {
	// Get the value of a lock, returns LockNotFound if the lock doesn't exist
	Get(service string) (string, error)

	// Creates a lock entry if it doesn't exist, or refreshes it if it does
	AcquireOrFreshenLock(service, value string) error
}
<commit_msg>EtcdNew shorthand<commit_after>package locker

import (
	"github.com/coreos/go-etcd/etcd"
	"time"
)

// Lock service client
type Client struct {
	Store Store
}

// Creates a new client using Etcd as a store
func EtcdNew(etcdclient *etcd.Client) Client {
	return Client{Store: EtcdStore{etcdclient}}
}

// Get the value of a lock, returns LockNotFound if the lock doesn't exist
func (c Client) Get(service string) (string, error) {
	return c.Store.Get(service)
}

// Lock a key and set its value
func (c Client) Lock(service, host string, stateChanges chan<- StateChange, quit <-chan bool) {
	lastState := StateChange{}
	tick := time.Tick(time.Second * 3)

	for {
		select {
		case <-quit:
			return
		case <-tick:
			state := c.updateNode(service, host)

			if stateChanges != nil && lastState.State != state.State {
				stateChanges <- state
			}

			lastState = state
		}
	}
}

// Update the lock node in the cluster.
func (c Client) updateNode(service, value string) StateChange {
	if err := c.Store.AcquireOrFreshenLock(service, value); err != nil {
		if _, ok := err.(LockDenied); ok {
			return StateChange{Lost, time.Now(), nil}
		}

		// no idea what just happened, just return the error
		return StateChange{Error, time.Now(), err}
	}

	return StateChange{Won, time.Now(), nil}
}

// A backing store for the lock service. Needs to be able to support
// querying and an atomic compare-and-swap.
type Store interface {
	// Get the value of a lock, returns LockNotFound if the lock doesn't exist
	Get(service string) (string, error)

	// Creates a lock entry if it doesn't exist, or refreshes it if it does
	AcquireOrFreshenLock(service, value string) error
}
<|endoftext|>


<commit_before>package Url

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
)

type serverResponse struct {
	Name    string
	Id      string `json:"id"`
	Kind    string `json:"kind"`
	LongUrl string `json:"longUrl"`
}

const (
	serverURL = "https://www.googleapis.com/urlshortener/v1/url"
)

func Request(longUrl string, ws *sync.WaitGroup) {
	data := new(serverResponse)
	data.Name = longUrl
	body := bytes.NewBufferString(fmt.Sprintf(`{"longUrl":"%s"}`, longUrl))
	client := new(http.Client)
	req, _ := http.NewRequest("POST", serverURL, body)
	req.Header.Add("Content-Type", "application/json")
	res, err := client.Do(req)
	if err != nil {
		log.Fatal("Could not do request to server")
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Fatal("No response from server. URL: " + longUrl)
	}
	resbody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatal("Could not read response")
	}
	err = json.Unmarshal(resbody, data)
	if err != nil {
		log.Fatal("could not unmarshal json")
	}
	fmt.Println(data.Name + " -> " + data.Id)
	//we are done here...
	ws.Done()
}
<commit_msg>removed crap from struct<commit_after>package Url

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
)

type serverResponse struct {
	Name string
	Id   string `json:"id"`
}

const (
	serverURL = "https://www.googleapis.com/urlshortener/v1/url"
)

func Request(longUrl string, ws *sync.WaitGroup) {
	data := new(serverResponse)
	data.Name = longUrl
	body := bytes.NewBufferString(fmt.Sprintf(`{"longUrl":"%s"}`, longUrl))
	client := new(http.Client)
	req, _ := http.NewRequest("POST", serverURL, body)
	req.Header.Add("Content-Type", "application/json")
	res, err := client.Do(req)
	if err != nil {
		log.Fatal("Could not do request to server")
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Fatal("No response from server. URL: " + longUrl)
	}
	resbody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatal("Could not read response")
	}
	err = json.Unmarshal(resbody, data)
	if err != nil {
		log.Fatal("could not unmarshal json")
	}
	fmt.Println(data.Name + " -> " + data.Id)
	//we are done here...
	ws.Done()
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/subtle"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/nbari/violetear"
	"github.com/nbari/violetear/middleware"
	"github.com/prometheus/client_golang/prometheus"
)

func index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "index %s!", r.URL.Path)
}
func foo(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "foo %s!", r.URL.Path)
}
func bar(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "bar %s!", r.URL.Path)
}

// BasicAuth secure /metrics endpoint by using the defined username and password
func BasicAuth(next http.Handler, username, password, realm string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, pass, ok := r.BasicAuth()
		if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
			w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func secondMW(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("Second middleware")
		// do something here
		next.ServeHTTP(w, r)
	})
}

func counterMW(c *prometheus.HistogramVec) middleware.Constructor {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()
			log.Println("Executing counter middleware")
			// do something here
			next.ServeHTTP(w, r)
			log.Println("updating prometheus counters")
			c.WithLabelValues(r.URL.Path).Observe(time.Since(start).Seconds())
		})
	}
}

func main() {
	counter := prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "myAPI",
			Name:      "requests_total",
			Help:      "Total number of requests.",
		}, []string{"endpoint"})
	prometheus.MustRegister(counter)

	// midleware
	stdChain := middleware.New(counterMW(counter), secondMW)

	router := violetear.New()
	router.Handle("/", stdChain.ThenFunc(index))
	router.Handle("/foo", stdChain.ThenFunc(foo), "GET")
	router.Handle("/bar", stdChain.ThenFunc(bar), "POST")
	router.Handle("/metrics", BasicAuth(prometheus.Handler(),
		"user",
		"password",
		"Please enter username and password"),
	)

	// configure server
	srv := &http.Server{
		Addr:           ":8080",
		Handler:        router,
		ReadTimeout:    5 * time.Second,
		WriteTimeout:   7 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	log.Fatal(srv.ListenAndServe())
}
<commit_msg>endpoint<commit_after>package main

import (
	"crypto/subtle"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/nbari/violetear"
	"github.com/nbari/violetear/middleware"
	"github.com/prometheus/client_golang/prometheus"
)

func index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "index %s!", r.URL.Path)
}
func foo(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "foo %s!", r.URL.Path)
}
func bar(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "bar %s!", r.URL.Path)
}

// BasicAuth secure /metrics endpoint by using the defined username and password
func BasicAuth(next http.Handler, username, password, realm string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, pass, ok := r.BasicAuth()
		if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
			w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func secondMW(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("Second middleware")
		// do something here
		next.ServeHTTP(w, r)
	})
}

func counterMW(c *prometheus.HistogramVec) middleware.Constructor {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()
			log.Println("Executing counter middleware")
			// do something here
			next.ServeHTTP(w, r)
			log.Println("updating prometheus counters")
			endpoint := violetear.GetRouteName(r)
			c.WithLabelValues(endpoint).Observe(time.Since(start).Seconds())
		})
	}
}

func main() {
	counter := prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "myAPI",
			Name:      "requests_total",
			Help:      "Total number of requests.",
		}, []string{"endpoint"})
	prometheus.MustRegister(counter)

	// midleware
	stdChain := middleware.New(counterMW(counter), secondMW)

	router := violetear.New()
	router.Handle("/", stdChain.ThenFunc(index))
	router.Handle("/foo", stdChain.ThenFunc(foo), "GET")
	router.Handle("/bar", stdChain.ThenFunc(bar), "POST")
	router.Handle("/metrics", BasicAuth(prometheus.Handler(),
		"user",
		"password",
		"Please enter username and password"),
	)

	// configure server
	srv := &http.Server{
		Addr:           ":8080",
		Handler:        router,
		ReadTimeout:    5 * time.Second,
		WriteTimeout:   7 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	log.Fatal(srv.ListenAndServe())
}
<|endoftext|>


<commit_before>package logprov

import (
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/scjalliance/resourceful/lease"
)

// Provider provides boltdb-backed lease management.
type Provider struct {
	source lease.Provider
	log    *log.Logger
	mutex  sync.RWMutex // Only locked for checkpointing
}

// New returns a new transaction logging provider.
func New(source lease.Provider, logger *log.Logger) *Provider {
	return &Provider{
		source: source,
		log:    logger,
	}
}

// Close releases any resources consumed by the provider and its source.
func (p *Provider) Close() error {
	return p.source.Close()
}

// ProviderName returns the name of the provider.
func (p *Provider) ProviderName() string {
	return fmt.Sprintf("%s (with logged transactions)", p.source.ProviderName())
}

// LeaseResources returns all of the resources with lease data.
func (p *Provider) LeaseResources() (resources []string, err error) {
	return p.source.LeaseResources()
}

// LeaseView returns the current revision and lease set for the resource.
func (p *Provider) LeaseView(resource string) (revision uint64, leases lease.Set, err error) {
	p.mutex.RLock()
	defer p.mutex.RUnlock()
	return p.source.LeaseView(resource)
}

// LeaseCommit will attempt to apply the operations described in the lease
// transaction.
func (p *Provider) LeaseCommit(tx *lease.Tx) error {
	p.mutex.RLock()
	defer p.mutex.RUnlock()

	err := p.source.LeaseCommit(tx)
	if err == nil {
		p.record(tx)
	}
	return err
}

// Checkpoint will write all of the lease data to the transaction log in a
// checkpoint block.
//
// In order for the checkpoint to obtain a consistent view of the lease data it
// must hold an exclusive lock while the chekcpoint is being performed. All
// other operations on the provider will block until the checkpoint has
// finished.
func (p *Provider) Checkpoint() (err error) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	at := time.Now().UnixNano()

	resources, err := p.source.LeaseResources()
	if err != nil {
		return
	}

	p.log.Printf("CP %v START", at)

	for _, resource := range resources {
		revision, leases, viewErr := p.source.LeaseView(resource)
		if viewErr != nil {
			p.log.Printf("CP %v RESOURCE %s ERR %v", at, resource, err)
		} else {
			p.log.Printf("CP %v RESOURCE %s REV %d", at, resource, revision)
			for _, ls := range leases {
				if ls.Consumptive() {
					p.log.Printf("CP %v LEASE %s %s", at, ls.Subject(), strings.ToUpper(string(ls.Status)))
				}
			}
		}
	}

	p.log.Printf("CP %v END", at)

	return
}

func (p *Provider) record(tx *lease.Tx) {
	for _, op := range tx.Ops() {
		if op.Type == lease.Update && op.UpdateType() == lease.Renew {
			// Don't record renewals
			continue
		}
		if !op.Consumptive() {
			// Don't record operations that don't affect consumption
			continue
		}
		for _, effect := range op.Effects() {
			p.log.Printf("TX %s", effect)
		}
	}
}
<commit_msg>logprov: Checkpoint on startup<commit_after>package logprov

import (
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/scjalliance/resourceful/lease"
)

// Provider provides boltdb-backed lease management.
type Provider struct {
	source lease.Provider
	log    *log.Logger
	mutex  sync.RWMutex // Only locked for checkpointing
}

// New returns a new transaction logging provider.
func New(source lease.Provider, logger *log.Logger) *Provider {
	p := &Provider{
		source: source,
		log:    logger,
	}
	p.Checkpoint()
	return p
}

// Close releases any resources consumed by the provider and its source.
func (p *Provider) Close() error {
	return p.source.Close()
}

// ProviderName returns the name of the provider.
func (p *Provider) ProviderName() string {
	return fmt.Sprintf("%s (with logged transactions)", p.source.ProviderName())
}

// LeaseResources returns all of the resources with lease data.
func (p *Provider) LeaseResources() (resources []string, err error) {
	return p.source.LeaseResources()
}

// LeaseView returns the current revision and lease set for the resource.
func (p *Provider) LeaseView(resource string) (revision uint64, leases lease.Set, err error) {
	p.mutex.RLock()
	defer p.mutex.RUnlock()
	return p.source.LeaseView(resource)
}

// LeaseCommit will attempt to apply the operations described in the lease
// transaction.
func (p *Provider) LeaseCommit(tx *lease.Tx) error {
	p.mutex.RLock()
	defer p.mutex.RUnlock()

	err := p.source.LeaseCommit(tx)
	if err == nil {
		p.record(tx)
	}
	return err
}

// Checkpoint will write all of the lease data to the transaction log in a
// checkpoint block.
//
// In order for the checkpoint to obtain a consistent view of the lease data it
// must hold an exclusive lock while the chekcpoint is being performed. All
// other operations on the provider will block until the checkpoint has
// finished.
func (p *Provider) Checkpoint() (err error) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	at := time.Now().UnixNano()

	resources, err := p.source.LeaseResources()
	if err != nil {
		return
	}

	p.log.Printf("CP %v START", at)

	for _, resource := range resources {
		revision, leases, viewErr := p.source.LeaseView(resource)
		if viewErr != nil {
			p.log.Printf("CP %v RESOURCE %s ERR %v", at, resource, err)
		} else {
			p.log.Printf("CP %v RESOURCE %s REV %d", at, resource, revision)
			for _, ls := range leases {
				if ls.Consumptive() {
					p.log.Printf("CP %v LEASE %s %s", at, ls.Subject(), strings.ToUpper(string(ls.Status)))
				}
			}
		}
	}

	p.log.Printf("CP %v END", at)

	return
}

func (p *Provider) record(tx *lease.Tx) {
	for _, op := range tx.Ops() {
		if op.Type == lease.Update && op.UpdateType() == lease.Renew {
			// Don't record renewals
			continue
		}
		if !op.Consumptive() {
			// Don't record operations that don't affect consumption
			continue
		}
		for _, effect := range op.Effects() {
			p.log.Printf("TX %s", effect)
		}
	}
}
<|endoftext|>


<commit_before>// Package discord implements the OAuth2 protocol for authenticating users through Discord.
// This package can be used as a reference implementation of an OAuth2 provider for Discord.
package discord

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

	"github.com/markbates/goth"
	"golang.org/x/oauth2"

	"fmt"
	"net/http"
)

const (
	authURL      string = "https://discord.com/api/oauth2/authorize"
	tokenURL     string = "https://discord.com/api/oauth2/token"
	userEndpoint string = "https://discord.com/api/users/@me"
)

const (
	// allows /users/@me without email
	ScopeIdentify string = "identify"
	// enables /users/@me to return an email
	ScopeEmail string = "email"
	// allows /users/@me/connections to return linked Twitch and YouTube accounts
	ScopeConnections string = "connections"
	// allows /users/@me/guilds to return basic information about all of a user's guilds
	ScopeGuilds string = "guilds"
	// allows /invites/{invite.id} to be used for joining a user's guild
	ScopeJoinGuild string = "guilds.join"
	// allows your app to join users to a group dm
	ScopeGroupDMjoin string = "gdm.join"
	// for oauth2 bots, this puts the bot in the user's selected guild by default
	ScopeBot string = "bot"
	// 	this generates a webhook that is returned in the oauth token response for authorization code grants
	ScopeWebhook string = "webhook.incoming"
)

// New creates a new Discord provider, and sets up important connection details.
// You should always call `discord.New` to get a new Provider. Never try to create
// one manually.
func New(clientKey string, secret string, callbackURL string, scopes ...string) *Provider {
	p := &Provider{
		ClientKey:    clientKey,
		Secret:       secret,
		CallbackURL:  callbackURL,
		providerName: "discord",
	}
	p.config = newConfig(p, scopes)
	return p
}

// Provider is the implementation of `goth.Provider` for accessing Discord
type Provider struct {
	ClientKey    string
	Secret       string
	CallbackURL  string
	HTTPClient   *http.Client
	config       *oauth2.Config
	providerName string
}

// Name gets the name used to retrieve this provider.
func (p *Provider) Name() string {
	return p.providerName
}

// SetName is to update the name of the provider (needed in case of multiple providers of 1 type)
func (p *Provider) SetName(name string) {
	p.providerName = name
}

func (p *Provider) Client() *http.Client {
	return goth.HTTPClientWithFallBack(p.HTTPClient)
}

// Debug is no-op for the Discord package.
func (p *Provider) Debug(debug bool) {}

// BeginAuth asks Discord for an authentication end-point.
func (p *Provider) BeginAuth(state string) (goth.Session, error) {

	url := p.config.AuthCodeURL(state, oauth2.AccessTypeOnline)

	s := &Session{
		AuthURL: url,
	}
	return s, nil
}

// FetchUser will go to Discord and access basic info about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {

	s := session.(*Session)

	user := goth.User{
		AccessToken:  s.AccessToken,
		Provider:     p.Name(),
		RefreshToken: s.RefreshToken,
		ExpiresAt:    s.ExpiresAt,
	}

	if user.AccessToken == "" {
		// data is not yet retrieved since accessToken is still empty
		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
	}

	req, err := http.NewRequest("GET", userEndpoint, nil)
	if err != nil {
		return user, err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", "Bearer "+s.AccessToken)
	resp, err := p.Client().Do(req)
	if err != nil {
		if resp != nil {
			resp.Body.Close()
		}
		return user, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, resp.StatusCode)
	}

	bits, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return user, err
	}

	err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData)
	if err != nil {
		return user, err
	}

	err = userFromReader(bytes.NewReader(bits), &user)
	if err != nil {
		return user, err
	}

	return user, err
}

func userFromReader(r io.Reader, user *goth.User) error {
	u := struct {
		Name          string `json:"username"`
		Email         string `json:"email"`
		AvatarID      string `json:"avatar"`
		MFAEnabled    bool   `json:"mfa_enabled"`
		Discriminator string `json:"discriminator"`
		Verified      bool   `json:"verified"`
		ID            string `json:"id"`
	}{}

	err := json.NewDecoder(r).Decode(&u)
	if err != nil {
		return err
	}

	user.Name = u.Name
	user.Email = u.Email
	user.AvatarURL = "https://media.discordapp.net/avatars/" + u.ID + "/" + u.AvatarID + ".jpg"
	user.UserID = u.ID

	return nil
}

func newConfig(p *Provider, scopes []string) *oauth2.Config {
	c := &oauth2.Config{
		ClientID:     p.ClientKey,
		ClientSecret: p.Secret,
		RedirectURL:  p.CallbackURL,
		Endpoint: oauth2.Endpoint{
			AuthURL:  authURL,
			TokenURL: tokenURL,
		},
		Scopes: []string{},
	}

	if len(scopes) > 0 {
		for _, scope := range scopes {
			c.Scopes = append(c.Scopes, scope)
		}
	} else {
		c.Scopes = []string{ScopeIdentify}
	}

	return c
}

//RefreshTokenAvailable refresh token is provided by auth provider or not
func (p *Provider) RefreshTokenAvailable() bool {
	return true
}

//RefreshToken get new access token based on the refresh token
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
	token := &oauth2.Token{RefreshToken: refreshToken}
	ts := p.config.TokenSource(oauth2.NoContext, token)
	newToken, err := ts.Token()
	if err != nil {
		return nil, err
	}
	return newToken, err
}
<commit_msg>fix: fix discord avatar url error<commit_after>// Package discord implements the OAuth2 protocol for authenticating users through Discord.
// This package can be used as a reference implementation of an OAuth2 provider for Discord.
package discord

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

	"github.com/markbates/goth"
	"golang.org/x/oauth2"

	"fmt"
	"net/http"
)

const (
	authURL      string = "https://discord.com/api/oauth2/authorize"
	tokenURL     string = "https://discord.com/api/oauth2/token"
	userEndpoint string = "https://discord.com/api/users/@me"
)

const (
	// allows /users/@me without email
	ScopeIdentify string = "identify"
	// enables /users/@me to return an email
	ScopeEmail string = "email"
	// allows /users/@me/connections to return linked Twitch and YouTube accounts
	ScopeConnections string = "connections"
	// allows /users/@me/guilds to return basic information about all of a user's guilds
	ScopeGuilds string = "guilds"
	// allows /invites/{invite.id} to be used for joining a user's guild
	ScopeJoinGuild string = "guilds.join"
	// allows your app to join users to a group dm
	ScopeGroupDMjoin string = "gdm.join"
	// for oauth2 bots, this puts the bot in the user's selected guild by default
	ScopeBot string = "bot"
	// 	this generates a webhook that is returned in the oauth token response for authorization code grants
	ScopeWebhook string = "webhook.incoming"
)

// New creates a new Discord provider, and sets up important connection details.
// You should always call `discord.New` to get a new Provider. Never try to create
// one manually.
func New(clientKey string, secret string, callbackURL string, scopes ...string) *Provider {
	p := &Provider{
		ClientKey:    clientKey,
		Secret:       secret,
		CallbackURL:  callbackURL,
		providerName: "discord",
	}
	p.config = newConfig(p, scopes)
	return p
}

// Provider is the implementation of `goth.Provider` for accessing Discord
type Provider struct {
	ClientKey    string
	Secret       string
	CallbackURL  string
	HTTPClient   *http.Client
	config       *oauth2.Config
	providerName string
}

// Name gets the name used to retrieve this provider.
func (p *Provider) Name() string {
	return p.providerName
}

// SetName is to update the name of the provider (needed in case of multiple providers of 1 type)
func (p *Provider) SetName(name string) {
	p.providerName = name
}

func (p *Provider) Client() *http.Client {
	return goth.HTTPClientWithFallBack(p.HTTPClient)
}

// Debug is no-op for the Discord package.
func (p *Provider) Debug(debug bool) {}

// BeginAuth asks Discord for an authentication end-point.
func (p *Provider) BeginAuth(state string) (goth.Session, error) {

	url := p.config.AuthCodeURL(state, oauth2.AccessTypeOnline)

	s := &Session{
		AuthURL: url,
	}
	return s, nil
}

// FetchUser will go to Discord and access basic info about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {

	s := session.(*Session)

	user := goth.User{
		AccessToken:  s.AccessToken,
		Provider:     p.Name(),
		RefreshToken: s.RefreshToken,
		ExpiresAt:    s.ExpiresAt,
	}

	if user.AccessToken == "" {
		// data is not yet retrieved since accessToken is still empty
		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
	}

	req, err := http.NewRequest("GET", userEndpoint, nil)
	if err != nil {
		return user, err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", "Bearer "+s.AccessToken)
	resp, err := p.Client().Do(req)
	if err != nil {
		if resp != nil {
			resp.Body.Close()
		}
		return user, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, resp.StatusCode)
	}

	bits, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return user, err
	}

	err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData)
	if err != nil {
		return user, err
	}

	err = userFromReader(bytes.NewReader(bits), &user)
	if err != nil {
		return user, err
	}

	return user, err
}

func userFromReader(r io.Reader, user *goth.User) error {
	u := struct {
		Name          string `json:"username"`
		Email         string `json:"email"`
		AvatarID      string `json:"avatar"`
		MFAEnabled    bool   `json:"mfa_enabled"`
		Discriminator string `json:"discriminator"`
		Verified      bool   `json:"verified"`
		ID            string `json:"id"`
	}{}

	err := json.NewDecoder(r).Decode(&u)
	if err != nil {
		return err
	}

	user.Name = u.Name
	user.Email = u.Email
	user.UserID = u.ID
	if u.AvatarID != "" {
		user.AvatarURL = "https://media.discordapp.net/avatars/" + u.ID + "/" + u.AvatarID + ".jpg"
	}

	return nil
}

func newConfig(p *Provider, scopes []string) *oauth2.Config {
	c := &oauth2.Config{
		ClientID:     p.ClientKey,
		ClientSecret: p.Secret,
		RedirectURL:  p.CallbackURL,
		Endpoint: oauth2.Endpoint{
			AuthURL:  authURL,
			TokenURL: tokenURL,
		},
		Scopes: []string{},
	}

	if len(scopes) > 0 {
		for _, scope := range scopes {
			c.Scopes = append(c.Scopes, scope)
		}
	} else {
		c.Scopes = []string{ScopeIdentify}
	}

	return c
}

//RefreshTokenAvailable refresh token is provided by auth provider or not
func (p *Provider) RefreshTokenAvailable() bool {
	return true
}

//RefreshToken get new access token based on the refresh token
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
	token := &oauth2.Token{RefreshToken: refreshToken}
	ts := p.config.TokenSource(oauth2.NoContext, token)
	newToken, err := ts.Token()
	if err != nil {
		return nil, err
	}
	return newToken, err
}
<|endoftext|>


<commit_before>package filters

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"

	cache "github.com/patrickmn/go-cache"
)

var matchCache = cache.New(5*time.Minute, 1*time.Minute)

type matcherT interface {
	setOperator(operator string)
	GetOperator() string
	Compare(valA, valB interface{}) bool
}

type abstractMatcher struct {
	matcherT
	Operator string
}

func (matcher *abstractMatcher) setOperator(operator string) {
	matcher.Operator = operator
}

func (matcher *abstractMatcher) GetOperator() string {
	return matcher.Operator
}

type equalMatcher struct {
	abstractMatcher
}

func (matcher *equalMatcher) Compare(valA, valB interface{}) bool {
	return valA == valB
}

type notEqualMatcher struct {
	abstractMatcher
}

func (matcher *notEqualMatcher) Compare(valA, valB interface{}) bool {
	return valA != valB
}

type moreThanMatcher struct {
	abstractMatcher
}

func (matcher *moreThanMatcher) Compare(valA, valB interface{}) bool {
	if valA == nil || valA == "" || valB == nil || valB == "" {
		return false
	}

	if intA, ok := valA.(int); ok {
		if intB, ok := valB.(int); ok {
			return intA > intB
		}
	}

	if atoiA, err := strconv.Atoi(valA.(string)); err == nil {
		if atoiB, err := strconv.Atoi(valB.(string)); err == nil {
			return atoiA > atoiB
		}
	}

	return string(valA.(string)) > string(valB.(string))
}

type lessThanMatcher struct {
	abstractMatcher
}

func (matcher *lessThanMatcher) Compare(valA, valB interface{}) bool {
	if valA == nil || valA == "" || valB == nil || valB == "" {
		return false
	}

	if intA, ok := valA.(int); ok {
		if intB, ok := valB.(int); ok {
			return intA < intB
		}
	}

	if atoiA, err := strconv.Atoi(valA.(string)); err == nil {
		if atoiB, err := strconv.Atoi(valB.(string)); err == nil {
			return atoiA < atoiB
		}
	}

	return string(valA.(string)) < string(valB.(string))
}

type regexpMatcher struct {
	abstractMatcher
}

func (matcher *regexpMatcher) Compare(valA, valB interface{}) bool {
	r, found := matchCache.Get(valB.(string))
	if !found {
		var err error
		r, err = regexp.Compile("(?i)" + valB.(string))
		if err != nil {
			return false
		}
		matchCache.Set(valB.(string), r, 1*time.Minute)
	}
	return r.(*regexp.Regexp).MatchString(valA.(string))
}

type negativeRegexMatcher struct {
	abstractMatcher
}

func (matcher *negativeRegexMatcher) Compare(valA, valB interface{}) bool {
	r := regexpMatcher{}
	return !r.Compare(valA, valB)
}

func newMatcher(matchType string) (matcherT, error) {
	if m, found := matcherConfig[matchType]; found {
		return m, nil
	}
	e := fmt.Sprintf("%s not matched with any know match type", matchType)
	return nil, errors.New(e)
}
<commit_msg>Remove unused code<commit_after>package filters

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"

	cache "github.com/patrickmn/go-cache"
)

var matchCache = cache.New(5*time.Minute, 1*time.Minute)

type matcherT interface {
	setOperator(operator string)
	GetOperator() string
	Compare(valA, valB interface{}) bool
}

type abstractMatcher struct {
	matcherT
	Operator string
}

func (matcher *abstractMatcher) GetOperator() string {
	return matcher.Operator
}

type equalMatcher struct {
	abstractMatcher
}

func (matcher *equalMatcher) Compare(valA, valB interface{}) bool {
	return valA == valB
}

type notEqualMatcher struct {
	abstractMatcher
}

func (matcher *notEqualMatcher) Compare(valA, valB interface{}) bool {
	return valA != valB
}

type moreThanMatcher struct {
	abstractMatcher
}

func (matcher *moreThanMatcher) Compare(valA, valB interface{}) bool {
	if valA == nil || valA == "" || valB == nil || valB == "" {
		return false
	}

	if intA, ok := valA.(int); ok {
		if intB, ok := valB.(int); ok {
			return intA > intB
		}
	}

	if atoiA, err := strconv.Atoi(valA.(string)); err == nil {
		if atoiB, err := strconv.Atoi(valB.(string)); err == nil {
			return atoiA > atoiB
		}
	}

	return string(valA.(string)) > string(valB.(string))
}

type lessThanMatcher struct {
	abstractMatcher
}

func (matcher *lessThanMatcher) Compare(valA, valB interface{}) bool {
	if valA == nil || valA == "" || valB == nil || valB == "" {
		return false
	}

	if intA, ok := valA.(int); ok {
		if intB, ok := valB.(int); ok {
			return intA < intB
		}
	}

	if atoiA, err := strconv.Atoi(valA.(string)); err == nil {
		if atoiB, err := strconv.Atoi(valB.(string)); err == nil {
			return atoiA < atoiB
		}
	}

	return string(valA.(string)) < string(valB.(string))
}

type regexpMatcher struct {
	abstractMatcher
}

func (matcher *regexpMatcher) Compare(valA, valB interface{}) bool {
	r, found := matchCache.Get(valB.(string))
	if !found {
		var err error
		r, err = regexp.Compile("(?i)" + valB.(string))
		if err != nil {
			return false
		}
		matchCache.Set(valB.(string), r, 1*time.Minute)
	}
	return r.(*regexp.Regexp).MatchString(valA.(string))
}

type negativeRegexMatcher struct {
	abstractMatcher
}

func (matcher *negativeRegexMatcher) Compare(valA, valB interface{}) bool {
	r := regexpMatcher{}
	return !r.Compare(valA, valB)
}

func newMatcher(matchType string) (matcherT, error) {
	if m, found := matcherConfig[matchType]; found {
		return m, nil
	}
	e := fmt.Sprintf("%s not matched with any know match type", matchType)
	return nil, errors.New(e)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"log"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models/devices"
	"github.com/stampzilla/stampzilla-go/pkg/node"
)

// MAIN - This is run when the init function is done
func main() {
	node := node.New("modbus")
	config := NewConfig()

	wait := node.WaitForFirstConfig()

	err := node.Connect()
	if err != nil {
		logrus.Error(err)
		return
	}
	logrus.Info("Waiting for config from server")
	err = wait()
	if err != nil {
		logrus.Error(err)
		return
	}

	modbusConnection := &Modbus{}
	logrus.Infof("Connecting to modbus device: %s", config.Device)
	err = modbusConnection.Connect()

	if err != nil {
		logrus.Error("error connecting to modbus: ", err)
		return
	}

	defer modbusConnection.Close()

	results, _ := modbusConnection.ReadInputRegister(214)
	log.Println("REG_HC_TEMP_IN1: ", results)
	results, _ = modbusConnection.ReadInputRegister(215)
	log.Println("REG_HC_TEMP_IN2: ", results)
	results, _ = modbusConnection.ReadInputRegister(216)
	log.Println("REG_HC_TEMP_IN3: ", results)
	results, _ = modbusConnection.ReadInputRegister(217)
	log.Println("REG_HC_TEMP_IN4: ", results)
	results, _ = modbusConnection.ReadInputRegister(218)
	log.Println("REG_HC_TEMP_IN5: ", binary.BigEndian.Uint16(results))
	results, _ = modbusConnection.ReadInputRegister(207)
	log.Println("REG_HC_TEMP_LVL: ", results)
	results, _ = modbusConnection.ReadInputRegister(301)
	log.Println("REG_DAMPER_PWM: ", results)
	results, _ = modbusConnection.ReadInputRegister(204)
	log.Println("REG_HC_WC_SIGNAL: ", results)
	results, _ = modbusConnection.ReadInputRegister(209)
	log.Println("REG_HC_TEMP_LVL1-5: ", results)
	results, _ = modbusConnection.ReadInputRegister(101)
	log.Println("100 REG_FAN_SPEED_LEVEL: ", results)

	//connection := basenode.Connect()
	dev := &devices.Device{
		Name:   "modbusDevice",
		Type:   "sensor", //TODO if we add modbus write support we need to have another type
		ID:     devices.ID{ID: "1"},
		Online: true,
		Traits: []string{},
		State:  make(devices.State),
	}

	node.AddOrUpdate(dev)

	//node.SetState(registers)

	// This worker recives all incomming commands
	ticker := time.NewTicker(30 * time.Second)
	for {
		select {
		case <-ticker.C:
			if len(config.Registers) == 0 {
				logrus.Info("no configured registers to poll yet")
				continue
			}
			fetchRegisters(config.Registers, modbusConnection)

			for _, v := range config.Registers {
				dev.State[v.Name] = v.Value
			}
			node.SyncDevices()
		case <-node.Stopped():
			ticker.Stop()
			log.Println("Stopping modbus node")
			return
		}
	}
}

func updatedConfig(config *Config, doOnce func()) func(data json.RawMessage) error {
	return func(data json.RawMessage) error {
		doOnce()
		return json.Unmarshal(data, config)
	}
}

func fetchRegisters(registers Registers, connection *Modbus) {
	for _, v := range registers {

		data, err := connection.ReadInputRegister(v.Id)
		if err != nil {
			/*
				if connection.handler.Logger == nil {
				log.Println("Adding debug logging to handler")
				connection.handler.Logger = log.New(os.Stdout, "modbus-debug: ", log.LstdFlags)
				}
			*/
			logrus.Error(err)
			continue
		}
		if len(data) != 2 {
			logrus.Error("Wrong length, expected 2")
			continue
		}
		if v.Base != 0 {
			v.Value = decode(data) / float64(v.Base)
			continue
		}
		v.Value = decode(data)
	}
}

func decode(data []byte) float64 {
	var i int16
	buf := bytes.NewBuffer(data)
	binary.Read(buf, binary.BigEndian, &i)
	return float64(i)
}
<commit_msg>Fix regression in modbus node after refactoring<commit_after>package main

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"log"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models/devices"
	"github.com/stampzilla/stampzilla-go/pkg/node"
)

// MAIN - This is run when the init function is done
func main() {
	node := node.New("modbus")
	config := NewConfig()

	node.OnConfig(updatedConfig(config))
	wait := node.WaitForFirstConfig()

	err := node.Connect()
	if err != nil {
		logrus.Error(err)
		return
	}
	logrus.Info("Waiting for config from server")
	err = wait()
	if err != nil {
		logrus.Error(err)
		return
	}

	modbusConnection := &Modbus{}
	logrus.Infof("Connecting to modbus device: %s", config.Device)
	err = modbusConnection.Connect()

	if err != nil {
		logrus.Error("error connecting to modbus: ", err)
		return
	}

	defer modbusConnection.Close()

	results, _ := modbusConnection.ReadInputRegister(214)
	log.Println("REG_HC_TEMP_IN1: ", results)
	results, _ = modbusConnection.ReadInputRegister(215)
	log.Println("REG_HC_TEMP_IN2: ", results)
	results, _ = modbusConnection.ReadInputRegister(216)
	log.Println("REG_HC_TEMP_IN3: ", results)
	results, _ = modbusConnection.ReadInputRegister(217)
	log.Println("REG_HC_TEMP_IN4: ", results)
	results, _ = modbusConnection.ReadInputRegister(218)
	log.Println("REG_HC_TEMP_IN5: ", binary.BigEndian.Uint16(results))
	results, _ = modbusConnection.ReadInputRegister(207)
	log.Println("REG_HC_TEMP_LVL: ", results)
	results, _ = modbusConnection.ReadInputRegister(301)
	log.Println("REG_DAMPER_PWM: ", results)
	results, _ = modbusConnection.ReadInputRegister(204)
	log.Println("REG_HC_WC_SIGNAL: ", results)
	results, _ = modbusConnection.ReadInputRegister(209)
	log.Println("REG_HC_TEMP_LVL1-5: ", results)
	results, _ = modbusConnection.ReadInputRegister(101)
	log.Println("100 REG_FAN_SPEED_LEVEL: ", results)

	//connection := basenode.Connect()
	dev := &devices.Device{
		Name:   "modbusDevice",
		Type:   "sensor", //TODO if we add modbus write support we need to have another type
		ID:     devices.ID{ID: "1"},
		Online: true,
		Traits: []string{},
		State:  make(devices.State),
	}

	node.AddOrUpdate(dev)

	//node.SetState(registers)

	// This worker recives all incomming commands
	ticker := time.NewTicker(30 * time.Second)
	for {
		select {
		case <-ticker.C:
			if len(config.Registers) == 0 {
				logrus.Info("no configured registers to poll yet")
				continue
			}
			fetchRegisters(config.Registers, modbusConnection)

			for _, v := range config.Registers {
				dev.State[v.Name] = v.Value
			}
			node.SyncDevices()
		case <-node.Stopped():
			ticker.Stop()
			log.Println("Stopping modbus node")
			return
		}
	}
}

func updatedConfig(config *Config) func(data json.RawMessage) error {
	return func(data json.RawMessage) error {
		return json.Unmarshal(data, config)
	}
}

func fetchRegisters(registers Registers, connection *Modbus) {
	for _, v := range registers {

		data, err := connection.ReadInputRegister(v.Id)
		if err != nil {
			/*
				if connection.handler.Logger == nil {
				log.Println("Adding debug logging to handler")
				connection.handler.Logger = log.New(os.Stdout, "modbus-debug: ", log.LstdFlags)
				}
			*/
			logrus.Error(err)
			continue
		}
		if len(data) != 2 {
			logrus.Error("Wrong length, expected 2")
			continue
		}
		if v.Base != 0 {
			v.Value = decode(data) / float64(v.Base)
			continue
		}
		v.Value = decode(data)
	}
}

func decode(data []byte) float64 {
	var i int16
	buf := bytes.NewBuffer(data)
	binary.Read(buf, binary.BigEndian, &i)
	return float64(i)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"time"

	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/protocol"
)

// MAIN - This is run when the init function is done
func main() {
	log.Println("Starting modbus node")

	// Parse all commandline arguments, host and port parameters are added in the basenode init function
	flag.Parse()

	//Get a config with the correct parameters
	config := basenode.NewConfig()

	//Activate the config
	basenode.SetConfig(config)

	node := protocol.NewNode("modbus")

	registers := NewRegisters()
	registers.ReadFromFile("registers.json")

	modbusConnection := &Modbus{}
	err := modbusConnection.Connect()

	if err != nil {
		log.Println(err)
		return
	}

	defer modbusConnection.Close()

	//REG_HC_TEMP_IN1 214 Reg
	//REG_HC_TEMP_IN2 215 Reg
	//REG_HC_TEMP_IN3 216 Reg
	//REG_HC_TEMP_IN4 217 Reg
	//REG_HC_TEMP_IN5 218 Reg

	//REG_DAMPER_PWM 301 Reg
	//REG_HC_WC_SIGNAL 204 Reg

	//client := modbus.NewClient(handler)
	//modbus.NewClient
	//results, _ := client.ReadHoldingRegisters(214, 1)
	//if err != nil {
	//log.Println(err)
	//}
	results, _ := modbusConnection.ReadInputRegister(214)
	log.Println("REG_HC_TEMP_IN1: ", results)
	results, _ = modbusConnection.ReadInputRegister(215)
	log.Println("REG_HC_TEMP_IN2: ", results)
	results, _ = modbusConnection.ReadInputRegister(216)
	log.Println("REG_HC_TEMP_IN3: ", results)
	results, _ = modbusConnection.ReadInputRegister(217)
	log.Println("REG_HC_TEMP_IN4: ", results)
	results, _ = modbusConnection.ReadInputRegister(218)
	log.Println("REG_HC_TEMP_IN5: ", results)
	results, _ = modbusConnection.ReadInputRegister(207)
	log.Println("REG_HC_TEMP_LVL: ", results)
	results, _ = modbusConnection.ReadInputRegister(301)
	log.Println("REG_DAMPER_PWM: ", results)
	results, _ = modbusConnection.ReadInputRegister(204)
	log.Println("REG_HC_WC_SIGNAL: ", results)
	results, _ = modbusConnection.ReadInputRegister(209)
	log.Println("REG_HC_TEMP_LVL1-5: ", results)
	results, _ = modbusConnection.ReadInputRegister(101)
	log.Println("100 REG_FAN_SPEED_LEVEL: ", results)

	//Start communication with the server
	connection := basenode.Connect()

	// Thit worker keeps track on our connection state, if we are connected or not
	go monitorState(node, connection)

	//node.AddElement(&protocol.Element{
	//Type: protocol.ElementTypeColorPicker,
	//Name: "Example color picker",
	//Command: &protocol.Command{
	//Cmd:  "color",
	//Args: []string{"1"},
	//},
	//Feedback: "Devices[4].State",
	//})

	//state := NewState()
	node.SetState(registers)

	// This worker recives all incomming commands
	go serverRecv(registers, connection, modbusConnection)
	periodicalFetcher(registers, modbusConnection, connection, node)
	select {}
}

func periodicalFetcher(registers *Registers, connection *Modbus, nodeConn *basenode.Connection, node *protocol.Node) chan bool {
	ticker := time.NewTicker(60 * time.Second)
	quit := make(chan bool)
	go func() {
		for {
			select {
			case <-ticker.C:
				fetchRegisters(registers, connection)
				nodeConn.Send <- node.Node()
			case <-quit:
				ticker.Stop()
				return
			}
		}
	}()

	return quit
}

func fetchRegisters(registers *Registers, connection *Modbus) {
	for _, v := range registers.Registers {

		data, err := connection.ReadInputRegister(v.Id)
		if err != nil {
			log.Println(err)
			continue
		}
		if len(data) != 2 {
			log.Println("Wrong length, expected 2")
			continue
		}

		if v.Base != 0 {
			v.Value = float64(int64(data[1]) / v.Base)
			continue
		}
		v.Value = data[1]
	}
}

// WORKER that monitors the current connection state
func monitorState(node *protocol.Node, connection *basenode.Connection) {
	for s := range connection.State {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send <- node.Node()
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(registers *Registers, connection *basenode.Connection, modbusConnection *Modbus) {
	for d := range connection.Receive {
		processCommand(registers, connection, d)
	}
}

// THis is called on each incomming command
func processCommand(registers *Registers, connection *basenode.Connection, cmd protocol.Command) {
	//if s, ok := node.State.(*Registers); ok {
	//log.Println("Incoming command from server:", cmd)
	//if len(cmd.Args) == 0 {
	//return
	//}
	//device := s.Device(cmd.Args[0])

	//switch cmd.Cmd {
	//case "on":
	//device.State = true
	//connection.Send <- node.Node()
	//case "off":
	//device.State = false
	//connection.Send <- node.Node()
	//case "toggle":
	//log.Println("got toggle")
	//if device.State {
	//device.State = false
	//} else {
	//device.State = true
	//}
	//connection.Send <- node.Node()
	//}
	//}
}
<commit_msg>fetch when we start<commit_after>package main

import (
	"flag"
	"log"
	"time"

	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/protocol"
)

// MAIN - This is run when the init function is done
func main() {
	log.Println("Starting modbus node")

	// Parse all commandline arguments, host and port parameters are added in the basenode init function
	flag.Parse()

	//Get a config with the correct parameters
	config := basenode.NewConfig()

	//Activate the config
	basenode.SetConfig(config)

	node := protocol.NewNode("modbus")

	registers := NewRegisters()
	registers.ReadFromFile("registers.json")

	modbusConnection := &Modbus{}
	err := modbusConnection.Connect()

	if err != nil {
		log.Println(err)
		return
	}

	defer modbusConnection.Close()

	//REG_HC_TEMP_IN1 214 Reg
	//REG_HC_TEMP_IN2 215 Reg
	//REG_HC_TEMP_IN3 216 Reg
	//REG_HC_TEMP_IN4 217 Reg
	//REG_HC_TEMP_IN5 218 Reg

	//REG_DAMPER_PWM 301 Reg
	//REG_HC_WC_SIGNAL 204 Reg

	//client := modbus.NewClient(handler)
	//modbus.NewClient
	//results, _ := client.ReadHoldingRegisters(214, 1)
	//if err != nil {
	//log.Println(err)
	//}
	results, _ := modbusConnection.ReadInputRegister(214)
	log.Println("REG_HC_TEMP_IN1: ", results)
	results, _ = modbusConnection.ReadInputRegister(215)
	log.Println("REG_HC_TEMP_IN2: ", results)
	results, _ = modbusConnection.ReadInputRegister(216)
	log.Println("REG_HC_TEMP_IN3: ", results)
	results, _ = modbusConnection.ReadInputRegister(217)
	log.Println("REG_HC_TEMP_IN4: ", results)
	results, _ = modbusConnection.ReadInputRegister(218)
	log.Println("REG_HC_TEMP_IN5: ", results)
	results, _ = modbusConnection.ReadInputRegister(207)
	log.Println("REG_HC_TEMP_LVL: ", results)
	results, _ = modbusConnection.ReadInputRegister(301)
	log.Println("REG_DAMPER_PWM: ", results)
	results, _ = modbusConnection.ReadInputRegister(204)
	log.Println("REG_HC_WC_SIGNAL: ", results)
	results, _ = modbusConnection.ReadInputRegister(209)
	log.Println("REG_HC_TEMP_LVL1-5: ", results)
	results, _ = modbusConnection.ReadInputRegister(101)
	log.Println("100 REG_FAN_SPEED_LEVEL: ", results)

	//Start communication with the server
	connection := basenode.Connect()

	// Thit worker keeps track on our connection state, if we are connected or not
	go monitorState(node, connection)

	//node.AddElement(&protocol.Element{
	//Type: protocol.ElementTypeColorPicker,
	//Name: "Example color picker",
	//Command: &protocol.Command{
	//Cmd:  "color",
	//Args: []string{"1"},
	//},
	//Feedback: "Devices[4].State",
	//})

	//state := NewState()
	node.SetState(registers)

	// This worker recives all incomming commands
	go serverRecv(registers, connection, modbusConnection)
	periodicalFetcher(registers, modbusConnection, connection, node)
	select {}
}

func periodicalFetcher(registers *Registers, connection *Modbus, nodeConn *basenode.Connection, node *protocol.Node) chan bool {

	//Fetch once straight away
	fetchRegisters(registers, connection)

	ticker := time.NewTicker(60 * time.Second)
	quit := make(chan bool)
	go func() {
		for {
			select {
			case <-ticker.C:
				fetchRegisters(registers, connection)
				nodeConn.Send <- node.Node()
			case <-quit:
				ticker.Stop()
				return
			}
		}
	}()

	return quit
}

func fetchRegisters(registers *Registers, connection *Modbus) {
	for _, v := range registers.Registers {

		data, err := connection.ReadInputRegister(v.Id)
		if err != nil {
			log.Println(err)
			continue
		}
		if len(data) != 2 {
			log.Println("Wrong length, expected 2")
			continue
		}

		if v.Base != 0 {
			v.Value = float64(int64(data[1]) / v.Base)
			continue
		}
		v.Value = data[1]
	}
}

// WORKER that monitors the current connection state
func monitorState(node *protocol.Node, connection *basenode.Connection) {
	for s := range connection.State {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send <- node.Node()
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(registers *Registers, connection *basenode.Connection, modbusConnection *Modbus) {
	for d := range connection.Receive {
		processCommand(registers, connection, d)
	}
}

// THis is called on each incomming command
func processCommand(registers *Registers, connection *basenode.Connection, cmd protocol.Command) {
	//if s, ok := node.State.(*Registers); ok {
	//log.Println("Incoming command from server:", cmd)
	//if len(cmd.Args) == 0 {
	//return
	//}
	//device := s.Device(cmd.Args[0])

	//switch cmd.Cmd {
	//case "on":
	//device.State = true
	//connection.Send <- node.Node()
	//case "off":
	//device.State = false
	//connection.Send <- node.Node()
	//case "toggle":
	//log.Println("got toggle")
	//if device.State {
	//device.State = false
	//} else {
	//device.State = true
	//}
	//connection.Send <- node.Node()
	//}
	//}
}
<|endoftext|>