<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package service

import (
	"fmt"
	"time"

	federation_release_1_3 "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_3"
	"k8s.io/kubernetes/pkg/api/errors"
	v1 "k8s.io/kubernetes/pkg/api/v1"
	cache "k8s.io/kubernetes/pkg/client/cache"
	"k8s.io/kubernetes/pkg/controller"

	"github.com/golang/glog"
	"reflect"
	"sort"
)

// worker runs a worker thread that just dequeues items, processes them, and marks them done.
// It enforces that the syncHandler is never invoked concurrently with the same key.
func (sc *ServiceController) clusterServiceWorker() {
	fedClient := sc.federationClient
	for clusterName, cache := range sc.clusterCache.clientMap {
		go func(cache *clusterCache, clusterName string) {
			for {
				func() {
					key, quit := cache.serviceQueue.Get()
					defer cache.serviceQueue.Done(key)
					if quit {
						return
					}
					err := sc.clusterCache.syncService(key.(string), clusterName, cache, sc.serviceCache, fedClient, sc)
					if err != nil {
						glog.Errorf("Failed to sync service: %+v", err)
					}
				}()
			}
		}(cache, clusterName)
	}
}

// Whenever there is change on service, the federation service should be updated
func (cc *clusterClientCache) syncService(key, clusterName string, clusterCache *clusterCache, serviceCache *serviceCache, fedClient federation_release_1_3.Interface, sc *ServiceController) error {
	// obj holds the latest service info from apiserver, return if there is no federation cache for the service
	cachedService, ok := serviceCache.get(key)
	if !ok {
		// if serviceCache does not exists, that means the service is not created by federation, we should skip it
		return nil
	}
	serviceInterface, exists, err := clusterCache.serviceStore.GetByKey(key)
	if err != nil {
		glog.Infof("Did not successfully get %v from store: %v, will retry later", key, err)
		clusterCache.serviceQueue.Add(key)
		return err
	}
	var needUpdate bool
	if exists {
		service, ok := serviceInterface.(*v1.Service)
		if ok {
			glog.V(4).Infof("Found service for federation service %s/%s from cluster %s", service.Namespace, service.Name, clusterName)
			needUpdate = cc.processServiceUpdate(cachedService, service, clusterName)
		} else {
			_, ok := serviceInterface.(cache.DeletedFinalStateUnknown)
			if !ok {
				return fmt.Errorf("Object contained wasn't a service or a deleted key: %+v", serviceInterface)
			}
			glog.Infof("Found tombstone for %v", key)
			needUpdate = cc.processServiceDeletion(cachedService, clusterName)
		}
	} else {
		glog.Infof("Can not get service %v for cluster %s from serviceStore", key, clusterName)
		needUpdate = cc.processServiceDeletion(cachedService, clusterName)
	}

	if needUpdate {
		for i := 0; i < clientRetryCount; i++ {
			if err := sc.ensureDnsRecords(clusterName, cachedService); err == nil {
				break
			}
			glog.V(4).Infof("Error ensuring DNS Records for service %s on cluster %s: %v", key, clusterName, err)
			time.Sleep(cachedService.nextDNSUpdateDelay())
			clusterCache.serviceQueue.Add(key)
			// did not retry here as we still want to persist federation apiserver even ensure dns records fails
		}
		err := cc.persistFedServiceUpdate(cachedService, fedClient)
		if err == nil {
			cachedService.appliedState = cachedService.lastState
			cachedService.resetFedUpdateDelay()
		} else {
			if err != nil {
				glog.Errorf("Failed to sync service: %+v, put back to service queue", err)
				clusterCache.serviceQueue.Add(key)
			}
		}
	}
	return nil
}

// processServiceDeletion is triggered when a service is delete from underlying k8s cluster
// the deletion function will wip out the cached ingress info of the service from federation service ingress
// the function returns a bool to indicate if actual update happened on federation service cache
// and if the federation service cache is updated, the updated info should be post to federation apiserver
func (cc *clusterClientCache) processServiceDeletion(cachedService *cachedService, clusterName string) bool {
	cachedService.rwlock.Lock()
	defer cachedService.rwlock.Unlock()
	cachedStatus, ok := cachedService.serviceStatusMap[clusterName]
	// cached status found, remove ingress info from federation service cache
	if ok {
		cachedFedServiceStatus := cachedService.lastState.Status.LoadBalancer
		removeIndexes := []int{}
		for i, fed := range cachedFedServiceStatus.Ingress {
			for _, new := range cachedStatus.Ingress {
				// remove if same ingress record found
				if new.IP == fed.IP && new.Hostname == fed.Hostname {
					removeIndexes = append(removeIndexes, i)
				}
			}
		}
		sort.Ints(removeIndexes)
		for i := len(removeIndexes) - 1; i >= 0; i-- {
			cachedFedServiceStatus.Ingress = append(cachedFedServiceStatus.Ingress[:removeIndexes[i]], cachedFedServiceStatus.Ingress[removeIndexes[i]+1:]...)
			glog.V(4).Infof("Remove old ingress %d for service %s/%s", removeIndexes[i], cachedService.lastState.Namespace, cachedService.lastState.Name)
		}
		delete(cachedService.serviceStatusMap, clusterName)
		delete(cachedService.endpointMap, clusterName)
		cachedService.lastState.Status.LoadBalancer = cachedFedServiceStatus
		return true
	} else {
		glog.V(4).Infof("Service removal %s/%s from cluster %s observed.", cachedService.lastState.Namespace, cachedService.lastState.Name, clusterName)
	}
	return false
}

// processServiceUpdate Update ingress info when service updated
// the function returns a bool to indicate if actual update happened on federation service cache
// and if the federation service cache is updated, the updated info should be post to federation apiserver
func (cc *clusterClientCache) processServiceUpdate(cachedService *cachedService, service *v1.Service, clusterName string) bool {
	glog.V(4).Infof("Processing service update for %s/%s, cluster %s", service.Namespace, service.Name, clusterName)
	cachedService.rwlock.Lock()
	defer cachedService.rwlock.Unlock()
	var needUpdate bool
	newServiceLB := service.Status.LoadBalancer
	cachedFedServiceStatus := cachedService.lastState.Status.LoadBalancer
	if len(newServiceLB.Ingress) == 0 {
		// not yet get LB IP
		return false
	}

	cachedStatus, ok := cachedService.serviceStatusMap[clusterName]
	if ok {
		if reflect.DeepEqual(cachedStatus, newServiceLB) {
			glog.V(4).Infof("Same ingress info observed for service %s/%s: %+v ", service.Namespace, service.Name, cachedStatus.Ingress)
		} else {
			glog.V(4).Infof("Ingress info was changed for service %s/%s: cache: %+v, new: %+v ",
				service.Namespace, service.Name, cachedStatus.Ingress, newServiceLB)
			needUpdate = true
		}
	} else {
		glog.V(4).Infof("Cached service status was not found for %s/%s, cluster %s, building one", service.Namespace, service.Name, clusterName)

		// cache is not always reliable(cache will be cleaned when service controller restart)
		// two cases will run into this branch:
		// 1. new service loadbalancer info received -> no info in cache, and no in federation service
		// 2. service controller being restarted -> no info in cache, but it is in federation service

		// check if the lb info is already in federation service

		cachedService.serviceStatusMap[clusterName] = newServiceLB
		needUpdate = false
		// iterate service ingress info
		for _, new := range newServiceLB.Ingress {
			var found bool
			// if it is known by federation service
			for _, fed := range cachedFedServiceStatus.Ingress {
				if new.IP == fed.IP && new.Hostname == fed.Hostname {
					found = true
				}
			}
			if !found {
				needUpdate = true
				break
			}
		}
	}

	if needUpdate {
		// new status = cached federation status - cached status + new status from k8s cluster

		removeIndexes := []int{}
		for i, fed := range cachedFedServiceStatus.Ingress {
			for _, new := range cachedStatus.Ingress {
				// remove if same ingress record found
				if new.IP == fed.IP && new.Hostname == fed.Hostname {
					removeIndexes = append(removeIndexes, i)
				}
			}
		}
		sort.Ints(removeIndexes)
		for i := len(removeIndexes) - 1; i >= 0; i-- {
			cachedFedServiceStatus.Ingress = append(cachedFedServiceStatus.Ingress[:removeIndexes[i]], cachedFedServiceStatus.Ingress[removeIndexes[i]+1:]...)
		}
		cachedFedServiceStatus.Ingress = append(cachedFedServiceStatus.Ingress, service.Status.LoadBalancer.Ingress...)
		cachedService.lastState.Status.LoadBalancer = cachedFedServiceStatus
		glog.V(4).Infof("Add new ingress info %+v for service %s/%s", service.Status.LoadBalancer, service.Namespace, service.Name)
	} else {
		glog.V(4).Infof("Same ingress info found for %s/%s, cluster %s", service.Namespace, service.Name, clusterName)
	}
	return needUpdate
}

func (cc *clusterClientCache) persistFedServiceUpdate(cachedService *cachedService, fedClient federation_release_1_3.Interface) error {
	service := cachedService.lastState
	glog.V(5).Infof("Persist federation service status %s/%s", service.Namespace, service.Name)
	var err error
	for i := 0; i < clientRetryCount; i++ {
		_, err := fedClient.Core().Services(service.Namespace).Get(service.Name)
		if errors.IsNotFound(err) {
			glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v",
				service.Namespace, service.Name, err)
			return nil
		}
		_, err = fedClient.Core().Services(service.Namespace).UpdateStatus(service)
		if err == nil {
			glog.V(2).Infof("Successfully update service %s/%s to federation apiserver", service.Namespace, service.Name)
			return nil
		}
		if errors.IsNotFound(err) {
			glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v",
				service.Namespace, service.Name, err)
			return nil
		}
		if errors.IsConflict(err) {
			glog.V(4).Infof("Not persisting update to service '%s/%s' that has been changed since we received it: %v",
				service.Namespace, service.Name, err)
			return err
		}
		time.Sleep(cachedService.nextFedUpdateDelay())
	}
	return err
}

// obj could be an *api.Service, or a DeletionFinalStateUnknown marker item.
func (cc *clusterClientCache) enqueueService(obj interface{}, clusterName string) {
	key, err := controller.KeyFunc(obj)
	if err != nil {
		glog.Errorf("Couldn't get key for object %+v: %v", obj, err)
		return
	}
	_, ok := cc.clientMap[clusterName]
	if ok {
		cc.clientMap[clusterName].serviceQueue.Add(key)
	}
}
<commit_msg>add kubernetes service back when it is deleted in kubernetes cluster<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package service

import (
	"fmt"
	"time"

	federation_release_1_3 "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_3"
	"k8s.io/kubernetes/pkg/api/errors"
	v1 "k8s.io/kubernetes/pkg/api/v1"
	cache "k8s.io/kubernetes/pkg/client/cache"
	"k8s.io/kubernetes/pkg/controller"

	"github.com/golang/glog"
	"reflect"
	"sort"
)

// worker runs a worker thread that just dequeues items, processes them, and marks them done.
// It enforces that the syncHandler is never invoked concurrently with the same key.
func (sc *ServiceController) clusterServiceWorker() {
	fedClient := sc.federationClient
	for clusterName, cache := range sc.clusterCache.clientMap {
		go func(cache *clusterCache, clusterName string) {
			for {
				func() {
					key, quit := cache.serviceQueue.Get()
					defer cache.serviceQueue.Done(key)
					if quit {
						return
					}
					err := sc.clusterCache.syncService(key.(string), clusterName, cache, sc.serviceCache, fedClient, sc)
					if err != nil {
						glog.Errorf("Failed to sync service: %+v", err)
					}
				}()
			}
		}(cache, clusterName)
	}
}

