<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 metrics

import (
	"bufio"
	//"fmt"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	utilnet "k8s.io/apimachinery/pkg/util/net"
	//utilruntime "k8s.io/apimachinery/pkg/util/runtime"

	"github.com/emicklei/go-restful"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	// TODO(a-robinson): Add unit tests for the handling of these metrics once
	// the upstream library supports it.
	requestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "apiserver_request_count",
			Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
		},
		[]string{"verb", "resource", "subresource", "scope", "client", "contentType", "code"},
	)
	requestLatencies = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "apiserver_request_latencies",
			Help: "Response latency distribution in microseconds for each verb, resource and subresource.",
			// Use buckets ranging from 125 ms to 8 seconds.
			Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
		},
		[]string{"verb", "resource", "subresource", "scope"},
	)
	requestLatenciesSummary = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name: "apiserver_request_latencies_summary",
			Help: "Response latency summary in microseconds for each verb, resource and subresource.",
			// Make the sliding window of 1h.
			MaxAge: time.Hour,
		},
		[]string{"verb", "resource", "subresource", "scope"},
	)
	responseSizes = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "apiserver_response_sizes",
			Help: "Response size distribution in bytes for each verb, resource, subresource and scope (namespace/cluster).",
			// Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
			Buckets: prometheus.ExponentialBuckets(1000, 10.0, 7),
		},
		[]string{"verb", "resource", "subresource", "scope"},
	)
	kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`)
)

// Register all metrics.
func Register() {
	prometheus.MustRegister(requestCounter)
	prometheus.MustRegister(requestLatencies)
	prometheus.MustRegister(requestLatenciesSummary)
	prometheus.MustRegister(responseSizes)
}

// Monitor records a request to the apiserver endpoints that follow the Kubernetes API conventions.  verb must be
// uppercase to be backwards compatible with existing monitoring tooling.
func Monitor(verb, resource, subresource, scope, client, contentType string, httpCode, respSize int, reqStart time.Time) {
	elapsed := float64((time.Since(reqStart)) / time.Microsecond)
	requestCounter.WithLabelValues(verb, resource, subresource, scope, client, contentType, codeToString(httpCode)).Inc()
	requestLatencies.WithLabelValues(verb, resource, subresource, scope).Observe(elapsed)
	requestLatenciesSummary.WithLabelValues(verb, resource, subresource, scope).Observe(elapsed)
	// We are only interested in response sizes of read requests.
	if verb == "GET" || verb == "LIST" {
		responseSizes.WithLabelValues(verb, resource, subresource, scope).Observe(float64(respSize))
	}
}

// MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record
// a request. verb must be uppercase to be backwards compatible with existing monitoring tooling.
func MonitorRequest(request *http.Request, verb, resource, subresource, scope, contentType string, httpCode, respSize int, reqStart time.Time) {
	reportedVerb := verb
	if verb == "LIST" {
		// see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
		if values := request.URL.Query()["watch"]; len(values) > 0 {
			if value := strings.ToLower(values[0]); value != "0" && value != "false" {
				reportedVerb = "WATCH"
			}
		}
	}
	// normalize the legacy WATCHLIST to WATCH to ensure users aren't surprised by metrics
	if verb == "WATCHLIST" {
		reportedVerb = "WATCH"
	}

	client := cleanUserAgent(utilnet.GetHTTPClient(request))
	Monitor(reportedVerb, resource, subresource, scope, client, contentType, httpCode, respSize, reqStart)
}

func Reset() {
	requestCounter.Reset()
	requestLatencies.Reset()
	requestLatenciesSummary.Reset()
	responseSizes.Reset()
}

// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
// the go-restful RouteFunction instead of a HandlerFunc
func InstrumentRouteFunc(verb, resource, subresource, scope string, routeFunc restful.RouteFunction) restful.RouteFunction {
	return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
		now := time.Now()

		delegate := &ResponseWriterDelegator{ResponseWriter: response.ResponseWriter}

		_, cn := response.ResponseWriter.(http.CloseNotifier)
		_, fl := response.ResponseWriter.(http.Flusher)
		_, hj := response.ResponseWriter.(http.Hijacker)
		var rw http.ResponseWriter
		if cn && fl && hj {
			rw = &fancyResponseWriterDelegator{delegate}
		} else {
			rw = delegate
		}
		response.ResponseWriter = rw

		routeFunc(request, response)

		MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), now)
	})
}

func cleanUserAgent(ua string) string {
	// We collapse all "web browser"-type user agents into one "browser" to reduce metric cardinality.
	if strings.HasPrefix(ua, "Mozilla/") {
		return "Browser"
	}
	// If an old "kubectl.exe" has passed us its full path, we discard the path portion.
	ua = kubectlExeRegexp.ReplaceAllString(ua, "$1")
	return ua
}

// ResponseWriterDelegator interface wraps http.ResponseWriter to additionally record content-length, status-code, etc.
type ResponseWriterDelegator struct {
	http.ResponseWriter

	status      int
	written     int64
	wroteHeader bool
}

func (r *ResponseWriterDelegator) WriteHeader(code int) {
	r.status = code
	r.wroteHeader = true
	r.ResponseWriter.WriteHeader(code)
}

func (r *ResponseWriterDelegator) Write(b []byte) (int, error) {
	if !r.wroteHeader {
		r.WriteHeader(http.StatusOK)
	}
	n, err := r.ResponseWriter.Write(b)
	r.written += int64(n)
	return n, err
}

func (r *ResponseWriterDelegator) Status() int {
	return r.status
}

func (r *ResponseWriterDelegator) ContentLength() int {
	return int(r.written)
}

type fancyResponseWriterDelegator struct {
	*ResponseWriterDelegator
}

func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
	return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

func (f *fancyResponseWriterDelegator) Flush() {
	f.ResponseWriter.(http.Flusher).Flush()
}

func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return f.ResponseWriter.(http.Hijacker).Hijack()
}

// Small optimization over Itoa
func codeToString(s int) string {
	switch s {
	case 100:
		return "100"
	case 101:
		return "101"

	case 200:
		return "200"
	case 201:
		return "201"
	case 202:
		return "202"
	case 203:
		return "203"
	case 204:
		return "204"
	case 205:
		return "205"
	case 206:
		return "206"

	case 300:
		return "300"
	case 301:
		return "301"
	case 302:
		return "302"
	case 304:
		return "304"
	case 305:
		return "305"
	case 307:
		return "307"

	case 400:
		return "400"
	case 401:
		return "401"
	case 402:
		return "402"
	case 403:
		return "403"
	case 404:
		return "404"
	case 405:
		return "405"
	case 406:
		return "406"
	case 407:
		return "407"
	case 408:
		return "408"
	case 409:
		return "409"
	case 410:
		return "410"
	case 411:
		return "411"
	case 412:
		return "412"
	case 413:
		return "413"
	case 414:
		return "414"
	case 415:
		return "415"
	case 416:
		return "416"
	case 417:
		return "417"
	case 418:
		return "418"

	case 500:
		return "500"
	case 501:
		return "501"
	case 502:
		return "502"
	case 503:
		return "503"
	case 504:
		return "504"
	case 505:
		return "505"

	case 428:
		return "428"
	case 429:
		return "429"
	case 431:
		return "431"
	case 511:
		return "511"

	default:
		return strconv.Itoa(s)
	}
}
<commit_msg>Increase sliding window to 5hr for request_latencies metric<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 metrics

import (
	"bufio"
	//"fmt"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	utilnet "k8s.io/apimachinery/pkg/util/net"
	//utilruntime "k8s.io/apimachinery/pkg/util/runtime"

	"github.com/emicklei/go-restful"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	// TODO(a-robinson): Add unit tests for the handling of these metrics once
	// the upstream library supports it.
	requestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "apiserver_request_count",
			Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
		},
		[]string{"verb", "resource", "subresource", "scope", "client", "contentType", "code"},
	)
	requestLatencies = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "apiserver_request_latencies",
			Help: "Response latency distribution in microseconds for each verb, resource and subresource.",
			// Use buckets ranging from 125 ms to 8 seconds.
			Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
		},
		[]string{"verb", "resource", "subresource", "scope"},
	)
	requestLatenciesSummary = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name: "apiserver_request_latencies_summary",
			Help: "Response latency summary in microseconds for each verb, resource and subresource.",
			// Make the sliding window of 5h.
			// TODO: The value for this should be based on our SLI definition (medium term).
			MaxAge: 5 * time.Hour,
		},
		[]string{"verb", "resource", "subresource", "scope"},
	)
	responseSizes = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "apiserver_response_sizes",
			Help: "Response size distribution in bytes for each verb, resource, subresource and scope (namespace/cluster).",
			// Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
			Buckets: prometheus.ExponentialBuckets(1000, 10.0, 7),
		},
		[]string{"verb", "resource", "subresource", "scope"},
	)
	kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`)
)

// Register all metrics.
func Register() {
	prometheus.MustRegister(requestCounter)
	prometheus.MustRegister(requestLatencies)
	prometheus.MustRegister(requestLatenciesSummary)
	prometheus.MustRegister(responseSizes)
}

// Monitor records a request to the apiserver endpoints that follow the Kubernetes API conventions.  verb must be
// uppercase to be backwards compatible with existing monitoring tooling.
func Monitor(verb, resource, subresource, scope, client, contentType string, httpCode, respSize int, reqStart time.Time) {
	elapsed := float64((time.Since(reqStart)) / time.Microsecond)
	requestCounter.WithLabelValues(verb, resource, subresource, scope, client, contentType, codeToString(httpCode)).Inc()
	requestLatencies.WithLabelValues(verb, resource, subresource, scope).Observe(elapsed)
	requestLatenciesSummary.WithLabelValues(verb, resource, subresource, scope).Observe(elapsed)
	// We are only interested in response sizes of read requests.
	if verb == "GET" || verb == "LIST" {
		responseSizes.WithLabelValues(verb, resource, subresource, scope).Observe(float64(respSize))
	}
}

// MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record
// a request. verb must be uppercase to be backwards compatible with existing monitoring tooling.
func MonitorRequest(request *http.Request, verb, resource, subresource, scope, contentType string, httpCode, respSize int, reqStart time.Time) {
	reportedVerb := verb
	if verb == "LIST" {
		// see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
		if values := request.URL.Query()["watch"]; len(values) > 0 {
			if value := strings.ToLower(values[0]); value != "0" && value != "false" {
				reportedVerb = "WATCH"
			}
		}
	}
	// normalize the legacy WATCHLIST to WATCH to ensure users aren't surprised by metrics
	if verb == "WATCHLIST" {
		reportedVerb = "WATCH"
	}

	client := cleanUserAgent(utilnet.GetHTTPClient(request))
	Monitor(reportedVerb, resource, subresource, scope, client, contentType, httpCode, respSize, reqStart)
}

func Reset() {
	requestCounter.Reset()
	requestLatencies.Reset()
	requestLatenciesSummary.Reset()
	responseSizes.Reset()
}

// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
// the go-restful RouteFunction instead of a HandlerFunc
func InstrumentRouteFunc(verb, resource, subresource, scope string, routeFunc restful.RouteFunction) restful.RouteFunction {
	return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
		now := time.Now()

		delegate := &ResponseWriterDelegator{ResponseWriter: response.ResponseWriter}

		_, cn := response.ResponseWriter.(http.CloseNotifier)
		_, fl := response.ResponseWriter.(http.Flusher)
		_, hj := response.ResponseWriter.(http.Hijacker)
		var rw http.ResponseWriter
		if cn && fl && hj {
			rw = &fancyResponseWriterDelegator{delegate}
		} else {
			rw = delegate
		}
		response.ResponseWriter = rw

		routeFunc(request, response)

		MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), now)
	})
}

func cleanUserAgent(ua string) string {
	// We collapse all "web browser"-type user agents into one "browser" to reduce metric cardinality.
	if strings.HasPrefix(ua, "Mozilla/") {
		return "Browser"
	}
	// If an old "kubectl.exe" has passed us its full path, we discard the path portion.
	ua = kubectlExeRegexp.ReplaceAllString(ua, "$1")
	return ua
}

