<commit_before>/*
Copyright 2019 The Rook 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 v1

import (
	rook "github.com/rook/rook/pkg/apis/rook.io/v1alpha2"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"strconv"
)

const (
	ResourcesKeyMgr    = "mgr"
	ResourcesKeyTarget = "target"

	hostLocalTimeVolName = "host-local-time"
	hostLocalTimePath    = "/etc/localtime"
)

// GetMgrResources returns the placement for the MGR service
func GetMgrResources(p rook.ResourceSpec) v1.ResourceRequirements {
	return p[ResourcesKeyMgr]
}

// GetTargetResources returns the placement for the Targets
func GetTargetResources(p rook.ResourceSpec) v1.ResourceRequirements {
	return p[ResourcesKeyTarget]
}

// GetInitiatorEnvArr returns array of EnvVar for all the Initiator based
// on configured resources and its profile
func GetInitiatorEnvArr(svctype string, embedded bool, chunkCacheSize resource.Quantity, resources v1.ResourceRequirements) []v1.EnvVar {
	retArr := []v1.EnvVar{}
	rMemLim := resources.Limits.Memory()
	if !rMemLim.IsZero() {
		// adjust chunk cache maximum size
		cacheSize := rMemLim.Value() * 75 / 100
		if embedded {
			if chunkCacheSize.IsZero() {
				// embedded default case, 100mb
				cacheSize = 100 * 1024 * 1024
			} else {
				if chunkCacheSize.CmpInt64(cacheSize) < 0 {
					// user wants to set custom that is less then 75% of total
					cacheSize = chunkCacheSize.Value()
				}
			}
		}

		if svctype == "target" {
			// adjust target's built-in cgroup
			cgroupTgt := rMemLim.Value() * 95 / 100
			if cgroupTgt <= cacheSize {
				// in targets case chunk cache only needed for
				// background operations. This was probably not
				// a user's intent, so adjust to a much lower values
				cacheSize = cgroupTgt / 8
			}
			retArr = append(retArr, v1.EnvVar{
				Name:  "CCOWD_CGROUP_MEMLIM",
				Value: strconv.FormatInt(cgroupTgt, 10),
			})
		} else if svctype == "isgw" {
			// ISGW doesn't need large chunk cache
			cacheSize = rMemLim.Value() * 25 / 100
		} else if svctype == "s3" || svctype == "swift" {
			// adjust restapi's node.js GC settings. This memory
			// will be shared among all the HTTP worker processes
			// and will be used to calculate optimal # of workers too.
			svcMem := rMemLim.Value() * 95 / 100
			if svcMem <= cacheSize {
				cacheSize = svcMem / 2
			}
			retArr = append(retArr, v1.EnvVar{
				Name:  "SVC_MEM_LIMIT",
				Value: strconv.FormatInt(svcMem, 10),
			})
			svcMemPerWorker := int64(1 * 1024 * 1024 * 1024)
			if embedded {
				svcMemPerWorker = int64(256 * 1024 * 1024)
			}
			if svcMem < svcMemPerWorker {
				svcMemPerWorker = svcMem / 2
			}
			retArr = append(retArr, v1.EnvVar{
				Name:  "SVC_MEM_PER_WORKER",
				Value: strconv.FormatInt(svcMemPerWorker, 10),
			})
		}

		retArr = append(retArr, v1.EnvVar{
			Name:  "CCOW_MEMORY_LIMIT",
			Value: strconv.FormatInt(cacheSize, 10),
		})
	}

	if embedded {
		retArr = append(retArr, v1.EnvVar{
			Name:  "CCOW_EMBEDDED",
			Value: "1",
		})
		retArr = append(retArr, v1.EnvVar{
			Name:  "JE_MALLOC_CONF",
			Value: "tcache:false",
		})
	}

	return retArr
}

func GetHostLocalTimeVolumeMount() v1.VolumeMount {
	return v1.VolumeMount{
		Name:      hostLocalTimeVolName,
		MountPath: hostLocalTimePath,
		ReadOnly:  true,
	}
}

func GetHostLocalTimeVolume() v1.Volume {
	return v1.Volume{
		Name: hostLocalTimeVolName,
		VolumeSource: v1.VolumeSource{
			HostPath: &v1.HostPathVolumeSource{
				Path: hostLocalTimePath,
			},
		},
	}
}
<commit_msg>edgefs: fix in resouse exposal code.<commit_after>/*
Copyright 2019 The Rook 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 v1

import (
	rook "github.com/rook/rook/pkg/apis/rook.io/v1alpha2"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"strconv"
)

const (
	ResourcesKeyMgr    = "mgr"
	ResourcesKeyTarget = "target"

	hostLocalTimeVolName = "host-local-time"
	hostLocalTimePath    = "/etc/localtime"
)

// GetMgrResources returns the placement for the MGR service
func GetMgrResources(p rook.ResourceSpec) v1.ResourceRequirements {
	return p[ResourcesKeyMgr]
}

// GetTargetResources returns the placement for the Targets
func GetTargetResources(p rook.ResourceSpec) v1.ResourceRequirements {
	return p[ResourcesKeyTarget]
}

// GetInitiatorEnvArr returns array of EnvVar for all the Initiator based
// on configured resources and its profile
func GetInitiatorEnvArr(svctype string, embedded bool, chunkCacheSize resource.Quantity, resources v1.ResourceRequirements) []v1.EnvVar {
	retArr := []v1.EnvVar{}
	rMemLim := resources.Limits.Memory()
	if !rMemLim.IsZero() {
		// adjust chunk cache maximum size
		cacheSize := rMemLim.Value() * 75 / 100
		if embedded {
			// embedded default case, 100mb
			cacheSize = 100 * 1024 * 1024
		}

		if !chunkCacheSize.IsZero() && chunkCacheSize.CmpInt64(cacheSize) < 0 {
			// user wants to set custom cache size
			// It might be lowered below
			cacheSize = chunkCacheSize.Value()
		}

		if svctype == "target" {
			// adjust target's built-in cgroup
			cgroupTgt := rMemLim.Value() * 95 / 100
			if cgroupTgt <= cacheSize {
				// in targets case chunk cache only needed for
				// background operations. This was probably not
				// a user's intent, so adjust to a much lower values
				cacheSize = cgroupTgt / 8
			}
			retArr = append(retArr, v1.EnvVar{
				Name:  "CCOWD_CGROUP_MEMLIM",
				Value: strconv.FormatInt(cgroupTgt, 10),
			})
		} else if svctype == "isgw" {
			// ISGW doesn't need large chunk cache
			cacheSize = rMemLim.Value() * 25 / 100
		} else if svctype == "s3" || svctype == "swift" {
			// adjust restapi's node.js GC settings. This memory
			// will be shared among all the HTTP worker processes
			// and will be used to calculate optimal # of workers too.
			svcMem := rMemLim.Value() * 95 / 100
			if svcMem <= cacheSize {
				cacheSize = svcMem / 2
			}
			retArr = append(retArr, v1.EnvVar{
				Name:  "SVC_MEM_LIMIT",
				Value: strconv.FormatInt(svcMem, 10),
			})
			svcMemPerWorker := int64(1 * 1024 * 1024 * 1024)
			if embedded {
				svcMemPerWorker = int64(256 * 1024 * 1024)
			}
			if svcMem < svcMemPerWorker {
				svcMemPerWorker = svcMem / 2
			}
			retArr = append(retArr, v1.EnvVar{
				Name:  "SVC_MEM_PER_WORKER",
				Value: strconv.FormatInt(svcMemPerWorker, 10),
			})
		}

		retArr = append(retArr, v1.EnvVar{
			Name:  "CCOW_MEMORY_LIMIT",
			Value: strconv.FormatInt(cacheSize, 10),
		})
	}
	rCpuLim := resources.Limits.Cpu()
	if !rCpuLim.IsZero() {
		retArr = append(retArr, v1.EnvVar{
			Name:  "SVC_CPU_LIMIT",
			Value: strconv.FormatInt(rCpuLim.Value(), 10),
		})
	}

	if embedded {
		retArr = append(retArr, v1.EnvVar{
			Name:  "CCOW_EMBEDDED",
			Value: "1",
		})
		retArr = append(retArr, v1.EnvVar{
			Name:  "JE_MALLOC_CONF",
			Value: "tcache:false",
		})
	}

	return retArr
}

func GetHostLocalTimeVolumeMount() v1.VolumeMount {
	return v1.VolumeMount{
		Name:      hostLocalTimeVolName,
		MountPath: hostLocalTimePath,
		ReadOnly:  true,
	}
}

func GetHostLocalTimeVolume() v1.Volume {
	return v1.Volume{
		Name: hostLocalTimeVolName,
		VolumeSource: v1.VolumeSource{
			HostPath: &v1.HostPathVolumeSource{
				Path: hostLocalTimePath,
			},
		},
	}
}
<|endoftext|>


<commit_before>package kloud

import (
	"fmt"
	"koding/kites/kloud/contexthelper/publickeys"
	"koding/kites/kloud/contexthelper/request"
	"koding/kites/kloud/eventer"
	"koding/kites/kloud/machinestate"
	"strings"
	"time"

	"github.com/koding/kite"
	"golang.org/x/net/context"
)

type ControlResult struct {
	EventId string `json:"eventId"`
}

type machineFunc func(context.Context, interface{}) error

// statePair defines a methods start and final states
type statePair struct {
	start machinestate.State
	final machinestate.State
}

var states = map[string]*statePair{
	"build":          &statePair{start: machinestate.Building, final: machinestate.Running},
	"start":          &statePair{start: machinestate.Starting, final: machinestate.Running},
	"stop":           &statePair{start: machinestate.Stopping, final: machinestate.Stopped},
	"destroy":        &statePair{start: machinestate.Terminating, final: machinestate.Terminated},
	"restart":        &statePair{start: machinestate.Rebooting, final: machinestate.Running},
	"resize":         &statePair{start: machinestate.Pending, final: machinestate.Running},
	"reinit":         &statePair{start: machinestate.Terminating, final: machinestate.Running},
	"createSnapshot": &statePair{start: machinestate.Snapshotting, final: machinestate.Running},
	"deleteSnapshot": &statePair{start: machinestate.Snapshotting, final: machinestate.Running},
}

// coreMethods is running and returning the response for the given machineFunc.
// This method is used to avoid duplicate codes in many codes (because we do
// the same steps for each of them).
func (k *Kloud) coreMethods(r *kite.Request, fn machineFunc) (result interface{}, reqErr error) {
	// calls with zero arguments causes args to be nil. Check it that we
	// don't get a beloved panic
	k.Log.Info("[DEBUG] Raw data: %s", string(r.Args.Raw))
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args struct {
		MachineId string
		Provider  string
	}

	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	k.Log.Info("[DEBUG] Received args %+v", args)

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	if args.Provider == "" {
		return nil, NewError(ErrProviderIsMissing)
	}

	k.track(args.Provider, args.MachineId, r.Method)

	// Lock the machine id so no one else can access it. It means this
	// kloud instance is now responsible for this machine id. Its basically
	// a distributed lock. It's unlocked when there is an error or if the
	// method call is finished (unlocking is done inside the responsible
	// method calls).
	if r.Method != "info" {
		if err := k.Locker.Lock(args.MachineId); err != nil {
			return nil, err
		}

		// if something goes wrong after step reset the document which is was
		// set in the by previous step by Locker.Lock(). If there is no error,
		// the lock will be unlocked in the respective method  function.
		defer func() {
			if reqErr != nil {
				// otherwise that means Locker.Lock or something else in
				// ControlFunc failed. Reset the lock again so it can be acquired by
				// others.
				k.Locker.Unlock(args.MachineId)
			}
		}()
	}

	provider, ok := k.providers[args.Provider]
	if !ok {
		return nil, NewError(ErrProviderNotFound)
	}

	p, ok := provider.(Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	ctx := request.NewContext(context.Background(), r)
	// add publicKeys to be deployed to the machine, the machine provider is
	// responsible of deploying it to the machine while building it.
	if k.PublicKeys != nil {
		ctx = publickeys.NewContext(ctx, k.PublicKeys)
	}

	if k.ContextCreator != nil {
		ctx = k.ContextCreator(ctx)
	}

	// each method has his own unique eventer
	eventId := r.Method + "-" + args.MachineId
	ev := k.NewEventer(eventId)
	ctx = eventer.NewContext(ctx, ev)

	machine, err := p.Machine(ctx, args.MachineId)
	if err != nil {
		return nil, err
	}

	stater, ok := machine.(Stater)
	if !ok {
		return nil, NewError(ErrStaterNotImplemented)
	}

	if stater.ProviderName() != args.Provider {
		return nil, NewError(ErrProviderIsWrong)
	}

	// Check if the given method is in valid methods of that current state. For
	// example if the method is "build", and the state is "stopped" than this
	// will return an error.
	if !methodIn(r.Method, stater.State().ValidMethods()...) {
		return nil, fmt.Errorf("%s not allowed for current state '%s'. Allowed methods are: %v",
			r.Method, strings.ToLower(stater.State().String()), stater.State().ValidMethods())
	}

	pair, ok := states[r.Method]
	if !ok {
		return nil, fmt.Errorf("no state pair available for %s", r.Method)
	}

	ev.Push(&eventer.Event{
		Message: r.Method + " started",
		Status:  pair.start,
	})

	// Start our core method in a goroutine to not block it for the client
	// side. However we do return an event id which is an unique for tracking
	// the current status of the running method.
	go func() {
		finalEvent := &eventer.Event{
			Message:    r.Method + " finished",
			Status:     pair.final,
			Percentage: 100,
		}

		k.Log.Info("[%s] ======> %s started (requester: %s, provider: %s)<======",
			args.MachineId, strings.ToUpper(r.Method), r.Username, args.Provider)
		start := time.Now()
		err := fn(ctx, machine)
		if err != nil {
			// don't pass the error directly to the eventer, mask it to avoid
			// error leaking to the client. We just log it here.
			k.Log.Error("[%s] ======> %s finished with error: '%s' (requester: %s, provider: %s) <======",
				args.MachineId, strings.ToUpper(r.Method), err, r.Username, args.Provider)

			finalEvent.Error = strings.ToTitle(r.Method) + " failed. Please contact support."

			// however, eventerErr is an error we want to pass explicitly to
			// the client side
			if eventerErr, ok := err.(*EventerError); ok {
				finalEvent.Error = eventerErr.Error()
			}

			finalEvent.Status = stater.State() // fallback to to old state
		} else {
			k.Log.Info("[%s] ======> %s finished (time: %s, requester: %s, provider: %s) <======",
				args.MachineId, strings.ToUpper(r.Method), time.Since(start), r.Username, args.Provider)
		}

		ev.Push(finalEvent)
		k.Locker.Unlock(args.MachineId)
	}()

	return ControlResult{
		EventId: eventId,
	}, nil
}

func (k *Kloud) GetMachine(r *kite.Request) (resp interface{}, reqErr error) {
	// calls with zero arguments causes args to be nil. Check it that we
	// don't get a beloved panic
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args struct {
		MachineId string
		Provider  string
	}

	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	// Lock the machine id so no one else can access it. It means this
	// kloud instance is now responsible for this machine id. Its basically
	// a distributed lock. It's unlocked when there is an error or if the
	// method call is finished (unlocking is done inside the responsible
	// method calls).
	if r.Method != "info" {
		if err := k.Locker.Lock(args.MachineId); err != nil {
			return nil, err
		}

		// if something goes wrong after step reset the document which is was
		// set in the by previous step by Locker.Lock(). If there is no error,
		// the lock will be unlocked in the respective method  function.
		defer func() {
			if reqErr != nil {
				// otherwise that means Locker.Lock or something else in
				// ControlFunc failed. Reset the lock again so it can be acquired by
				// others.
				k.Locker.Unlock(args.MachineId)
			}
		}()
	}

	provider, ok := k.providers[args.Provider]
	if !ok {
		return nil, NewError(ErrProviderAvailable)
	}

	p, ok := provider.(Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	ctx := request.NewContext(context.Background(), r)

	return p.Machine(ctx, args.MachineId)
}

// methodIn checks if the method exist in the given methods
func methodIn(method string, methods ...string) bool {
	for _, m := range methods {
		if method == m {
			return true
		}
	}
	return false
}
<commit_msg>kloud: remove non used logs<commit_after>package kloud

import (
	"fmt"
	"koding/kites/kloud/contexthelper/publickeys"
	"koding/kites/kloud/contexthelper/request"
	"koding/kites/kloud/eventer"
	"koding/kites/kloud/machinestate"
	"strings"
	"time"

	"github.com/koding/kite"
	"golang.org/x/net/context"
)

type ControlResult struct {
	EventId string `json:"eventId"`
}

type machineFunc func(context.Context, interface{}) error

// statePair defines a methods start and final states
type statePair struct {
	start machinestate.State
	final machinestate.State
}

var states = map[string]*statePair{
	"build":          &statePair{start: machinestate.Building, final: machinestate.Running},
	"start":          &statePair{start: machinestate.Starting, final: machinestate.Running},
	"stop":           &statePair{start: machinestate.Stopping, final: machinestate.Stopped},
	"destroy":        &statePair{start: machinestate.Terminating, final: machinestate.Terminated},
	"restart":        &statePair{start: machinestate.Rebooting, final: machinestate.Running},
	"resize":         &statePair{start: machinestate.Pending, final: machinestate.Running},
	"reinit":         &statePair{start: machinestate.Terminating, final: machinestate.Running},
	"createSnapshot": &statePair{start: machinestate.Snapshotting, final: machinestate.Running},
	"deleteSnapshot": &statePair{start: machinestate.Snapshotting, final: machinestate.Running},
}

// coreMethods is running and returning the response for the given machineFunc.
// This method is used to avoid duplicate codes in many codes (because we do
// the same steps for each of them).
func (k *Kloud) coreMethods(r *kite.Request, fn machineFunc) (result interface{}, reqErr error) {
	// calls with zero arguments causes args to be nil. Check it that we
	// don't get a beloved panic
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args struct {
		MachineId string
		Provider  string
	}

	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	if args.Provider == "" {
		return nil, NewError(ErrProviderIsMissing)
	}

	k.track(args.Provider, args.MachineId, r.Method)

	// Lock the machine id so no one else can access it. It means this
	// kloud instance is now responsible for this machine id. Its basically
	// a distributed lock. It's unlocked when there is an error or if the
	// method call is finished (unlocking is done inside the responsible
	// method calls).
	if r.Method != "info" {
		if err := k.Locker.Lock(args.MachineId); err != nil {
			return nil, err
		}

		// if something goes wrong after step reset the document which is was
		// set in the by previous step by Locker.Lock(). If there is no error,
		// the lock will be unlocked in the respective method  function.
		defer func() {
			if reqErr != nil {
				// otherwise that means Locker.Lock or something else in
				// ControlFunc failed. Reset the lock again so it can be acquired by
				// others.
				k.Locker.Unlock(args.MachineId)
			}
		}()
	}

	provider, ok := k.providers[args.Provider]
	if !ok {
		return nil, NewError(ErrProviderNotFound)
	}

	p, ok := provider.(Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	ctx := request.NewContext(context.Background(), r)
	// add publicKeys to be deployed to the machine, the machine provider is
	// responsible of deploying it to the machine while building it.
	if k.PublicKeys != nil {
		ctx = publickeys.NewContext(ctx, k.PublicKeys)
	}

	if k.ContextCreator != nil {
		ctx = k.ContextCreator(ctx)
	}

	// each method has his own unique eventer
	eventId := r.Method + "-" + args.MachineId
	ev := k.NewEventer(eventId)
	ctx = eventer.NewContext(ctx, ev)

	machine, err := p.Machine(ctx, args.MachineId)
	if err != nil {
		return nil, err
	}

	stater, ok := machine.(Stater)
	if !ok {
		return nil, NewError(ErrStaterNotImplemented)
	}

	if stater.ProviderName() != args.Provider {
		return nil, NewError(ErrProviderIsWrong)
	}

	// Check if the given method is in valid methods of that current state. For
	// example if the method is "build", and the state is "stopped" than this
	// will return an error.
	if !methodIn(r.Method, stater.State().ValidMethods()...) {
		return nil, fmt.Errorf("%s not allowed for current state '%s'. Allowed methods are: %v",
			r.Method, strings.ToLower(stater.State().String()), stater.State().ValidMethods())
	}

	pair, ok := states[r.Method]
	if !ok {
		return nil, fmt.Errorf("no state pair available for %s", r.Method)
	}

	ev.Push(&eventer.Event{
		Message: r.Method + " started",
		Status:  pair.start,
	})

	// Start our core method in a goroutine to not block it for the client
	// side. However we do return an event id which is an unique for tracking
	// the current status of the running method.
	go func() {
		finalEvent := &eventer.Event{
			Message:    r.Method + " finished",
			Status:     pair.final,
			Percentage: 100,
		}

		k.Log.Info("[%s] ======> %s started (requester: %s, provider: %s)<======",
			args.MachineId, strings.ToUpper(r.Method), r.Username, args.Provider)
		start := time.Now()
		err := fn(ctx, machine)
		if err != nil {
			// don't pass the error directly to the eventer, mask it to avoid
			// error leaking to the client. We just log it here.
			k.Log.Error("[%s] ======> %s finished with error: '%s' (requester: %s, provider: %s) <======",
				args.MachineId, strings.ToUpper(r.Method), err, r.Username, args.Provider)

			finalEvent.Error = strings.ToTitle(r.Method) + " failed. Please contact support."

			// however, eventerErr is an error we want to pass explicitly to
			// the client side
			if eventerErr, ok := err.(*EventerError); ok {
				finalEvent.Error = eventerErr.Error()
			}

			finalEvent.Status = stater.State() // fallback to to old state
		} else {
			k.Log.Info("[%s] ======> %s finished (time: %s, requester: %s, provider: %s) <======",
				args.MachineId, strings.ToUpper(r.Method), time.Since(start), r.Username, args.Provider)
		}

		ev.Push(finalEvent)
		k.Locker.Unlock(args.MachineId)
	}()

	return ControlResult{
		EventId: eventId,
	}, nil
}

func (k *Kloud) GetMachine(r *kite.Request) (resp interface{}, reqErr error) {
	// calls with zero arguments causes args to be nil. Check it that we
	// don't get a beloved panic
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args struct {
		MachineId string
		Provider  string
	}

	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	// Lock the machine id so no one else can access it. It means this
	// kloud instance is now responsible for this machine id. Its basically
	// a distributed lock. It's unlocked when there is an error or if the
	// method call is finished (unlocking is done inside the responsible
	// method calls).
	if r.Method != "info" {
		if err := k.Locker.Lock(args.MachineId); err != nil {
			return nil, err
		}

		// if something goes wrong after step reset the document which is was
		// set in the by previous step by Locker.Lock(). If there is no error,
		// the lock will be unlocked in the respective method  function.
		defer func() {
			if reqErr != nil {
				// otherwise that means Locker.Lock or something else in
				// ControlFunc failed. Reset the lock again so it can be acquired by
				// others.
				k.Locker.Unlock(args.MachineId)
			}
		}()
	}

	provider, ok := k.providers[args.Provider]
	if !ok {
		return nil, NewError(ErrProviderAvailable)
	}

	p, ok := provider.(Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	ctx := request.NewContext(context.Background(), r)

	return p.Machine(ctx, args.MachineId)
}

// methodIn checks if the method exist in the given methods
func methodIn(method string, methods ...string) bool {
	for _, m := range methods {
		if method == m {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package cmd

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"text/template"

	"github.com/pkg/errors"
	"github.com/rsteube/carapace"
	"github.com/spf13/cobra"
	gitconfig "github.com/tcnksm/go-gitconfig"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/internal/action"
	"github.com/zaquestion/lab/internal/git"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

// mrCmd represents the mr command
var mrCreateCmd = &cobra.Command{
	Use:     "create [remote [branch]]",
	Aliases: []string{"new"},
	Short:   "Open a merge request on GitLab",
	Long:    `Creates a merge request (MR created on origin master by default)`,
	Args:    cobra.MaximumNArgs(2),
	Run:     runMRCreate,
}

func init() {
	mrCreateCmd.Flags().StringSliceP("message", "m", []string{}, "Use the given <msg>; multiple -m are concatenated as separate paragraphs")
	mrCreateCmd.Flags().StringSliceP("assignee", "a", []string{}, "Set assignee by username; can be specified multiple times for multiple assignees")
	mrCreateCmd.Flags().StringSliceP("label", "l", []string{}, "Add label <label>; can be specified multiple times for multiple labels")
	mrCreateCmd.Flags().BoolP("remove-source-branch", "d", false, "Remove source branch from remote after merge")
	mrCreateCmd.Flags().BoolP("squash", "s", false, "Squash commits when merging")
	mrCreateCmd.Flags().Bool("allow-collaboration", false, "Allow commits from other members")
	mrCreateCmd.Flags().Int("milestone", -1, "Set milestone by milestone ID")
	mergeRequestCmd.Flags().AddFlagSet(mrCreateCmd.Flags())

	mrCmd.AddCommand(mrCreateCmd)
	carapace.Gen(mrCreateCmd).PositionalCompletion(
		action.Remotes(),
		action.RemoteBranches(0),
	)
}

// getAssignee returns the assigneeID for use with other GitLab API calls.
// NOTE: It is also used by issue_create.go
func getAssigneeID(assignee string) *int {
	if assignee == "" {
		return nil
	}
	if assignee[0] == '@' {
		assignee = assignee[1:]
	}
	assigneeID, err := lab.UserIDFromUsername(assignee)
	if err != nil {
		return nil
	}
	if assigneeID == -1 {
		return nil
	}
	return gitlab.Int(assigneeID)
}

// getAssignees returns the assigneeIDs for use with other GitLab API calls.
func getAssigneeIDs(assignees []string) []int {
	var ids []int
	for _, a := range assignees {
		ids = append(ids, *getAssigneeID(a))
	}
	return ids
}

func runMRCreate(cmd *cobra.Command, args []string) {
	msgs, err := cmd.Flags().GetStringSlice("message")
	if err != nil {
		log.Fatal(err)
	}
	assignees, err := cmd.Flags().GetStringSlice("assignee")
	if err != nil {
		log.Fatal(err)
	}
	branch, err := git.CurrentBranch()
	if err != nil {
		log.Fatal(err)
	}

	sourceRemote := determineSourceRemote(branch)
	sourceProjectName, err := git.PathWithNameSpace(sourceRemote)
	if err != nil {
		log.Fatal(err)
	}

	p, err := lab.FindProject(sourceProjectName)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := lab.GetCommit(p.ID, branch); err != nil {
		err = errors.Wrapf(
			err,
			"aborting MR, source branch %s not present on remote %s. did you forget to push?",
			branch, sourceRemote)
		log.Fatal(err)
	}

	targetRemote := forkedFromRemote
	if len(args) > 0 {
		targetRemote = args[0]
		ok, err := git.IsRemote(targetRemote)
		if err != nil || !ok {
			log.Fatal(errors.Wrapf(err, "%s is not a valid remote", targetRemote))
		}
	}
	targetProjectName, err := git.PathWithNameSpace(targetRemote)
	if err != nil {
		log.Fatal(err)
	}
	targetProject, err := lab.FindProject(targetProjectName)
	if err != nil {
		log.Fatal(err)
	}
	targetBranch := "master"
	if len(args) > 1 && targetBranch != args[1] {
		targetBranch = args[1]
		if _, err := lab.GetCommit(targetProject.ID, targetBranch); err != nil {
			err = errors.Wrapf(
				err,
				"aborting MR, target branch %s not present on remote %s. did you forget to push?",
				targetBranch, targetRemote)
			log.Fatal(err)
		}
	}

	var title, body string

	if len(msgs) > 0 {
		title, body = msgs[0], strings.Join(msgs[1:], "\n\n")
	} else {
		msg, err := mrText(targetBranch, branch, sourceRemote, forkedFromRemote)
		if err != nil {
			log.Fatal(err)
		}

		title, body, err = git.Edit("MERGEREQ", msg)
		if err != nil {
			_, f, l, _ := runtime.Caller(0)
			log.Fatal(f+":"+strconv.Itoa(l)+" ", err)
		}
	}

	removeSourceBranch, _ := cmd.Flags().GetBool("remove-source-branch")
	squash, _ := cmd.Flags().GetBool("squash")
	allowCollaboration, _ := cmd.Flags().GetBool("allow-collaboration")

	labels, err := cmd.Flags().GetStringSlice("label")
	if err != nil {
		log.Fatal(err)
	}

	milestoneID, _ := cmd.Flags().GetInt("milestone")
	var milestone *int
	if milestoneID < 0 {
		milestone = nil
	} else {
		milestone = &milestoneID
	}

	if title == "" {
		log.Fatal("aborting MR due to empty MR msg")
	}

	mrURL, err := lab.MRCreate(sourceProjectName, &gitlab.CreateMergeRequestOptions{
		SourceBranch:       &branch,
		TargetBranch:       gitlab.String(targetBranch),
		TargetProjectID:    &targetProject.ID,
		Title:              &title,
		Description:        &body,
		AssigneeIDs:        getAssigneeIDs(assignees),
		RemoveSourceBranch: &removeSourceBranch,
		Squash:             &squash,
		AllowCollaboration: &allowCollaboration,
		Labels:             lab.Labels(labels),
		MilestoneID:        milestone,
	})
	if err != nil {
		// FIXME: not exiting fatal here to allow code coverage to
		// generate during Test_mrCreate. In the meantime API failures
		// will exit 0
		fmt.Fprintln(os.Stderr, err)
	}
	fmt.Println(mrURL + "/diffs")
}

func determineSourceRemote(branch string) string {
	// Check if the branch is being tracked
	r, err := gitconfig.Local("branch." + branch + ".remote")
	if err == nil {
		return r
	}

	return forkRemote
}

func mrText(base, head, sourceRemote, forkedFromRemote string) (string, error) {
	lastCommitMsg, err := git.LastCommitMessage()
	if err != nil {
		return "", err
	}
	const tmpl = `{{if .InitMsg}}{{.InitMsg}}{{end}}

{{if .Tmpl}}{{.Tmpl}}{{end}}
{{.CommentChar}} Requesting a merge into {{.Base}} from {{.Head}}
{{.CommentChar}}
{{.CommentChar}} Write a message for this merge request. The first block
{{.CommentChar}} of text is the title and the rest is the description.{{if .CommitLogs}}
{{.CommentChar}}
{{.CommentChar}} Changes:
{{.CommentChar}}
{{.CommitLogs}}{{end}}`

	mrTmpl := lab.LoadGitLabTmpl(lab.TmplMR)

	remoteBase := fmt.Sprintf("%s/%s", forkedFromRemote, base)
	commitLogs, err := git.Log(remoteBase, head)
	if err != nil {
		return "", err
	}
	startRegexp := regexp.MustCompilePOSIX("^")
	commentChar := git.CommentChar()
	commitLogs = strings.TrimSpace(commitLogs)
	commitLogs = startRegexp.ReplaceAllString(commitLogs, fmt.Sprintf("%s ", commentChar))

	t, err := template.New("tmpl").Parse(tmpl)
	if err != nil {
		return "", err
	}

	msg := &struct {
		InitMsg     string
		Tmpl        string
		CommentChar string
		Base        string
		Head        string
		CommitLogs  string
	}{
		InitMsg:     lastCommitMsg,
		Tmpl:        mrTmpl,
		CommentChar: commentChar,
		Base:        forkedFromRemote + ":" + base,
		Head:        sourceRemote + ":" + head,
		CommitLogs:  commitLogs,
	}

	var b bytes.Buffer
	err = t.Execute(&b, msg)
	if err != nil {
		return "", err
	}

	return b.String(), nil
}
<commit_msg>mr create: Set default target branch<commit_after>package cmd

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"text/template"

	"github.com/pkg/errors"
	"github.com/rsteube/carapace"
	"github.com/spf13/cobra"
	gitconfig "github.com/tcnksm/go-gitconfig"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/internal/action"
	"github.com/zaquestion/lab/internal/git"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

// mrCmd represents the mr command
var mrCreateCmd = &cobra.Command{
	Use:     "create [remote [branch]]",
	Aliases: []string{"new"},
	Short:   "Open a merge request on GitLab",
	Long:    `Creates a merge request (default: MR created on default branch of origin)`,
	Args:    cobra.MaximumNArgs(2),
	Run:     runMRCreate,
}

func init() {
	mrCreateCmd.Flags().StringSliceP("message", "m", []string{}, "Use the given <msg>; multiple -m are concatenated as separate paragraphs")
	mrCreateCmd.Flags().StringSliceP("assignee", "a", []string{}, "Set assignee by username; can be specified multiple times for multiple assignees")
	mrCreateCmd.Flags().StringSliceP("label", "l", []string{}, "Add label <label>; can be specified multiple times for multiple labels")
	mrCreateCmd.Flags().BoolP("remove-source-branch", "d", false, "Remove source branch from remote after merge")
	mrCreateCmd.Flags().BoolP("squash", "s", false, "Squash commits when merging")
	mrCreateCmd.Flags().Bool("allow-collaboration", false, "Allow commits from other members")
	mrCreateCmd.Flags().Int("milestone", -1, "Set milestone by milestone ID")
	mergeRequestCmd.Flags().AddFlagSet(mrCreateCmd.Flags())

	mrCmd.AddCommand(mrCreateCmd)
	carapace.Gen(mrCreateCmd).PositionalCompletion(
		action.Remotes(),
		action.RemoteBranches(0),
	)
}

// getAssignee returns the assigneeID for use with other GitLab API calls.
// NOTE: It is also used by issue_create.go
func getAssigneeID(assignee string) *int {
	if assignee == "" {
		return nil
	}
	if assignee[0] == '@' {
		assignee = assignee[1:]
	}
	assigneeID, err := lab.UserIDFromUsername(assignee)
	if err != nil {
		return nil
	}
	if assigneeID == -1 {
		return nil
	}
	return gitlab.Int(assigneeID)
}

// getAssignees returns the assigneeIDs for use with other GitLab API calls.
func getAssigneeIDs(assignees []string) []int {
	var ids []int
	for _, a := range assignees {
		ids = append(ids, *getAssigneeID(a))
	}
	return ids
}

func runMRCreate(cmd *cobra.Command, args []string) {
	msgs, err := cmd.Flags().GetStringSlice("message")
	if err != nil {
		log.Fatal(err)
	}
	assignees, err := cmd.Flags().GetStringSlice("assignee")
	if err != nil {
		log.Fatal(err)
	}
	branch, err := git.CurrentBranch()
	if err != nil {
		log.Fatal(err)
	}

	sourceRemote := determineSourceRemote(branch)
	sourceProjectName, err := git.PathWithNameSpace(sourceRemote)
	if err != nil {
		log.Fatal(err)
	}

	p, err := lab.FindProject(sourceProjectName)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := lab.GetCommit(p.ID, branch); err != nil {
		err = errors.Wrapf(
			err,
			"aborting MR, source branch %s not present on remote %s. did you forget to push?",
			branch, sourceRemote)
		log.Fatal(err)
	}

	targetRemote := forkedFromRemote
	if len(args) > 0 {
		targetRemote = args[0]
		ok, err := git.IsRemote(targetRemote)
		if err != nil || !ok {
			log.Fatal(errors.Wrapf(err, "%s is not a valid remote", targetRemote))
		}
	}
	targetProjectName, err := git.PathWithNameSpace(targetRemote)
	if err != nil {
		log.Fatal(err)
	}
	targetProject, err := lab.FindProject(targetProjectName)
	if err != nil {
		log.Fatal(err)
	}

	targetBranch := targetProject.DefaultBranch
	if len(args) > 1 && targetBranch != args[1] {
		targetBranch = args[1]
		if _, err := lab.GetCommit(targetProject.ID, targetBranch); err != nil {
			err = errors.Wrapf(
				err,
				"aborting MR, %s:%s is not a valid target. Did you forget to push %s to %s?",
				targetRemote, branch, branch, targetRemote)
			log.Fatal(err)
		}
	}

	var title, body string

	if len(msgs) > 0 {
		title, body = msgs[0], strings.Join(msgs[1:], "\n\n")
	} else {
		msg, err := mrText(targetBranch, branch, sourceRemote, forkedFromRemote)
		if err != nil {
			log.Fatal(err)
		}

		title, body, err = git.Edit("MERGEREQ", msg)
		if err != nil {
			_, f, l, _ := runtime.Caller(0)
			log.Fatal(f+":"+strconv.Itoa(l)+" ", err)
		}
	}

	removeSourceBranch, _ := cmd.Flags().GetBool("remove-source-branch")
	squash, _ := cmd.Flags().GetBool("squash")
	allowCollaboration, _ := cmd.Flags().GetBool("allow-collaboration")

	labels, err := cmd.Flags().GetStringSlice("label")
	if err != nil {
		log.Fatal(err)
	}

	milestoneID, _ := cmd.Flags().GetInt("milestone")
	var milestone *int
	if milestoneID < 0 {
		milestone = nil
	} else {
		milestone = &milestoneID
	}

	if title == "" {
		log.Fatal("aborting MR due to empty MR msg")
	}

	mrURL, err := lab.MRCreate(sourceProjectName, &gitlab.CreateMergeRequestOptions{
		SourceBranch:       &branch,
		TargetBranch:       gitlab.String(targetBranch),
		TargetProjectID:    &targetProject.ID,
		Title:              &title,
		Description:        &body,
		AssigneeIDs:        getAssigneeIDs(assignees),
		RemoveSourceBranch: &removeSourceBranch,
		Squash:             &squash,
		AllowCollaboration: &allowCollaboration,
		Labels:             lab.Labels(labels),
		MilestoneID:        milestone,
	})
	if err != nil {
		// FIXME: not exiting fatal here to allow code coverage to
		// generate during Test_mrCreate. In the meantime API failures
		// will exit 0
		fmt.Fprintln(os.Stderr, err)
	}
	fmt.Println(mrURL + "/diffs")
}

func determineSourceRemote(branch string) string {
	// Check if the branch is being tracked
	r, err := gitconfig.Local("branch." + branch + ".remote")
	if err == nil {
		return r
	}

	return forkRemote
}

func mrText(base, head, sourceRemote, forkedFromRemote string) (string, error) {
	lastCommitMsg, err := git.LastCommitMessage()
	if err != nil {
		return "", err
	}
	const tmpl = `{{if .InitMsg}}{{.InitMsg}}{{end}}

{{if .Tmpl}}{{.Tmpl}}{{end}}
{{.CommentChar}} Requesting a merge into {{.Base}} from {{.Head}}
{{.CommentChar}}
{{.CommentChar}} Write a message for this merge request. The first block
{{.CommentChar}} of text is the title and the rest is the description.{{if .CommitLogs}}
{{.CommentChar}}
{{.CommentChar}} Changes:
{{.CommentChar}}
{{.CommitLogs}}{{end}}`

	mrTmpl := lab.LoadGitLabTmpl(lab.TmplMR)

	remoteBase := fmt.Sprintf("%s/%s", forkedFromRemote, base)
	commitLogs, err := git.Log(remoteBase, head)
	if err != nil {
		return "", err
	}
	startRegexp := regexp.MustCompilePOSIX("^")
	commentChar := git.CommentChar()
	commitLogs = strings.TrimSpace(commitLogs)
	commitLogs = startRegexp.ReplaceAllString(commitLogs, fmt.Sprintf("%s ", commentChar))

	t, err := template.New("tmpl").Parse(tmpl)
	if err != nil {
		return "", err
	}

	msg := &struct {
		InitMsg     string
		Tmpl        string
		CommentChar string
		Base        string
		Head        string
		CommitLogs  string
	}{
		InitMsg:     lastCommitMsg,
		Tmpl:        mrTmpl,
		CommentChar: commentChar,
		Base:        forkedFromRemote + ":" + base,
		Head:        sourceRemote + ":" + head,
		CommitLogs:  commitLogs,
	}

	var b bytes.Buffer
	err = t.Execute(&b, msg)
	if err != nil {
		return "", err
	}

	return b.String(), nil
}
<|endoftext|>


<commit_before>package connector

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/user"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/pkg/errors"
	"google.golang.org/grpc"

	"github.com/datawire/telepresence2/pkg/client"
	"github.com/datawire/telepresence2/pkg/rpc/manager"
)

// trafficManager is a handle to access the Traffic Manager in a
// cluster.
type trafficManager struct {
	aiListener      aiListener
	iiListener      iiListener
	conn            *grpc.ClientConn
	grpc            manager.ManagerClient
	startup         chan bool
	apiPort         int32
	sshPort         int32
	userAndHost     string
	installID       string // telepresence's install ID
	sessionID       string // sessionID returned by the traffic-manager
	apiErr          error  // holds the latest traffic-manager API error
	connectCI       bool   // whether --ci was passed to connect
	installer       *installer
	myIntercept     string
	cancelIntercept context.CancelFunc
	// previewHost string // hostname to use for preview URLs, if enabled
}

// newTrafficManager returns a TrafficManager resource for the given
// cluster if it has a Traffic Manager service.
func newTrafficManager(c context.Context, cluster *k8sCluster, installID string, isCI bool) (*trafficManager, error) {
	name, err := user.Current()
	if err != nil {
		return nil, errors.Wrap(err, "user.Current()")
	}
	host, err := os.Hostname()
	if err != nil {
		return nil, errors.Wrap(err, "os.Hostname()")
	}

	// Ensure that we have a traffic-manager to talk to.
	ti, err := newTrafficManagerInstaller(cluster)
	if err != nil {
		return nil, errors.Wrap(err, "new installer")
	}
	localAPIPort, err := getFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for API")
	}
	localSSHPort, err := getFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for ssh")
	}
	tm := &trafficManager{
		installer:   ti,
		apiPort:     localAPIPort,
		sshPort:     localSSHPort,
		installID:   installID,
		connectCI:   isCI,
		startup:     make(chan bool),
		userAndHost: fmt.Sprintf("%s@%s", name, host)}

	dgroup.ParentGroup(c).Go("traffic-manager", tm.start)
	return tm, nil
}

func (tm *trafficManager) waitUntilStarted() error {
	<-tm.startup
	return tm.apiErr
}

func (tm *trafficManager) start(c context.Context) error {
	remoteSSHPort, remoteAPIPort, err := tm.installer.ensureManager(c)
	if err != nil {
		tm.apiErr = err
		close(tm.startup)
		return err
	}
	kpfArgs := []string{
		"port-forward",
		"svc/traffic-manager",
		fmt.Sprintf("%d:%d", tm.sshPort, remoteSSHPort),
		fmt.Sprintf("%d:%d", tm.apiPort, remoteAPIPort)}

	err = client.Retry(c, func(c context.Context) error {
		return tm.installer.portForwardAndThen(c, kpfArgs, "init-grpc", tm.initGrpc)
	}, 2*time.Second, 15*time.Second, time.Minute)
	if err != nil && tm.apiErr == nil {
		tm.apiErr = err
		close(tm.startup)
	}
	return err
}

func (tm *trafficManager) initGrpc(c context.Context) (err error) {
	defer func() {
		tm.apiErr = err
		close(tm.startup)
	}()

	// First check. Establish connection
	tc, cancel := context.WithTimeout(c, connectTimeout)
	defer cancel()

	var conn *grpc.ClientConn
	conn, err = grpc.DialContext(tc, fmt.Sprintf("127.0.0.1:%d", tm.apiPort),
		grpc.WithInsecure(),
		grpc.WithNoProxy(),
		grpc.WithBlock())
	if err != nil {
		dlog.Errorf(c, "error when dialing traffic-manager: %s", err.Error())
		return err
	}

	mClient := manager.NewManagerClient(conn)
	si, err := mClient.ArriveAsClient(c, &manager.ClientInfo{
		Name:      tm.userAndHost,
		InstallId: tm.installID,
		Product:   "telepresence",
		Version:   client.Version(),
	})

	if err != nil {
		dlog.Errorf(c, "ArriveAsClient: %s", err.Error())
		conn.Close()
		return err
	}
	tm.conn = conn
	tm.grpc = mClient
	tm.sessionID = si.SessionId

	g := dgroup.ParentGroup(c)
	g.Go("remain", tm.remain)
	g.Go("watch-agents", tm.watchAgents)
	g.Go("watch-intercepts", tm.watchIntercepts)
	return nil
}

func (tm *trafficManager) watchAgents(c context.Context) error {
	ac, err := tm.grpc.WatchAgents(c, tm.session())
	if err != nil {
		return err
	}
	return tm.aiListener.start(c, ac)
}

func (tm *trafficManager) watchIntercepts(c context.Context) error {
	ic, err := tm.grpc.WatchIntercepts(c, tm.session())
	if err != nil {
		return err
	}
	return tm.iiListener.start(c, ic)
}

func (tm *trafficManager) session() *manager.SessionInfo {
	return &manager.SessionInfo{SessionId: tm.sessionID}
}

func (tm *trafficManager) agentInfoSnapshot() *manager.AgentInfoSnapshot {
	return tm.aiListener.getData()
}

func (tm *trafficManager) interceptInfoSnapshot() *manager.InterceptInfoSnapshot {
	return tm.iiListener.getData()
}

func (tm *trafficManager) remain(c context.Context) error {
	ticker := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-c.Done():
			return nil
		case <-ticker.C:
			_, err := tm.grpc.Remain(c, tm.session())
			if err != nil {
				return err
			}
		}
	}
}

// Close implements io.Closer
func (tm *trafficManager) Close() error {
	if tm.conn != nil {
		_ = tm.conn.Close()
		tm.conn = nil
		tm.grpc = nil
	}
	return nil
}

// A watcher listens on a grpc.ClientStream and notifies listeners when
// something arrives.
type watcher struct {
	entryMaker    func() interface{} // returns an instance of the type produced by the stream
	listeners     []listener
	listenersLock sync.RWMutex
	stream        grpc.ClientStream
}

// watch reads messages from the stream and passes them onto registered listeners. The
// function terminates when the context used when the stream was acquired is cancelled,
// when io.EOF is encountered, or an error occurs during read.
func (r *watcher) watch(c context.Context) error {
	dataChan := make(chan interface{}, 1000)
	go func() {
		for {
			select {
			case <-c.Done():
				return
			case data := <-dataChan:
				if data == nil {
					return
				}

				r.listenersLock.RLock()
				lc := make([]listener, len(r.listeners))
				copy(lc, r.listeners)
				r.listenersLock.RUnlock()

				for _, l := range lc {
					l.onData(data)
				}
			}
		}
	}()

	for {
		data := r.entryMaker()
		if err := r.stream.RecvMsg(data); err != nil {
			if err == io.EOF || strings.HasSuffix(err.Error(), " is closing") {
				err = nil
			}
			close(dataChan)
			return err
		}
		dataChan <- data
	}
}

func (r *watcher) addListener(l listener) {
	r.listenersLock.Lock()
	r.listeners = append(r.listeners, l)
	r.listenersLock.Unlock()
}

func (r *watcher) removeListener(l listener) {
	r.listenersLock.Lock()
	ls := r.listeners
	for i, x := range ls {
		if l == x {
			last := len(ls) - 1
			ls[i] = ls[last]
			ls[last] = nil
			r.listeners = ls[:last]
			break
		}
	}
	r.listenersLock.Unlock()
}

// A listener gets notified by a watcher when something arrives on the stream
type listener interface {
	onData(data interface{})
}

// An aiListener keeps track of the latest received AgentInfoSnapshot and provides the
// watcher needed to register other listeners.
type aiListener struct {
	watcher
	data atomic.Value
}

func (al *aiListener) getData() *manager.AgentInfoSnapshot {
	v := al.data.Load()
	if v == nil {
		return nil
	}
	return v.(*manager.AgentInfoSnapshot)
}

func (al *aiListener) onData(d interface{}) {
	al.data.Store(d)
}

func (al *aiListener) start(c context.Context, stream grpc.ClientStream) error {
	al.stream = stream
	al.listeners = []listener{al}
	al.entryMaker = func() interface{} { return new(manager.AgentInfoSnapshot) }
	return al.watch(c)
}

func (il *iiListener) onData(d interface{}) {
	il.data.Store(d)
}

func (il *iiListener) start(c context.Context, stream grpc.ClientStream) error {
	il.stream = stream
	il.listeners = []listener{il}
	il.entryMaker = func() interface{} { return new(manager.InterceptInfoSnapshot) }
	return il.watch(c)
}

// iiActive is a listener that waits for an intercept with a given id to become active
type iiActive struct {
	id   string
	done chan *manager.InterceptInfo
}

func (ia *iiActive) onData(d interface{}) {
	if iis, ok := d.(*manager.InterceptInfoSnapshot); ok {
		for _, ii := range iis.Intercepts {
			if ii.Id == ia.id && ii.Disposition != manager.InterceptDispositionType_WAITING {
				done := ia.done
				ia.done = nil
				if done != nil {
					done <- ii
					close(done)
				}
				break
			}
		}
	}
}

// aiPresent is a listener that waits for an agent with a given name to be present
type aiPresent struct {
	name string
	done chan *manager.AgentInfo
}

func (ap *aiPresent) onData(d interface{}) {
	if ais, ok := d.(*manager.AgentInfoSnapshot); ok {
		for _, ai := range ais.Agents {
			if ai.Name == ap.name {
				done := ap.done
				ap.done = nil
				if done != nil {
					done <- ai
					close(done)
				}
				break
			}
		}
	}
}
<commit_msg>Avoid potential deadlock when watching for snapshots from manager<commit_after>package connector

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/user"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/pkg/errors"
	"google.golang.org/grpc"

	"github.com/datawire/telepresence2/pkg/client"
	"github.com/datawire/telepresence2/pkg/rpc/manager"
)

// trafficManager is a handle to access the Traffic Manager in a
// cluster.
type trafficManager struct {
	aiListener      aiListener
	iiListener      iiListener
	conn            *grpc.ClientConn
	grpc            manager.ManagerClient
	startup         chan bool
	apiPort         int32
	sshPort         int32
	userAndHost     string
	installID       string // telepresence's install ID
	sessionID       string // sessionID returned by the traffic-manager
	apiErr          error  // holds the latest traffic-manager API error
	connectCI       bool   // whether --ci was passed to connect
	installer       *installer
	myIntercept     string
	cancelIntercept context.CancelFunc
	// previewHost string // hostname to use for preview URLs, if enabled
}

// newTrafficManager returns a TrafficManager resource for the given
// cluster if it has a Traffic Manager service.
func newTrafficManager(c context.Context, cluster *k8sCluster, installID string, isCI bool) (*trafficManager, error) {
	name, err := user.Current()
	if err != nil {
		return nil, errors.Wrap(err, "user.Current()")
	}
	host, err := os.Hostname()
	if err != nil {
		return nil, errors.Wrap(err, "os.Hostname()")
	}

	// Ensure that we have a traffic-manager to talk to.
	ti, err := newTrafficManagerInstaller(cluster)
	if err != nil {
		return nil, errors.Wrap(err, "new installer")
	}
	localAPIPort, err := getFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for API")
	}
	localSSHPort, err := getFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for ssh")
	}
	tm := &trafficManager{
		installer:   ti,
		apiPort:     localAPIPort,
		sshPort:     localSSHPort,
		installID:   installID,
		connectCI:   isCI,
		startup:     make(chan bool),
		userAndHost: fmt.Sprintf("%s@%s", name, host)}

	dgroup.ParentGroup(c).Go("traffic-manager", tm.start)
	return tm, nil
}

func (tm *trafficManager) waitUntilStarted() error {
	<-tm.startup
	return tm.apiErr
}

func (tm *trafficManager) start(c context.Context) error {
	remoteSSHPort, remoteAPIPort, err := tm.installer.ensureManager(c)
	if err != nil {
		tm.apiErr = err
		close(tm.startup)
		return err
	}
	kpfArgs := []string{
		"port-forward",
		"svc/traffic-manager",
		fmt.Sprintf("%d:%d", tm.sshPort, remoteSSHPort),
		fmt.Sprintf("%d:%d", tm.apiPort, remoteAPIPort)}

	err = client.Retry(c, func(c context.Context) error {
		return tm.installer.portForwardAndThen(c, kpfArgs, "init-grpc", tm.initGrpc)
	}, 2*time.Second, 15*time.Second, time.Minute)
	if err != nil && tm.apiErr == nil {
		tm.apiErr = err
		close(tm.startup)
	}
	return err
}

func (tm *trafficManager) initGrpc(c context.Context) (err error) {
	defer func() {
		tm.apiErr = err
		close(tm.startup)
	}()

	// First check. Establish connection
	tc, cancel := context.WithTimeout(c, connectTimeout)
	defer cancel()

	var conn *grpc.ClientConn
	conn, err = grpc.DialContext(tc, fmt.Sprintf("127.0.0.1:%d", tm.apiPort),
		grpc.WithInsecure(),
		grpc.WithNoProxy(),
		grpc.WithBlock())
	if err != nil {
		dlog.Errorf(c, "error when dialing traffic-manager: %s", err.Error())
		return err
	}

	mClient := manager.NewManagerClient(conn)
	si, err := mClient.ArriveAsClient(c, &manager.ClientInfo{
		Name:      tm.userAndHost,
		InstallId: tm.installID,
		Product:   "telepresence",
		Version:   client.Version(),
	})

	if err != nil {
		dlog.Errorf(c, "ArriveAsClient: %s", err.Error())
		conn.Close()
		return err
	}
	tm.conn = conn
	tm.grpc = mClient
	tm.sessionID = si.SessionId

	g := dgroup.ParentGroup(c)
	g.Go("remain", tm.remain)
	g.Go("watch-agents", tm.watchAgents)
	g.Go("watch-intercepts", tm.watchIntercepts)
	return nil
}

func (tm *trafficManager) watchAgents(c context.Context) error {
	ac, err := tm.grpc.WatchAgents(c, tm.session())
	if err != nil {
		return err
	}
	return tm.aiListener.start(c, ac)
}

func (tm *trafficManager) watchIntercepts(c context.Context) error {
	ic, err := tm.grpc.WatchIntercepts(c, tm.session())
	if err != nil {
		return err
	}
	return tm.iiListener.start(c, ic)
}

func (tm *trafficManager) session() *manager.SessionInfo {
	return &manager.SessionInfo{SessionId: tm.sessionID}
}

func (tm *trafficManager) agentInfoSnapshot() *manager.AgentInfoSnapshot {
	return tm.aiListener.getData()
}

func (tm *trafficManager) interceptInfoSnapshot() *manager.InterceptInfoSnapshot {
	return tm.iiListener.getData()
}

func (tm *trafficManager) remain(c context.Context) error {
	ticker := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-c.Done():
			return nil
		case <-ticker.C:
			_, err := tm.grpc.Remain(c, tm.session())
			if err != nil {
				return err
			}
		}
	}
}

// Close implements io.Closer
func (tm *trafficManager) Close() error {
	if tm.conn != nil {
		_ = tm.conn.Close()
		tm.conn = nil
		tm.grpc = nil
	}
	return nil
}

// A watcher listens on a grpc.ClientStream and notifies listeners when
// something arrives.
type watcher struct {
	entryMaker    func() interface{} // returns an instance of the type produced by the stream
	listeners     []listener
	listenersLock sync.RWMutex
	stream        grpc.ClientStream
}

// watch reads messages from the stream and passes them onto registered listeners. The
// function terminates when the context used when the stream was acquired is cancelled,
// when io.EOF is encountered, or an error occurs during read.
func (r *watcher) watch(c context.Context) error {
	dataChan := make(chan interface{}, 1000)
	defer close(dataChan)

	done := int32(0)
	go func() {
		for {
			select {
			case <-c.Done():
				// ensure no more writes and drain channel to unblock writer
				atomic.StoreInt32(&done, 1)
				for range dataChan {
				}
				return
			case data := <-dataChan:
				if data == nil {
					return
				}

				r.listenersLock.RLock()
				lc := make([]listener, len(r.listeners))
				copy(lc, r.listeners)
				r.listenersLock.RUnlock()

				for _, l := range lc {
					l.onData(data)
				}
			}
		}
	}()

	var err error
	for {
		data := r.entryMaker()
		if err = r.stream.RecvMsg(data); err != nil {
			if err == io.EOF || strings.HasSuffix(err.Error(), " is closing") {
				err = nil
			}
			break
		}
		if atomic.LoadInt32(&done) != 0 {
			break
		}
		dataChan <- data
		if atomic.LoadInt32(&done) != 0 {
			break
		}
	}
	return err
}

func (r *watcher) addListener(l listener) {
	r.listenersLock.Lock()
	r.listeners = append(r.listeners, l)
	r.listenersLock.Unlock()
}

func (r *watcher) removeListener(l listener) {
	r.listenersLock.Lock()
	ls := r.listeners
	for i, x := range ls {
		if l == x {
			last := len(ls) - 1
			ls[i] = ls[last]
			ls[last] = nil
			r.listeners = ls[:last]
			break
		}
	}
	r.listenersLock.Unlock()
}

// A listener gets notified by a watcher when something arrives on the stream
type listener interface {
	onData(data interface{})
}

// An aiListener keeps track of the latest received AgentInfoSnapshot and provides the
// watcher needed to register other listeners.
type aiListener struct {
	watcher
	data atomic.Value
}

func (al *aiListener) getData() *manager.AgentInfoSnapshot {
	v := al.data.Load()
	if v == nil {
		return nil
	}
	return v.(*manager.AgentInfoSnapshot)
}

func (al *aiListener) onData(d interface{}) {
	al.data.Store(d)
}

func (al *aiListener) start(c context.Context, stream grpc.ClientStream) error {
	al.stream = stream
	al.listeners = []listener{al}
	al.entryMaker = func() interface{} { return new(manager.AgentInfoSnapshot) }
	return al.watch(c)
}

func (il *iiListener) onData(d interface{}) {
	il.data.Store(d)
}

func (il *iiListener) start(c context.Context, stream grpc.ClientStream) error {
	il.stream = stream
	il.listeners = []listener{il}
	il.entryMaker = func() interface{} { return new(manager.InterceptInfoSnapshot) }
	return il.watch(c)
}

// iiActive is a listener that waits for an intercept with a given id to become active
type iiActive struct {
	id   string
	done chan *manager.InterceptInfo
}

func (ia *iiActive) onData(d interface{}) {
	if iis, ok := d.(*manager.InterceptInfoSnapshot); ok {
		for _, ii := range iis.Intercepts {
			if ii.Id == ia.id && ii.Disposition != manager.InterceptDispositionType_WAITING {
				done := ia.done
				ia.done = nil
				if done != nil {
					done <- ii
					close(done)
				}
				break
			}
		}
	}
}

// aiPresent is a listener that waits for an agent with a given name to be present
type aiPresent struct {
	name string
	done chan *manager.AgentInfo
}

func (ap *aiPresent) onData(d interface{}) {
	if ais, ok := d.(*manager.AgentInfoSnapshot); ok {
		for _, ai := range ais.Agents {
			if ai.Name == ap.name {
				done := ap.done
				ap.done = nil
				if done != nil {
					done <- ai
					close(done)
				}
				break
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// push cross compiles one or multiple executables and pushes them to a micro
// computer over ssh.
package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

// run is a shorthand for exec.Command().Run().
func run(name string, arg ...string) error {
	c := exec.Command(name, arg...)
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	return c.Run()
}

type tool int

const (
	none tool = iota
	rsync
	pscp
	scp
)
const toolName = "nonersyncpscpscp"

var toolIndex = [...]uint8{0, 4, 9, 13, 16}

func (i tool) String() string {
	if i < 0 || i >= tool(len(toolIndex)-1) {
		return fmt.Sprintf("tool(%d)", i)
	}
	return toolName[toolIndex[i]:toolIndex[i+1]]
}

func (t tool) push(verbose bool, src, dst string) error {
	switch t {
	case rsync:
		// Push all files via rsync. This is the fastest method.
		if verbose {
			return run("rsync", "--archive", "-v", "--info=progress2", "--compress", src+"/", dst)
		}
		return run("rsync", "--archive", "--info=progress2", "--compress", src+"/", dst)
	case pscp:
		// Push all files via pscp, provided by PuTTY.
		//
		// It is slower than rsync and will fail if one of the destination
		// executable is under use, but it is a reasonable fallback.
		// TODO(maruel): pscp with an alternate name, then plink in to rename the
		// files.
		if verbose {
			return run("pscp", "-v", "-C", "-p", "-r", src, dst)
		}
		return run("pscp", "-C", "-p", "-r", src, dst)
	case scp:
		// Push all files via scp, provided by OpenSSH.
		//
		// It is slower than rsync and will fail if one of the destination
		// executable is under use, but it is a reasonable fallback.
		// TODO(maruel): scp with an alternate name, then ssh in to rename the
		// files.
		if verbose {
			return run("scp", "-v", "-C", "-p", "-r", src, dst)
		}
		return run("scp", "-C", "-p", "-r", src, dst)
	default:
		return errors.New("please make sure at least one of rsync, scp or pscp is in PATH")
	}
}

// detect returns which tool to use.
func detect() tool {
	if _, err := exec.Command("rsync", "--version").CombinedOutput(); err == nil {
		return rsync
	}
	if runtime.GOOS == "windows" {
		if _, err := exec.Command("pscp", "-V").CombinedOutput(); err == nil {
			return pscp
		}
	}
	_, err := exec.Command("scp", "-V").CombinedOutput()
	if err2, ok := err.(*exec.Error); ok && err2.Err == exec.ErrNotFound {
		return none
	}
	return scp
}

// toPkg returns one or multiple packages matching the relpath.
func toPkg(item string) ([]string, error) {
	out, err := exec.Command("go", "list", item).Output()
	if err != nil {
		return nil, fmt.Errorf("failed to list %s: %v", item, err)
	}
	return strings.Split(strings.TrimSpace(string(out)), "\n"), nil
}

// pushInner does the actual work: build then push.
func pushInner(verbose bool, t tool, pkgs []string, host, rel, d string) error {
	exes := make([]string, len(pkgs))
	for i, pkg := range pkgs {
		exes[i] = filepath.Join(d, filepath.Base(pkg))
	}

	// First build everything.
	for i, pkg := range pkgs {
		fmt.Printf("- Building %s\n", pkg)
		if err := run("go", "build", "-v", "-i", "-o", exes[i], pkg); err != nil {
			fmt.Fprintf(os.Stderr, "Failed to build %s\n", pkg)
			return err
		}
	}

	// Then push it all as one swoop.
	dst := fmt.Sprintf("%s:%s", host, rel)
	fmt.Printf("- Pushing %d executables to %s via %s\n", len(pkgs), dst, t)
	return t.push(verbose, d, dst)
}

// push wraps pushInner with a temporary directory.
func push(verbose bool, t tool, items []string, host, rel string) error {
	// First convert the passed strings into real package names.
	var pkgs []string
	for _, item := range items {
		i, err := toPkg(item)
		if err != nil {
			return err
		}
		pkgs = append(pkgs, i...)
	}

	d, err := ioutil.TempDir("", "push")
	if err != nil {
		return err
	}
	err = pushInner(verbose, t, pkgs, host, rel, d)
	if err1 := os.RemoveAll(d); err == nil {
		err = err1
	}
	return err
}

func mainImpl() error {
	goarch := flag.String("goarch", "arm", "GOARCH value to use")
	goos := flag.String("goos", "linux", "GOOS value to use")
	rel := flag.String("rel", "go/bin", "relative directory to push files into")
	host := flag.String("host", os.Getenv("PUSH_HOST"), "host to push to; defaults to content of environment variable PUSH_HOST")
	verbose := flag.Bool("v", false, "verbose output")
	flag.Parse()
	if flag.NArg() == 0 {
		return errors.New("expected argument, try -help")
	}
	if !*verbose {
		log.SetOutput(ioutil.Discard)
	}

	t := detect()
	if t == none {
		return errors.New("Please make sure at least one of rsync, scp or pscp is in PATH")
	}

	// Simplify our life and just set it process wide.
	os.Setenv("GOARCH", *goarch)
	os.Setenv("GOOS", *goos)
	return push(*verbose, t, flag.Args(), *host, *rel)
}

func main() {
	if err := mainImpl(); err != nil {
		fmt.Fprintf(os.Stderr, "push: %s.\n\nVisit https://github.com/periph/bootstrap#troubleshooting-push for help.\n", err)
		os.Exit(1)
	}
}
<commit_msg>push: improvements, tested on Windows<commit_after>// Copyright 2017 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// push cross compiles one or multiple executables and pushes them to a micro
// computer over ssh.
package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

// run is a shorthand for exec.Command().Run().
func run(name string, arg ...string) error {
	c := exec.Command(name, arg...)
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	return c.Run()
}

type tool int

const (
	none tool = iota
	rsync
	pscp
	scp
)
const toolName = "nonersyncpscpscp"

var toolIndex = [...]uint8{0, 4, 9, 13, 16}

func (i tool) String() string {
	if i < 0 || i >= tool(len(toolIndex)-1) {
		return fmt.Sprintf("tool(%d)", i)
	}
	return toolName[toolIndex[i]:toolIndex[i+1]]
}

func (t tool) push(verbose bool, src string, pkgs []string, host, rel string) error {
	dst := fmt.Sprintf("%s:%s", host, rel)
	var args []string
	switch t {
	case rsync:
		// Push all files via rsync. This is the fastest method.
		args = []string{"--archive", "--info=progress2", "--compress", src + "/", dst}
		if verbose {
			args = append([]string{"-v"}, args...)
		}
	case pscp, scp:
		// Push all files via pscp/scp, provided by PuTTY/OpenSSH.
		//
		// It is slower than rsync and will fail if one of the destination
		// executable is under use, but it is a reasonable fallback.
		// TODO(maruel): pscp/scp with an alternate name, then plink/ssh in to
		// rename the files.
		args = []string{"-C", "-p", "-r", filepath.Join(src, "."), dst}
		if verbose {
			args = append([]string{"-v"}, args...)
		}
	default:
		return errors.New("please make sure at least one of rsync, scp or pscp is in PATH")
	}
	if err := run(t.String(), args...); err != nil {
		return err
	}
	if runtime.GOOS == "windows" {
		// On Windows, the +x bit is lost, so we are required to ssh in to change
		// the file mode.
		args = []string{host, "chmod", "+x"}
		for _, pkg := range pkgs {
			args = append(args, filepath.Join(rel, filepath.Base(pkg)))
		}
		switch t {
		case rsync, scp:
			return run("ssh", args...)
		case pscp:
			return run("plink", args...)
		}
	}
	return nil

}

// detect returns which tool to use.
func detect() tool {
	if _, err := exec.Command("rsync", "--version").CombinedOutput(); err == nil {
		return rsync
	}
	if runtime.GOOS == "windows" {
		if _, err := exec.Command("pscp", "-V").CombinedOutput(); err == nil {
			return pscp
		}
	}
	_, err := exec.Command("scp", "-V").CombinedOutput()
	if err2, ok := err.(*exec.Error); ok && err2.Err == exec.ErrNotFound {
		return none
	}
	return scp
}

// toPkg returns one or multiple packages matching the relpath.
func toPkg(item string) ([]string, error) {
	out, err := exec.Command("go", "list", item).CombinedOutput()
	s := strings.TrimSpace(string(out))
	if err != nil {
		return nil, fmt.Errorf("failed to list package %q: %v\n%s", item, err, s)
	}
	return strings.Split(s, "\n"), nil
}

// pushInner does the actual work: build then push.
func pushInner(verbose bool, t tool, pkgs []string, host, rel, d string) error {
	// First build everything.
	for _, pkg := range pkgs {
		fmt.Printf("- Building %s\n", pkg)
		if err := run("go", "build", "-v", "-i", "-o", filepath.Join(d, filepath.Base(pkg)), pkg); err != nil {
			fmt.Fprintf(os.Stderr, "Failed to build %s\n", pkg)
			return err
		}
	}

	// Then push it all as one swoop.
	fmt.Printf("- Pushing %d executables to %s in %s via %s\n", len(pkgs), rel, host, t)
	return t.push(verbose, d, pkgs, host, rel)
}

// push wraps pushInner with a temporary directory.
func push(verbose bool, t tool, items []string, host, rel string) error {
	// First convert the passed strings into real package names.
	var pkgs []string
	for _, item := range items {
		i, err := toPkg(item)
		if err != nil {
			return err
		}
		pkgs = append(pkgs, i...)
	}

	d, err := ioutil.TempDir("", "push")
	if err != nil {
		return err
	}
	err = pushInner(verbose, t, pkgs, host, rel, d)
	if err1 := os.RemoveAll(d); err == nil {
		err = err1
	}
	return err
}

func mainImpl() error {
	goarch := flag.String("goarch", "arm", "GOARCH value to use")
	goos := flag.String("goos", "linux", "GOOS value to use")
	rel := flag.String("rel", ".", "directory on remote host to push files into")
	host := flag.String("host", os.Getenv("PUSH_HOST"), "host to push to; defaults to content of environment variable PUSH_HOST")
	verbose := flag.Bool("v", false, "verbose output")
	flag.Parse()
	if flag.NArg() == 0 {
		return errors.New("expected argument, try -help")
	}
	if !*verbose {
		log.SetOutput(ioutil.Discard)
	}

	t := detect()
	if t == none {
		return errors.New("Please make sure at least one of rsync, scp or pscp is in PATH")
	}

	// Simplify our life and just set it process wide.
	os.Setenv("GOARCH", *goarch)
	os.Setenv("GOOS", *goos)
	return push(*verbose, t, flag.Args(), *host, *rel)
}

func main() {
	if err := mainImpl(); err != nil {
		fmt.Fprintf(os.Stderr, "push: %s\n\nVisit https://github.com/periph/bootstrap#troubleshooting-push for help.\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"

	"github.com/go-task/task"
)

func main() {
	flag.CommandLine.Usage = func() {
		fmt.Println(`
task [target1 target2 ...]: Runs commands under targets like make.

Example: 'task hello' with the following 'Taskfile.json' file will generate
an 'output.txt' file.
'''
hello:
  cmds:
    - echo "I am going to write a file named 'output.txt' now."
    - echo "hello" > output.txt
  generates:
    - output.txt
'''
`)
	}
	flag.Parse()
	task.Run()
}
<commit_msg>Fix extension on help text<commit_after>package main

import (
	"flag"
	"fmt"

	"github.com/go-task/task"
)

func main() {
	flag.CommandLine.Usage = func() {
		fmt.Println(`
task [target1 target2 ...]: Runs commands under targets like make.

Example: 'task hello' with the following 'Taskfile.yml' file will generate
an 'output.txt' file.
'''
hello:
  cmds:
    - echo "I am going to write a file named 'output.txt' now."
    - echo "hello" > output.txt
  generates:
    - output.txt
'''
`)
	}
	flag.Parse()
	task.Run()
}
<|endoftext|>


<commit_before>/*
tsuru is a command line tool for application developers.

It provide some commands that allow a developer to register himself/herself,
manage teams, apps and services.

Usage:

	% tsuru <command> [args]

The currently available commands are (grouped by subject):

    target            changes or retrive the current tsuru server

    user-create       creates a new user
    login             authenticates the user with tsuru server
    logout            finishes the session with tsuru server
    key-add           adds a public key to tsuru deploy server
    key-remove        removes a public key from tsuru deploy server

    team-create       creates a new team (adding the current user to it automatically)
    team-list         list teams that the user is member
    team-user-add     adds a user to a team
    team-user-remove  removes a user from a team

    app-create        creates an app
    app-remove        removes an app
    app-list          lists apps that the user has access (see app-grant and team-user-add)
    app-grant         allows a team to have access to an app
    app-revoke        revokes access to an app from a team
    log               shows log for an app
    run               runs a command in all units of an app
	restart           restarts the app's application server

    env-get           display environment variables for an app
    env-set           set environment variable(s) to an app
    env-unset         unset environment variable(s) from an app

    bind              binds an app to a service instance
    unbind            unbinds an app from a service instance

    service-list      list all services, and instances of each service
    service-add       creates a new instance of a service
    service-remove    removes a instance of a service
    service-status    checks the status of a service instance
    service-info      list instances of a service, and apps binded to each instance
    service-doc       displays documentation for a service

Use "tsuru help <command>" for more information about a command.

Change/retrieve remote tsuru server

Usage:

	% tsuru target [target]

This command should be used to get current tsuru target, or retrieve current
target.

The target is the tsuru server to which all operations will be directed to.

Create a user

Usage:

	% tsuru user-create <email>

user-create creates a user within tsuru remote server. It will ask for the
password before issue the request.

Authenticate within remote tsuru server

Usage:

	% tsuru login <email>

Login will ask for the password and check if the user is successfully
authenticated. If so, the token generated by the tsuru server will be stored in
${HOME}/.tsuru_token.

All tsuru actions require the user to be authenticated (except login and
user-create, obviously).

Logout from remote tsuru server

Usage:

	% tsuru logout

Logout will delete the token file and terminate the session within tsuru
server.

Add SSH public key to tsuru's git server

Usage:

	% tsuru key-add [${HOME}/.ssh/id_rsa.pub]

key-add sends your public key to tsuru's git server. By default, it will try
send a public RSA key, located at ${HOME}/.ssh/id_rsa.pub. If you want to send
other file, you can call it with the path to the file. For example:

	% tsuru key-add /etc/my-keys/id_dsa.pub

The key will be added to the current logged in user.

Remove SSH public key from tsuru's git server

Usage:

	% tsuru key-remove [${HOME}/.ssh/id_rsa.pub]

key-remove removes your public key from tsuru's git server. By default, it will
try to remove a key that match you public RSA key located at
${HOME}/.ssh/id_rsa.pub. If you want to remove a key located somewhere else,
you can pass it as parameter to key-remove:

	% tsuru key-remove /etc/my-keys/id_dsa.pub

The key will be removed from the current logged in user.

Create a new team for the user

Usage:

	% tsuru team-create <teamname>

team-create will create a team for the user. Tsuru requires a user to be a
member of at least one team in order to create an app or a service instance.

When you create a team, you're automatically member of this team.

List teams that the user is member of

Usage:

	% tsuru team-list

team-list will list all teams that you are member of.

Add a user to a team

Usage:

	% tsuru team-user-add <teamname> <useremail>

team-user-add adds a user to a team. You need to be a member of the team to be
able to add a user to it.

Remove a user from a team

Usage:

	% tsuru team-user-remove <teamname> <useremail>

team-user-remove removes a user from a team. You need to be a member of the
team to be able to remove a user from it.

A team can never have 0 users. If you are the last member of a team, you can't
remove yourself from it.

Create an app

Usage:

	% tsuru app-create <appname> <platform>

app-create will create a new app using the given name and platform. For tsuru,
a platform is a Juju charm. To check the available platforms/charms, check this
URL: https://github.com/timeredbull/charms/tree/master/centos.

In order to create an app, you need to be member of at least one team. All
teams that you are member (see "tsuru team-list") will be able to access the
app.

Remove an app

Usage:

	% tsuru app-remove <appname>

app-remove removes an app. If the app is binded to any service instance, it
will be unbinded before be removed (see "tsuru unbind"). You need to be a
member of a team that has access to the app to be able to remove it (you are
able to remove any app that you see in "tsuru app-list").

List apps that the user has access to

Usage:

	% tsuru app-list

app-list will list all apps that you have access to. App access is controlled
by teams. If your team has access to an app, then you have access to it.

Allow a team to access an app

Usage:

	% tsuru app-grant <appname> <teamname>

app-grant will allow a team to access an app. You need to be a member of a team
that has access to the app to allow another team to access it.

Revoke from a team access to an app

Usage:

	% tsuru app-revoke <appname> <teamname>

app-revoke will revoke the permission to access an app from a team. You need to
have access to the app to revoke access from a team.

An app cannot be orphaned, so it will always have at least one authorized team.

See app's logs

Usage:

	% tsuru log <appname>

Log will show log entries for an app. These logs are not related to the code of
the app itself, but to actions of the app in tsuru server (deployments,
restarts, etc.).

Run an arbitrary command in the app machine

Usage:

	% tsuru run <appname> <command> [commandarg1] [commandarg2] ... [commandargn]

Run will run an arbitrary command in the app machine. Base directory for all
commands is the root of the app. For example, in a Django app, "tsuru run" may
show the following output:

	% tsuru run polls ls -l
	app.conf
	brogui
	deploy
	foo
	__init__.py
	__init__.pyc
	main.go
	manage.py
	settings.py
	settings.pyc
	templates
	urls.py
	urls.pyc

Restart the app's application server

Usage:

	% tsuru restart <appname>

Restart will call the restart hook from the app platform (the "restart" hook
from the Juju charm).
*/
package documentation
<commit_msg>cmd/tsuru: fix doc (using spaces)<commit_after>/*
tsuru is a command line tool for application developers.

It provide some commands that allow a developer to register himself/herself,
manage teams, apps and services.

Usage:

	% tsuru <command> [args]

The currently available commands are (grouped by subject):

    target            changes or retrive the current tsuru server

    user-create       creates a new user
    login             authenticates the user with tsuru server
    logout            finishes the session with tsuru server
    key-add           adds a public key to tsuru deploy server
    key-remove        removes a public key from tsuru deploy server

    team-create       creates a new team (adding the current user to it automatically)
    team-list         list teams that the user is member
    team-user-add     adds a user to a team
    team-user-remove  removes a user from a team

    app-create        creates an app
    app-remove        removes an app
    app-list          lists apps that the user has access (see app-grant and team-user-add)
    app-grant         allows a team to have access to an app
    app-revoke        revokes access to an app from a team
    log               shows log for an app
    run               runs a command in all units of an app
    restart           restarts the app's application server

    env-get           display environment variables for an app
    env-set           set environment variable(s) to an app
    env-unset         unset environment variable(s) from an app

    bind              binds an app to a service instance
    unbind            unbinds an app from a service instance

    service-list      list all services, and instances of each service
    service-add       creates a new instance of a service
    service-remove    removes a instance of a service
    service-status    checks the status of a service instance
    service-info      list instances of a service, and apps binded to each instance
    service-doc       displays documentation for a service

Use "tsuru help <command>" for more information about a command.

Change/retrieve remote tsuru server

Usage:

	% tsuru target [target]

This command should be used to get current tsuru target, or retrieve current
target.

The target is the tsuru server to which all operations will be directed to.

Create a user

Usage:

	% tsuru user-create <email>

user-create creates a user within tsuru remote server. It will ask for the
password before issue the request.

Authenticate within remote tsuru server

Usage:

	% tsuru login <email>

Login will ask for the password and check if the user is successfully
authenticated. If so, the token generated by the tsuru server will be stored in
${HOME}/.tsuru_token.

All tsuru actions require the user to be authenticated (except login and
user-create, obviously).

Logout from remote tsuru server

Usage:

	% tsuru logout

Logout will delete the token file and terminate the session within tsuru
server.

Add SSH public key to tsuru's git server

Usage:

	% tsuru key-add [${HOME}/.ssh/id_rsa.pub]

key-add sends your public key to tsuru's git server. By default, it will try
send a public RSA key, located at ${HOME}/.ssh/id_rsa.pub. If you want to send
other file, you can call it with the path to the file. For example:

	% tsuru key-add /etc/my-keys/id_dsa.pub

The key will be added to the current logged in user.

Remove SSH public key from tsuru's git server

Usage:

	% tsuru key-remove [${HOME}/.ssh/id_rsa.pub]

key-remove removes your public key from tsuru's git server. By default, it will
try to remove a key that match you public RSA key located at
${HOME}/.ssh/id_rsa.pub. If you want to remove a key located somewhere else,
you can pass it as parameter to key-remove:

	% tsuru key-remove /etc/my-keys/id_dsa.pub

The key will be removed from the current logged in user.

Create a new team for the user

Usage:

	% tsuru team-create <teamname>

team-create will create a team for the user. Tsuru requires a user to be a
member of at least one team in order to create an app or a service instance.

When you create a team, you're automatically member of this team.

List teams that the user is member of

Usage:

	% tsuru team-list

team-list will list all teams that you are member of.

Add a user to a team

Usage:

	% tsuru team-user-add <teamname> <useremail>

team-user-add adds a user to a team. You need to be a member of the team to be
able to add a user to it.

Remove a user from a team

Usage:

	% tsuru team-user-remove <teamname> <useremail>

team-user-remove removes a user from a team. You need to be a member of the
team to be able to remove a user from it.

A team can never have 0 users. If you are the last member of a team, you can't
remove yourself from it.

Create an app

Usage:

	% tsuru app-create <appname> <platform>

app-create will create a new app using the given name and platform. For tsuru,
a platform is a Juju charm. To check the available platforms/charms, check this
URL: https://github.com/timeredbull/charms/tree/master/centos.

In order to create an app, you need to be member of at least one team. All
teams that you are member (see "tsuru team-list") will be able to access the
app.

Remove an app

Usage:

	% tsuru app-remove <appname>

app-remove removes an app. If the app is binded to any service instance, it
will be unbinded before be removed (see "tsuru unbind"). You need to be a
member of a team that has access to the app to be able to remove it (you are
able to remove any app that you see in "tsuru app-list").

List apps that the user has access to

Usage:

	% tsuru app-list

app-list will list all apps that you have access to. App access is controlled
by teams. If your team has access to an app, then you have access to it.

Allow a team to access an app

Usage:

	% tsuru app-grant <appname> <teamname>

app-grant will allow a team to access an app. You need to be a member of a team
that has access to the app to allow another team to access it.

Revoke from a team access to an app

Usage:

	% tsuru app-revoke <appname> <teamname>

app-revoke will revoke the permission to access an app from a team. You need to
have access to the app to revoke access from a team.

An app cannot be orphaned, so it will always have at least one authorized team.

See app's logs

Usage:

	% tsuru log <appname>

Log will show log entries for an app. These logs are not related to the code of
the app itself, but to actions of the app in tsuru server (deployments,
restarts, etc.).

Run an arbitrary command in the app machine

Usage:

	% tsuru run <appname> <command> [commandarg1] [commandarg2] ... [commandargn]

Run will run an arbitrary command in the app machine. Base directory for all
commands is the root of the app. For example, in a Django app, "tsuru run" may
show the following output:

	% tsuru run polls ls -l
	app.conf
	brogui
	deploy
	foo
	__init__.py
	__init__.pyc
	main.go
	manage.py
	settings.py
	settings.pyc
	templates
	urls.py
	urls.pyc

Restart the app's application server

Usage:

	% tsuru restart <appname>

Restart will call the restart hook from the app platform (the "restart" hook
from the Juju charm).
*/
package documentation
<|endoftext|>