// Whenever there is change on service, the federation service should be updated
func (cc *clusterClientCache) syncService(key, clusterName string, clusterCache *clusterCache, serviceCache *serviceCache, fedClient federation_release_1_3.Interface, sc *ServiceController) error {
	// obj holds the latest service info from apiserver, return if there is no federation cache for the service
	cachedService, ok := serviceCache.get(key)
	if !ok {
		// if serviceCache does not exists, that means the service is not created by federation, we should skip it
		return nil
	}
	serviceInterface, exists, err := clusterCache.serviceStore.GetByKey(key)
	if err != nil {
		glog.Infof("Did not successfully get %v from store: %v, will retry later", key, err)
		clusterCache.serviceQueue.Add(key)
		return err
	}
	var needUpdate, isDeletion bool
	if exists {
		service, ok := serviceInterface.(*v1.Service)
		if ok {
			glog.V(4).Infof("Found service for federation service %s/%s from cluster %s", service.Namespace, service.Name, clusterName)
			needUpdate = cc.processServiceUpdate(cachedService, service, clusterName)
		} else {
			_, ok := serviceInterface.(cache.DeletedFinalStateUnknown)
			if !ok {
				return fmt.Errorf("Object contained wasn't a service or a deleted key: %+v", serviceInterface)
			}
			glog.Infof("Found tombstone for %v", key)
			needUpdate = cc.processServiceDeletion(cachedService, clusterName)
			isDeletion = true
		}
	} else {
		glog.Infof("Can not get service %v for cluster %s from serviceStore", key, clusterName)
		needUpdate = cc.processServiceDeletion(cachedService, clusterName)
		isDeletion = true
	}

	if needUpdate {
		for i := 0; i < clientRetryCount; i++ {
			if err := sc.ensureDnsRecords(clusterName, cachedService); err == nil {
				break
			}
			glog.V(4).Infof("Error ensuring DNS Records for service %s on cluster %s: %v", key, clusterName, err)
			time.Sleep(cachedService.nextDNSUpdateDelay())
			clusterCache.serviceQueue.Add(key)
			// did not retry here as we still want to persist federation apiserver even ensure dns records fails
		}
		err := cc.persistFedServiceUpdate(cachedService, fedClient)
		if err == nil {
			cachedService.appliedState = cachedService.lastState
			cachedService.resetFedUpdateDelay()
		} else {
			if err != nil {
				glog.Errorf("Failed to sync service: %+v, put back to service queue", err)
				clusterCache.serviceQueue.Add(key)
			}
		}
	}
	if isDeletion {
		// cachedService is not reliable here as
		// deleting cache is the last step of federation service deletion
		_, err := fedClient.Core().Services(cachedService.lastState.Namespace).Get(cachedService.lastState.Name)
		// rebuild service if federation service still exists
		if err == nil || !errors.IsNotFound(err) {
			return sc.ensureClusterService(cachedService, clusterName, cachedService.appliedState, clusterCache.clientset)
		}
	}
	return nil
}

// processServiceDeletion is triggered when a service is delete from underlying k8s cluster
// the deletion function will wip out the cached ingress info of the service from federation service ingress
// the function returns a bool to indicate if actual update happened on federation service cache
// and if the federation service cache is updated, the updated info should be post to federation apiserver
func (cc *clusterClientCache) processServiceDeletion(cachedService *cachedService, clusterName string) bool {
	cachedService.rwlock.Lock()
	defer cachedService.rwlock.Unlock()
	cachedStatus, ok := cachedService.serviceStatusMap[clusterName]
	// cached status found, remove ingress info from federation service cache
	if ok {
		cachedFedServiceStatus := cachedService.lastState.Status.LoadBalancer
		removeIndexes := []int{}
		for i, fed := range cachedFedServiceStatus.Ingress {
			for _, new := range cachedStatus.Ingress {
				// remove if same ingress record found
				if new.IP == fed.IP && new.Hostname == fed.Hostname {
					removeIndexes = append(removeIndexes, i)
				}
			}
		}
		sort.Ints(removeIndexes)
		for i := len(removeIndexes) - 1; i >= 0; i-- {
			cachedFedServiceStatus.Ingress = append(cachedFedServiceStatus.Ingress[:removeIndexes[i]], cachedFedServiceStatus.Ingress[removeIndexes[i]+1:]...)
			glog.V(4).Infof("Remove old ingress %d for service %s/%s", removeIndexes[i], cachedService.lastState.Namespace, cachedService.lastState.Name)
		}
		delete(cachedService.serviceStatusMap, clusterName)
		delete(cachedService.endpointMap, clusterName)
		cachedService.lastState.Status.LoadBalancer = cachedFedServiceStatus
		return true
	} else {
		glog.V(4).Infof("Service removal %s/%s from cluster %s observed.", cachedService.lastState.Namespace, cachedService.lastState.Name, clusterName)
	}
	return false
}

// processServiceUpdate Update ingress info when service updated
// the function returns a bool to indicate if actual update happened on federation service cache
// and if the federation service cache is updated, the updated info should be post to federation apiserver
func (cc *clusterClientCache) processServiceUpdate(cachedService *cachedService, service *v1.Service, clusterName string) bool {
	glog.V(4).Infof("Processing service update for %s/%s, cluster %s", service.Namespace, service.Name, clusterName)
	cachedService.rwlock.Lock()
	defer cachedService.rwlock.Unlock()
	var needUpdate bool
	newServiceLB := service.Status.LoadBalancer
	cachedFedServiceStatus := cachedService.lastState.Status.LoadBalancer
	if len(newServiceLB.Ingress) == 0 {
		// not yet get LB IP
		return false
	}

	cachedStatus, ok := cachedService.serviceStatusMap[clusterName]
	if ok {
		if reflect.DeepEqual(cachedStatus, newServiceLB) {
			glog.V(4).Infof("Same ingress info observed for service %s/%s: %+v ", service.Namespace, service.Name, cachedStatus.Ingress)
		} else {
			glog.V(4).Infof("Ingress info was changed for service %s/%s: cache: %+v, new: %+v ",
				service.Namespace, service.Name, cachedStatus.Ingress, newServiceLB)
			needUpdate = true
		}
	} else {
		glog.V(4).Infof("Cached service status was not found for %s/%s, cluster %s, building one", service.Namespace, service.Name, clusterName)

		// cache is not always reliable(cache will be cleaned when service controller restart)
		// two cases will run into this branch:
		// 1. new service loadbalancer info received -> no info in cache, and no in federation service
		// 2. service controller being restarted -> no info in cache, but it is in federation service

		// check if the lb info is already in federation service

		cachedService.serviceStatusMap[clusterName] = newServiceLB
		needUpdate = false
		// iterate service ingress info
		for _, new := range newServiceLB.Ingress {
			var found bool
			// if it is known by federation service
			for _, fed := range cachedFedServiceStatus.Ingress {
				if new.IP == fed.IP && new.Hostname == fed.Hostname {
					found = true
				}
			}
			if !found {
				needUpdate = true
				break
			}
		}
	}

	if needUpdate {
		// new status = cached federation status - cached status + new status from k8s cluster

		removeIndexes := []int{}
		for i, fed := range cachedFedServiceStatus.Ingress {
			for _, new := range cachedStatus.Ingress {
				// remove if same ingress record found
				if new.IP == fed.IP && new.Hostname == fed.Hostname {
					removeIndexes = append(removeIndexes, i)
				}
			}
		}
		sort.Ints(removeIndexes)
		for i := len(removeIndexes) - 1; i >= 0; i-- {
			cachedFedServiceStatus.Ingress = append(cachedFedServiceStatus.Ingress[:removeIndexes[i]], cachedFedServiceStatus.Ingress[removeIndexes[i]+1:]...)
		}
		cachedFedServiceStatus.Ingress = append(cachedFedServiceStatus.Ingress, service.Status.LoadBalancer.Ingress...)
		cachedService.lastState.Status.LoadBalancer = cachedFedServiceStatus
		glog.V(4).Infof("Add new ingress info %+v for service %s/%s", service.Status.LoadBalancer, service.Namespace, service.Name)
	} else {
		glog.V(4).Infof("Same ingress info found for %s/%s, cluster %s", service.Namespace, service.Name, clusterName)
	}
	return needUpdate
}

func (cc *clusterClientCache) persistFedServiceUpdate(cachedService *cachedService, fedClient federation_release_1_3.Interface) error {
	service := cachedService.lastState
	glog.V(5).Infof("Persist federation service status %s/%s", service.Namespace, service.Name)
	var err error
	for i := 0; i < clientRetryCount; i++ {
		_, err := fedClient.Core().Services(service.Namespace).Get(service.Name)
		if errors.IsNotFound(err) {
			glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v",
				service.Namespace, service.Name, err)
			return nil
		}
		_, err = fedClient.Core().Services(service.Namespace).UpdateStatus(service)
		if err == nil {
			glog.V(2).Infof("Successfully update service %s/%s to federation apiserver", service.Namespace, service.Name)
			return nil
		}
		if errors.IsNotFound(err) {
			glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v",
				service.Namespace, service.Name, err)
			return nil
		}
		if errors.IsConflict(err) {
			glog.V(4).Infof("Not persisting update to service '%s/%s' that has been changed since we received it: %v",
				service.Namespace, service.Name, err)
			return err
		}
		time.Sleep(cachedService.nextFedUpdateDelay())
	}
	return err
}

// obj could be an *api.Service, or a DeletionFinalStateUnknown marker item.
func (cc *clusterClientCache) enqueueService(obj interface{}, clusterName string) {
	key, err := controller.KeyFunc(obj)
	if err != nil {
		glog.Errorf("Couldn't get key for object %+v: %v", obj, err)
		return
	}
	_, ok := cc.clientMap[clusterName]
	if ok {
		cc.clientMap[clusterName].serviceQueue.Add(key)
	}
}
<|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 util

import (
	"fmt"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	pkgruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/record"
	"k8s.io/kubernetes/pkg/api"
	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
)

// Type of the operation that can be executed in Federated.
type FederatedOperationType string

const (
	OperationTypeAdd    = "add"
	OperationTypeUpdate = "update"
	OperationTypeDelete = "delete"
)

// FederatedOperation definition contains type (add/update/delete) and the object itself.
type FederatedOperation struct {
	Type        FederatedOperationType
	ClusterName string
	Obj         pkgruntime.Object
	Key         string
}

// A helper that executes the given set of updates on federation, in parallel.
type FederatedUpdater interface {
	// Executes the given set of operations.
	Update([]FederatedOperation) error
}

// A function that executes some operation using the passed client and object.
type FederatedOperationHandler func(kubeclientset.Interface, pkgruntime.Object) error

type federatedUpdaterImpl struct {
	federation FederationView

	kind string

	timeout time.Duration

	eventRecorder record.EventRecorder

	addFunction    FederatedOperationHandler
	updateFunction FederatedOperationHandler
	deleteFunction FederatedOperationHandler
}

func NewFederatedUpdater(federation FederationView, kind string, timeout time.Duration, recorder record.EventRecorder, add, update, del FederatedOperationHandler) FederatedUpdater {
	return &federatedUpdaterImpl{
		federation:     federation,
		kind:           kind,
		timeout:        timeout,
		eventRecorder:  recorder,
		addFunction:    add,
		updateFunction: update,
		deleteFunction: del,
	}
}

func (fu *federatedUpdaterImpl) recordEvent(obj runtime.Object, eventType, eventVerb string, args ...interface{}) {
	messageFmt := eventVerb + " %s %q in cluster %s"
	fu.eventRecorder.Eventf(obj, api.EventTypeNormal, eventType, messageFmt, args...)
}