// ResponseWriterDelegator interface wraps http.ResponseWriter to additionally record content-length, status-code, etc.
type ResponseWriterDelegator struct {
	http.ResponseWriter

	status      int
	written     int64
	wroteHeader bool
}

func (r *ResponseWriterDelegator) WriteHeader(code int) {
	r.status = code
	r.wroteHeader = true
	r.ResponseWriter.WriteHeader(code)
}

func (r *ResponseWriterDelegator) Write(b []byte) (int, error) {
	if !r.wroteHeader {
		r.WriteHeader(http.StatusOK)
	}
	n, err := r.ResponseWriter.Write(b)
	r.written += int64(n)
	return n, err
}

func (r *ResponseWriterDelegator) Status() int {
	return r.status
}

func (r *ResponseWriterDelegator) ContentLength() int {
	return int(r.written)
}

type fancyResponseWriterDelegator struct {
	*ResponseWriterDelegator
}

func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
	return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

func (f *fancyResponseWriterDelegator) Flush() {
	f.ResponseWriter.(http.Flusher).Flush()
}

func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return f.ResponseWriter.(http.Hijacker).Hijack()
}

// Small optimization over Itoa
func codeToString(s int) string {
	switch s {
	case 100:
		return "100"
	case 101:
		return "101"

	case 200:
		return "200"
	case 201:
		return "201"
	case 202:
		return "202"
	case 203:
		return "203"
	case 204:
		return "204"
	case 205:
		return "205"
	case 206:
		return "206"

	case 300:
		return "300"
	case 301:
		return "301"
	case 302:
		return "302"
	case 304:
		return "304"
	case 305:
		return "305"
	case 307:
		return "307"

	case 400:
		return "400"
	case 401:
		return "401"
	case 402:
		return "402"
	case 403:
		return "403"
	case 404:
		return "404"
	case 405:
		return "405"
	case 406:
		return "406"
	case 407:
		return "407"
	case 408:
		return "408"
	case 409:
		return "409"
	case 410:
		return "410"
	case 411:
		return "411"
	case 412:
		return "412"
	case 413:
		return "413"
	case 414:
		return "414"
	case 415:
		return "415"
	case 416:
		return "416"
	case 417:
		return "417"
	case 418:
		return "418"

	case 500:
		return "500"
	case 501:
		return "501"
	case 502:
		return "502"
	case 503:
		return "503"
	case 504:
		return "504"
	case 505:
		return "505"

	case 428:
		return "428"
	case 429:
		return "429"
	case 431:
		return "431"
	case 511:
		return "511"

	default:
		return strconv.Itoa(s)
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2019 The Knative 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 ingress

import (
	"context"
	"crypto/tls"
	"fmt"
	"kourier/pkg/config"
	"net"
	"net/http"
	"reflect"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/sirupsen/logrus"

	"knative.dev/serving/pkg/reconciler/ingress/resources"

	"knative.dev/pkg/system"

	"knative.dev/serving/pkg/apis/networking/v1alpha1"

	"go.uber.org/zap"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
	"k8s.io/apimachinery/pkg/util/wait"
	corev1listers "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/util/workqueue"

	"knative.dev/serving/pkg/network/prober"
)

const (
	probeConcurrency          = 5
	probeInterval             = 1 * time.Second
	stateExpiration           = 5 * time.Minute
	cleanupPeriod             = 1 * time.Minute
	gatewayLabelSelectorKey   = "app"
	gatewayLabelSelectorValue = "3scale-kourier-gateway"
)

var dialContext = (&net.Dialer{}).DialContext

// IngressState represents the probing progress at the Ingress scope
type IngressState struct {
	id      string
	ingress *v1alpha1.Ingress
	// pendingCount is the number of pods that haven't been successfully probed yet
	pendingCount int32
	lastAccessed time.Time

	cancel func()
}

// podState represents the probing progress at the Pod scope
type podState struct {
	successCount int32

	context context.Context
	cancel  func()
}

type workItem struct {
	ingressState *IngressState
	podState     *podState
	url          string
	podIP        string
	hostname     string
}

// StatusManager provides a way to check if a VirtualService is ready
type StatusManager interface {
	IsReady(snapshotID string) (bool, error)
}

// StatusProber provides a way to check if a VirtualService is ready by probing the Envoy pods
// handling that VirtualService.
type StatusProber struct {
	logger *zap.SugaredLogger

	// mu guards snapshotStates and podStates
	mu            sync.Mutex
	ingressStates map[string]*IngressState
	podStates     map[string]*podState

	workQueue workqueue.RateLimitingInterface
	podLister corev1listers.PodLister

	readyCallback    func(ingress *v1alpha1.Ingress)
	probeConcurrency int
	stateExpiration  time.Duration
	cleanupPeriod    time.Duration
}

// NewStatusProber creates a new instance of StatusProber
func NewStatusProber(
	logger *zap.SugaredLogger,
	podLister corev1listers.PodLister,
	readyCallback func(ingress *v1alpha1.Ingress)) *StatusProber {
	return &StatusProber{
		logger:        logger,
		ingressStates: make(map[string]*IngressState),
		podStates:     make(map[string]*podState),
		workQueue: workqueue.NewNamedRateLimitingQueue(
			workqueue.DefaultControllerRateLimiter(),
			"ProbingQueue"),
		readyCallback:    readyCallback,
		podLister:        podLister,
		probeConcurrency: probeConcurrency,
		stateExpiration:  stateExpiration,
		cleanupPeriod:    cleanupPeriod,
	}
}

// IsReady checks if the provided Ingress is ready, i.e. the Envoy pods serving the Ingress
// have all been updated. This function is designed to be used by the Ingress controller, i.e. it
// will be called in the order of reconciliation. This means that if IsReady is called on an Ingress,
// this Ingress is the latest known version and therefore anything related to older versions can be ignored.
// Also, it means that IsReady is not called concurrently.
func (m *StatusProber) IsReady(ingress *v1alpha1.Ingress) (bool, error) {

	hash, err := resources.ComputeIngressHash(ingress)
	if err != nil {
		return false, err
	}

	ingressKey := fmt.Sprintf("%x", hash)

	if ready, ok := func() (bool, bool) {
		m.mu.Lock()
		defer m.mu.Unlock()
		if state, ok := m.ingressStates[ingressKey]; ok {
			if state.id == ingressKey {
				state.lastAccessed = time.Now()
				return atomic.LoadInt32(&state.pendingCount) == 0, true
			}

			// Cancel the polling for the outdated version
			state.cancel()
			delete(m.ingressStates, ingressKey)
		}
		return false, false
	}(); ok {
		return ready, nil
	}

	ingCtx, cancel := context.WithCancel(context.Background())
	snapshotState := &IngressState{
		id:           ingressKey,
		ingress:      ingress,
		pendingCount: 0,
		lastAccessed: time.Now(),
		cancel:       cancel,
	}

	var workItems []*workItem
	selector := labels.NewSelector()
	labelSelector, err := labels.NewRequirement(gatewayLabelSelectorKey, selection.Equals, []string{gatewayLabelSelectorValue})
	if err != nil {
		m.logger.Errorf("Failed to create 'Equals' requirement from %q=%q: %w", gatewayLabelSelectorKey, gatewayLabelSelectorValue, err)
	}
	selector = selector.Add(*labelSelector)

	gatewayPods, err := m.podLister.Pods(system.Namespace()).List(selector)
	if err != nil {
		m.logger.Errorf("failed to get gateway pods: %w", err)
	}

	if len(gatewayPods) == 0 {
		return false, nil
	}

	for _, gwPod := range gatewayPods {
		ip := gwPod.Status.PodIP
		ctx, cancel := context.WithCancel(ingCtx)
		podState := &podState{
			successCount: 0,
			context:      ctx,
			cancel:       cancel,
		}
		// Save the podState to be able to cancel it in case of Pod deletion
		func() {
			m.mu.Lock()
			defer m.mu.Unlock()
			m.podStates[ip] = podState
		}()

		// Update states and cleanup m.podStates when probing is done or cancelled
		go func(ip string) {
			<-podState.context.Done()
			m.updateStates(snapshotState, podState)

			m.mu.Lock()
			defer m.mu.Unlock()
			// It is critical to check that the current podState is also the one stored in the map
			// before deleting it because it could have been replaced if a new version of the ingress
			// has started being probed.
			if state, ok := m.podStates[ip]; ok && state == podState {
				delete(m.podStates, ip)
			}
		}(ip)

		port := strconv.Itoa(int(config.HttpPortInternal))

		workItem := &workItem{
			ingressState: snapshotState,
			podState:     podState,
			url:          "http://" + gwPod.Status.PodIP + ":" + port + config.InternalKourierPath + "/" + ingressKey,
			podIP:        ip,
			hostname:     config.InternalKourierDomain,
		}
		workItems = append(workItems, workItem)

	}

	snapshotState.pendingCount += int32(len(gatewayPods))

	func() {
		m.mu.Lock()
		defer m.mu.Unlock()
		m.ingressStates[ingressKey] = snapshotState
	}()
	for _, workItem := range workItems {
		m.workQueue.AddRateLimited(workItem)
		m.logger.Infof("Queuing probe for %s, IP: %s (depth: %d)", workItem.url, workItem.podIP, m.workQueue.Len())
	}
	return len(workItems) == 0, nil
}

// Start starts the StatusManager background operations
func (m *StatusProber) Start(done <-chan struct{}) {
	// Start the worker goroutines
	for i := 0; i < m.probeConcurrency; i++ {
		go func() {
			for m.processWorkItem() {
			}
		}()
	}

	// Cleanup the states periodically
	go wait.Until(m.expireOldStates, m.cleanupPeriod, done)

	// Stop processing the queue when cancelled
	go func() {
		<-done
		m.workQueue.ShutDown()
	}()
}

// CancelIngress cancels probing of the provided Ingress.
func (m *StatusProber) CancelIngress(ingress *v1alpha1.Ingress) {
	m.mu.Lock()
	defer m.mu.Unlock()
	hash, err := resources.ComputeIngressHash(ingress)
	if err != nil {
		logrus.Error("failed to compute ingress Hash: %s", err)
	}
	ingressKey := fmt.Sprintf("%x", hash)
	if state, ok := m.ingressStates[ingressKey]; ok {
		state.cancel()
	}
}

// CancelPodProbing cancels probing of the provided Pod IP.
func (m *StatusProber) CancelPodProbing(pod *corev1.Pod) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if state, ok := m.podStates[pod.Status.PodIP]; ok {
		state.cancel()
	}
}

// expireOldStates removes the states that haven't been accessed in a while.
func (m *StatusProber) expireOldStates() {
	m.mu.Lock()
	defer m.mu.Unlock()
	for key, state := range m.ingressStates {
		if time.Since(state.lastAccessed) > m.stateExpiration {
			state.cancel()
			delete(m.ingressStates, key)
		}
	}
}

// processWorkItem processes a single work item from workQueue.
// It returns false when there is no more items to process, true otherwise.
func (m *StatusProber) processWorkItem() bool {
	obj, shutdown := m.workQueue.Get()
	if shutdown {
		return false
	}

	defer m.workQueue.Done(obj)

	// Crash if the item is not of the expected type
	item, ok := obj.(*workItem)
	if !ok {
		m.logger.Fatalf("Unexpected work item type: want: %s, got: %s\n", reflect.TypeOf(&workItem{}).Name(), reflect.TypeOf(obj).Name())
	}
	m.logger.Infof("Processing probe for %s, IP: %s (depth: %d)", item.url, item.podIP, m.workQueue.Len())

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			// We only want to know that the Gateway is configured, not that the configuration is valid.
			// Therefore, we can safely ignore any TLS certificate validation.
			InsecureSkipVerify: true,
		},
		DisableKeepAlives: true,
		DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
			// Requests with the IP as hostname and the Host header set do no pass client-side validation
			// because the HTTP client validates that the hostname (not the Host header) matches the server
			// TLS certificate Common Name or Alternative Names. Therefore, http.Request.URL is set to the
			// hostname and it is substituted it here with the target IP.
			return dialContext(ctx, network, net.JoinHostPort(item.podIP, strconv.Itoa(int(config.HttpPortInternal))))
		}}

	ok, err := prober.Do(
		item.podState.context,
		transport,
		item.url,
		prober.WithHost(config.InternalKourierDomain),
		prober.ExpectsStatusCodes([]int{http.StatusOK}),
	)

	// In case of cancellation, drop the work item
	select {
	case <-item.podState.context.Done():
		m.workQueue.Forget(obj)
		return true
	default:
	}

	if err != nil || !ok {
		// In case of error, enqueue for retry
		m.workQueue.AddRateLimited(obj)
		m.logger.Errorf("Probing of %s failed, IP: %s, ready: %t, error: %v (depth: %d)", item.url, item.podIP, ok, err, m.workQueue.Len())
	} else {
		m.updateStates(item.ingressState, item.podState)
	}
	return true
}