// Update executes the given set of operations within the timeout specified for
// the instance. Timeout is best-effort. There is no guarantee that the
// underlying operations are stopped when it is reached. However the function
// will return after the timeout with a non-nil error.
func (fu *federatedUpdaterImpl) Update(ops []FederatedOperation) error {
	done := make(chan error, len(ops))
	for _, op := range ops {
		go func(op FederatedOperation) {
			clusterName := op.ClusterName

			// TODO: Ensure that the clientset has reasonable timeout.
			clientset, err := fu.federation.GetClientsetForCluster(clusterName)
			if err != nil {
				done <- err
				return
			}

			eventArgs := []interface{}{fu.kind, op.Key, clusterName}
			baseEventType := fmt.Sprintf("%s", op.Type)
			eventType := fmt.Sprintf("%sInCluster", strings.Title(baseEventType))

			switch op.Type {
			case OperationTypeAdd:
				// TODO s+OperationTypeAdd+OperationTypeCreate+
				baseEventType = "create"
				eventType := "CreateInCluster"

				fu.recordEvent(op.Obj, eventType, "Creating", eventArgs...)
				err = fu.addFunction(clientset, op.Obj)
			case OperationTypeUpdate:
				fu.recordEvent(op.Obj, eventType, "Updating", eventArgs...)
				err = fu.updateFunction(clientset, op.Obj)
			case OperationTypeDelete:
				fu.recordEvent(op.Obj, eventType, "Deleting", eventArgs...)
				err = fu.deleteFunction(clientset, op.Obj)
				// IsNotFound error is fine since that means the object is deleted already.
				if err != nil && !errors.IsNotFound(err) {
					err = nil
				}
			}

			if err != nil {
				eventType := eventType + "Failed"
				messageFmt := "Failed to " + baseEventType + " %s %q in cluster %s: %v"
				eventArgs = append(eventArgs, err)
				fu.eventRecorder.Eventf(op.Obj, api.EventTypeWarning, eventType, messageFmt, eventArgs...)
			}

			done <- err
		}(op)
	}
	start := time.Now()
	for i := 0; i < len(ops); i++ {
		now := time.Now()
		if !now.Before(start.Add(fu.timeout)) {
			return fmt.Errorf("failed to finish all operations in %v", fu.timeout)
		}
		select {
		case err := <-done:
			if err != nil {
				return err
			}
		case <-time.After(start.Add(fu.timeout).Sub(now)):
			return fmt.Errorf("failed to finish all operations in %v", fu.timeout)
		}
	}
	// All operations finished in time.
	return nil
}
<commit_msg>fed: Fix bad logic of deletion error handling for federated updater<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 util

import (
	"fmt"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	pkgruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/record"
	"k8s.io/kubernetes/pkg/api"
	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
)

// Type of the operation that can be executed in Federated.
type FederatedOperationType string

const (
	OperationTypeAdd    = "add"
	OperationTypeUpdate = "update"
	OperationTypeDelete = "delete"
)

// FederatedOperation definition contains type (add/update/delete) and the object itself.
type FederatedOperation struct {
	Type        FederatedOperationType
	ClusterName string
	Obj         pkgruntime.Object
	Key         string
}

// A helper that executes the given set of updates on federation, in parallel.
type FederatedUpdater interface {
	// Executes the given set of operations.
	Update([]FederatedOperation) error
}

// A function that executes some operation using the passed client and object.
type FederatedOperationHandler func(kubeclientset.Interface, pkgruntime.Object) error

type federatedUpdaterImpl struct {
	federation FederationView

	kind string

	timeout time.Duration

	eventRecorder record.EventRecorder

	addFunction    FederatedOperationHandler
	updateFunction FederatedOperationHandler
	deleteFunction FederatedOperationHandler
}

func NewFederatedUpdater(federation FederationView, kind string, timeout time.Duration, recorder record.EventRecorder, add, update, del FederatedOperationHandler) FederatedUpdater {
	return &federatedUpdaterImpl{
		federation:     federation,
		kind:           kind,
		timeout:        timeout,
		eventRecorder:  recorder,
		addFunction:    add,
		updateFunction: update,
		deleteFunction: del,
	}
}

func (fu *federatedUpdaterImpl) recordEvent(obj runtime.Object, eventType, eventVerb string, args ...interface{}) {
	messageFmt := eventVerb + " %s %q in cluster %s"
	fu.eventRecorder.Eventf(obj, api.EventTypeNormal, eventType, messageFmt, args...)
}

// Update executes the given set of operations within the timeout specified for
// the instance. Timeout is best-effort. There is no guarantee that the
// underlying operations are stopped when it is reached. However the function
// will return after the timeout with a non-nil error.
func (fu *federatedUpdaterImpl) Update(ops []FederatedOperation) error {
	done := make(chan error, len(ops))
	for _, op := range ops {
		go func(op FederatedOperation) {
			clusterName := op.ClusterName

			// TODO: Ensure that the clientset has reasonable timeout.
			clientset, err := fu.federation.GetClientsetForCluster(clusterName)
			if err != nil {
				done <- err
				return
			}

			eventArgs := []interface{}{fu.kind, op.Key, clusterName}
			baseEventType := fmt.Sprintf("%s", op.Type)
			eventType := fmt.Sprintf("%sInCluster", strings.Title(baseEventType))

			switch op.Type {
			case OperationTypeAdd:
				// TODO s+OperationTypeAdd+OperationTypeCreate+
				baseEventType = "create"
				eventType := "CreateInCluster"

				fu.recordEvent(op.Obj, eventType, "Creating", eventArgs...)
				err = fu.addFunction(clientset, op.Obj)
			case OperationTypeUpdate:
				fu.recordEvent(op.Obj, eventType, "Updating", eventArgs...)
				err = fu.updateFunction(clientset, op.Obj)
			case OperationTypeDelete:
				fu.recordEvent(op.Obj, eventType, "Deleting", eventArgs...)
				err = fu.deleteFunction(clientset, op.Obj)
				// IsNotFound error is fine since that means the object is deleted already.
				if errors.IsNotFound(err) {
					err = nil
				}
			}

			if err != nil {
				eventType := eventType + "Failed"
				messageFmt := "Failed to " + baseEventType + " %s %q in cluster %s: %v"
				eventArgs = append(eventArgs, err)
				fu.eventRecorder.Eventf(op.Obj, api.EventTypeWarning, eventType, messageFmt, eventArgs...)
			}

			done <- err
		}(op)
	}
	start := time.Now()
	for i := 0; i < len(ops); i++ {
		now := time.Now()
		if !now.Before(start.Add(fu.timeout)) {
			return fmt.Errorf("failed to finish all operations in %v", fu.timeout)
		}
		select {
		case err := <-done:
			if err != nil {
				return err
			}
		case <-time.After(start.Add(fu.timeout).Sub(now)):
			return fmt.Errorf("failed to finish all operations in %v", fu.timeout)
		}
	}
	// All operations finished in time.
	return nil
}
<|endoftext|>


<commit_before>package mboxrd

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"
)

const timestampFormat = "060102150405"

var (
	loc *time.Location
)

func init() {
	var err error
	loc, err = time.LoadLocation("UTC")
	if err != nil {
		log.Fatal(err)
	}
}