func (m *StatusProber) updateStates(ingressState *IngressState, podState *podState) {
	if atomic.AddInt32(&podState.successCount, 1) == 1 {
		// This is the first successful probe call for the pod, cancel all other work items for this pod
		podState.cancel()

		// This is the last pod being successfully probed, the Ingress is ready
		if atomic.AddInt32(&ingressState.pendingCount, -1) == 0 {
			m.readyCallback(ingressState.ingress)
		}
	}
}
<commit_msg>fixup: Use internal logger and fix format<commit_after>/*
   Copyright 2019 The Knative 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 ingress

import (
	"context"
	"crypto/tls"
	"fmt"
	"kourier/pkg/config"
	"net"
	"net/http"
	"reflect"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"knative.dev/serving/pkg/reconciler/ingress/resources"

	"knative.dev/pkg/system"

	"knative.dev/serving/pkg/apis/networking/v1alpha1"

	"go.uber.org/zap"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
	"k8s.io/apimachinery/pkg/util/wait"
	corev1listers "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/util/workqueue"

	"knative.dev/serving/pkg/network/prober"
)

const (
	probeConcurrency          = 5
	probeInterval             = 1 * time.Second
	stateExpiration           = 5 * time.Minute
	cleanupPeriod             = 1 * time.Minute
	gatewayLabelSelectorKey   = "app"
	gatewayLabelSelectorValue = "3scale-kourier-gateway"
)

var dialContext = (&net.Dialer{}).DialContext

// IngressState represents the probing progress at the Ingress scope
type IngressState struct {
	id      string
	ingress *v1alpha1.Ingress
	// pendingCount is the number of pods that haven't been successfully probed yet
	pendingCount int32
	lastAccessed time.Time

	cancel func()
}

// podState represents the probing progress at the Pod scope
type podState struct {
	successCount int32

	context context.Context
	cancel  func()
}

type workItem struct {
	ingressState *IngressState
	podState     *podState
	url          string
	podIP        string
	hostname     string
}

// StatusManager provides a way to check if a VirtualService is ready
type StatusManager interface {
	IsReady(snapshotID string) (bool, error)
}

// StatusProber provides a way to check if a VirtualService is ready by probing the Envoy pods
// handling that VirtualService.
type StatusProber struct {
	logger *zap.SugaredLogger

	// mu guards snapshotStates and podStates
	mu            sync.Mutex
	ingressStates map[string]*IngressState
	podStates     map[string]*podState

	workQueue workqueue.RateLimitingInterface
	podLister corev1listers.PodLister

	readyCallback    func(ingress *v1alpha1.Ingress)
	probeConcurrency int
	stateExpiration  time.Duration
	cleanupPeriod    time.Duration
}

// NewStatusProber creates a new instance of StatusProber
func NewStatusProber(
	logger *zap.SugaredLogger,
	podLister corev1listers.PodLister,
	readyCallback func(ingress *v1alpha1.Ingress)) *StatusProber {
	return &StatusProber{
		logger:        logger,
		ingressStates: make(map[string]*IngressState),
		podStates:     make(map[string]*podState),
		workQueue: workqueue.NewNamedRateLimitingQueue(
			workqueue.DefaultControllerRateLimiter(),
			"ProbingQueue"),
		readyCallback:    readyCallback,
		podLister:        podLister,
		probeConcurrency: probeConcurrency,
		stateExpiration:  stateExpiration,
		cleanupPeriod:    cleanupPeriod,
	}
}

// IsReady checks if the provided Ingress is ready, i.e. the Envoy pods serving the Ingress
// have all been updated. This function is designed to be used by the Ingress controller, i.e. it
// will be called in the order of reconciliation. This means that if IsReady is called on an Ingress,
// this Ingress is the latest known version and therefore anything related to older versions can be ignored.
// Also, it means that IsReady is not called concurrently.
func (m *StatusProber) IsReady(ingress *v1alpha1.Ingress) (bool, error) {

	hash, err := resources.ComputeIngressHash(ingress)
	if err != nil {
		return false, err
	}

	ingressKey := fmt.Sprintf("%x", hash)

	if ready, ok := func() (bool, bool) {
		m.mu.Lock()
		defer m.mu.Unlock()
		if state, ok := m.ingressStates[ingressKey]; ok {
			if state.id == ingressKey {
				state.lastAccessed = time.Now()
				return atomic.LoadInt32(&state.pendingCount) == 0, true
			}

			// Cancel the polling for the outdated version
			state.cancel()
			delete(m.ingressStates, ingressKey)
		}
		return false, false
	}(); ok {
		return ready, nil
	}

	ingCtx, cancel := context.WithCancel(context.Background())
	snapshotState := &IngressState{
		id:           ingressKey,
		ingress:      ingress,
		pendingCount: 0,
		lastAccessed: time.Now(),
		cancel:       cancel,
	}

	var workItems []*workItem
	selector := labels.NewSelector()
	labelSelector, err := labels.NewRequirement(gatewayLabelSelectorKey, selection.Equals, []string{gatewayLabelSelectorValue})
	if err != nil {
		m.logger.Errorf("Failed to create 'Equals' requirement from %q=%q: %w", gatewayLabelSelectorKey, gatewayLabelSelectorValue, err)
	}
	selector = selector.Add(*labelSelector)

	gatewayPods, err := m.podLister.Pods(system.Namespace()).List(selector)
	if err != nil {
		m.logger.Errorf("failed to get gateway pods: %w", err)
	}

	if len(gatewayPods) == 0 {
		return false, nil
	}

	for _, gwPod := range gatewayPods {
		ip := gwPod.Status.PodIP
		ctx, cancel := context.WithCancel(ingCtx)
		podState := &podState{
			successCount: 0,
			context:      ctx,
			cancel:       cancel,
		}
		// Save the podState to be able to cancel it in case of Pod deletion
		func() {
			m.mu.Lock()
			defer m.mu.Unlock()
			m.podStates[ip] = podState
		}()

		// Update states and cleanup m.podStates when probing is done or cancelled
		go func(ip string) {
			<-podState.context.Done()
			m.updateStates(snapshotState, podState)

			m.mu.Lock()
			defer m.mu.Unlock()
			// It is critical to check that the current podState is also the one stored in the map
			// before deleting it because it could have been replaced if a new version of the ingress
			// has started being probed.
			if state, ok := m.podStates[ip]; ok && state == podState {
				delete(m.podStates, ip)
			}
		}(ip)

		port := strconv.Itoa(int(config.HttpPortInternal))

		workItem := &workItem{
			ingressState: snapshotState,
			podState:     podState,
			url:          "http://" + gwPod.Status.PodIP + ":" + port + config.InternalKourierPath + "/" + ingressKey,
			podIP:        ip,
			hostname:     config.InternalKourierDomain,
		}
		workItems = append(workItems, workItem)

	}

	snapshotState.pendingCount += int32(len(gatewayPods))

	func() {
		m.mu.Lock()
		defer m.mu.Unlock()
		m.ingressStates[ingressKey] = snapshotState
	}()
	for _, workItem := range workItems {
		m.workQueue.AddRateLimited(workItem)
		m.logger.Infof("Queuing probe for %s, IP: %s (depth: %d)", workItem.url, workItem.podIP, m.workQueue.Len())
	}
	return len(workItems) == 0, nil
}

// Start starts the StatusManager background operations
func (m *StatusProber) Start(done <-chan struct{}) {
	// Start the worker goroutines
	for i := 0; i < m.probeConcurrency; i++ {
		go func() {
			for m.processWorkItem() {
			}
		}()
	}

	// Cleanup the states periodically
	go wait.Until(m.expireOldStates, m.cleanupPeriod, done)

	// Stop processing the queue when cancelled
	go func() {
		<-done
		m.workQueue.ShutDown()
	}()
}

// CancelIngress cancels probing of the provided Ingress.
func (m *StatusProber) CancelIngress(ingress *v1alpha1.Ingress) {
	m.mu.Lock()
	defer m.mu.Unlock()
	hash, err := resources.ComputeIngressHash(ingress)
	if err != nil {
		m.logger.Errorf("failed to compute ingress Hash: %s", err)
	}
	ingressKey := fmt.Sprintf("%x", hash)
	if state, ok := m.ingressStates[ingressKey]; ok {
		state.cancel()
	}
}

// CancelPodProbing cancels probing of the provided Pod IP.
func (m *StatusProber) CancelPodProbing(pod *corev1.Pod) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if state, ok := m.podStates[pod.Status.PodIP]; ok {
		state.cancel()
	}
}

// expireOldStates removes the states that haven't been accessed in a while.
func (m *StatusProber) expireOldStates() {
	m.mu.Lock()
	defer m.mu.Unlock()
	for key, state := range m.ingressStates {
		if time.Since(state.lastAccessed) > m.stateExpiration {
			state.cancel()
			delete(m.ingressStates, key)
		}
	}
}

// processWorkItem processes a single work item from workQueue.
// It returns false when there is no more items to process, true otherwise.
func (m *StatusProber) processWorkItem() bool {
	obj, shutdown := m.workQueue.Get()
	if shutdown {
		return false
	}

	defer m.workQueue.Done(obj)

	// Crash if the item is not of the expected type
	item, ok := obj.(*workItem)
	if !ok {
		m.logger.Fatalf("Unexpected work item type: want: %s, got: %s\n", reflect.TypeOf(&workItem{}).Name(), reflect.TypeOf(obj).Name())
	}
	m.logger.Infof("Processing probe for %s, IP: %s (depth: %d)", item.url, item.podIP, m.workQueue.Len())

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			// We only want to know that the Gateway is configured, not that the configuration is valid.
			// Therefore, we can safely ignore any TLS certificate validation.
			InsecureSkipVerify: true,
		},
		DisableKeepAlives: true,
		DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) {
			// Requests with the IP as hostname and the Host header set do no pass client-side validation
			// because the HTTP client validates that the hostname (not the Host header) matches the server
			// TLS certificate Common Name or Alternative Names. Therefore, http.Request.URL is set to the
			// hostname and it is substituted it here with the target IP.
			return dialContext(ctx, network, net.JoinHostPort(item.podIP, strconv.Itoa(int(config.HttpPortInternal))))
		}}

	ok, err := prober.Do(
		item.podState.context,
		transport,
		item.url,
		prober.WithHost(config.InternalKourierDomain),
		prober.ExpectsStatusCodes([]int{http.StatusOK}),
	)

	// In case of cancellation, drop the work item
	select {
	case <-item.podState.context.Done():
		m.workQueue.Forget(obj)
		return true
	default:
	}

	if err != nil || !ok {
		// In case of error, enqueue for retry
		m.workQueue.AddRateLimited(obj)
		m.logger.Errorf("Probing of %s failed, IP: %s, ready: %t, error: %v (depth: %d)", item.url, item.podIP, ok, err, m.workQueue.Len())
	} else {
		m.updateStates(item.ingressState, item.podState)
	}
	return true
}

func (m *StatusProber) updateStates(ingressState *IngressState, podState *podState) {
	if atomic.AddInt32(&podState.successCount, 1) == 1 {
		// This is the first successful probe call for the pod, cancel all other work items for this pod
		podState.cancel()

		// This is the last pod being successfully probed, the Ingress is ready
		if atomic.AddInt32(&ingressState.pendingCount, -1) == 0 {
			m.readyCallback(ingressState.ingress)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package cache

import (
	"sync"
	"time"

	"github.com/golang/groupcache/lru"
)

type LRUExpireCache struct {
	cache *lru.Cache
	lock  sync.RWMutex
}

func NewLRUExpireCache(maxSize int) *LRUExpireCache {
	return &LRUExpireCache{cache: lru.New(maxSize)}
}

type cacheEntry struct {
	value      interface{}
	expireTime time.Time
}

func (c *LRUExpireCache) Add(key lru.Key, value interface{}, ttl time.Duration) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.cache.Add(key, &cacheEntry{value, time.Now().Add(ttl)})
	// Remove entry from cache after ttl.
	time.AfterFunc(ttl, func() { c.remove(key) })
}

func (c *LRUExpireCache) Get(key lru.Key) (interface{}, bool) {
	c.lock.RLock()
	defer c.lock.RUnlock()
	e, ok := c.cache.Get(key)
	if !ok {
		return nil, false
	}
	if time.Now().After(e.(*cacheEntry).expireTime) {
		go c.remove(key)
		return nil, false
	}
	return e.(*cacheEntry).value, true
}

func (c *LRUExpireCache) remove(key lru.Key) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.cache.Remove(key)
}
<commit_msg>LRUExpireCache#Get requires write lock<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package cache

import (
	"sync"
	"time"

	"github.com/golang/groupcache/lru"
)

type LRUExpireCache struct {
	cache *lru.Cache
	lock  sync.Mutex
}

func NewLRUExpireCache(maxSize int) *LRUExpireCache {
	return &LRUExpireCache{cache: lru.New(maxSize)}
}

type cacheEntry struct {
	value      interface{}
	expireTime time.Time
}

func (c *LRUExpireCache) Add(key lru.Key, value interface{}, ttl time.Duration) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.cache.Add(key, &cacheEntry{value, time.Now().Add(ttl)})
	// Remove entry from cache after ttl.
	time.AfterFunc(ttl, func() { c.remove(key) })
}

func (c *LRUExpireCache) Get(key lru.Key) (interface{}, bool) {
	c.lock.Lock()
	defer c.lock.Unlock()
	e, ok := c.cache.Get(key)
	if !ok {
		return nil, false
	}
	if time.Now().After(e.(*cacheEntry).expireTime) {
		go c.remove(key)
		return nil, false
	}
	return e.(*cacheEntry).value, true
}

func (c *LRUExpireCache) remove(key lru.Key) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.cache.Remove(key)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"code.google.com/p/goauth2/oauth"
	"code.google.com/p/goauth2/oauth/jwt"
	"encoding/gob"
	"encoding/json"
	"net/http"
)

const tokensFile = "~tokens.gob"

func authStart() {
	args := endpointFs.Args()
	if len(args) != 3 {
		fmt.Println("Invalid arguments, must provide API and method, e.g.:")
		fmt.Println("  gapi auth.start <api> <method>")
		return
	}
	api := loadAPI(args[1])
	if api == nil || (len(api.Resources) == 0 && len(api.Methods) == 0) {
		fmt.Println("Couldn't load API ", args[1])
		return
	}
	m := findMethod(args[2], *api)
	if m.Scopes == nil {
		fmt.Println("Method doesn't require auth")
		return
	}
	oauthConfig.Scope = strings.Join(m.Scopes, " ")
	fmt.Println("Open a browser and visit the following URL:")
	fmt.Println(oauthConfig.AuthCodeURL(""))
	fmt.Println("Then run the following command with the resulting auth code:")
	fmt.Println("gapi auth.finish <code>")
}
func authFinish() {
	args := endpointFs.Args()
	if len(args) != 2 {
		fmt.Println("Invalid arguments, must provide code, e.g.:")
		fmt.Println("  gapi auth.finish <code>")
		return
	}
	code := args[1]
	t := &oauth.Transport{Config: oauthConfig}
	tok, err := t.Exchange(code)
	maybeFatal("error exchanging code", err)
	toks, err := loadTokens()
	maybeFatal("error loading tokens", err)
	inf, err := getTokenInfo(tok.AccessToken)
	maybeFatal("error getting tokeninfo", err)
	toks.Tok[inf.Scope] = token{
		AccessToken:  tok.AccessToken,
		RefreshToken: tok.RefreshToken,
	}
	err = toks.save()
	maybeFatal("error saving tokens", err)
	fmt.Println("Token saved")
}
func authPrint() {
	args := endpointFs.Args()
	if len(args) != 3 {
		fmt.Println("Invalid arguments, must provide API and method, e.g.:")
		fmt.Println("  gapi auth.print <api> <method>")
		return
	}
	api := loadAPI(args[1])
	if api == nil || (len(api.Resources) == 0 && len(api.Methods) == 0) {
		log.Fatal("Couldn't load API ", args[1])
	}
	m := findMethod(args[2], *api)
	toks, err := loadTokens()
	maybeFatal("error loading tokens", err)
	if tok, found := toks.Tok[strings.Join(m.Scopes, " ")]; found {
		// TODO: If necessary, refresh, store, and print the new token.
		fmt.Println(tok.AccessToken)
	} else {
		fmt.Println("No token found. Run the following command to store a token:")
		fmt.Println("gapi auth.start", api.Name, m.ID[len(api.Name)+1:])
	}
}
func authRevoke() {
	args := endpointFs.Args()
	if len(args) != 3 {
		fmt.Println("Invalid arguments, must provide API and method, e.g.:")
		fmt.Println("  gapi auth.print <api> <method>")
		return
	}
	api := loadAPI(args[1])
	if api == nil || (len(api.Resources) == 0 && len(api.Methods) == 0) {
		log.Fatal("Couldn't load API ", args[1])
	}
	m := findMethod(args[2], *api)
	toks, err := loadTokens()
	maybeFatal("error loading tokens", err)
	if tok, found := toks.Tok[strings.Join(m.Scopes, " ")]; found {
		_, err := http.Get("https://accounts.google.com/o/oauth2/revoke?token=" + tok)
		maybeFatal("error revoking token", err)
		delete(toks.Tok, strings.Join(m.Scopes, " "))
		toks.save()
	}
}

type tokenInfo struct {
	Scope      string
	ExpiresIn  int    `json:"expires_in"`
	AccessType string `json:"access_type"`
}

func (inf tokenInfo) expired() bool {
	return inf.ExpiresIn < 0
}
func getTokenInfo(tok string) (*tokenInfo, error) {
	r, err := http.Post("https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+tok, "", nil)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	var info tokenInfo
	err = json.NewDecoder(r.Body).Decode(&info)
	return &info, err
}

type tokens struct {
	Tok map[string]token
}

type token struct {
	AccessToken, RefreshToken string
}

func loadTokens() (*tokens, error) {
	fi, err := os.Stat(tokensFile)
	if err == os.ErrNotExist || err == io.EOF {
		return &tokens{make(map[string]token)}, nil
	} else if err != nil {
		return &tokens{make(map[string]token)}, nil
	}
	if fi.Size() == 0 {
		return &tokens{make(map[string]token)}, nil
	}
	f, err := os.Open(tokensFile)
	if err != nil && err != io.EOF {
		return &tokens{make(map[string]token)}, nil
	}
	defer f.Close()
	var t tokens
	_ = gob.NewDecoder(f).Decode(&t)
	return &t, nil
}

func (t *tokens) save() error {
	f, err := os.Create(tokensFile)
	if err != nil {
		return err
	}
	defer f.Close()
	return gob.NewEncoder(f).Encode(t)
}

func accessTokenFromPemFile(scope string) string {
	secretBytes, err := ioutil.ReadFile(*flagSecrets)
	maybeFatal("error reading secrets file:", err)
	var config struct {
		Web struct {
			ClientEmail string `json:"client_email"`
			TokenURI    string `json:"token_uri"`
		}
	}
	err = json.Unmarshal(secretBytes, &config)
	maybeFatal("error unmarshalling secrets:", err)

	keyBytes, err := ioutil.ReadFile(*flagPem)
	maybeFatal("error reading private key file:", err)

	// Craft the ClaimSet and JWT token.
	t := jwt.NewToken(config.Web.ClientEmail, scope, keyBytes)
	t.ClaimSet.Aud = config.Web.TokenURI

	// We need to provide a client.
	c := &http.Client{}

	// Get the access token.
	o, err := t.Assert(c)
	maybeFatal("assertion error:", err)

	return o.AccessToken
}
<commit_msg>Fix revokeToken<commit_after>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"code.google.com/p/goauth2/oauth"
	"code.google.com/p/goauth2/oauth/jwt"
	"encoding/gob"
	"encoding/json"
	"net/http"
)

const tokensFile = "~tokens.gob"

func authStart() {
	args := endpointFs.Args()
	if len(args) != 3 {
		fmt.Println("Invalid arguments, must provide API and method, e.g.:")
		fmt.Println("  gapi auth.start <api> <method>")
		return
	}
	api := loadAPI(args[1])
	if api == nil || (len(api.Resources) == 0 && len(api.Methods) == 0) {
		fmt.Println("Couldn't load API ", args[1])
		return
	}
	m := findMethod(args[2], *api)
	if m.Scopes == nil {
		fmt.Println("Method doesn't require auth")
		return
	}
	oauthConfig.Scope = strings.Join(m.Scopes, " ")
	fmt.Println("Open a browser and visit the following URL:")
	fmt.Println(oauthConfig.AuthCodeURL(""))
	fmt.Println("Then run the following command with the resulting auth code:")
	fmt.Println("gapi auth.finish <code>")
}
func authFinish() {
	args := endpointFs.Args()
	if len(args) != 2 {
		fmt.Println("Invalid arguments, must provide code, e.g.:")
		fmt.Println("  gapi auth.finish <code>")
		return
	}
	code := args[1]
	t := &oauth.Transport{Config: oauthConfig}
	tok, err := t.Exchange(code)
	maybeFatal("error exchanging code", err)
	toks, err := loadTokens()
	maybeFatal("error loading tokens", err)
	inf, err := getTokenInfo(tok.AccessToken)
	maybeFatal("error getting tokeninfo", err)
	toks.Tok[inf.Scope] = token{
		AccessToken:  tok.AccessToken,
		RefreshToken: tok.RefreshToken,
	}
	err = toks.save()
	maybeFatal("error saving tokens", err)
	fmt.Println("Token saved")
}
func authPrint() {
	args := endpointFs.Args()
	if len(args) != 3 {
		fmt.Println("Invalid arguments, must provide API and method, e.g.:")
		fmt.Println("  gapi auth.print <api> <method>")
		return
	}
	api := loadAPI(args[1])
	if api == nil || (len(api.Resources) == 0 && len(api.Methods) == 0) {
		log.Fatal("Couldn't load API ", args[1])
	}
	m := findMethod(args[2], *api)
	toks, err := loadTokens()
	maybeFatal("error loading tokens", err)
	if tok, found := toks.Tok[strings.Join(m.Scopes, " ")]; found {
		// TODO: If necessary, refresh, store, and print the new token.
		fmt.Println(tok.AccessToken)
	} else {
		fmt.Println("No token found. Run the following command to store a token:")
		fmt.Println("gapi auth.start", api.Name, m.ID[len(api.Name)+1:])
	}
}
func authRevoke() {
	args := endpointFs.Args()
	if len(args) != 3 {
		fmt.Println("Invalid arguments, must provide API and method, e.g.:")
		fmt.Println("  gapi auth.print <api> <method>")
		return
	}
	api := loadAPI(args[1])
	if api == nil || (len(api.Resources) == 0 && len(api.Methods) == 0) {
		log.Fatal("Couldn't load API ", args[1])
	}
	m := findMethod(args[2], *api)
	toks, err := loadTokens()
	maybeFatal("error loading tokens", err)
	if tok, found := toks.Tok[strings.Join(m.Scopes, " ")]; found {
		_, err := http.Get("https://accounts.google.com/o/oauth2/revoke?token=" + tok.AccessToken)
		maybeFatal("error revoking token", err)
		delete(toks.Tok, strings.Join(m.Scopes, " "))
		toks.save()
	}
}

type tokenInfo struct {
	Scope      string
	ExpiresIn  int    `json:"expires_in"`
	AccessType string `json:"access_type"`
}

func (inf tokenInfo) expired() bool {
	return inf.ExpiresIn < 0
}
func getTokenInfo(tok string) (*tokenInfo, error) {
	r, err := http.Post("https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+tok, "", nil)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	var info tokenInfo
	err = json.NewDecoder(r.Body).Decode(&info)
	return &info, err
}

type tokens struct {
	Tok map[string]token
}

type token struct {
	AccessToken, RefreshToken string
}

func loadTokens() (*tokens, error) {
	fi, err := os.Stat(tokensFile)
	if err == os.ErrNotExist || err == io.EOF {
		return &tokens{make(map[string]token)}, nil
	} else if err != nil {
		return &tokens{make(map[string]token)}, nil
	}
	if fi.Size() == 0 {
		return &tokens{make(map[string]token)}, nil
	}
	f, err := os.Open(tokensFile)
	if err != nil && err != io.EOF {
		return &tokens{make(map[string]token)}, nil
	}
	defer f.Close()
	var t tokens
	_ = gob.NewDecoder(f).Decode(&t)
	return &t, nil
}

func (t *tokens) save() error {
	f, err := os.Create(tokensFile)
	if err != nil {
		return err
	}
	defer f.Close()
	return gob.NewEncoder(f).Encode(t)
}

func accessTokenFromPemFile(scope string) string {
	secretBytes, err := ioutil.ReadFile(*flagSecrets)
	maybeFatal("error reading secrets file:", err)
	var config struct {
		Web struct {
			ClientEmail string `json:"client_email"`
			TokenURI    string `json:"token_uri"`
		}
	}
	err = json.Unmarshal(secretBytes, &config)
	maybeFatal("error unmarshalling secrets:", err)

	keyBytes, err := ioutil.ReadFile(*flagPem)
	maybeFatal("error reading private key file:", err)

	// Craft the ClaimSet and JWT token.
	t := jwt.NewToken(config.Web.ClientEmail, scope, keyBytes)
	t.ClaimSet.Aud = config.Web.TokenURI

	// We need to provide a client.
	c := &http.Client{}

	// Get the access token.
	o, err := t.Assert(c)
	maybeFatal("assertion error:", err)

	return o.AccessToken
}
<|endoftext|>


<commit_before>package dnsimple

import (
	"fmt"
)

// ServicesService handles communication with the service related
// methods of the DNSimple API.
//
// See https://developer.dnsimple.com/v2/services/
type ServicesService struct {
	client *Client
}

// ServiceSetting represents a single group of settings for a DNSimple Service.
type ServiceSetting struct {
	Name        string `json:"name,omitempty"`
	Label       string `json:"label,omitempty"`
	Append      string `json:"append,omitempty"`
	Description string `json:"description,omitempty"`
	Example     string `json:"example,omitempty"`
	Password    bool   `json:"password,omitempty"`
}

// Service represents a Service in DNSimple.
type Service struct {
	ID               int              `json:"id,omitempty"`
	Name             string           `json:"name,omitempty"`
	ShortName        string           `json:"short_name,omitempty"`
	Description      string           `json:"description,omitempty"`
	SetupDescription string           `json:"setup_description,omitempty"`
	RequiresSetup    bool             `json:"requires_setup,omitempty"`
	DefaultSubdomain string           `json:"default_subdomain,omitempty"`
	CreatedAt        string           `json:"created_at,omitempty"`
	UpdatedAt        string           `json:"updated_at,omitempty"`
	Settings         []ServiceSetting `json:"settings,omitempty"`
}

func servicePath(serviceID string) string {
	if serviceID != "" {
		return fmt.Sprintf("/services/%v", serviceID)
	}
	return fmt.Sprintf("/services")
}

// ServiceResponse represents a response from an API method that returns a Service struct.
type ServiceResponse struct {
	Response
	Data *Service `json:"data"`
}

// ServicesResponse represents a response from an API method that returns a collection of Service struct.
type ServicesResponse struct {
	Response
	Data []Service `json:"data"`
}

// ListServices list the services for an account.
//
// See https://developer.dnsimple.com/v2/services/#list
func (s *ServicesService) ListServices(options *ListOptions) (*ServicesResponse, error) {
	path := versioned(servicePath(""))
	servicesResponse := &ServicesResponse{}

	path, err := addURLQueryOptions(path, options)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.get(path, servicesResponse)
	if err != nil {
		return servicesResponse, err
	}

	servicesResponse.HttpResponse = resp
	return servicesResponse, nil
}

// GetService fetches a service.
//
// See https://developer.dnsimple.com/v2/services/#get
func (s *ServicesService) GetService(serviceID string) (*ServiceResponse, error) {
	path := versioned(servicePath(serviceID))
	serviceResponse := &ServiceResponse{}

	resp, err := s.client.get(path, serviceResponse)
	if err != nil {
		return nil, err
	}

	serviceResponse.HttpResponse = resp
	return serviceResponse, nil
}
<commit_msg>Fix misleading method comment<commit_after>package dnsimple

import (
	"fmt"
)

// ServicesService handles communication with the service related
// methods of the DNSimple API.
//
// See https://developer.dnsimple.com/v2/services/
type ServicesService struct {
	client *Client
}

// ServiceSetting represents a single group of settings for a DNSimple Service.
type ServiceSetting struct {
	Name        string `json:"name,omitempty"`
	Label       string `json:"label,omitempty"`
	Append      string `json:"append,omitempty"`
	Description string `json:"description,omitempty"`
	Example     string `json:"example,omitempty"`
	Password    bool   `json:"password,omitempty"`
}

// Service represents a Service in DNSimple.
type Service struct {
	ID               int              `json:"id,omitempty"`
	Name             string           `json:"name,omitempty"`
	ShortName        string           `json:"short_name,omitempty"`
	Description      string           `json:"description,omitempty"`
	SetupDescription string           `json:"setup_description,omitempty"`
	RequiresSetup    bool             `json:"requires_setup,omitempty"`
	DefaultSubdomain string           `json:"default_subdomain,omitempty"`
	CreatedAt        string           `json:"created_at,omitempty"`
	UpdatedAt        string           `json:"updated_at,omitempty"`
	Settings         []ServiceSetting `json:"settings,omitempty"`
}

func servicePath(serviceID string) string {
	if serviceID != "" {
		return fmt.Sprintf("/services/%v", serviceID)
	}
	return fmt.Sprintf("/services")
}

// ServiceResponse represents a response from an API method that returns a Service struct.
type ServiceResponse struct {
	Response
	Data *Service `json:"data"`
}

// ServicesResponse represents a response from an API method that returns a collection of Service struct.
type ServicesResponse struct {
	Response
	Data []Service `json:"data"`
}

// ListServices list the one-click services available in DNSimple.
//
// See https://developer.dnsimple.com/v2/services/#list
func (s *ServicesService) ListServices(options *ListOptions) (*ServicesResponse, error) {
	path := versioned(servicePath(""))
	servicesResponse := &ServicesResponse{}

	path, err := addURLQueryOptions(path, options)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.get(path, servicesResponse)
	if err != nil {
		return servicesResponse, err
	}

	servicesResponse.HttpResponse = resp
	return servicesResponse, nil
}

// GetService fetches a one-click service.
//
// See https://developer.dnsimple.com/v2/services/#get
func (s *ServicesService) GetService(serviceID string) (*ServiceResponse, error) {
	path := versioned(servicePath(serviceID))
	serviceResponse := &ServiceResponse{}

	resp, err := s.client.get(path, serviceResponse)
	if err != nil {
		return nil, err
	}

	serviceResponse.HttpResponse = resp
	return serviceResponse, nil
}
<|endoftext|>


<commit_before>package leancloud

import (
	log "github.com/Sirupsen/logrus"
	"github.com/oxfeeefeee/appgo"
	"github.com/parnurzeal/gorequest"
	"strings"
)

const (
	sendMethod      = "POST"
	sendUrl         = "https://leancloud.cn/1.1/push"
	push_batch_size = 5000
)

var (
	appId         string
	appKey        string
	androidAction string
	expiration    int
	prod          string
)

func init() {
	appId = appgo.Conf.Leancloud.AppId
	appKey = appgo.Conf.Leancloud.AppKey
	androidAction = appgo.Conf.Leancloud.AndroidAction
	expiration = appgo.Conf.Leancloud.ExpirationInterval
	prod = "prod"
	if appgo.Conf.DevMode {
		prod = "dev"
	}
}

type IosApnsAlert struct {
	Title        string   `json:"title,omitempty"`
	Body         string   `json:"body,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

type IosData struct {
	Alert            *IosApnsAlert `json:"alert,omitempty"`
	Badge            int           `json:"badge,omitempty"`
	Sound            string        `json:"sound,omitempty"`
	ContentAvailable int           `json:"content-available,omitempty"`
	Custom           interface{}   `json:"custom,omitempty"`
}

type AndroidData struct {
	Alert     string      `json:"alert,omitempty"`
	Alert2    string      `json:"alert2,omitempty"`
	Title     string      `json:"title,omitempty"`
	Action    string      `json:"action,omitempty"`
	PlaySound bool        `json:"play_sound"`
	Silent    bool        `json:"silent"`
	Custom    interface{} `json:"custom,omitempty"`
}

type IosAndroidData struct {
	Ios     *IosData     `json:"ios,omitempty"`
	Android *AndroidData `json:"android,omitempty"`
}

type LeancloudPush struct {
	ExpirationInterval int             `json:"expiration_interval,omitempty"`
	Prod               string          `json:"prod,omitempty"`
	Data               *IosAndroidData `json:"data,omitempty"`
	Cql                string          `json:"cql,omitempty"`
}

type Leancloud struct{}

func (_ Leancloud) Name() string {
	return "leancloud"
}

func (l Leancloud) PushNotif(pushInfo map[appgo.Id]*appgo.PushInfo, content *appgo.PushData) {
	userIds := make([]string, 0, len(pushInfo))
	for uid, _ := range pushInfo {
		userIds = append(userIds, "\""+uid.String()+"\"")
	}
	for i := 0; i < len(userIds); i += push_batch_size {
		end := i + push_batch_size
		if end > len(userIds) {
			end = len(userIds)
		}
		idstr := strings.Join(userIds[i:end], ",")
		go l.doPushNotif(idstr, content)
	}
}

func (_ Leancloud) doPushNotif(ids string, content *appgo.PushData) {
	defer func() {
		if r := recover(); r != nil {
			log.Errorln("doPushNotif paniced: ", r)
		}
	}()
	cql := "select * from _Installation where userId in ( " + ids + " )"
	pl := buildPayload(content)
	request(&LeancloudPush{
		ExpirationInterval: expiration,
		Prod:               prod,
		Data:               pl,
		Cql:                cql,
	})
}

func buildPayload(content *appgo.PushData) *IosAndroidData {
	return &IosAndroidData{
		&IosData{
			Alert: &IosApnsAlert{
				Title: content.Title,
				Body:  content.Message,
			},
			Badge:  content.Badge,
			Sound:  content.Sound,
			Custom: content.Custom,
		},
		&AndroidData{
			Alert:     content.Message,
			Alert2:    content.Message,
			Title:     content.Title,
			Action:    androidAction,
			PlaySound: len(content.Sound) > 0,
			Silent:    true,
			Custom:    content.Custom,
		},
	}
}

func request(p *LeancloudPush) {
	_, ret, errs := gorequest.New().SetDebug(true).
		Post(sendUrl).
		Set("X-LC-Id", appId).
		Set("X-LC-Key", appKey).
		SendStruct(p).
		End()
	if errs != nil {
		log.WithFields(log.Fields{
			"errors": errs,
			"url":    sendUrl,
			"body":   ret,
		}).Error("Failed to request leancloud push")
		return
	}
	log.Infoln("leancloud push return: ", ret)
}
<commit_msg>leave iOS push title as default<commit_after>package leancloud

import (
	log "github.com/Sirupsen/logrus"
	"github.com/oxfeeefeee/appgo"
	"github.com/parnurzeal/gorequest"
	"strings"
)

const (
	sendMethod      = "POST"
	sendUrl         = "https://leancloud.cn/1.1/push"
	push_batch_size = 5000
)

var (
	appId         string
	appKey        string
	androidAction string
	expiration    int
	prod          string
)

func init() {
	appId = appgo.Conf.Leancloud.AppId
	appKey = appgo.Conf.Leancloud.AppKey
	androidAction = appgo.Conf.Leancloud.AndroidAction
	expiration = appgo.Conf.Leancloud.ExpirationInterval
	prod = "prod"
	if appgo.Conf.DevMode {
		prod = "dev"
	}
}

type IosApnsAlert struct {
	Title        string   `json:"title,omitempty"`
	Body         string   `json:"body,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

type IosData struct {
	Alert            *IosApnsAlert `json:"alert,omitempty"`
	Badge            int           `json:"badge,omitempty"`
	Sound            string        `json:"sound,omitempty"`
	ContentAvailable int           `json:"content-available,omitempty"`
	Custom           interface{}   `json:"custom,omitempty"`
}

type AndroidData struct {
	Alert     string      `json:"alert,omitempty"`
	Alert2    string      `json:"alert2,omitempty"`
	Title     string      `json:"title,omitempty"`
	Action    string      `json:"action,omitempty"`
	PlaySound bool        `json:"play_sound"`
	Silent    bool        `json:"silent"`
	Custom    interface{} `json:"custom,omitempty"`
}

type IosAndroidData struct {
	Ios     *IosData     `json:"ios,omitempty"`
	Android *AndroidData `json:"android,omitempty"`
}

type LeancloudPush struct {
	ExpirationInterval int             `json:"expiration_interval,omitempty"`
	Prod               string          `json:"prod,omitempty"`
	Data               *IosAndroidData `json:"data,omitempty"`
	Cql                string          `json:"cql,omitempty"`
}

type Leancloud struct{}

func (_ Leancloud) Name() string {
	return "leancloud"
}

func (l Leancloud) PushNotif(pushInfo map[appgo.Id]*appgo.PushInfo, content *appgo.PushData) {
	userIds := make([]string, 0, len(pushInfo))
	for uid, _ := range pushInfo {
		userIds = append(userIds, "\""+uid.String()+"\"")
	}
	for i := 0; i < len(userIds); i += push_batch_size {
		end := i + push_batch_size
		if end > len(userIds) {
			end = len(userIds)
		}
		idstr := strings.Join(userIds[i:end], ",")
		go l.doPushNotif(idstr, content)
	}
}

func (_ Leancloud) doPushNotif(ids string, content *appgo.PushData) {
	defer func() {
		if r := recover(); r != nil {
			log.Errorln("doPushNotif paniced: ", r)
		}
	}()
	cql := "select * from _Installation where userId in ( " + ids + " )"
	pl := buildPayload(content)
	request(&LeancloudPush{
		ExpirationInterval: expiration,
		Prod:               prod,
		Data:               pl,
		Cql:                cql,
	})
}

func buildPayload(content *appgo.PushData) *IosAndroidData {
	return &IosAndroidData{
		&IosData{
			Alert: &IosApnsAlert{
				Title: "",
				Body:  content.Message,
			},
			Badge:  content.Badge,
			Sound:  content.Sound,
			Custom: content.Custom,
		},
		&AndroidData{
			Alert:     content.Message,
			Alert2:    content.Message,
			Title:     content.Title,
			Action:    androidAction,
			PlaySound: len(content.Sound) > 0,
			Silent:    true,
			Custom:    content.Custom,
		},
	}
}

func request(p *LeancloudPush) {
	_, ret, errs := gorequest.New().SetDebug(true).
		Post(sendUrl).
		Set("X-LC-Id", appId).
		Set("X-LC-Key", appKey).
		SendStruct(p).
		End()
	if errs != nil {
		log.WithFields(log.Fields{
			"errors": errs,
			"url":    sendUrl,
			"body":   ret,
		}).Error("Failed to request leancloud push")
		return
	}
	log.Infoln("leancloud push return: ", ret)
}
<|endoftext|>


<commit_before>package manager

import (
	"net/url"
	"testing"

	"github.com/coreos/dex/db"
	"github.com/coreos/dex/session"
	"github.com/coreos/go-oidc/oidc"
)

func staticGenerateCodeFunc(code string) GenerateCodeFunc {
	return func() (string, error) {
		return code, nil
	}
}

func newManager(t *testing.T) *SessionManager {
	dbMap := db.NewMemDB()
	return NewSessionManager(db.NewSessionRepo(dbMap), db.NewSessionKeyRepo(dbMap))
}

func TestSessionManagerNewSession(t *testing.T) {
	sm := newManager(t)
	sm.GenerateCode = staticGenerateCodeFunc("boo")
	got, err := sm.NewSession("bogus_idpc", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if got != "boo" {
		t.Fatalf("Incorrect Session ID: want=%s got=%s", "boo", got)
	}
}

func TestSessionAttachRemoteIdentityTwice(t *testing.T) {
	sm := newManager(t)
	sessionID, err := sm.NewSession("bogus_idpc", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	ident := oidc.Identity{ID: "YYY", Name: "elroy", Email: "elroy@example.com"}
	if _, err := sm.AttachRemoteIdentity(sessionID, ident); err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	if _, err := sm.AttachRemoteIdentity(sessionID, ident); err == nil {
		t.Fatalf("Expected non-nil error")
	}
}

func TestSessionManagerExchangeKey(t *testing.T) {
	sm := newManager(t)
	sessionID, err := sm.NewSession("connector_id", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	key, err := sm.NewSessionKey(sessionID)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	got, err := sm.ExchangeKey(key)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if got != sessionID {
		t.Fatalf("Incorrect Session ID: want=%s got=%s", sessionID, got)
	}

	if _, err := sm.ExchangeKey(key); err == nil {
		t.Fatalf("Received nil response from attempt with spent Session key")
	}
}

func TestSessionManagerGetSessionInStateNoExist(t *testing.T) {
	sm := newManager(t)
	ses, err := sm.getSessionInState("123", session.SessionStateNew)
	if err == nil {
		t.Errorf("Expected non-nil error")
	}
	if ses != nil {
		t.Errorf("Expected nil Session")
	}
}

func TestSessionManagerGetSessionInStateWrongState(t *testing.T) {
	sm := newManager(t)
	sessionID, err := sm.NewSession("connector_id", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	ses, err := sm.getSessionInState(sessionID, session.SessionStateDead)
	if err == nil {
		t.Errorf("Expected non-nil error")
	}
	if ses != nil {
		t.Errorf("Expected nil Session")
	}
}

func TestSessionManagerKill(t *testing.T) {
	sm := newManager(t)
	sessionID, err := sm.NewSession("connector_id", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	ident := oidc.Identity{ID: "YYY", Name: "elroy", Email: "elroy@example.com"}
	if _, err := sm.AttachRemoteIdentity(sessionID, ident); err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	ses, err := sm.Kill(sessionID)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
	if ses == nil {
		t.Fatalf("Expected non-nil Session")
	}

	if ses.ClientState != "bogus" {
		t.Errorf("Unexpected Session: %#v", ses)
	}
}
<commit_msg>session: remove unused function argument<commit_after>package manager

import (
	"net/url"
	"testing"

	"github.com/coreos/dex/db"
	"github.com/coreos/dex/session"
	"github.com/coreos/go-oidc/oidc"
)

func staticGenerateCodeFunc(code string) GenerateCodeFunc {
	return func() (string, error) {
		return code, nil
	}
}

func newManager() *SessionManager {
	dbMap := db.NewMemDB()
	return NewSessionManager(db.NewSessionRepo(dbMap), db.NewSessionKeyRepo(dbMap))
}

func TestSessionManagerNewSession(t *testing.T) {
	sm := newManager()
	sm.GenerateCode = staticGenerateCodeFunc("boo")
	got, err := sm.NewSession("bogus_idpc", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if got != "boo" {
		t.Fatalf("Incorrect Session ID: want=%s got=%s", "boo", got)
	}
}

func TestSessionAttachRemoteIdentityTwice(t *testing.T) {
	sm := newManager()
	sessionID, err := sm.NewSession("bogus_idpc", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	ident := oidc.Identity{ID: "YYY", Name: "elroy", Email: "elroy@example.com"}
	if _, err := sm.AttachRemoteIdentity(sessionID, ident); err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	if _, err := sm.AttachRemoteIdentity(sessionID, ident); err == nil {
		t.Fatalf("Expected non-nil error")
	}
}

func TestSessionManagerExchangeKey(t *testing.T) {
	sm := newManager()
	sessionID, err := sm.NewSession("connector_id", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	key, err := sm.NewSessionKey(sessionID)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	got, err := sm.ExchangeKey(key)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	if got != sessionID {
		t.Fatalf("Incorrect Session ID: want=%s got=%s", sessionID, got)
	}

	if _, err := sm.ExchangeKey(key); err == nil {
		t.Fatalf("Received nil response from attempt with spent Session key")
	}
}

func TestSessionManagerGetSessionInStateNoExist(t *testing.T) {
	sm := newManager()
	ses, err := sm.getSessionInState("123", session.SessionStateNew)
	if err == nil {
		t.Errorf("Expected non-nil error")
	}
	if ses != nil {
		t.Errorf("Expected nil Session")
	}
}

func TestSessionManagerGetSessionInStateWrongState(t *testing.T) {
	sm := newManager()
	sessionID, err := sm.NewSession("connector_id", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	ses, err := sm.getSessionInState(sessionID, session.SessionStateDead)
	if err == nil {
		t.Errorf("Expected non-nil error")
	}
	if ses != nil {
		t.Errorf("Expected nil Session")
	}
}

func TestSessionManagerKill(t *testing.T) {
	sm := newManager()
	sessionID, err := sm.NewSession("connector_id", "XXX", "bogus", url.URL{}, "", false, []string{"openid"})
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	ident := oidc.Identity{ID: "YYY", Name: "elroy", Email: "elroy@example.com"}
	if _, err := sm.AttachRemoteIdentity(sessionID, ident); err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	ses, err := sm.Kill(sessionID)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
	if ses == nil {
		t.Fatalf("Expected non-nil Session")
	}

	if ses.ClientState != "bogus" {
		t.Errorf("Unexpected Session: %#v", ses)
	}
}
<|endoftext|>


<commit_before>package odb

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"strings"
	"sync/atomic"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/git"
	"github.com/git-lfs/git-lfs/lfs"
)

// ObjectDatabase enables the reading and writing of objects against a storage
// backend.
type ObjectDatabase struct {
	// s is the storage backend which opens/creates/reads/writes.
	s storer

	// closed is a uint32 managed by sync/atomic's <X>Uint32 methods. It
	// yields a value of 0 if the *ObjectDatabase it is stored upon is open,
	// and a value of 1 if it is closed.
	closed uint32
	// objectScanner is the running instance of `*git.ObjectScanner` used to
	// scan packed objects not found in .git/objects/xx/... directly.
	objectScanner *git.ObjectScanner
}

// FromFilesystem constructs an *ObjectDatabase instance that is backed by a
// directory on the filesystem. Specifically, this should point to:
//
//  /absolute/repo/path/.git/objects
func FromFilesystem(root string) (*ObjectDatabase, error) {
	os, err := git.NewObjectScanner()
	if err != nil {
		return nil, err
	}

	return &ObjectDatabase{
		s:             newFileStorer(root),
		objectScanner: os,
	}, nil
}

// Close closes the *ObjectDatabase, freeing any open resources (namely: the
// `*git.ObjectScanner instance), and returning any errors encountered in
// closing them.
//
// If Close() has already been called, this function will return an error.
func (o *ObjectDatabase) Close() error {
	if !atomic.CompareAndSwapUint32(&o.closed, 0, 1) {
		return errors.New("git/odb: *ObjectDatabase already closed")
	}

	if err := o.objectScanner.Close(); err != nil {
		return err
	}
	return nil
}

// Blob returns a *Blob as identified by the SHA given, or an error if one was
// encountered.
func (o *ObjectDatabase) Blob(sha []byte) (*Blob, error) {
	var b Blob

	if err := o.decode(sha, &b); err != nil {
		return nil, err
	}
	return &b, nil
}

// Tree returns a *Tree as identified by the SHA given, or an error if one was
// encountered.
func (o *ObjectDatabase) Tree(sha []byte) (*Tree, error) {
	var t Tree
	if err := o.decode(sha, &t); err != nil {
		return nil, err
	}
	return &t, nil
}

// Commit returns a *Commit as identified by the SHA given, or an error if one
// was encountered.
func (o *ObjectDatabase) Commit(sha []byte) (*Commit, error) {
	var c Commit

	if err := o.decode(sha, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

// WriteBlob stores a *Blob on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteBlob(b *Blob) ([]byte, error) {
	buf, err := lfs.TempFile("")
	if err != nil {
		return nil, err
	}
	defer buf.Close()

	sha, _, err := o.encodeBuffer(b, buf)
	if err != nil {
		return nil, err
	}

	if err = b.Close(); err != nil {
		return nil, err
	}

	return sha, nil
}

// WriteTree stores a *Tree on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteTree(t *Tree) ([]byte, error) {
	sha, _, err := o.encode(t)
	if err != nil {
		return nil, err
	}
	return sha, nil
}

// WriteCommit stores a *Commit on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteCommit(c *Commit) ([]byte, error) {
	sha, _, err := o.encode(c)
	if err != nil {
		return nil, err
	}
	return sha, nil
}

// Root returns the filesystem root that this *ObjectDatabase works within, if
// backed by a fileStorer (constructed by FromFilesystem). If so, it returns
// the fully-qualified path on a disk and a value of true.
//
// Otherwise, it returns empty-string and a value of false.
func (o *ObjectDatabase) Root() (string, bool) {
	type rooter interface {
		Root() string
	}

	if root, ok := o.s.(rooter); ok {
		return root.Root(), true
	}
	return "", false
}

// encode encodes and saves an object to the storage backend and uses an
// in-memory buffer to calculate the object's encoded body.
func (d *ObjectDatabase) encode(object Object) (sha []byte, n int64, err error) {
	return d.encodeBuffer(object, bytes.NewBuffer(nil))
}

// encodeBuffer encodes and saves an object to the storage backend by using the
// given buffer to calculate and store the object's encoded body.
func (d *ObjectDatabase) encodeBuffer(object Object, buf io.ReadWriter) (sha []byte, n int64, err error) {
	cn, err := object.Encode(buf)
	if err != nil {
		return nil, 0, err
	}

	tmp, err := lfs.TempFile("")
	if err != nil {
		return nil, 0, err
	}
	defer tmp.Close()

	to := NewObjectWriter(tmp)
	if _, err = to.WriteHeader(object.Type(), int64(cn)); err != nil {
		return nil, 0, err
	}

	if seek, ok := buf.(io.Seeker); ok {
		if _, err = seek.Seek(0, io.SeekStart); err != nil {
			return nil, 0, err
		}
	}

	if _, err = io.Copy(to, buf); err != nil {
		return nil, 0, err
	}

	if err = to.Close(); err != nil {
		return nil, 0, err
	}

	if _, err := tmp.Seek(0, io.SeekStart); err != nil {
		return nil, 0, err
	}
	return d.save(to.Sha(), tmp)
}

// save writes the given buffer to the location given by the storer "o.s" as
// identified by the sha []byte.
func (o *ObjectDatabase) save(sha []byte, buf io.Reader) ([]byte, int64, error) {
	n, err := o.s.Store(sha, buf)

	return sha, n, err
}

// open gives an `*ObjectReader` for the given loose object keyed by the given
// "sha" []byte, or an error.
func (o *ObjectDatabase) open(sha []byte) (*ObjectReader, error) {
	f, err := o.s.Open(sha)
	if err != nil {
		if !os.IsNotExist(err) {
			// If there was some other issue beyond not being able
			// to find the object, return that immediately and don't
			// try and fallback to the *git.ObjectScanner.
			return nil, err
		}

		// Otherwise, if the file simply couldn't be found, attempt to
		// load its contents from the *git.ObjectScanner by leveraging
		// `git-cat-file --batch`.
		if atomic.LoadUint32(&o.closed) == 1 {
			return nil, errors.New("git/odb: cannot use closed *git.ObjectScanner")
		}

		if !o.objectScanner.Scan(hex.EncodeToString(sha)) {
			return nil, o.objectScanner.Err()
		}

		return NewUncompressedObjectReader(io.MultiReader(
			// Git object header:
			strings.NewReader(fmt.Sprintf("%s %d\x00",
				o.objectScanner.Type(), o.objectScanner.Size(),
			)),

			// Git object (uncompressed) contents:
			o.objectScanner.Contents(),
		))
	}

	return NewObjectReadCloser(f)
}

// decode decodes an object given by the sha "sha []byte" into the given object
// "into", or returns an error if one was encountered.
//
// Ordinarily, it closes the object's underlying io.ReadCloser (if it implements
// the `io.Closer` interface), but skips this if the "into" Object is of type
// BlobObjectType. Blob's don't exhaust the buffer completely (they instead
// maintain a handle on the blob's contents via an io.LimitedReader) and
// therefore cannot be closed until signaled explicitly by git/odb.Blob.Close().
func (o *ObjectDatabase) decode(sha []byte, into Object) error {
	r, err := o.open(sha)
	if err != nil {
		return err
	}

	typ, size, err := r.Header()
	if err != nil {
		return err
	} else if typ != into.Type() {
		return &UnexpectedObjectType{Got: typ, Wanted: into.Type()}
	}

	if _, err = into.Decode(r, size); err != nil {
		return err
	}

	if into.Type() == BlobObjectType {
		return nil
	}
	return r.Close()
}
<commit_msg>git/odb: remove temporary files after migration<commit_after>package odb

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"strings"
	"sync/atomic"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/git"
	"github.com/git-lfs/git-lfs/lfs"
)

// ObjectDatabase enables the reading and writing of objects against a storage
// backend.
type ObjectDatabase struct {
	// s is the storage backend which opens/creates/reads/writes.
	s storer

	// closed is a uint32 managed by sync/atomic's <X>Uint32 methods. It
	// yields a value of 0 if the *ObjectDatabase it is stored upon is open,
	// and a value of 1 if it is closed.
	closed uint32
	// objectScanner is the running instance of `*git.ObjectScanner` used to
	// scan packed objects not found in .git/objects/xx/... directly.
	objectScanner *git.ObjectScanner
}

// FromFilesystem constructs an *ObjectDatabase instance that is backed by a
// directory on the filesystem. Specifically, this should point to:
//
//  /absolute/repo/path/.git/objects
func FromFilesystem(root string) (*ObjectDatabase, error) {
	os, err := git.NewObjectScanner()
	if err != nil {
		return nil, err
	}

	return &ObjectDatabase{
		s:             newFileStorer(root),
		objectScanner: os,
	}, nil
}

// Close closes the *ObjectDatabase, freeing any open resources (namely: the
// `*git.ObjectScanner instance), and returning any errors encountered in
// closing them.
//
// If Close() has already been called, this function will return an error.
func (o *ObjectDatabase) Close() error {
	if !atomic.CompareAndSwapUint32(&o.closed, 0, 1) {
		return errors.New("git/odb: *ObjectDatabase already closed")
	}

	if err := o.objectScanner.Close(); err != nil {
		return err
	}
	return nil
}

// Blob returns a *Blob as identified by the SHA given, or an error if one was
// encountered.
func (o *ObjectDatabase) Blob(sha []byte) (*Blob, error) {
	var b Blob

	if err := o.decode(sha, &b); err != nil {
		return nil, err
	}
	return &b, nil
}

// Tree returns a *Tree as identified by the SHA given, or an error if one was
// encountered.
func (o *ObjectDatabase) Tree(sha []byte) (*Tree, error) {
	var t Tree
	if err := o.decode(sha, &t); err != nil {
		return nil, err
	}
	return &t, nil
}

// Commit returns a *Commit as identified by the SHA given, or an error if one
// was encountered.
func (o *ObjectDatabase) Commit(sha []byte) (*Commit, error) {
	var c Commit

	if err := o.decode(sha, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

// WriteBlob stores a *Blob on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteBlob(b *Blob) ([]byte, error) {
	buf, err := lfs.TempFile("")
	if err != nil {
		return nil, err
	}
	defer os.Rename(buf.Name())

	sha, _, err := o.encodeBuffer(b, buf)
	if err != nil {
		return nil, err
	}

	if err = b.Close(); err != nil {
		return nil, err
	}

	return sha, nil
}

// WriteTree stores a *Tree on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteTree(t *Tree) ([]byte, error) {
	sha, _, err := o.encode(t)
	if err != nil {
		return nil, err
	}
	return sha, nil
}

// WriteCommit stores a *Commit on disk and returns the SHA it is uniquely
// identified by, or an error if one was encountered.
func (o *ObjectDatabase) WriteCommit(c *Commit) ([]byte, error) {
	sha, _, err := o.encode(c)
	if err != nil {
		return nil, err
	}
	return sha, nil
}

// Root returns the filesystem root that this *ObjectDatabase works within, if
// backed by a fileStorer (constructed by FromFilesystem). If so, it returns
// the fully-qualified path on a disk and a value of true.
//
// Otherwise, it returns empty-string and a value of false.
func (o *ObjectDatabase) Root() (string, bool) {
	type rooter interface {
		Root() string
	}

	if root, ok := o.s.(rooter); ok {
		return root.Root(), true
	}
	return "", false
}

// encode encodes and saves an object to the storage backend and uses an
// in-memory buffer to calculate the object's encoded body.
func (d *ObjectDatabase) encode(object Object) (sha []byte, n int64, err error) {
	return d.encodeBuffer(object, bytes.NewBuffer(nil))
}

// encodeBuffer encodes and saves an object to the storage backend by using the
// given buffer to calculate and store the object's encoded body.
func (d *ObjectDatabase) encodeBuffer(object Object, buf io.ReadWriter) (sha []byte, n int64, err error) {
	cn, err := object.Encode(buf)
	if err != nil {
		return nil, 0, err
	}

	tmp, err := lfs.TempFile("")
	if err != nil {
		return nil, 0, err
	}
	defer os.Remove(tmp.Name())

	to := NewObjectWriter(tmp)
	if _, err = to.WriteHeader(object.Type(), int64(cn)); err != nil {
		return nil, 0, err
	}

	if seek, ok := buf.(io.Seeker); ok {
		if _, err = seek.Seek(0, io.SeekStart); err != nil {
			return nil, 0, err
		}
	}

	if _, err = io.Copy(to, buf); err != nil {
		return nil, 0, err
	}

	if err = to.Close(); err != nil {
		return nil, 0, err
	}

	if _, err := tmp.Seek(0, io.SeekStart); err != nil {
		return nil, 0, err
	}
	return d.save(to.Sha(), tmp)
}

// save writes the given buffer to the location given by the storer "o.s" as
// identified by the sha []byte.
func (o *ObjectDatabase) save(sha []byte, buf io.Reader) ([]byte, int64, error) {
	n, err := o.s.Store(sha, buf)

	return sha, n, err
}

// open gives an `*ObjectReader` for the given loose object keyed by the given
// "sha" []byte, or an error.
func (o *ObjectDatabase) open(sha []byte) (*ObjectReader, error) {
	f, err := o.s.Open(sha)
	if err != nil {
		if !os.IsNotExist(err) {
			// If there was some other issue beyond not being able
			// to find the object, return that immediately and don't
			// try and fallback to the *git.ObjectScanner.
			return nil, err
		}

		// Otherwise, if the file simply couldn't be found, attempt to
		// load its contents from the *git.ObjectScanner by leveraging
		// `git-cat-file --batch`.
		if atomic.LoadUint32(&o.closed) == 1 {
			return nil, errors.New("git/odb: cannot use closed *git.ObjectScanner")
		}

		if !o.objectScanner.Scan(hex.EncodeToString(sha)) {
			return nil, o.objectScanner.Err()
		}

		return NewUncompressedObjectReader(io.MultiReader(
			// Git object header:
			strings.NewReader(fmt.Sprintf("%s %d\x00",
				o.objectScanner.Type(), o.objectScanner.Size(),
			)),

			// Git object (uncompressed) contents:
			o.objectScanner.Contents(),
		))
	}

	return NewObjectReadCloser(f)
}

// decode decodes an object given by the sha "sha []byte" into the given object
// "into", or returns an error if one was encountered.
//
// Ordinarily, it closes the object's underlying io.ReadCloser (if it implements
// the `io.Closer` interface), but skips this if the "into" Object is of type
// BlobObjectType. Blob's don't exhaust the buffer completely (they instead
// maintain a handle on the blob's contents via an io.LimitedReader) and
// therefore cannot be closed until signaled explicitly by git/odb.Blob.Close().
func (o *ObjectDatabase) decode(sha []byte, into Object) error {
	r, err := o.open(sha)
	if err != nil {
		return err
	}

	typ, size, err := r.Header()
	if err != nil {
		return err
	} else if typ != into.Type() {
		return &UnexpectedObjectType{Got: typ, Wanted: into.Type()}
	}

	if _, err = into.Decode(r, size); err != nil {
		return err
	}

	if into.Type() == BlobObjectType {
		return nil
	}
	return r.Close()
}
<|endoftext|>


<commit_before>package main

import (
    "git.lukas.moe/sn0w/Karen/logger"
    "github.com/sn0w/discordgo"
    "time"
)

var BETA_GUILDS = [...]string{
    "259831440064118784", // FADED's Sandbox        (Me)
    "180818466847064065", // Karen's Sandbox        (Me)
    "172041631258640384", // P0WERPLANT             (Me)
    "161637499939192832", // Coding Lounge          (Devsome)
    "225168913808228352", // Emily's Space          (Kaaz)
    "267186654312136704", // Shinda Sekai Sensen    (黒ゲロロロ)
    "244110097599430667", // S E K A I              (Senpai /「 ステ 」Abuse)
    "268279577598492672", // ZAKINET                (Senpai /「 ステ 」Abuse)
    "266326434505687041", // Bot Test               (quoththeraven)
    "267658193407049728", // Bot Creation           (quoththeraven)
    "106029722458136576", // Shadow Realm           (WhereIsMyAim)
    "268143270520029187", // Joel's Beasts          (Joel)
    "271346578189582339", // Universe Internet Ltd. (Inside24)
}

// Automatically leaves guilds that are not registered beta testers
func autoLeaver(session *discordgo.Session) {
    for {
        for _, guild := range session.State.Guilds {
            match := false

            for _, betaGuild := range BETA_GUILDS {
                if guild.ID == betaGuild {
                    match = true
                    break
                }
            }

            if !match {
                logger.WARNING.L("beta", "Leaving guild " + guild.ID + " (" + guild.Name + ") because it didn't apply for the beta")
                session.GuildLeave(guild.ID)
            }
        }

        time.Sleep(10 * time.Second)
    }
}
<commit_msg>Re add turdy-rep. (GIT Y U NO WORK?)<commit_after>package main

import (
    "git.lukas.moe/sn0w/Karen/logger"
    "github.com/sn0w/discordgo"
    "time"
)

var BETA_GUILDS = [...]string{
    "259831440064118784", // FADED's Sandbox        (Me)
    "180818466847064065", // Karen's Sandbox        (Me)
    "172041631258640384", // P0WERPLANT             (Me)
    "161637499939192832", // Coding Lounge          (Devsome)
    "225168913808228352", // Emily's Space          (Kaaz)
    "267186654312136704", // Shinda Sekai Sensen    (黒ゲロロロ)
    "244110097599430667", // S E K A I              (Senpai /「 ステ 」Abuse)
    "268279577598492672", // ZAKINET                (Senpai /「 ステ 」Abuse)
    "266326434505687041", // Bot Test               (quoththeraven)
    "267658193407049728", // Bot Creation           (quoththeraven)
    "106029722458136576", // Shadow Realm           (WhereIsMyAim)
    "268143270520029187", // Joel's Beasts          (Joel)
    "271346578189582339", // Universe Internet Ltd. (Inside24)
    "270353850085408780", // Turdy Republic         (Moopdedoop)
}

// Automatically leaves guilds that are not registered beta testers
func autoLeaver(session *discordgo.Session) {
    for {
        for _, guild := range session.State.Guilds {
            match := false

            for _, betaGuild := range BETA_GUILDS {
                if guild.ID == betaGuild {
                    match = true
                    break
                }
            }

            if !match {
                logger.WARNING.L("beta", "Leaving guild " + guild.ID + " (" + guild.Name + ") because it didn't apply for the beta")
                session.GuildLeave(guild.ID)
            }
        }

        time.Sleep(10 * time.Second)
    }
}
<|endoftext|>


<commit_before>package sfv

import (
	"bytes"
	"io/ioutil"
	"os"
	"path"
	"reflect"
	"strings"
	"testing"
	"text/template"
)

const sfv = `
; Generated by BSDSFV for UNiX, $Revision: 1.18 $ on 2014-08-10 at 20:47.28
;
{{.File1}} 7E3265A8
{{.File2}} 04A2B3E9
`

type values struct {
	File1 string
	File2 string
}

func tempFile(content string) (*os.File, error) {
	f, err := ioutil.TempFile("", "gosfv")
	if err != nil {
		return nil, err
	}
	b := []byte(content)
	if err := ioutil.WriteFile(f.Name(), b, 0600); err != nil {
		return nil, err
	}
	return f, err
}

func createSFVFile() (*os.File, error) {
	f1, err := tempFile("foo\n")
	if err != nil {
		return nil, err
	}
	f2, err := tempFile("bar\n")
	if err != nil {
		return nil, err
	}
	values := values{
		File1: path.Base(f1.Name()),
		File2: path.Base(f2.Name()),
	}
	var b bytes.Buffer
	t := template.Must(template.New("sfv").Parse(sfv))
	if err := t.Execute(&b, values); err != nil {
		return nil, err
	}
	sfvFile, err := tempFile(b.String())
	if err != nil {
		return nil, err
	}
	return sfvFile, nil
}

func TestParseChecksum(t *testing.T) {
	line := "foo 7E3265A8"
	checksum, err := parseChecksum("/tmp", line)
	if err != nil {
		t.Fatal(err)
	}
	if expected := "/tmp/foo"; checksum.Path != expected {
		t.Fatalf("Expected %s, got %s", expected, checksum.Path)
	}
	if expected := uint32(2117232040); checksum.CRC32 != expected {
		t.Fatalf("Expected %d, got %d", expected, checksum.CRC32)
	}
}

func TestParseChecksums(t *testing.T) {
	in := "; comment\n" +
		"file1  7E3265A8\n" +
		"file2 04A2B3E7\r\n" +
		"file3 04A2B3E9\n" +
		"file4 \t04A2B3E6"
	out := []Checksum{
		Checksum{Path: "/tmp/file1", Filename: "file1", CRC32: 2117232040},
		Checksum{Path: "/tmp/file2", Filename: "file2", CRC32: 77771751},
		Checksum{Path: "/tmp/file3", Filename: "file3", CRC32: 77771753},
		Checksum{Path: "/tmp/file4", Filename: "file4", CRC32: 77771750},
	}
	checksums, err := parseChecksums("/tmp", strings.NewReader(in))
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(checksums, out) {
		t.Fatalf("Expected %+v, got %+v", out, checksums)
	}
}

func TestRead(t *testing.T) {
	f, err := createSFVFile()
	if err != nil {
		t.Fatal(err)
	}
	sfv, err := Read(f.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		for _, c := range sfv.Checksums {
			os.Remove(c.Path) // Ignore error
		}
		os.Remove(sfv.Path) // Ignore error
	}()
	if path := f.Name(); sfv.Path != path {
		t.Fatalf("Expected %s, got %s", path, sfv.Path)
	}
	if exist := sfv.IsExist(); !exist {
		t.Fatal("Expected true, got false")
	}
	ok, err := sfv.Verify()
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("Expected true, got false")
	}
}

func TestEmptySFV(t *testing.T) {
	sfv := SFV{Path: "/tmp/sfv.sfv"}
	if _, err := sfv.Verify(); err == nil {
		t.Fatal("Expected error")
	}
}
<commit_msg>Test Find<commit_after>package sfv

import (
	"bytes"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"text/template"
)

const sfv = `
; Generated by BSDSFV for UNiX, $Revision: 1.18 $ on 2014-08-10 at 20:47.28
;
{{.File1}} 7E3265A8
{{.File2}} 04A2B3E9
`

type values struct {
	File1 string
	File2 string
}

func tempFile(content string) (*os.File, error) {
	f, err := ioutil.TempFile("", "gosfv")
	if err != nil {
		return nil, err
	}
	b := []byte(content)
	if err := ioutil.WriteFile(f.Name(), b, 0600); err != nil {
		return nil, err
	}
	return f, err
}

func createSFVFile() (*os.File, error) {
	f1, err := tempFile("foo\n")
	if err != nil {
		return nil, err
	}
	f2, err := tempFile("bar\n")
	if err != nil {
		return nil, err
	}
	values := values{
		File1: path.Base(f1.Name()),
		File2: path.Base(f2.Name()),
	}
	var b bytes.Buffer
	t := template.Must(template.New("sfv").Parse(sfv))
	if err := t.Execute(&b, values); err != nil {
		return nil, err
	}
	sfvFile, err := tempFile(b.String())
	if err != nil {
		return nil, err
	}
	return sfvFile, nil
}

func TestParseChecksum(t *testing.T) {
	line := "foo 7E3265A8"
	checksum, err := parseChecksum("/tmp", line)
	if err != nil {
		t.Fatal(err)
	}
	if expected := "/tmp/foo"; checksum.Path != expected {
		t.Fatalf("Expected %s, got %s", expected, checksum.Path)
	}
	if expected := uint32(2117232040); checksum.CRC32 != expected {
		t.Fatalf("Expected %d, got %d", expected, checksum.CRC32)
	}
}

func TestParseChecksums(t *testing.T) {
	in := "; comment\n" +
		"file1  7E3265A8\n" +
		"file2 04A2B3E7\r\n" +
		"file3 04A2B3E9\n" +
		"file4 \t04A2B3E6"
	out := []Checksum{
		Checksum{Path: "/tmp/file1", Filename: "file1", CRC32: 2117232040},
		Checksum{Path: "/tmp/file2", Filename: "file2", CRC32: 77771751},
		Checksum{Path: "/tmp/file3", Filename: "file3", CRC32: 77771753},
		Checksum{Path: "/tmp/file4", Filename: "file4", CRC32: 77771750},
	}
	checksums, err := parseChecksums("/tmp", strings.NewReader(in))
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(checksums, out) {
		t.Fatalf("Expected %+v, got %+v", out, checksums)
	}
}

func TestRead(t *testing.T) {
	f, err := createSFVFile()
	if err != nil {
		t.Fatal(err)
	}
	sfv, err := Read(f.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		for _, c := range sfv.Checksums {
			os.Remove(c.Path) // Ignore error
		}
		os.Remove(sfv.Path) // Ignore error
	}()
	if path := f.Name(); sfv.Path != path {
		t.Fatalf("Expected %s, got %s", path, sfv.Path)
	}
	if exist := sfv.IsExist(); !exist {
		t.Fatal("Expected true, got false")
	}
	ok, err := sfv.Verify()
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("Expected true, got false")
	}
}

func TestFind(t *testing.T) {
	dir, err := ioutil.TempDir("", "gosfv")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dir)

	// No SFV file in dir should result in error
	if _, err := Find(dir); err == nil {
		t.Fatal("Expected error")
	}
	filepath := filepath.Join(dir, "test.sfv")
	if err := ioutil.WriteFile(filepath, []byte{}, 0600); err != nil {
		t.Fatal(err)
	}
	sfv, err := Find(dir)
	if err != nil {
		t.Fatal(err)
	}
	if sfv.Path != filepath {
		t.Fatalf("Expected %q, got %q", filepath, sfv.Path)
	}
}

func TestEmptySFV(t *testing.T) {
	sfv := SFV{Path: "/tmp/sfv.sfv"}
	if _, err := sfv.Verify(); err == nil {
		t.Fatal("Expected error")
	}
}
<|endoftext|>