func TimeNorm(line string, errors chan error) (string, error) {
	var l string

	if li := strings.LastIndex(line, ` (`); li == -1 {
		l = line
	} else {
		l = line[:li]
	}

	l = strings.TrimSpace(strings.Replace(strings.TrimSuffix(l, " UT"), `GMT`, ``, 1))

	t, er := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("2 Jan 2006 15:04:05 -0700", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("2 Jan 2006 15:04:05 MST", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("Mon, 2 Jan 2006 15:04:05 MST", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("Mon, 2 Jan 2006 15:04:05", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("2006-01-02 15:04:05 -0700", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	//Wed, 6 Aug 2014 09:59:18 GMT-07:00

	t, er = time.Parse("Mon, 2 Jan 2006 15:04:05 Z07:00", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	return "", er
}

// NameFromTimeUser returns a closed function used to extract
// a message file name based on the message timestamp and sender's
// username part of the email.
//
// It is an example on how to construct the file name from multiple
// headers.
func NameFromTimeUser(format string, errors chan error) ByLineName {
	const (
		fromPrefix = "From: "
		headPrefix = "From "
	)

	var (
		ts, fr, hd string
		fromRE = regexp.MustCompile(`(.*<)?(.*)(@.*)`)
		headRE = regexp.MustCompile(`^From (.*)(@.*)`)
		dateRE = regexp.MustCompile(`(?i)^date: (.*)`)
	)

	return func(line string, errors chan error) string {
		var er error

		if ts == "" && dateRE.MatchString(line) {
			parsedTS := dateRE.FindStringSubmatch(line)
			if parsedTS == nil {
				errors <- fmt.Errorf(
					"Failed to parse the timestamp from the line %q",
					line)
				return ""
			}

			ts, er = TimeNorm(parsedTS[1], errors)
			if er != nil {
				errors <- fmt.Errorf(
					"Failed to parse the timestamp. Error: %s",
					er.Error())
				return ""
			}
		}

		if hd == "" && strings.HasPrefix(line, headPrefix) {
			parsedHead := headRE.FindStringSubmatch(line)

			if parsedHead == nil {
				errors <- fmt.Errorf(
					"Failed to extract sender ID from the header line %q",
					line)
				return ""
			}

			hd_temp := parsedHead[1]
			hd = hd_temp[len(hd_temp)-8:]
		}

		if fr == "" && strings.HasPrefix(line, fromPrefix) {
			email := strings.TrimPrefix(line, fromPrefix)
			parsedEmail := fromRE.FindStringSubmatch(email)

			if parsedEmail == nil {
				errors <- fmt.Errorf(
					"Failed to extract user name from the email address %q",
					email)
				return ""
			}

			fr = parsedEmail[2]
		}

		if ts == "" || fr == "" {
			return ""
		}

		return fmt.Sprintf(format, ts, hd, fr)
	}
}
<commit_msg>Multi-line "from", better address matching<commit_after>package mboxrd

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"
)

const timestampFormat = "060102150405"

var (
	loc *time.Location
)

func init() {
	var err error
	loc, err = time.LoadLocation("UTC")
	if err != nil {
		log.Fatal(err)
	}
}

func TimeNorm(line string, errors chan error) (string, error) {
	var l string

	if li := strings.LastIndex(line, ` (`); li == -1 {
		l = line
	} else {
		l = line[:li]
	}

	l = strings.TrimSpace(strings.Replace(strings.TrimSuffix(l, " UT"), `GMT`, ``, 1))

	t, er := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("2 Jan 2006 15:04:05 -0700", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("2 Jan 2006 15:04:05 MST", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("Mon, 2 Jan 2006 15:04:05 MST", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("Mon, 2 Jan 2006 15:04:05", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	t, er = time.Parse("2006-01-02 15:04:05 -0700", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	//Wed, 6 Aug 2014 09:59:18 GMT-07:00

	t, er = time.Parse("Mon, 2 Jan 2006 15:04:05 Z07:00", l)
	if er == nil {
		return t.In(loc).Format(timestampFormat), nil
	}

	return "", er
}

// NameFromTimeUser returns a closed function used to extract
// a message file name based on the message timestamp and sender's
// username part of the email.
//
// It is an example on how to construct the file name from multiple
// headers.
func NameFromTimeUser(format string, errors chan error) ByLineName {
	const headPrefix = "From "

	var (
		ts, fr, fr_acc, hd string

		in_from = false

		addrRE = regexp.MustCompile(`(\w{1,})["']?(@.*)`)
		headRE = regexp.MustCompile(`^From (.*)(@.*)`)
		dateRE = regexp.MustCompile(`(?i)^date: (.*)`)
		fromRE = regexp.MustCompile(`(?i)^(from:)(.*)`)
		indentRE = regexp.MustCompile(`^\s`)
	)

	return func(line string, errors chan error) string {
		var er error

		if ts == "" && dateRE.MatchString(line) {
			parsedTS := dateRE.FindStringSubmatch(line)
			if parsedTS == nil {
				errors <- fmt.Errorf(
					"Failed to parse the timestamp from the line %q",
					line)
				return ""
			}

			ts, er = TimeNorm(parsedTS[1], errors)
			if er != nil {
				errors <- fmt.Errorf(
					"Failed to parse the timestamp. Error: %s",
					er.Error())
				return ""
			}
		}

		if hd == "" && strings.HasPrefix(line, headPrefix) {
			parsedHead := headRE.FindStringSubmatch(line)

			if parsedHead == nil {
				errors <- fmt.Errorf(
					"Failed to extract sender ID from the header line %q",
					line)
				return ""
			}

			hd_temp := parsedHead[1]
			hd = hd_temp[len(hd_temp)-8:]
		}

		if fr == "" {
			if parsedFrom := fromRE.FindStringSubmatch(line); parsedFrom != nil {
				in_from = true
				fr_acc = strings.TrimSpace( parsedFrom[2] )

			} else if in_from && indentRE.MatchString(line) {
				fr_acc = fr_acc + strings.TrimSpace(line)

			} else if in_from {
				in_from = false

				parsedEmail := addrRE.FindStringSubmatch(fr_acc)
				if parsedEmail == nil {
					errors <- fmt.Errorf(
						"Failed to extract user name from the email address %q",
						fr_acc)
					return ""
				}

				fr = parsedEmail[1]
			}
		}

		if ts == "" || fr == "" || hd == "" {
			return ""
		}

		return fmt.Sprintf(format, ts, hd, fr)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package cmd

import (
	"bytes"
	"io"

	"github.com/spf13/cobra"

	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
	completion_long = `Output shell completion code for the given shell (bash or zsh).

This command prints shell code which must be evaluation to provide interactive
completion of kubectl commands.
`
	completion_example = `
$ source <(kubectl completion bash)

will load the kubectl completion code for bash. Note that this depends on the bash-completion
framework. It must be sourced before sourcing the kubectl completion, i.e. on the Mac:

$ brew install bash-completion
$ source $(brew --prefix)/etc/bash_completion
$ source <(kubectl completion bash)

If you use zsh*, the following will load kubectl zsh completion:

$ source <(kubectl completion zsh)

* zsh completions are only supported in versions of zsh >= 5.2`
)

var (
	completion_shells = map[string]func(out io.Writer, cmd *cobra.Command) error{
		"bash": runCompletionBash,
		"zsh":  runCompletionZsh,
	}
)

func NewCmdCompletion(f *cmdutil.Factory, out io.Writer) *cobra.Command {
	shells := []string{}
	for s := range completion_shells {
		shells = append(shells, s)
	}

	cmd := &cobra.Command{
		Use:     "completion SHELL",
		Short:   "Output shell completion code for the given shell (bash or zsh)",
		Long:    completion_long,
		Example: completion_example,
		Run: func(cmd *cobra.Command, args []string) {
			err := RunCompletion(f, out, cmd, args)
			cmdutil.CheckErr(err)
		},
		ValidArgs: shells,
	}

	return cmd
}

func RunCompletion(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
	if len(args) == 0 {
		return cmdutil.UsageError(cmd, "Shell not specified.")
	}
	if len(args) > 1 {
		return cmdutil.UsageError(cmd, "Too many arguments. Expected only the shell type.")
	}
	run, found := completion_shells[args[0]]
	if !found {
		return cmdutil.UsageError(cmd, "Unsupported shell type %q.", args[0])
	}

	return run(out, cmd.Parent())
}

func runCompletionBash(out io.Writer, kubectl *cobra.Command) error {
	return kubectl.GenBashCompletion(out)
}

func runCompletionZsh(out io.Writer, kubectl *cobra.Command) error {
	zsh_initialilzation := `# Copyright 2016 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__kubectl_bash_source() {
	alias shopt=':'
	alias _expand=_bash_expand
	alias _complete=_bash_comp
	emulate -L sh
	setopt kshglob noshglob braceexpand

	source "$@"
}

__kubectl_type() {
	# -t is not supported by zsh
	if [ "$1" == "-t" ]; then
		shift

		# fake Bash 4 to disable "complete -o nospace". Instead
		# "compopt +-o nospace" is used in the code to toggle trailing
		# spaces. We don't support that, but leave trailing spaces on
		# all the time
		if [ "$1" = "__kubectl_compopt" ]; then
			echo builtin
			return 0
		fi
	fi
	type "$@"
}

__kubectl_compgen() {
	local completions w
	completions=( $(compgen "$@") ) || return $?

	# filter by given word as prefix
	while [[ "$1" = -* && "$1" != -- ]]; do
		shift
		shift
	done
	if [[ "$1" == -- ]]; then
		shift
	fi
	for w in "${completions[@]}"; do
		if [[ "${w}" = "$1"* ]]; then
			echo "${w}"
		fi
	done
}

__kubectl_compopt() {
	true # don't do anything. Not supported by bashcompinit in zsh
}

__kubectl_declare() {
	if [ "$1" == "-F" ]; then
		whence -w "$@"
	else
		builtin declare "$@"
	fi
}

__kubectl_ltrim_colon_completions()
{
	if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
		# Remove colon-word prefix from COMPREPLY items
		local colon_word=${1%${1##*:}}
		local i=${#COMPREPLY[*]}
		while [[ $((--i)) -ge 0 ]]; do
			COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
		done
	fi
}

__kubectl_get_comp_words_by_ref() {
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[${COMP_CWORD}-1]}"
	words=("${COMP_WORDS[@]}")
	cword=("${COMP_CWORD[@]}")
}

__kubectl_filedir() {
	local RET OLD_IFS w qw

	__debug "_filedir $@ cur=$cur"
	if [[ "$1" = \~* ]]; then
		# somehow does not work. Maybe, zsh does not call this at all
		eval echo "$1"
		return 0
	fi

	OLD_IFS="$IFS"
	IFS=$'\n'
	if [ "$1" = "-d" ]; then
		shift
		RET=( $(compgen -d) )
	else
		RET=( $(compgen -f) )
	fi
	IFS="$OLD_IFS"

	IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"

	for w in ${RET[@]}; do
		if [[ ! "${w}" = "${cur}"* ]]; then
			continue
		fi
		if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
			qw="$(__kubectl_quote "${w}")"
			if [ -d "${w}" ]; then
				COMPREPLY+=("${qw}/")
			else
				COMPREPLY+=("${qw}")
			fi
		fi
	done
}

__kubectl_quote() {
    if [[ $1 == \'* || $1 == \"* ]]; then
        # Leave out first character
        printf %q "${1:1}"
    else
    	printf %q "$1"
    fi
}

autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit

# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
	LWORD='\<'
	RWORD='\>'
fi

__kubectl_bash_source <(sed \
	-e 's/declare -F/whence -w/' \
	-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
	-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
	-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
	-e "s/${LWORD}_filedir${RWORD}/__kubectl_filedir/g" \
	-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__kubectl_get_comp_words_by_ref/g" \
	-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__kubectl_ltrim_colon_completions/g" \
	-e "s/${LWORD}compgen${RWORD}/__kubectl_compgen/g" \
	-e "s/${LWORD}compopt${RWORD}/__kubectl_compopt/g" \
	-e "s/${LWORD}declare${RWORD}/__kubectl_declare/g" \
	-e "s/\\\$(type${RWORD}/\$(__kubectl_type/g" \
	<<'BASH_COMPLETION_EOF'
`
	out.Write([]byte(zsh_initialilzation))

	buf := new(bytes.Buffer)
	kubectl.GenBashCompletion(buf)
	out.Write(buf.Bytes())

	zsh_tail := `
BASH_COMPLETION_EOF
)
`
	out.Write([]byte(zsh_tail))
	return nil
}
<commit_msg>UPSTREAM: 32142: remove call to compinit in zsh completion output<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package cmd

import (
	"bytes"
	"io"

	"github.com/spf13/cobra"

	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
	completion_long = `Output shell completion code for the given shell (bash or zsh).

This command prints shell code which must be evaluation to provide interactive
completion of kubectl commands.
`
	completion_example = `
$ source <(kubectl completion bash)

will load the kubectl completion code for bash. Note that this depends on the bash-completion
framework. It must be sourced before sourcing the kubectl completion, i.e. on the Mac:

$ brew install bash-completion
$ source $(brew --prefix)/etc/bash_completion
$ source <(kubectl completion bash)

If you use zsh*, the following will load kubectl zsh completion:

$ source <(kubectl completion zsh)

* zsh completions are only supported in versions of zsh >= 5.2`
)

var (
	completion_shells = map[string]func(out io.Writer, cmd *cobra.Command) error{
		"bash": runCompletionBash,
		"zsh":  runCompletionZsh,
	}
)

func NewCmdCompletion(f *cmdutil.Factory, out io.Writer) *cobra.Command {
	shells := []string{}
	for s := range completion_shells {
		shells = append(shells, s)
	}

	cmd := &cobra.Command{
		Use:     "completion SHELL",
		Short:   "Output shell completion code for the given shell (bash or zsh)",
		Long:    completion_long,
		Example: completion_example,
		Run: func(cmd *cobra.Command, args []string) {
			err := RunCompletion(f, out, cmd, args)
			cmdutil.CheckErr(err)
		},
		ValidArgs: shells,
	}

	return cmd
}

func RunCompletion(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
	if len(args) == 0 {
		return cmdutil.UsageError(cmd, "Shell not specified.")
	}
	if len(args) > 1 {
		return cmdutil.UsageError(cmd, "Too many arguments. Expected only the shell type.")
	}
	run, found := completion_shells[args[0]]
	if !found {
		return cmdutil.UsageError(cmd, "Unsupported shell type %q.", args[0])
	}

	return run(out, cmd.Parent())
}

func runCompletionBash(out io.Writer, kubectl *cobra.Command) error {
	return kubectl.GenBashCompletion(out)
}

func runCompletionZsh(out io.Writer, kubectl *cobra.Command) error {
	zsh_initialilzation := `# Copyright 2016 The Kubernetes Authors All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__kubectl_bash_source() {
	alias shopt=':'
	alias _expand=_bash_expand
	alias _complete=_bash_comp
	emulate -L sh
	setopt kshglob noshglob braceexpand

	source "$@"
}

__kubectl_type() {
	# -t is not supported by zsh
	if [ "$1" == "-t" ]; then
		shift

		# fake Bash 4 to disable "complete -o nospace". Instead
		# "compopt +-o nospace" is used in the code to toggle trailing
		# spaces. We don't support that, but leave trailing spaces on
		# all the time
		if [ "$1" = "__kubectl_compopt" ]; then
			echo builtin
			return 0
		fi
	fi
	type "$@"
}

__kubectl_compgen() {
	local completions w
	completions=( $(compgen "$@") ) || return $?

	# filter by given word as prefix
	while [[ "$1" = -* && "$1" != -- ]]; do
		shift
		shift
	done
	if [[ "$1" == -- ]]; then
		shift
	fi
	for w in "${completions[@]}"; do
		if [[ "${w}" = "$1"* ]]; then
			echo "${w}"
		fi
	done
}

__kubectl_compopt() {
	true # don't do anything. Not supported by bashcompinit in zsh
}

__kubectl_declare() {
	if [ "$1" == "-F" ]; then
		whence -w "$@"
	else
		builtin declare "$@"
	fi
}

__kubectl_ltrim_colon_completions()
{
	if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
		# Remove colon-word prefix from COMPREPLY items
		local colon_word=${1%${1##*:}}
		local i=${#COMPREPLY[*]}
		while [[ $((--i)) -ge 0 ]]; do
			COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
		done
	fi
}

__kubectl_get_comp_words_by_ref() {
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[${COMP_CWORD}-1]}"
	words=("${COMP_WORDS[@]}")
	cword=("${COMP_CWORD[@]}")
}

__kubectl_filedir() {
	local RET OLD_IFS w qw

	__debug "_filedir $@ cur=$cur"
	if [[ "$1" = \~* ]]; then
		# somehow does not work. Maybe, zsh does not call this at all
		eval echo "$1"
		return 0
	fi

	OLD_IFS="$IFS"
	IFS=$'\n'
	if [ "$1" = "-d" ]; then
		shift
		RET=( $(compgen -d) )
	else
		RET=( $(compgen -f) )
	fi
	IFS="$OLD_IFS"

	IFS="," __debug "RET=${RET[@]} len=${#RET[@]}"

	for w in ${RET[@]}; do
		if [[ ! "${w}" = "${cur}"* ]]; then
			continue
		fi
		if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then
			qw="$(__kubectl_quote "${w}")"
			if [ -d "${w}" ]; then
				COMPREPLY+=("${qw}/")
			else
				COMPREPLY+=("${qw}")
			fi
		fi
	done
}

__kubectl_quote() {
    if [[ $1 == \'* || $1 == \"* ]]; then
        # Leave out first character
        printf %q "${1:1}"
    else
    	printf %q "$1"
    fi
}

autoload -U +X bashcompinit && bashcompinit

# use word boundary patterns for BSD or GNU sed
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
	LWORD='\<'
	RWORD='\>'
fi

__kubectl_bash_source <(sed \
	-e 's/declare -F/whence -w/' \
	-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
	-e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \
	-e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \
	-e "s/${LWORD}_filedir${RWORD}/__kubectl_filedir/g" \
	-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__kubectl_get_comp_words_by_ref/g" \
	-e "s/${LWORD}__ltrim_colon_completions${RWORD}/__kubectl_ltrim_colon_completions/g" \
	-e "s/${LWORD}compgen${RWORD}/__kubectl_compgen/g" \
	-e "s/${LWORD}compopt${RWORD}/__kubectl_compopt/g" \
	-e "s/${LWORD}declare${RWORD}/__kubectl_declare/g" \
	-e "s/\\\$(type${RWORD}/\$(__kubectl_type/g" \
	<<'BASH_COMPLETION_EOF'
`
	out.Write([]byte(zsh_initialilzation))

	buf := new(bytes.Buffer)
	kubectl.GenBashCompletion(buf)
	out.Write(buf.Bytes())

	zsh_tail := `
BASH_COMPLETION_EOF
)
`
	out.Write([]byte(zsh_tail))
	return nil
}
<|endoftext|>


<commit_before>package tunnel

import (
	"github.com/golang/glog"
	"io"
	"sync"
)

type SockChan struct {
	id     uint32
	closed bool
	read   chan []byte
}

type ConnManager struct {
	chans    map[uint32]*SockChan
	write_ch chan []byte
	next_id  uint32
	lock     sync.RWMutex
}

func NewConnManager(write_ch chan []byte) *ConnManager {
	cm := new(ConnManager)
	cm.chans = make(map[uint32]*SockChan)
	cm.write_ch = write_ch
	cm.next_id = 1
	return cm
}

func (cm *ConnManager) newSockChan(rw io.ReadWriteCloser) *SockChan {
	sc := new(SockChan)
	sc.read = make(chan []byte, 128)

	cm.lock.Lock()
	defer cm.lock.Unlock()
	id := cm.next_id
	for {
		if _, ok := cm.chans[id]; !ok {
			cm.next_id += 1
			break
		}
		id += 1
		if id == 0 {
			id = 1
		}
	}
	sc.id = id
	sc.closed = false
	cm.chans[id] = sc
	return sc
}

func (cm *ConnManager) delSockChan(conn_id uint32) {
	cm.lock.Lock()
	delete(cm.chans, conn_id)
	cm.lock.Unlock()
}

func (cm *ConnManager) CloseConn(conn_id uint32) {
	cm.lock.RLock()
	sc := cm.chans[conn_id]
	cm.lock.RUnlock()

	if sc != nil {
		close(sc.read)
		cm.delSockChan(conn_id)
	}
}

func (cm *ConnManager) CloseAllConns() {
	cm.lock.Lock()
	for _, sc := range cm.chans {
		close(sc.read)
	}
	cm.chans = make(map[uint32]*SockChan)
	cm.lock.Unlock()
}

func (cm *ConnManager) WriteToLocalConn(conn_id uint32, data []byte) {
	defer func() {
		err := recover()
		if err != nil {
			glog.V(1).Infof("write to local panic: %v", err)
		}
	}()

	cm.lock.RLock()
	sc := cm.chans[conn_id]
	cm.lock.RUnlock()

	if sc != nil {
		sc.read <- data
	} else {
		glog.V(2).Infof("write to deled sock: %d", conn_id)
	}
}

func (cm *ConnManager) DoProxy(conn_type byte, addr []byte, port int, rw io.ReadWriteCloser) {
	defer rw.Close()

	sc := cm.newSockChan(rw)
	req := make([]byte, 12+len(addr))
	req[0] = PROTO_MAGIC
	req[1] = PACKET_NEW_CONN
	WriteN2(req, 2, uint16(4+len(addr)))
	WriteN4(req, 4, sc.id)
	req[8] = conn_type
	req[9] = byte(len(addr))
	WriteN2(req, 10, uint16(port))
	copy(req[12:], addr)
	cm.write_ch <- req

	cm.copyConn(sc, rw)
}

func (cm *ConnManager) copyConn(sc *SockChan, rw io.ReadWriteCloser) {
	exit_ch := make(chan bool, 1)
	go func() {
		for {
			bs := make([]byte, 2048)
			if n, err := rw.Read(bs[8:]); err == nil {
				bs[0] = PROTO_MAGIC
				bs[1] = PACKET_PROXY
				WriteN2(bs, 2, uint16(n))
				WriteN4(bs, 4, sc.id)
				cm.write_ch <- bs[:8+n]
			} else {
				glog.V(1).Infof("read local(%d) fail: %v", sc.id, err)
				exit_ch <- true
				return
			}
		}
	}()

for_loop:
	for {
		select {
		case data, ok := <-sc.read:
			if !ok {
				// closed via cm.CloseConn
				return
			}
			if _, err := rw.Write(data); err != nil {
				glog.V(1).Infof("write local(%d) fail: %v", sc.id, err)
				break for_loop
			}
		case <-exit_ch:
			break for_loop
		}
	}

	bs := make([]byte, 8)
	bs[0] = PROTO_MAGIC
	bs[1] = PACKET_CLOSE_CONN
	WriteN2(bs, 2, 0)
	WriteN4(bs, 4, sc.id)
	cm.write_ch <- bs
	cm.delSockChan(sc.id)
	close(sc.read)
	glog.V(1).Infof("local(%d) closed", sc.id)
}
<commit_msg>local closed fix<commit_after>package tunnel

import (
	"github.com/golang/glog"
	"io"
	"sync"
)

type SockChan struct {
	id     uint32
	closed bool
	read   chan []byte
}

type ConnManager struct {
	chans    map[uint32]*SockChan
	write_ch chan []byte
	next_id  uint32
	lock     sync.RWMutex
}

func NewConnManager(write_ch chan []byte) *ConnManager {
	cm := new(ConnManager)
	cm.chans = make(map[uint32]*SockChan)
	cm.write_ch = write_ch
	cm.next_id = 1
	return cm
}

func (cm *ConnManager) newSockChan(rw io.ReadWriteCloser) *SockChan {
	sc := new(SockChan)
	sc.read = make(chan []byte, 128)

	cm.lock.Lock()
	defer cm.lock.Unlock()
	id := cm.next_id
	for {
		if _, ok := cm.chans[id]; !ok {
			cm.next_id += 1
			break
		}
		id += 1
		if id == 0 {
			id = 1
		}
	}
	sc.id = id
	sc.closed = false
	cm.chans[id] = sc
	return sc
}

func (cm *ConnManager) delSockChan(conn_id uint32) {
	cm.lock.Lock()
	delete(cm.chans, conn_id)
	cm.lock.Unlock()
}

func (cm *ConnManager) CloseConn(conn_id uint32) {
	cm.lock.RLock()
	sc := cm.chans[conn_id]
	cm.lock.RUnlock()

	if sc != nil {
		sc.closed = true
		close(sc.read)
		cm.delSockChan(conn_id)
	}
}

func (cm *ConnManager) CloseAllConns() {
	cm.lock.Lock()
	for _, sc := range cm.chans {
		close(sc.read)
	}
	cm.chans = make(map[uint32]*SockChan)
	cm.lock.Unlock()
}

func (cm *ConnManager) WriteToLocalConn(conn_id uint32, data []byte) {
	defer func() {
		err := recover()
		if err != nil {
			glog.V(1).Infof("write to local panic: %v", err)
		}
	}()

	cm.lock.RLock()
	sc := cm.chans[conn_id]
	cm.lock.RUnlock()

	if sc != nil {
		sc.read <- data
	} else {
		glog.V(2).Infof("write to deled sock: %d", conn_id)
	}
}

func (cm *ConnManager) DoProxy(conn_type byte, addr []byte, port int, rw io.ReadWriteCloser) {
	defer rw.Close()

	sc := cm.newSockChan(rw)
	req := make([]byte, 12+len(addr))
	req[0] = PROTO_MAGIC
	req[1] = PACKET_NEW_CONN
	WriteN2(req, 2, uint16(4+len(addr)))
	WriteN4(req, 4, sc.id)
	req[8] = conn_type
	req[9] = byte(len(addr))
	WriteN2(req, 10, uint16(port))
	copy(req[12:], addr)
	cm.write_ch <- req

	cm.copyConn(sc, rw)
}

func (cm *ConnManager) copyConn(sc *SockChan, rw io.ReadWriteCloser) {
	exit_ch := make(chan bool, 1)
	go func() {
		for {
			bs := make([]byte, 2048)
			if n, err := rw.Read(bs[8:]); err == nil {
				bs[0] = PROTO_MAGIC
				bs[1] = PACKET_PROXY
				WriteN2(bs, 2, uint16(n))
				WriteN4(bs, 4, sc.id)
				cm.write_ch <- bs[:8+n]
			} else {
				glog.V(1).Infof("read local(%d) fail: %v", sc.id, err)
				exit_ch <- true
				return
			}
		}
	}()

for_loop:
	for {
		select {
		case data, ok := <-sc.read:
			if !ok {
				// closed via cm.CloseConn
				return
			}
			if _, err := rw.Write(data); err != nil {
				glog.V(1).Infof("write local(%d) fail: %v", sc.id, err)
				break for_loop
			}
		case <-exit_ch:
			break for_loop
		}
	}

	bs := make([]byte, 8)
	bs[0] = PROTO_MAGIC
	bs[1] = PACKET_CLOSE_CONN
	WriteN2(bs, 2, 0)
	WriteN4(bs, 4, sc.id)
	cm.write_ch <- bs
	cm.CloseConn(sc.id)
	glog.V(1).Infof("local(%d) closed", sc.id)
}
<|endoftext|>


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

package faygo

import (
	"errors"
	"reflect"
	"sort"

	"github.com/henrylee2cn/faygo/apiware"
)

type (
	// Handler is the main Faygo Handler interface.
	Handler interface {
		Serve(ctx *Context) error
	}
	// APIHandler is the Faygo Handler interface,
	// which is implemented by a struct with API descriptor information.
	// It is an intelligent Handler of binding parameters.
	APIHandler interface {
		Handler
		APIDoc
	}
	// HandlerWithBody is the Faygo APIHandler interface but with DecodeBody method.
	HandlerWithBody interface {
		Handler
		Bodydecoder // Decode params from request body
	}
	// Bodydecoder is an interface to customize decoding operation
	Bodydecoder interface {
		Decode(dest reflect.Value, body []byte) error
	}
	// HandlerWithoutPath is handler without binding path parameter for middleware.
	HandlerWithoutPath interface {
		Handler
	}
	// APIDoc provides the API's note, result or parameters information.
	APIDoc interface {
		Doc() Doc
	}
	// ParamInfo is the request parameter information
	ParamInfo struct {
		Name     string      // Parameter name
		In       string      // The position of the parameter
		Required bool        // Is a required parameter
		Model    interface{} // A parameter value that is used to infer a value type and as a default value
		Desc     string      // Description
	}
	// Doc api information
	Doc struct {
		Note   string      `json:"note" xml:"note"`
		Return interface{} `json:"return,omitempty" xml:"return,omitempty"`
		Params []ParamInfo `json:"params,omitempty" xml:"params,omitempty"`
	}
	// Notes implementation notes of a response
	Notes struct {
		Note   string      `json:"note" xml:"note"`
		Return interface{} `json:"return,omitempty" xml:"return,omitempty"`
	}
	// JSONMsg is commonly used to return JSON format response.
	JSONMsg struct {
		Code int         `json:"code" xml:"code"`                     // the status code of the business process (required)
		Info interface{} `json:"info,omitempty" xml:"info,omitempty"` // response's schema and example value (optional)
	}
	// apiHandler is an intelligent Handler of binding parameters.
	apiHandler struct {
		paramsAPI *apiware.ParamsAPI
	}
	// HandlerFunc type is an adapter to allow the use of
	// ordinary functions as HTTP handlers.  If f is a function
	// with the appropriate signature, HandlerFunc(f) is a
	// Handler that calls f.
	HandlerFunc func(ctx *Context) error
	// HandlerChain is the chain of handlers for a request.
	HandlerChain []Handler
	// ErrorFunc replies to the request with the specified error message and HTTP code.
	// It does not otherwise end the request; the caller should ensure no further
	// writes are done to ctx.
	// The error message should be plain text.
	ErrorFunc func(ctx *Context, errStr string, status int)
	// BinderrorFunc is called when binding or validation apiHandler parameters are wrong.
	BinderrorFunc func(ctx *Context, err error)
)

// Serve implements the Handler, is like ServeHTTP but for Faygo.
func (h HandlerFunc) Serve(ctx *Context) error {
	return h(ctx)
}

// common errors
var (
	ErrNotStructPtr   = errors.New("handler must be a structure type or a structure pointer type")
	ErrNoParamHandler = errors.New("handler does not define any parameter tags")
)
var _ APIDoc = new(apiHandler)

// ToAPIHandler tries converts it to an *apiHandler.
func ToAPIHandler(handler Handler) (*apiHandler, error) {
	v := reflect.Indirect(reflect.ValueOf(handler))
	if v.Kind() != reflect.Struct {
		return nil, ErrNotStructPtr
	}

	var structPointer = v.Addr().Interface()
	var bodydecoder = global.bodydecoder
	if h, ok := structPointer.(HandlerWithBody); ok {
		bodydecoder = h.Decode
	}

	paramsAPI, err := apiware.NewParamsAPI(structPointer, global.paramNameMapper, bodydecoder)
	if err != nil {
		return nil, err
	}
	if paramsAPI.Number() == 0 {
		return nil, ErrNoParamHandler
	}

	// Reduce the creation of unnecessary field paramValues.
	return &apiHandler{
		paramsAPI: paramsAPI,
	}, nil
}

// IsHandlerWithoutPath verifies that the Handler is an HandlerWithoutPath.
func IsHandlerWithoutPath(handler Handler) bool {
	v := reflect.Indirect(reflect.ValueOf(handler))
	if v.Kind() != reflect.Struct {
		return true
	}
	paramsAPI, err := apiware.NewParamsAPI(v.Addr().Interface(), nil, nil)
	if err != nil {
		return true
	}
	for _, param := range paramsAPI.Params() {
		if param.In() == "path" {
			return false
		}
	}
	return true
}

// Serve implements the APIHandler.
// creates a new `*apiHandler`;
// binds the request path params to `apiHandler.handler`;
// calls Handler.Serve() method.
func (h *apiHandler) Serve(ctx *Context) error {
	obj, err := h.paramsAPI.BindNew(ctx.R, ctx.pathParams)
	if err != nil {
		global.binderrorFunc(ctx, err)
		return nil
	}
	return obj.(Handler).Serve(ctx)
}

// Doc returns the API's note, result or parameters information.
func (h *apiHandler) Doc() Doc {
	var doc Doc
	if d, ok := h.paramsAPI.Raw().(APIDoc); ok {
		doc = d.Doc()
	}
	for _, param := range h.paramsAPI.Params() {
		var had bool
		var info = ParamInfo{
			Name:     param.Name(),
			In:       param.In(),
			Required: param.IsRequired(),
			Desc:     param.Description(),
			Model:    param.Raw(),
		}
		for i, p := range doc.Params {
			if p.Name == info.Name {
				doc.Params[i] = info
				had = true
				break
			}
		}
		if !had {
			doc.Params = append(doc.Params, info)
		}
	}
	return doc
}

// Get distinct and sorted parameters information.
func distinctAndSortedParamInfos(infos []ParamInfo) []ParamInfo {
	infoMap := make(map[string]ParamInfo, len(infos))
	ks := make([]string, 0, len(infos))
	for _, info := range infos {
		k := info.Name + "<\r-\n-\t>" + info.In
		ks = append(ks, k)
		// Filter duplicate parameters, and maximize access to information.
		if newinfo, ok := infoMap[k]; ok {
			if !newinfo.Required && info.Required {
				newinfo.Required = info.Required
			}
			if len(newinfo.Desc) == 0 && len(info.Desc) > 0 {
				newinfo.Desc = info.Desc
			}
			infoMap[k] = newinfo
			continue
		}
		infoMap[k] = info
	}
	sort.Strings(ks)
	newinfos := make([]ParamInfo, 0, len(ks))
	for _, k := range ks {
		newinfos = append(newinfos, infoMap[k])
	}
	return newinfos
}
<commit_msg>fix bug<commit_after>// Copyright 2016 HenryLee. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package faygo

import (
	"errors"
	"reflect"
	"sort"

	"github.com/henrylee2cn/faygo/apiware"
)

type (
	// Handler is the main Faygo Handler interface.
	Handler interface {
		Serve(ctx *Context) error
	}
	// APIHandler is the Faygo Handler interface,
	// which is implemented by a struct with API descriptor information.
	// It is an intelligent Handler of binding parameters.
	APIHandler interface {
		Handler
		APIDoc
	}
	// HandlerWithBody is the Faygo APIHandler interface but with DecodeBody method.
	HandlerWithBody interface {
		Handler
		Bodydecoder // Decode params from request body
	}
	// Bodydecoder is an interface to customize decoding operation
	Bodydecoder interface {
		Decode(dest reflect.Value, body []byte) error
	}
	// HandlerWithoutPath is handler without binding path parameter for middleware.
	HandlerWithoutPath interface {
		Handler
	}
	// APIDoc provides the API's note, result or parameters information.
	APIDoc interface {
		Doc() Doc
	}
	// ParamInfo is the request parameter information
	ParamInfo struct {
		Name     string      // Parameter name
		In       string      // The position of the parameter
		Required bool        // Is a required parameter
		Model    interface{} // A parameter value that is used to infer a value type and as a default value
		Desc     string      // Description
	}
	// Doc api information
	Doc struct {
		Note   string      `json:"note" xml:"note"`
		Return interface{} `json:"return,omitempty" xml:"return,omitempty"`
		Params []ParamInfo `json:"params,omitempty" xml:"params,omitempty"`
	}
	// Notes implementation notes of a response
	Notes struct {
		Note   string      `json:"note" xml:"note"`
		Return interface{} `json:"return,omitempty" xml:"return,omitempty"`
	}
	// JSONMsg is commonly used to return JSON format response.
	JSONMsg struct {
		Code int         `json:"code" xml:"code"`                     // the status code of the business process (required)
		Info interface{} `json:"info,omitempty" xml:"info,omitempty"` // response's schema and example value (optional)
	}
	// apiHandler is an intelligent Handler of binding parameters.
	apiHandler struct {
		paramsAPI *apiware.ParamsAPI
	}
	// HandlerFunc type is an adapter to allow the use of
	// ordinary functions as HTTP handlers.  If f is a function
	// with the appropriate signature, HandlerFunc(f) is a
	// Handler that calls f.
	HandlerFunc func(ctx *Context) error
	// HandlerChain is the chain of handlers for a request.
	HandlerChain []Handler
	// ErrorFunc replies to the request with the specified error message and HTTP code.
	// It does not otherwise end the request; the caller should ensure no further
	// writes are done to ctx.
	// The error message should be plain text.
	ErrorFunc func(ctx *Context, errStr string, status int)
	// BinderrorFunc is called when binding or validation apiHandler parameters are wrong.
	BinderrorFunc func(ctx *Context, err error)
)

// Serve implements the Handler, is like ServeHTTP but for Faygo.
func (h HandlerFunc) Serve(ctx *Context) error {
	return h(ctx)
}

// common errors
var (
	ErrNotStructPtr   = errors.New("handler must be a structure type or a structure pointer type")
	ErrNoParamHandler = errors.New("handler does not define any parameter tags")
)
var _ APIDoc = new(apiHandler)

// ToAPIHandler tries converts it to an *apiHandler.
func ToAPIHandler(handler Handler) (*apiHandler, error) {
	v := reflect.Indirect(reflect.ValueOf(handler))
	if v.Kind() != reflect.Struct {
		return nil, ErrNotStructPtr
	}

	var structPointer = v.Addr().Interface()
	var bodydecoder = global.bodydecoder
	if h, ok := structPointer.(HandlerWithBody); ok {
		bodydecoder = h.Decode
	}

	paramsAPI, err := apiware.NewParamsAPI(structPointer, global.paramNameMapper, bodydecoder)
	if err != nil {
		return nil, err
	}
	if paramsAPI.Number() == 0 {
		return nil, ErrNoParamHandler
	}

	// Reduce the creation of unnecessary field paramValues.
	return &apiHandler{
		paramsAPI: paramsAPI,
	}, nil
}

// IsHandlerWithoutPath verifies that the Handler is an HandlerWithoutPath.
func IsHandlerWithoutPath(handler Handler) bool {
	v := reflect.Indirect(reflect.ValueOf(handler))
	if v.Kind() != reflect.Struct {
		return true
	}
	paramsAPI, err := apiware.NewParamsAPI(v.Addr().Interface(), nil, nil)
	if err != nil {
		return true
	}
	for _, param := range paramsAPI.Params() {
		if param.In() == "path" {
			return false
		}
	}
	return true
}

// Serve implements the APIHandler.
// creates a new `*apiHandler`;
// binds the request path params to `apiHandler.handler`;
// calls Handler.Serve() method.
func (h *apiHandler) Serve(ctx *Context) error {
	obj, err := h.paramsAPI.BindNew(ctx.R, ctx.pathParams)
	if err != nil {
		global.binderrorFunc(ctx, err)
		ctx.Stop()
		return nil
	}
	return obj.(Handler).Serve(ctx)
}

// Doc returns the API's note, result or parameters information.
func (h *apiHandler) Doc() Doc {
	var doc Doc
	if d, ok := h.paramsAPI.Raw().(APIDoc); ok {
		doc = d.Doc()
	}
	for _, param := range h.paramsAPI.Params() {
		var had bool
		var info = ParamInfo{
			Name:     param.Name(),
			In:       param.In(),
			Required: param.IsRequired(),
			Desc:     param.Description(),
			Model:    param.Raw(),
		}
		for i, p := range doc.Params {
			if p.Name == info.Name {
				doc.Params[i] = info
				had = true
				break
			}
		}
		if !had {
			doc.Params = append(doc.Params, info)
		}
	}
	return doc
}

// Get distinct and sorted parameters information.
func distinctAndSortedParamInfos(infos []ParamInfo) []ParamInfo {
	infoMap := make(map[string]ParamInfo, len(infos))
	ks := make([]string, 0, len(infos))
	for _, info := range infos {
		k := info.Name + "<\r-\n-\t>" + info.In
		ks = append(ks, k)
		// Filter duplicate parameters, and maximize access to information.
		if newinfo, ok := infoMap[k]; ok {
			if !newinfo.Required && info.Required {
				newinfo.Required = info.Required
			}
			if len(newinfo.Desc) == 0 && len(info.Desc) > 0 {
				newinfo.Desc = info.Desc
			}
			infoMap[k] = newinfo
			continue
		}
		infoMap[k] = info
	}
	sort.Strings(ks)
	newinfos := make([]ParamInfo, 0, len(ks))
	for _, k := range ks {
		newinfos = append(newinfos, infoMap[k])
	}
	return newinfos
}
<|endoftext|>


<commit_before>// Copyright (C) 2016 JT Olds
// See LICENSE for copying information

package webhelp

import (
	"net/http"

	"github.com/spacemonkeygo/errors/errhttp"
	"github.com/spacemonkeygo/spacelog"
	"golang.org/x/net/context"
)

var (
	logger = spacelog.GetLogger()
)

// Handler is extended to include a Context, a ResponseWriter with some useful
// additional methods, and return an error. Errors can be tagged with status
// codes and messages using github.com/spacemonkeygo/errors/errhttp.
type Handler interface {
	HandleHTTP(ctx context.Context, w ResponseWriter, r *http.Request) error
}

type HandlerFunc func(ctx context.Context, w ResponseWriter,
	r *http.Request) error

func (f HandlerFunc) HandleHTTP(ctx context.Context, w ResponseWriter,
	r *http.Request) error {
	return f(ctx, w, r)
}

// Base turns a webhelp.Handler into a http.Handler. You specify the root
// webhelp.Handler and an optional error recovery function.
type Base struct {
	// Root must be specified
	Root Handler

	// ErrHandler is optional. If unspecified, http.Error will be used with
	// the errhttp.GetErrorBody and errhttp.GetStatusCode values.
	ErrHandler func(w ResponseWriter, r *http.Request, err error)
}

func (b Base) handleError(w ResponseWriter, r *http.Request, err error) {
	if err == nil || w.WroteHeader() {
		return
	}
	if b.ErrHandler != nil {
		b.ErrHandler(w, r, err)
		return
	}
	http.Error(w, errhttp.GetErrorBody(err),
		errhttp.GetStatusCode(err, http.StatusInternalServerError))
}

func (b Base) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	nw, ok := w.(ResponseWriter)
	if !ok {
		nw = wrapResponseWriter(w)
	}
	if b.Root == nil {
		b.handleError(nw, r, ErrInternalServerError.New("No root handler installed"))
		return
	}
	ctx := context.Background()
	if cnw, ok := nw.(http.CloseNotifier); ok {
		doneChan := make(chan bool)
		defer close(doneChan)
		closeChan := cnw.CloseNotify()
		var cancelFunc func()
		ctx, cancelFunc = context.WithCancel(ctx)
		go func() {
			select {
			case <-doneChan:
				cancelFunc()
			case <-closeChan:
				cancelFunc()
			}
		}()
	}
	b.handleError(nw, r, b.Root.HandleHTTP(ctx, nw, r))
}

type loggingHandler struct {
	h Handler
}

// LoggingHandler takes a Handler and makes it log requests
func LoggingHandler(h Handler) Handler {
	return loggingHandler{h: h}
}

func (lh loggingHandler) HandleHTTP(ctx context.Context, w ResponseWriter,
	r *http.Request) error {
	logger.Noticef("%s %s", r.Method, r.RequestURI)
	err := lh.h.HandleHTTP(ctx, w, r)
	if err != nil {
		logger.Errorf("%s %s: %v", r.Method, r.RequestURI, err)
	}
	return err
}

func (lh loggingHandler) Routes(cb func(method, path string,
	annotations []string)) {
	Routes(lh.h, cb)
}

var _ RouteLister = loggingHandler{}

// StandardHandler takes an http.Handler and turns it into a webhelp.Handler
func StandardHandler(h http.Handler) Handler {
	return HandlerFunc(func(ctx context.Context, w ResponseWriter,
		r *http.Request) error {
		h.ServeHTTP(w, r)
		return nil
	})
}
<commit_msg>log swallowed errors<commit_after>// Copyright (C) 2016 JT Olds
// See LICENSE for copying information

package webhelp

import (
	"net/http"

	"github.com/spacemonkeygo/errors/errhttp"
	"github.com/spacemonkeygo/spacelog"
	"golang.org/x/net/context"
)

var (
	logger = spacelog.GetLogger()
)

// Handler is extended to include a Context, a ResponseWriter with some useful
// additional methods, and return an error. Errors can be tagged with status
// codes and messages using github.com/spacemonkeygo/errors/errhttp.
type Handler interface {
	HandleHTTP(ctx context.Context, w ResponseWriter, r *http.Request) error
}

type HandlerFunc func(ctx context.Context, w ResponseWriter,
	r *http.Request) error

func (f HandlerFunc) HandleHTTP(ctx context.Context, w ResponseWriter,
	r *http.Request) error {
	return f(ctx, w, r)
}

// Base turns a webhelp.Handler into a http.Handler. You specify the root
// webhelp.Handler and an optional error recovery function.
type Base struct {
	// Root must be specified
	Root Handler

	// ErrHandler is optional. If unspecified, http.Error will be used with
	// the errhttp.GetErrorBody and errhttp.GetStatusCode values.
	ErrHandler func(w ResponseWriter, r *http.Request, err error)
}

func (b Base) handleError(w ResponseWriter, r *http.Request, err error) {
	if err == nil {
		return
	}
	if w.WroteHeader() {
		logger.Errore(err)
		return
	}
	if b.ErrHandler != nil {
		b.ErrHandler(w, r, err)
		return
	}
	http.Error(w, errhttp.GetErrorBody(err),
		errhttp.GetStatusCode(err, http.StatusInternalServerError))
}

func (b Base) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	nw, ok := w.(ResponseWriter)
	if !ok {
		nw = wrapResponseWriter(w)
	}
	if b.Root == nil {
		b.handleError(nw, r, ErrInternalServerError.New("No root handler installed"))
		return
	}
	ctx := context.Background()
	if cnw, ok := nw.(http.CloseNotifier); ok {
		doneChan := make(chan bool)
		defer close(doneChan)
		closeChan := cnw.CloseNotify()
		var cancelFunc func()
		ctx, cancelFunc = context.WithCancel(ctx)
		go func() {
			select {
			case <-doneChan:
				cancelFunc()
			case <-closeChan:
				cancelFunc()
			}
		}()
	}
	b.handleError(nw, r, b.Root.HandleHTTP(ctx, nw, r))
}

type loggingHandler struct {
	h Handler
}

// LoggingHandler takes a Handler and makes it log requests
func LoggingHandler(h Handler) Handler {
	return loggingHandler{h: h}
}

func (lh loggingHandler) HandleHTTP(ctx context.Context, w ResponseWriter,
	r *http.Request) error {
	logger.Noticef("%s %s", r.Method, r.RequestURI)
	err := lh.h.HandleHTTP(ctx, w, r)
	if err != nil {
		logger.Errorf("%s %s: %v", r.Method, r.RequestURI, err)
	}
	return err
}

func (lh loggingHandler) Routes(cb func(method, path string,
	annotations []string)) {
	Routes(lh.h, cb)
}

var _ RouteLister = loggingHandler{}

// StandardHandler takes an http.Handler and turns it into a webhelp.Handler
func StandardHandler(h http.Handler) Handler {
	return HandlerFunc(func(ctx context.Context, w ResponseWriter,
		r *http.Request) error {
		h.ServeHTTP(w, r)
		return nil
	})
}
<|endoftext|>


<commit_before>package minio

import (
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
)

func GatewayHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "Gateway")
}

func RegisterGatewayHandlers(router *mux.Router) {
	router.HandleFunc("/gateway/rpc", GatewayHandler)
}
<commit_msg>Adding bucket and object route in gateway<commit_after>package minio

import (
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
)

func GatewayHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "Gateway")
}

func GatewayGetObjectHandler(w http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	bucket := vars["bucket"]
	object := vars["object"]
	fmt.Fprintf(w, "bucket: "+bucket)
	fmt.Fprintf(w, "\r")
	fmt.Fprintf(w, "object: "+object)
}

func RegisterGatewayHandlers(router *mux.Router) {
	router.HandleFunc("/{bucket}/{object:.*}", GatewayGetObjectHandler).Methods("GET")
}
<|endoftext|>


<commit_before>package peer

import (
	"fmt"

	"golang.org/x/mobile/exp/sprite"
)

type Sprite struct {
	W              float32
	H              float32
	X              float32
	Y              float32
	R              float32
	touchListeners []*TouchListener
}

func (self *Sprite) AddTouchListener(listener TouchListener) {
	LogDebug("IN")
	self.touchListeners = append(self.touchListeners, &listener)
	LogDebug("OUT")
}

func (self *Sprite) RemoveAllTouchListener() {
	LogDebug("IN")
	self.touchListeners = nil
	LogDebug("OUT")
}

type SpriteNodePair struct {
	sprite *Sprite
	node   *sprite.Node
}

type SpriteContainer struct {
	spriteNodePairs []*SpriteNodePair
}

var spriteContainer *SpriteContainer

func GetSpriteContainer() *SpriteContainer {
	if spriteContainer == nil {
		spriteContainer = &SpriteContainer{}
	}
	return spriteContainer
}

func (self *SpriteContainer) Initialize() {
	LogDebug("IN")
	GetTouchPeer().AddTouchListener(self)
	LogDebug("OUT")
}

func (self *SpriteContainer) AddSprite(s *Sprite, subTex sprite.SubTex) {
	LogDebug("IN")
	var sn SpriteNodePair
	sn.sprite = s
	sn.node = GetGLPeer().newNode()
	self.spriteNodePairs = append(self.spriteNodePairs, &sn)
	GetGLPeer().eng.SetSubTex(sn.node, subTex)
	LogDebug("OUT")
}

func (self *SpriteContainer) RemoveSprites() {
	LogDebug("IN")
	self.spriteNodePairs = nil
	LogDebug("OUT")
}

func (self *SpriteContainer) OnTouchBegin(x, y float32) {
	LogDebug("IN")
	for i := range self.spriteNodePairs {
		listeners := self.spriteNodePairs[i].sprite.touchListeners
		for j := range listeners {
			listener := listeners[j]
			if listener == nil {
				fmt.Println("listener is nil!")
				continue
			}
			// TODO: check if the touched position is contained by sprite's bounding box
			(*listener).OnTouchBegin(x, y)
		}
	}
	LogDebug("OUT")
}
func (self *SpriteContainer) OnTouchMove(x, y float32) {
	LogDebug("IN")
	for i := range self.spriteNodePairs {
		listeners := self.spriteNodePairs[i].sprite.touchListeners
		for j := range listeners {
			listener := listeners[j]
			if listener == nil {
				fmt.Println("listener is nil!")
				continue
			}
			// TODO: check if the touched position is contained by sprite's bounding box
			(*listener).OnTouchMove(x, y)
		}
	}
	LogDebug("OUT")
}

func (self *SpriteContainer) OnTouchEnd(x, y float32) {
	LogDebug("IN")
	for i := range self.spriteNodePairs {
		listeners := self.spriteNodePairs[i].sprite.touchListeners
		for j := range listeners {
			listener := listeners[j]
			if listener == nil {
				fmt.Println("listener is nil!")
				continue
			}
			// TODO: check if the touched position is contained by sprite's bounding box
			(*listener).OnTouchEnd(x, y)
		}
	}
	LogDebug("OUT")
}
<commit_msg>[#20] implement isContain function<commit_after>package peer

import (
	"fmt"

	"golang.org/x/mobile/exp/sprite"
)

type Sprite struct {
	W              float32
	H              float32
	X              float32
	Y              float32
	R              float32
	touchListeners []*TouchListener
}

func (self *Sprite) AddTouchListener(listener TouchListener) {
	LogDebug("IN")
	self.touchListeners = append(self.touchListeners, &listener)
	LogDebug("OUT")
}

func (self *Sprite) RemoveAllTouchListener() {
	LogDebug("IN")
	self.touchListeners = nil
	LogDebug("OUT")
}

type SpriteNodePair struct {
	sprite *Sprite
	node   *sprite.Node
}

type SpriteContainer struct {
	spriteNodePairs []*SpriteNodePair
}

var spriteContainer *SpriteContainer

func GetSpriteContainer() *SpriteContainer {
	if spriteContainer == nil {
		spriteContainer = &SpriteContainer{}
	}
	return spriteContainer
}

func (self *SpriteContainer) Initialize() {
	LogDebug("IN")
	GetTouchPeer().AddTouchListener(self)
	LogDebug("OUT")
}

func (self *SpriteContainer) AddSprite(s *Sprite, subTex sprite.SubTex) {
	LogDebug("IN")
	var sn SpriteNodePair
	sn.sprite = s
	sn.node = GetGLPeer().newNode()
	self.spriteNodePairs = append(self.spriteNodePairs, &sn)
	GetGLPeer().eng.SetSubTex(sn.node, subTex)
	LogDebug("OUT")
}

func (self *SpriteContainer) RemoveSprites() {
	LogDebug("IN")
	self.spriteNodePairs = nil
	LogDebug("OUT")
}

func isContain(sprite *Sprite, x, y float32) bool {
	if x >= sprite.X &&
		x <= sprite.X+sprite.W &&
		y >= sprite.Y &&
		y <= sprite.Y+sprite.H {
		return true
	}
	return false
}

func (self *SpriteContainer) OnTouchBegin(x, y float32) {
	LogDebug("IN")
	for i := range self.spriteNodePairs {
		listeners := self.spriteNodePairs[i].sprite.touchListeners
		for j := range listeners {
			listener := listeners[j]
			if listener == nil {
				fmt.Println("listener is nil!")
				continue
			}

			if isContain(self.spriteNodePairs[i].sprite, x, y) {
				(*listener).OnTouchBegin(x, y)
			}
		}
	}
	LogDebug("OUT")
}
func (self *SpriteContainer) OnTouchMove(x, y float32) {
	LogDebug("IN")
	for i := range self.spriteNodePairs {
		listeners := self.spriteNodePairs[i].sprite.touchListeners
		for j := range listeners {
			listener := listeners[j]
			if listener == nil {
				fmt.Println("listener is nil!")
				continue
			}

			if isContain(self.spriteNodePairs[i].sprite, x, y) {
				(*listener).OnTouchMove(x, y)
			}
		}
	}
	LogDebug("OUT")
}

func (self *SpriteContainer) OnTouchEnd(x, y float32) {
	LogDebug("IN")
	for i := range self.spriteNodePairs {
		listeners := self.spriteNodePairs[i].sprite.touchListeners
		for j := range listeners {
			listener := listeners[j]
			if listener == nil {
				fmt.Println("listener is nil!")
				continue
			}

			if isContain(self.spriteNodePairs[i].sprite, x, y) {
				(*listener).OnTouchEnd(x, y)
			}
		}
	}
	LogDebug("OUT")
}
<|endoftext|>


<commit_before>package gcp

import (
	"crypto"
	"encoding/base64"
	"errors"
	"fmt"

	"github.com/lestrrat/go-pdebug"

	"google.golang.org/api/dns/v1"
)

// NewDNS creates a service that attempts to fulfill the dns-01
// challenge using Google CloudDNS.
func NewDNS(s *dns.Service, projectID, zoneName string) *CloudDNSComplete {
	return &CloudDNSComplete{
		Project: projectID,
		Service: s,
		Zone:    zoneName,
	}
}

func (c *CloudDNSComplete) lookupChallengeRecord(domain string) (*dns.ResourceRecordSet, error) {
	fqdn := "_acme-challenge." + domain + "."

	rrres, err := c.Service.ResourceRecordSets.List(c.Project, c.Zone).Do()
	if err != nil {
		return nil, err
	}

	for _, rr := range rrres.Rrsets {
		if rr.Kind != "dns#resourceRecordSet" {
			continue
		}

		if rr.Name != fqdn {
			continue
		}

		return rr, nil
	}
	return nil, errors.New("resource not found")
}

func (c *CloudDNSComplete) Cleanup(domain, token string) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("CloudDNSComplete.Cleanup(%s, %s)", domain, token).BindError(&err)
		defer g.End()
	}

	// Deleting the resource record set requires a pretty complete
	// set of data. As such, it's much easier if we just look it up
	// and use that data to set the Deletions field
	var del *dns.ResourceRecordSet
	if rr, err := c.lookupChallengeRecord(domain); err == nil {
		del = rr
	}

	if del == nil {
		// Nothing to do
		return nil
	}

	// Send this to CloudDNS to create DNS entry
	ch := dns.Change{
		Deletions: []*dns.ResourceRecordSet{del},
	}

	if _, err := c.Service.Changes.Create(c.Project, c.Zone, &ch).Do(); err != nil {
		return err
	}
	return nil
}

// Complete attempts to fulfill a dns-01 challenge using Google CloudDNS.
func (c *CloudDNSComplete) Complete(domain, token string) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("CloudDNSComplete.Complete(%s, %s)", domain, token).BindError(&err)
		defer g.End()
	}

	fqdn := "_acme-challenge." + domain + "."
	hasher := crypto.SHA256.New()
	fmt.Fprint(hasher, token)
	sum := hasher.Sum(nil)
	v := base64.RawURLEncoding.EncodeToString(sum)

	// Check if this record already exists
	var del *dns.ResourceRecordSet
	if rr, err := c.lookupChallengeRecord(domain); err == nil {
		del = rr
	}

	// Send this to CloudDNS to create DNS entry
	ch := dns.Change{
		Additions: []*dns.ResourceRecordSet{
			&dns.ResourceRecordSet{
				Kind:    "dns#resourceRecordSet",
				Name:    fqdn,
				Rrdatas: []string{v},
				Ttl:     10,
				Type:    "TXT",
			},
		},
	}

	if del != nil {
		ch.Deletions = []*dns.ResourceRecordSet{del}
	}

	if _, err := c.Service.Changes.Create(c.Project, c.Zone, &ch).Do(); err != nil {
		return err
	}

	return nil
}
<commit_msg>Check DNS entries before proceeding<commit_after>package gcp

import (
	"crypto"
	"encoding/base64"
	"errors"
	"fmt"
	"net"
	"time"

	"github.com/lestrrat/go-pdebug"

	"google.golang.org/api/dns/v1"
)

// NewDNS creates a service that attempts to fulfill the dns-01
// challenge using Google CloudDNS.
func NewDNS(s *dns.Service, projectID, zoneName string) *CloudDNSComplete {
	return &CloudDNSComplete{
		Project: projectID,
		Service: s,
		Zone:    zoneName,
	}
}

func (c *CloudDNSComplete) lookupChallengeRecord(domain string) (*dns.ResourceRecordSet, error) {
	fqdn := "_acme-challenge." + domain + "."

	rrres, err := c.Service.ResourceRecordSets.List(c.Project, c.Zone).Do()
	if err != nil {
		return nil, err
	}

	for _, rr := range rrres.Rrsets {
		if rr.Kind != "dns#resourceRecordSet" {
			continue
		}

		if rr.Name != fqdn {
			continue
		}

		return rr, nil
	}
	return nil, errors.New("resource not found")
}

func (c *CloudDNSComplete) Cleanup(domain, token string) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("CloudDNSComplete.Cleanup(%s, %s)", domain, token).BindError(&err)
		defer g.End()
	}

	// Deleting the resource record set requires a pretty complete
	// set of data. As such, it's much easier if we just look it up
	// and use that data to set the Deletions field
	var del *dns.ResourceRecordSet
	if rr, err := c.lookupChallengeRecord(domain); err == nil {
		del = rr
	}

	if del == nil {
		// Nothing to do
		return nil
	}

	// Send this to CloudDNS to create DNS entry
	ch := dns.Change{
		Deletions: []*dns.ResourceRecordSet{del},
	}

	if _, err := c.Service.Changes.Create(c.Project, c.Zone, &ch).Do(); err != nil {
		return err
	}
	return nil
}

// Complete attempts to fulfill a dns-01 challenge using Google CloudDNS.
func (c *CloudDNSComplete) Complete(domain, token string) (err error) {
	if pdebug.Enabled {
		g := pdebug.Marker("CloudDNSComplete.Complete(%s, %s)", domain, token).BindError(&err)
		defer g.End()
	}

	fqdn := "_acme-challenge." + domain + "."
	hasher := crypto.SHA256.New()
	fmt.Fprint(hasher, token)
	sum := hasher.Sum(nil)
	v := base64.RawURLEncoding.EncodeToString(sum)

	// Check if this record already exists
	var del *dns.ResourceRecordSet
	if rr, err := c.lookupChallengeRecord(domain); err == nil {
		del = rr
	}

	// Send this to CloudDNS to create DNS entry
	ch := dns.Change{
		Additions: []*dns.ResourceRecordSet{
			&dns.ResourceRecordSet{
				Kind:    "dns#resourceRecordSet",
				Name:    fqdn,
				Rrdatas: []string{v},
				Ttl:     10,
				Type:    "TXT",
			},
		},
	}

	if del != nil {
		ch.Deletions = []*dns.ResourceRecordSet{del}
	}

	if _, err := c.Service.Changes.Create(c.Project, c.Zone, &ch).Do(); err != nil {
		return err
	}

	// Wait for the record to be available
	timeout := time.After(30 * time.Second)
	ticker := time.Tick(time.Second)
	for {
		select {
		case <-timeout:
			return errors.New("timed out waiting for DNS record to be available")
		case <-ticker:
			txt, err := net.LookupTXT(fqdn)
			if err == nil {
				for _, txtv := range txt {
					if txtv == v {
						goto DnsReady
					}
				}
			}
		}
	}

DnsReady:
	return nil
}
<|endoftext|>