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

import (
	"fmt"
	"time"

	"github.com/spf13/cobra"
	"k8s.io/klog/v2"

	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scale"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

var (
	scaleLong = templates.LongDesc(i18n.T(`
		Set a new size for a Deployment, ReplicaSet, Replication Controller, or StatefulSet.

		Scale also allows users to specify one or more preconditions for the scale action.

		If --current-replicas or --resource-version is specified, it is validated before the
		scale is attempted, and it is guaranteed that the precondition holds true when the
		scale is sent to the server.`))

	scaleExample = templates.Examples(i18n.T(`
		# Scale a replicaset named 'foo' to 3.
		kubectl scale --replicas=3 rs/foo

		# Scale a resource identified by type and name specified in "foo.yaml" to 3.
		kubectl scale --replicas=3 -f foo.yaml

		# If the deployment named mysql's current size is 2, scale mysql to 3.
		kubectl scale --current-replicas=2 --replicas=3 deployment/mysql

		# Scale multiple replication controllers.
		kubectl scale --replicas=5 rc/foo rc/bar rc/baz

		# Scale statefulset named 'web' to 3.
		kubectl scale --replicas=3 statefulset/web`))
)

const (
	timeout = 5 * time.Minute
)

type ScaleOptions struct {
	FilenameOptions resource.FilenameOptions
	RecordFlags     *genericclioptions.RecordFlags
	PrintFlags      *genericclioptions.PrintFlags
	PrintObj        printers.ResourcePrinterFunc

	Selector        string
	All             bool
	Replicas        int
	ResourceVersion string
	CurrentReplicas int
	Timeout         time.Duration

	Recorder                     genericclioptions.Recorder
	builder                      *resource.Builder
	namespace                    string
	enforceNamespace             bool
	args                         []string
	shortOutput                  bool
	clientSet                    kubernetes.Interface
	scaler                       scale.Scaler
	unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
	parent                       string
	dryRunStrategy               cmdutil.DryRunStrategy
	dryRunVerifier               *resource.DryRunVerifier

	genericclioptions.IOStreams
}

func NewScaleOptions(ioStreams genericclioptions.IOStreams) *ScaleOptions {
	return &ScaleOptions{
		PrintFlags:      genericclioptions.NewPrintFlags("scaled"),
		RecordFlags:     genericclioptions.NewRecordFlags(),
		CurrentReplicas: -1,
		Recorder:        genericclioptions.NoopRecorder{},
		IOStreams:       ioStreams,
	}
}

// NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale
func NewCmdScale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewScaleOptions(ioStreams)

	validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}

	cmd := &cobra.Command{
		Use:                   "scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | TYPE NAME)",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Set a new size for a Deployment, ReplicaSet or Replication Controller"),
		Long:                  scaleLong,
		Example:               scaleExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate(cmd))
			cmdutil.CheckErr(o.RunScale())
		},
		ValidArgs: validArgs,
	}

	o.RecordFlags.AddFlags(cmd)
	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
	cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types")
	cmd.Flags().StringVar(&o.ResourceVersion, "resource-version", o.ResourceVersion, i18n.T("Precondition for resource version. Requires that the current resource version match this value in order to scale."))
	cmd.Flags().IntVar(&o.CurrentReplicas, "current-replicas", o.CurrentReplicas, "Precondition for current size. Requires that the current size of the resource match this value in order to scale.")
	cmd.Flags().IntVar(&o.Replicas, "replicas", o.Replicas, "The new desired number of replicas. Required.")
	cmd.MarkFlagRequired("replicas")
	cmd.Flags().DurationVar(&o.Timeout, "timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to set a new size")
	cmdutil.AddDryRunFlag(cmd)
	return cmd
}

func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.RecordFlags.Complete(cmd)
	o.Recorder, err = o.RecordFlags.ToRecorder()
	if err != nil {
		return err
	}
	printer, err := o.PrintFlags.ToPrinter()
	if err != nil {
		return err
	}
	o.PrintObj = printer.PrintObj

	o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
	if err != nil {
		return err
	}
	dynamicClient, err := f.DynamicClient()
	if err != nil {
		return err
	}
	discoveryClient, err := f.ToDiscoveryClient()
	if err != nil {
		return err
	}
	o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)

	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}
	o.builder = f.NewBuilder()
	o.args = args
	o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
	o.clientSet, err = f.KubernetesClientSet()
	if err != nil {
		return err
	}
	o.scaler, err = scaler(f)
	if err != nil {
		return err
	}
	o.unstructuredClientForMapping = f.UnstructuredClientForMapping
	o.parent = cmd.Parent().Name()

	return nil
}

func (o *ScaleOptions) Validate(cmd *cobra.Command) error {
	if o.Replicas < 0 {
		return fmt.Errorf("The --replicas=COUNT flag is required, and COUNT must be greater than or equal to 0")
	}

	return nil
}

// RunScale executes the scaling
func (o *ScaleOptions) RunScale() error {
	r := o.builder.
		Unstructured().
		ContinueOnError().
		NamespaceParam(o.namespace).DefaultNamespace().
		FilenameParam(o.enforceNamespace, &o.FilenameOptions).
		ResourceTypeOrNameArgs(o.All, o.args...).
		Flatten().
		LabelSelectorParam(o.Selector).
		Do()
	err := r.Err()
	if err != nil {
		return err
	}

	infos := []*resource.Info{}
	err = r.Visit(func(info *resource.Info, err error) error {
		if err == nil {
			infos = append(infos, info)
		}
		return nil
	})

	if len(o.ResourceVersion) != 0 && len(infos) > 1 {
		return fmt.Errorf("cannot use --resource-version with multiple resources")
	}

	// only set a precondition if the user has requested one.  A nil precondition means we can do a blind update, so
	// we avoid a Scale GET that may or may not succeed
	var precondition *scale.ScalePrecondition
	if o.CurrentReplicas != -1 || len(o.ResourceVersion) > 0 {
		precondition = &scale.ScalePrecondition{Size: o.CurrentReplicas, ResourceVersion: o.ResourceVersion}
	}
	retry := scale.NewRetryParams(1*time.Second, 5*time.Minute)

	var waitForReplicas *scale.RetryParams
	if o.Timeout != 0 && o.dryRunStrategy == cmdutil.DryRunNone {
		waitForReplicas = scale.NewRetryParams(1*time.Second, timeout)
	}

	counter := 0
	err = r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		counter++

		mapping := info.ResourceMapping()
		if o.dryRunStrategy == cmdutil.DryRunClient {
			return o.PrintObj(info.Object, o.Out)
		}
		if err := o.scaler.Scale(info.Namespace, info.Name, uint(o.Replicas), precondition, retry, waitForReplicas, mapping.Resource, o.dryRunStrategy == cmdutil.DryRunServer); err != nil {
			return err
		}

		// if the recorder makes a change, compute and create another patch
		if mergePatch, err := o.Recorder.MakeRecordMergePatch(info.Object); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		} else if len(mergePatch) > 0 {
			client, err := o.unstructuredClientForMapping(mapping)
			if err != nil {
				return err
			}
			helper := resource.NewHelper(client, mapping)
			if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil {
				klog.V(4).Infof("error recording reason: %v", err)
			}
		}

		return o.PrintObj(info.Object, o.Out)
	})
	if err != nil {
		return err
	}
	if counter == 0 {
		return fmt.Errorf("no objects passed to scale")
	}
	return nil
}

func scaler(f cmdutil.Factory) (scale.Scaler, error) {
	scalesGetter, err := cmdutil.ScaleClientFn(f)
	if err != nil {
		return nil, err
	}

	return scale.NewScaler(scalesGetter), nil
}
<commit_msg>kubectl: fix the bug that kubectl scale does not honor --timeout flag<commit_after>/*
Copyright 2014 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 scale

import (
	"fmt"
	"time"

	"github.com/spf13/cobra"
	"k8s.io/klog/v2"

	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scale"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

var (
	scaleLong = templates.LongDesc(i18n.T(`
		Set a new size for a Deployment, ReplicaSet, Replication Controller, or StatefulSet.

		Scale also allows users to specify one or more preconditions for the scale action.

		If --current-replicas or --resource-version is specified, it is validated before the
		scale is attempted, and it is guaranteed that the precondition holds true when the
		scale is sent to the server.`))

	scaleExample = templates.Examples(i18n.T(`
		# Scale a replicaset named 'foo' to 3.
		kubectl scale --replicas=3 rs/foo

		# Scale a resource identified by type and name specified in "foo.yaml" to 3.
		kubectl scale --replicas=3 -f foo.yaml

		# If the deployment named mysql's current size is 2, scale mysql to 3.
		kubectl scale --current-replicas=2 --replicas=3 deployment/mysql

		# Scale multiple replication controllers.
		kubectl scale --replicas=5 rc/foo rc/bar rc/baz

		# Scale statefulset named 'web' to 3.
		kubectl scale --replicas=3 statefulset/web`))
)

type ScaleOptions struct {
	FilenameOptions resource.FilenameOptions
	RecordFlags     *genericclioptions.RecordFlags
	PrintFlags      *genericclioptions.PrintFlags
	PrintObj        printers.ResourcePrinterFunc

	Selector        string
	All             bool
	Replicas        int
	ResourceVersion string
	CurrentReplicas int
	Timeout         time.Duration

	Recorder                     genericclioptions.Recorder
	builder                      *resource.Builder
	namespace                    string
	enforceNamespace             bool
	args                         []string
	shortOutput                  bool
	clientSet                    kubernetes.Interface
	scaler                       scale.Scaler
	unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
	parent                       string
	dryRunStrategy               cmdutil.DryRunStrategy
	dryRunVerifier               *resource.DryRunVerifier

	genericclioptions.IOStreams
}

func NewScaleOptions(ioStreams genericclioptions.IOStreams) *ScaleOptions {
	return &ScaleOptions{
		PrintFlags:      genericclioptions.NewPrintFlags("scaled"),
		RecordFlags:     genericclioptions.NewRecordFlags(),
		CurrentReplicas: -1,
		Recorder:        genericclioptions.NoopRecorder{},
		IOStreams:       ioStreams,
	}
}

// NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale
func NewCmdScale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewScaleOptions(ioStreams)

	validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}

	cmd := &cobra.Command{
		Use:                   "scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | TYPE NAME)",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Set a new size for a Deployment, ReplicaSet or Replication Controller"),
		Long:                  scaleLong,
		Example:               scaleExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate(cmd))
			cmdutil.CheckErr(o.RunScale())
		},
		ValidArgs: validArgs,
	}

	o.RecordFlags.AddFlags(cmd)
	o.PrintFlags.AddFlags(cmd)

	cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
	cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types")
	cmd.Flags().StringVar(&o.ResourceVersion, "resource-version", o.ResourceVersion, i18n.T("Precondition for resource version. Requires that the current resource version match this value in order to scale."))
	cmd.Flags().IntVar(&o.CurrentReplicas, "current-replicas", o.CurrentReplicas, "Precondition for current size. Requires that the current size of the resource match this value in order to scale.")
	cmd.Flags().IntVar(&o.Replicas, "replicas", o.Replicas, "The new desired number of replicas. Required.")
	cmd.MarkFlagRequired("replicas")
	cmd.Flags().DurationVar(&o.Timeout, "timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to set a new size")
	cmdutil.AddDryRunFlag(cmd)
	return cmd
}

func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.RecordFlags.Complete(cmd)
	o.Recorder, err = o.RecordFlags.ToRecorder()
	if err != nil {
		return err
	}
	printer, err := o.PrintFlags.ToPrinter()
	if err != nil {
		return err
	}
	o.PrintObj = printer.PrintObj

	o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
	if err != nil {
		return err
	}
	dynamicClient, err := f.DynamicClient()
	if err != nil {
		return err
	}
	discoveryClient, err := f.ToDiscoveryClient()
	if err != nil {
		return err
	}
	o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)

	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}
	o.builder = f.NewBuilder()
	o.args = args
	o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
	o.clientSet, err = f.KubernetesClientSet()
	if err != nil {
		return err
	}
	o.scaler, err = scaler(f)
	if err != nil {
		return err
	}
	o.unstructuredClientForMapping = f.UnstructuredClientForMapping
	o.parent = cmd.Parent().Name()

	return nil
}

func (o *ScaleOptions) Validate(cmd *cobra.Command) error {
	if o.Replicas < 0 {
		return fmt.Errorf("The --replicas=COUNT flag is required, and COUNT must be greater than or equal to 0")
	}

	return nil
}

// RunScale executes the scaling
func (o *ScaleOptions) RunScale() error {
	r := o.builder.
		Unstructured().
		ContinueOnError().
		NamespaceParam(o.namespace).DefaultNamespace().
		FilenameParam(o.enforceNamespace, &o.FilenameOptions).
		ResourceTypeOrNameArgs(o.All, o.args...).
		Flatten().
		LabelSelectorParam(o.Selector).
		Do()
	err := r.Err()
	if err != nil {
		return err
	}

	infos := []*resource.Info{}
	err = r.Visit(func(info *resource.Info, err error) error {
		if err == nil {
			infos = append(infos, info)
		}
		return nil
	})

	if len(o.ResourceVersion) != 0 && len(infos) > 1 {
		return fmt.Errorf("cannot use --resource-version with multiple resources")
	}

	// only set a precondition if the user has requested one.  A nil precondition means we can do a blind update, so
	// we avoid a Scale GET that may or may not succeed
	var precondition *scale.ScalePrecondition
	if o.CurrentReplicas != -1 || len(o.ResourceVersion) > 0 {
		precondition = &scale.ScalePrecondition{Size: o.CurrentReplicas, ResourceVersion: o.ResourceVersion}
	}
	retry := scale.NewRetryParams(1*time.Second, 5*time.Minute)

	var waitForReplicas *scale.RetryParams
	if o.Timeout != 0 && o.dryRunStrategy == cmdutil.DryRunNone {
		waitForReplicas = scale.NewRetryParams(1*time.Second, o.Timeout)
	}

	counter := 0
	err = r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		counter++

		mapping := info.ResourceMapping()
		if o.dryRunStrategy == cmdutil.DryRunClient {
			return o.PrintObj(info.Object, o.Out)
		}
		if err := o.scaler.Scale(info.Namespace, info.Name, uint(o.Replicas), precondition, retry, waitForReplicas, mapping.Resource, o.dryRunStrategy == cmdutil.DryRunServer); err != nil {
			return err
		}

		// if the recorder makes a change, compute and create another patch
		if mergePatch, err := o.Recorder.MakeRecordMergePatch(info.Object); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		} else if len(mergePatch) > 0 {
			client, err := o.unstructuredClientForMapping(mapping)
			if err != nil {
				return err
			}
			helper := resource.NewHelper(client, mapping)
			if _, err := helper.Patch(info.Namespace, info.Name, types.MergePatchType, mergePatch, nil); err != nil {
				klog.V(4).Infof("error recording reason: %v", err)
			}
		}

		return o.PrintObj(info.Object, o.Out)
	})
	if err != nil {
		return err
	}
	if counter == 0 {
		return fmt.Errorf("no objects passed to scale")
	}
	return nil
}

func scaler(f cmdutil.Factory) (scale.Scaler, error) {
	scalesGetter, err := cmdutil.ScaleClientFn(f)
	if err != nil {
		return nil, err
	}

	return scale.NewScaler(scalesGetter), nil
}
<|endoftext|>


<commit_before>package command

const VERSION = "0.5.3"

var (
	GitCommit string
	BuildTime string
)
<commit_msg>Version bump: 0.6.0<commit_after>package command

const VERSION = "0.6.0"

var (
	GitCommit string
	BuildTime string
)
<|endoftext|>


<commit_before>package connpool

import (
	"context"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/sys/unix"

	"github.com/datawire/dlib/dlog"
	rpc "github.com/telepresenceio/telepresence/rpc/v2/manager"
)

// The idleDuration controls how long a dialer for a specific proto+from-to address combination remains alive without
// reading or writing any messages. The dialer is normally closed by one of the peers.
const connTTL = time.Minute
const dialTimeout = 30 * time.Second

const (
	notConnected = int32(iota)
	halfConnected
	connected
)

type TunnelStream interface {
	Send(*rpc.ConnMessage) error
	Recv() (*rpc.ConnMessage, error)
}

// The dialer takes care of dispatching messages between gRPC and UDP connections
type dialer struct {
	id         ConnID
	release    func()
	bidiStream TunnelStream
	incoming   chan Message
	conn       net.Conn
	idleTimer  *time.Timer
	idleLock   sync.Mutex
	connected  int32
}

// NewDialer creates a new handler that dispatches messages in both directions between the given gRPC server
// and the destination identified by the given connID.
//
// The handler remains active until it's been idle for idleDuration, at which time it will automatically close
// and call the release function it got from the connpool.Pool to ensure that it gets properly released.
func NewDialer(connID ConnID, bidiStream TunnelStream, release func()) Handler {
	return &dialer{
		id:         connID,
		bidiStream: bidiStream,
		release:    release,
		incoming:   make(chan Message, 10),
		connected:  notConnected,
	}
}

// HandlerFromConn is like NewHandler but initializes the handler with an already existing connection.
func HandlerFromConn(connID ConnID, bidiStream TunnelStream, release func(), conn net.Conn) Handler {
	return &dialer{
		id:         connID,
		bidiStream: bidiStream,
		release:    release,
		incoming:   make(chan Message, 10),
		connected:  halfConnected,
		conn:       conn,
	}
}

func (h *dialer) Start(ctx context.Context) {
	// Set up the idle timer to close and release this handler when it's been idle for a while.
	h.idleTimer = time.NewTimer(connTTL)

	switch h.connected {
	case notConnected:
		if h.id.Protocol() == unix.IPPROTO_UDP {
			h.open(ctx)
		}
	case halfConnected:
		// Connection is created by listener on this side. Establish other
		// side using a control message and start the loops
		h.connected = connected
		h.sendTCD(ctx, Connect)
	}
}

func (h *dialer) open(ctx context.Context) ControlCode {
	if !atomic.CompareAndSwapInt32(&h.connected, notConnected, connected) {
		// already connected
		return ConnectOK
	}
	dlog.Debugf(ctx, "   CONN %s, dialing", h.id)
	dialer := net.Dialer{Timeout: dialTimeout}
	conn, err := dialer.DialContext(ctx, h.id.ProtocolString(), h.id.DestinationAddr().String())
	if err != nil {
		dlog.Errorf(ctx, "%s: failed to establish connection: %v", h.id, err)
		return ConnectReject
	}
	h.conn = conn
	dlog.Debugf(ctx, "   CONN %s, dial answered", h.id)
	go h.writeLoop(ctx)
	go h.readLoop(ctx)
	return ConnectOK
}

func (h *dialer) handleControl(ctx context.Context, cm Control) {
	dlog.Debugf(ctx, "-> GRPC %s", cm)
	switch cm.Code() {
	case Connect:
		h.sendTCD(ctx, h.open(ctx))
	case ConnectOK:
		go h.writeLoop(ctx)
		go h.readLoop(ctx)
	case Disconnect:
		h.Close(ctx)
		h.sendTCD(ctx, DisconnectOK)
	case ReadClosed, WriteClosed:
		h.Close(ctx)
	case KeepAlive:
		h.resetIdle()
	default:
		dlog.Errorf(ctx, "%s: unhandled connection control message: %s", h.id, cm)
	}
}

// HandleMessage sends a package to the underlying TCP/UDP connection
func (h *dialer) HandleMessage(ctx context.Context, dg Message) {
	if ctrl, ok := dg.(Control); ok {
		h.handleControl(ctx, ctrl)
		return
	}
	select {
	case <-ctx.Done():
		return
	case h.incoming <- dg:
	}
}

// Close will close the underlying TCP/UDP connection
func (h *dialer) Close(_ context.Context) {
	if atomic.CompareAndSwapInt32(&h.connected, connected, notConnected) {
		h.release()
		if h.conn != nil {
			_ = h.conn.Close()
		}
	}
}

func (h *dialer) sendTCD(ctx context.Context, code ControlCode) {
	ctrl := NewControl(h.id, code, nil)
	// dlog.Debugf(ctx, "<- GRPC %s", ctrl)
	err := h.bidiStream.Send(ctrl.TunnelMessage())
	if err != nil {
		dlog.Errorf(ctx, "failed to send control message: %v", err)
	}
}

func (h *dialer) readLoop(ctx context.Context) {
	defer func() {
		// dlog.Debugf(ctx, "<- CONN %s, closing", h.id)
		h.Close(ctx)
	}()
	b := make([]byte, 0x8000)
	for ctx.Err() == nil {
		n, err := h.conn.Read(b)
		if err != nil {
			if atomic.LoadInt32(&h.connected) > 0 && ctx.Err() == nil {
				if h.id.Protocol() == unix.IPPROTO_TCP {
					h.sendTCD(ctx, ReadClosed)
				}
				dlog.Errorf(ctx, "!! CONN %s, conn read: %v", h.id, err)
			}
			return
		}
		if !h.resetIdle() {
			return
		}
		if n > 0 {
			// dlog.Debugf(ctx, "<- CONN %s, len %d", h.id, n)
			if err = h.bidiStream.Send(NewMessage(h.id, b[:n]).TunnelMessage()); err != nil {
				if ctx.Err() == nil {
					dlog.Errorf(ctx, "!! GRPC %s, send: %v", h.id, err)
				}
				return
			}
			// dlog.Debugf(ctx, "-> GRPC %s, len %d", h.id, n)
		}
	}
}

func (h *dialer) writeLoop(ctx context.Context) {
	defer h.Close(ctx)
	for {
		select {
		case <-ctx.Done():
			return
		case <-h.idleTimer.C:
			return
		case dg := <-h.incoming:
			if !h.resetIdle() {
				return
			}
			payload := dg.Payload()
			pn := len(payload)
			dlog.Debugf(ctx, "<- GRPC %s, len %d", h.id, pn)
			for n := 0; n < pn; {
				wn, err := h.conn.Write(payload[n:])
				if err != nil {
					if atomic.LoadInt32(&h.connected) > 0 && ctx.Err() == nil {
						if h.id.Protocol() == unix.IPPROTO_TCP {
							h.sendTCD(ctx, WriteClosed)
						}
						dlog.Errorf(ctx, "!! CONN %s, write: %v", h.id, err)
					}
					return
				}
				dlog.Debugf(ctx, "-> CONN %s, len %d", h.id, wn)
				n += wn
			}
		}
	}
}

func (h *dialer) resetIdle() bool {
	h.idleLock.Lock()
	stopped := h.idleTimer.Stop()
	if stopped {
		h.idleTimer.Reset(connTTL)
	}
	h.idleLock.Unlock()
	return stopped
}
<commit_msg>Ensure that connpool.dialer write queue drains when read closes.<commit_after>package connpool

import (
	"context"
	"io"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/sys/unix"

	"github.com/datawire/dlib/dlog"
	rpc "github.com/telepresenceio/telepresence/rpc/v2/manager"
)

// The idleDuration controls how long a dialer for a specific proto+from-to address combination remains alive without
// reading or writing any messages. The dialer is normally closed by one of the peers.
const connTTL = time.Minute
const dialTimeout = 30 * time.Second

const (
	notConnected = int32(iota)
	halfConnected
	connected
)

type TunnelStream interface {
	Send(*rpc.ConnMessage) error
	Recv() (*rpc.ConnMessage, error)
}

// The dialer takes care of dispatching messages between gRPC and UDP connections
type dialer struct {
	id         ConnID
	release    func()
	bidiStream TunnelStream
	incoming   chan Message
	conn       net.Conn
	idleTimer  *time.Timer
	idleLock   sync.Mutex
	connected  int32
}

// NewDialer creates a new handler that dispatches messages in both directions between the given gRPC server
// and the destination identified by the given connID.
//
// The handler remains active until it's been idle for idleDuration, at which time it will automatically close
// and call the release function it got from the connpool.Pool to ensure that it gets properly released.
func NewDialer(connID ConnID, bidiStream TunnelStream, release func()) Handler {
	return &dialer{
		id:         connID,
		bidiStream: bidiStream,
		release:    release,
		incoming:   make(chan Message, 10),
		connected:  notConnected,
	}
}

// HandlerFromConn is like NewHandler but initializes the handler with an already existing connection.
func HandlerFromConn(connID ConnID, bidiStream TunnelStream, release func(), conn net.Conn) Handler {
	return &dialer{
		id:         connID,
		bidiStream: bidiStream,
		release:    release,
		incoming:   make(chan Message, 10),
		connected:  halfConnected,
		conn:       conn,
	}
}

func (h *dialer) Start(ctx context.Context) {
	// Set up the idle timer to close and release this handler when it's been idle for a while.
	h.idleTimer = time.NewTimer(connTTL)

	switch h.connected {
	case notConnected:
		if h.id.Protocol() == unix.IPPROTO_UDP {
			h.open(ctx)
		}
	case halfConnected:
		// Connection is created by listener on this side. Establish other
		// side using a control message and start the loops
		h.connected = connected
		h.sendTCD(ctx, Connect)
	}
}

func (h *dialer) open(ctx context.Context) ControlCode {
	if !atomic.CompareAndSwapInt32(&h.connected, notConnected, connected) {
		// already connected
		return ConnectOK
	}
	dlog.Debugf(ctx, "   CONN %s, dialing", h.id)
	dialer := net.Dialer{Timeout: dialTimeout}
	conn, err := dialer.DialContext(ctx, h.id.ProtocolString(), h.id.DestinationAddr().String())
	if err != nil {
		dlog.Errorf(ctx, "%s: failed to establish connection: %v", h.id, err)
		return ConnectReject
	}
	h.conn = conn
	dlog.Debugf(ctx, "   CONN %s, dial answered", h.id)
	go h.writeLoop(ctx)
	go h.readLoop(ctx)
	return ConnectOK
}

func (h *dialer) handleControl(ctx context.Context, cm Control) {
	dlog.Debugf(ctx, "-> GRPC %s", cm)
	switch cm.Code() {
	case Connect:
		h.sendTCD(ctx, h.open(ctx))
	case ConnectOK:
		go h.writeLoop(ctx)
		go h.readLoop(ctx)
	case Disconnect:
		h.Close(ctx)
		h.sendTCD(ctx, DisconnectOK)
	case ReadClosed, WriteClosed:
		h.Close(ctx)
	case KeepAlive:
		h.resetIdle()
	default:
		dlog.Errorf(ctx, "%s: unhandled connection control message: %s", h.id, cm)
	}
}

// HandleMessage sends a package to the underlying TCP/UDP connection
func (h *dialer) HandleMessage(ctx context.Context, dg Message) {
	if ctrl, ok := dg.(Control); ok {
		h.handleControl(ctx, ctrl)
		return
	}
	select {
	case <-ctx.Done():
		return
	case h.incoming <- dg:
	}
}

// Close will close the underlying TCP/UDP connection
func (h *dialer) Close(_ context.Context) {
	if atomic.CompareAndSwapInt32(&h.connected, connected, notConnected) {
		h.release()
		if h.conn != nil {
			_ = h.conn.Close()
		}
	}
}

func (h *dialer) sendTCD(ctx context.Context, code ControlCode) {
	ctrl := NewControl(h.id, code, nil)
	// dlog.Debugf(ctx, "<- GRPC %s", ctrl)
	err := h.bidiStream.Send(ctrl.TunnelMessage())
	if err != nil {
		dlog.Errorf(ctx, "failed to send control message: %v", err)
	}
}

func (h *dialer) readLoop(ctx context.Context) {
	defer close(h.incoming) // allow write to drain and perform the close of the connection
	b := make([]byte, 0x8000)
	for ctx.Err() == nil {
		n, err := h.conn.Read(b)
		if err != nil {
			if atomic.LoadInt32(&h.connected) > 0 && ctx.Err() == nil {
				if h.id.Protocol() == unix.IPPROTO_TCP {
					h.sendTCD(ctx, ReadClosed)
				}
				if err != io.EOF {
					dlog.Errorf(ctx, "!! CONN %s, conn read: %v", h.id, err)
				}
			}
			return
		}
		if !h.resetIdle() {
			return
		}
		if n > 0 {
			// dlog.Debugf(ctx, "<- CONN %s, len %d", h.id, n)
			if err = h.bidiStream.Send(NewMessage(h.id, b[:n]).TunnelMessage()); err != nil {
				if ctx.Err() == nil {
					dlog.Errorf(ctx, "!! GRPC %s, send: %v", h.id, err)
				}
				return
			}
			// dlog.Debugf(ctx, "-> GRPC %s, len %d", h.id, n)
		}
	}
}

func (h *dialer) writeLoop(ctx context.Context) {
	defer h.Close(ctx)
	for {
		select {
		case <-ctx.Done():
			return
		case <-h.idleTimer.C:
			return
		case dg := <-h.incoming:
			if dg == nil {
				return
			}
			if !h.resetIdle() {
				return
			}
			payload := dg.Payload()
			pn := len(payload)
			dlog.Debugf(ctx, "<- GRPC %s, len %d", h.id, pn)
			for n := 0; n < pn; {
				wn, err := h.conn.Write(payload[n:])
				if err != nil {
					if atomic.LoadInt32(&h.connected) > 0 && ctx.Err() == nil {
						if h.id.Protocol() == unix.IPPROTO_TCP {
							h.sendTCD(ctx, WriteClosed)
						}
						dlog.Errorf(ctx, "!! CONN %s, write: %v", h.id, err)
					}
					return
				}
				dlog.Debugf(ctx, "-> CONN %s, len %d", h.id, wn)
				n += wn
			}
		}
	}
}

func (h *dialer) resetIdle() bool {
	h.idleLock.Lock()
	stopped := h.idleTimer.Stop()
	if stopped {
		h.idleTimer.Reset(connTTL)
	}
	h.idleLock.Unlock()
	return stopped
}
<|endoftext|>


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

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"

	"github.com/blang/semver"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/mcnutils"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/golang/glog"
	"github.com/hashicorp/go-getter"
	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/version"

	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/util"
)

// GetDiskPath returns the path of the machine disk image
func GetDiskPath(d *drivers.BaseDriver) string {
	return filepath.Join(d.ResolveStorePath("."), d.GetMachineName()+".rawdisk")
}

// CommonDriver is the common driver base class
type CommonDriver struct{}

// GetCreateFlags is not implemented yet
func (d *CommonDriver) GetCreateFlags() []mcnflag.Flag {
	return nil
}

// SetConfigFromFlags is not implemented yet
func (d *CommonDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
	return nil
}

func createRawDiskImage(sshKeyPath, diskPath string, diskSizeMb int) error {
	tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath)
	if err != nil {
		return errors.Wrap(err, "make disk image")
	}

	file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
	if err != nil {
		return errors.Wrap(err, "open")
	}
	defer file.Close()
	if _, err := file.Seek(0, io.SeekStart); err != nil {
		return errors.Wrap(err, "seek")
	}

	if _, err := file.Write(tarBuf.Bytes()); err != nil {
		return errors.Wrap(err, "write tar")
	}
	if err := file.Close(); err != nil {
		return errors.Wrapf(err, "closing file %s", diskPath)
	}

	if err := os.Truncate(diskPath, int64(diskSizeMb*1000000)); err != nil {
		return errors.Wrap(err, "truncate")
	}
	return nil
}

func publicSSHKeyPath(d *drivers.BaseDriver) string {
	return d.GetSSHKeyPath() + ".pub"
}

// Restart a host. This may just call Stop(); Start() if the provider does not
// have any special restart behaviour.
func Restart(d drivers.Driver) error {
	if err := d.Stop(); err != nil {
		return err
	}

	return d.Start()
}

// MakeDiskImage makes a boot2docker VM disk image.
func MakeDiskImage(d *drivers.BaseDriver, boot2dockerURL string, diskSize int) error {
	glog.Infof("Making disk image using store path: %s", d.StorePath)
	b2 := mcnutils.NewB2dUtils(d.StorePath)
	if err := b2.CopyIsoToMachineDir(boot2dockerURL, d.MachineName); err != nil {
		return errors.Wrap(err, "copy iso to machine dir")
	}

	keyPath := d.GetSSHKeyPath()
	glog.Infof("Creating ssh key: %s...", keyPath)
	if err := ssh.GenerateSSHKey(keyPath); err != nil {
		return errors.Wrap(err, "generate ssh key")
	}

	diskPath := GetDiskPath(d)
	glog.Infof("Creating raw disk image: %s...", diskPath)
	if _, err := os.Stat(diskPath); os.IsNotExist(err) {
		if err := createRawDiskImage(publicSSHKeyPath(d), diskPath, diskSize); err != nil {
			return errors.Wrapf(err, "createRawDiskImage(%s)", diskPath)
		}
		machPath := d.ResolveStorePath(".")
		if err := fixPermissions(machPath); err != nil {
			return errors.Wrapf(err, "fixing permissions on %s", machPath)
		}
	}
	return nil
}

func fixPermissions(path string) error {
	glog.Infof("Fixing permissions on %s ...", path)
	if err := os.Chown(path, syscall.Getuid(), syscall.Getegid()); err != nil {
		return errors.Wrap(err, "chown dir")
	}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return errors.Wrap(err, "read dir")
	}
	for _, f := range files {
		fp := filepath.Join(path, f.Name())
		if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil {
			return errors.Wrap(err, "chown file")
		}
	}
	return nil
}

// InstallOrUpdate downloads driver if it is not present, or updates it if there's a newer version
func InstallOrUpdate(driver, destination string, v semver.Version) error {
	glog.Infof("InstallOrUpdate(%s): dest=%s, version=%s, PATH=%s", driver, destination, v, os.Getenv("PATH"))

	_, err := exec.LookPath(driver)
	// if file driver doesn't exist, download it
	if err != nil {
		glog.Infof("LookPath %s: %v", driver, err)
		return download(driver, v, destination)
	}

	cmd := exec.Command(driver, "version")
	output, err := cmd.Output()
	// if driver doesnt support 'version', it is old, download it
	if err != nil {
		glog.Infof("%s version: %v", driver, err)
		return download(driver, v, destination)
	}

	ev := ExtractVMDriverVersion(string(output))

	// if the driver doesn't return any version, download it
	if len(ev) == 0 {
		glog.Infof("%s: unable to extract version from %q", driver, output)
		return download(driver, v, destination)
	}

	vmDriverVersion, err := semver.Make(ev)
	if err != nil {
		return errors.Wrap(err, "can't parse driver version")
	}

	// if the current driver version is older, download newer
	if vmDriverVersion.LT(v) {
		glog.Infof("%s is version %s, want %s", driver, vmDriverVersion, v)
		return download(driver, v, destination)
	}

	return nil
}

func driverWithChecksumURL(driver string, v semver.Version) string {
	base := fmt.Sprintf("https://github.com/kubernetes/minikube/releases/download/v%s/%s", v, driver)
	return fmt.Sprintf("%s?checksum=file:%s.sha256", base, base)
}

func download(driver string, v semver.Version, destination string) error {
	// supports kvm2 and hyperkit
	if driver != "docker-machine-driver-kvm2" && driver != "docker-machine-driver-hyperkit" {
		return nil
	}

	out.T(out.FileDownload, "Downloading driver {{.driver}}:", out.V{"driver": driver})
	targetFilepath := path.Join(destination, driver)
	os.Remove(targetFilepath)
	url := driverWithChecksumURL(driver, v)
	client := &getter.Client{
		Src:     url,
		Dst:     targetFilepath,
		Mode:    getter.ClientModeFile,
		Options: []getter.ClientOption{getter.WithProgress(util.DefaultProgressBar)},
	}

	glog.Infof("Downloading: %+v", client)

	if err := client.Get(); err != nil {
		return errors.Wrapf(err, "download failed: %s", url)
	}

	err := os.Chmod(targetFilepath, 0755)
	if err != nil {
		return errors.Wrap(err, "chmod error")
	}

	if driver == "docker-machine-driver-hyperkit" {
		err := setHyperKitPermissions(targetFilepath)
		if err != nil {
			return errors.Wrap(err, "setting hyperkit permission")
		}
	}

	return nil
}

// ExtractVMDriverVersion extracts the driver version.
// KVM and Hyperkit drivers support the 'version' command, that display the information as:
// version: vX.X.X
// commit: XXXX
// This method returns the version 'vX.X.X' or empty if the version isn't found.
func ExtractVMDriverVersion(s string) string {
	versionRegex := regexp.MustCompile(`version:(.*)`)
	matches := versionRegex.FindStringSubmatch(s)

	if len(matches) != 2 {
		return ""
	}

	v := strings.TrimSpace(matches[1])
	return strings.TrimPrefix(v, version.VersionPrefix)
}

func setHyperKitPermissions(driverPath string) error {
	msg := fmt.Sprintf("A new hyperkit driver was installed. It needs elevated permissions to run. The following commands will be executed:\n  sudo chown root:wheel %s\n  sudo chmod u+s %s", driverPath, driverPath)
	out.T(out.Permissions, msg, out.V{})

	cmd := exec.Command("sudo", "chown", "root:wheel", driverPath)
	err := cmd.Run()
	if err != nil {
		return errors.Wrap(err, "chown root:wheel")
	}

	cmd = exec.Command("sudo", "chmod", "u+s", driverPath)
	err = cmd.Run()
	if err != nil {
		return errors.Wrap(err, "chmod u+s")
	}

	return nil
}
<commit_msg>Improve visibility of sudo commands<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 drivers

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"

	"github.com/blang/semver"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/mcnflag"
	"github.com/docker/machine/libmachine/mcnutils"
	"github.com/docker/machine/libmachine/ssh"
	"github.com/golang/glog"
	"github.com/hashicorp/go-getter"
	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/version"

	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/util"
)

// GetDiskPath returns the path of the machine disk image
func GetDiskPath(d *drivers.BaseDriver) string {
	return filepath.Join(d.ResolveStorePath("."), d.GetMachineName()+".rawdisk")
}

// CommonDriver is the common driver base class
type CommonDriver struct{}

// GetCreateFlags is not implemented yet
func (d *CommonDriver) GetCreateFlags() []mcnflag.Flag {
	return nil
}

// SetConfigFromFlags is not implemented yet
func (d *CommonDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
	return nil
}

func createRawDiskImage(sshKeyPath, diskPath string, diskSizeMb int) error {
	tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath)
	if err != nil {
		return errors.Wrap(err, "make disk image")
	}

	file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
	if err != nil {
		return errors.Wrap(err, "open")
	}
	defer file.Close()
	if _, err := file.Seek(0, io.SeekStart); err != nil {
		return errors.Wrap(err, "seek")
	}

	if _, err := file.Write(tarBuf.Bytes()); err != nil {
		return errors.Wrap(err, "write tar")
	}
	if err := file.Close(); err != nil {
		return errors.Wrapf(err, "closing file %s", diskPath)
	}

	if err := os.Truncate(diskPath, int64(diskSizeMb*1000000)); err != nil {
		return errors.Wrap(err, "truncate")
	}
	return nil
}

func publicSSHKeyPath(d *drivers.BaseDriver) string {
	return d.GetSSHKeyPath() + ".pub"
}

// Restart a host. This may just call Stop(); Start() if the provider does not
// have any special restart behaviour.
func Restart(d drivers.Driver) error {
	if err := d.Stop(); err != nil {
		return err
	}

	return d.Start()
}

// MakeDiskImage makes a boot2docker VM disk image.
func MakeDiskImage(d *drivers.BaseDriver, boot2dockerURL string, diskSize int) error {
	glog.Infof("Making disk image using store path: %s", d.StorePath)
	b2 := mcnutils.NewB2dUtils(d.StorePath)
	if err := b2.CopyIsoToMachineDir(boot2dockerURL, d.MachineName); err != nil {
		return errors.Wrap(err, "copy iso to machine dir")
	}

	keyPath := d.GetSSHKeyPath()
	glog.Infof("Creating ssh key: %s...", keyPath)
	if err := ssh.GenerateSSHKey(keyPath); err != nil {
		return errors.Wrap(err, "generate ssh key")
	}

	diskPath := GetDiskPath(d)
	glog.Infof("Creating raw disk image: %s...", diskPath)
	if _, err := os.Stat(diskPath); os.IsNotExist(err) {
		if err := createRawDiskImage(publicSSHKeyPath(d), diskPath, diskSize); err != nil {
			return errors.Wrapf(err, "createRawDiskImage(%s)", diskPath)
		}
		machPath := d.ResolveStorePath(".")
		if err := fixPermissions(machPath); err != nil {
			return errors.Wrapf(err, "fixing permissions on %s", machPath)
		}
	}
	return nil
}

func fixPermissions(path string) error {
	glog.Infof("Fixing permissions on %s ...", path)
	if err := os.Chown(path, syscall.Getuid(), syscall.Getegid()); err != nil {
		return errors.Wrap(err, "chown dir")
	}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return errors.Wrap(err, "read dir")
	}
	for _, f := range files {
		fp := filepath.Join(path, f.Name())
		if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil {
			return errors.Wrap(err, "chown file")
		}
	}
	return nil
}

// InstallOrUpdate downloads driver if it is not present, or updates it if there's a newer version
func InstallOrUpdate(driver, destination string, v semver.Version) error {
	glog.Infof("InstallOrUpdate(%s): dest=%s, version=%s, PATH=%s", driver, destination, v, os.Getenv("PATH"))

	_, err := exec.LookPath(driver)
	// if file driver doesn't exist, download it
	if err != nil {
		glog.Infof("LookPath %s: %v", driver, err)
		return download(driver, v, destination)
	}

	cmd := exec.Command(driver, "version")
	output, err := cmd.Output()
	// if driver doesnt support 'version', it is old, download it
	if err != nil {
		glog.Infof("%s version: %v", driver, err)
		return download(driver, v, destination)
	}

	ev := ExtractVMDriverVersion(string(output))

	// if the driver doesn't return any version, download it
	if len(ev) == 0 {
		glog.Infof("%s: unable to extract version from %q", driver, output)
		return download(driver, v, destination)
	}

	vmDriverVersion, err := semver.Make(ev)
	if err != nil {
		return errors.Wrap(err, "can't parse driver version")
	}

	// if the current driver version is older, download newer
	if vmDriverVersion.LT(v) {
		glog.Infof("%s is version %s, want %s", driver, vmDriverVersion, v)
		return download(driver, v, destination)
	}

	return nil
}

func driverWithChecksumURL(driver string, v semver.Version) string {
	base := fmt.Sprintf("https://github.com/kubernetes/minikube/releases/download/v%s/%s", v, driver)
	return fmt.Sprintf("%s?checksum=file:%s.sha256", base, base)
}

func download(driver string, v semver.Version, destination string) error {
	// supports kvm2 and hyperkit
	if driver != "docker-machine-driver-kvm2" && driver != "docker-machine-driver-hyperkit" {
		return nil
	}

	out.T(out.FileDownload, "Downloading driver {{.driver}}:", out.V{"driver": driver})
	targetFilepath := path.Join(destination, driver)
	os.Remove(targetFilepath)
	url := driverWithChecksumURL(driver, v)
	client := &getter.Client{
		Src:     url,
		Dst:     targetFilepath,
		Mode:    getter.ClientModeFile,
		Options: []getter.ClientOption{getter.WithProgress(util.DefaultProgressBar)},
	}

	glog.Infof("Downloading: %+v", client)

	if err := client.Get(); err != nil {
		return errors.Wrapf(err, "download failed: %s", url)
	}

	err := os.Chmod(targetFilepath, 0755)
	if err != nil {
		return errors.Wrap(err, "chmod error")
	}

	if driver == "docker-machine-driver-hyperkit" {
		err := setHyperKitPermissions(targetFilepath)
		if err != nil {
			return errors.Wrap(err, "setting hyperkit permission")
		}
	}

	return nil
}

// ExtractVMDriverVersion extracts the driver version.
// KVM and Hyperkit drivers support the 'version' command, that display the information as:
// version: vX.X.X
// commit: XXXX
// This method returns the version 'vX.X.X' or empty if the version isn't found.
func ExtractVMDriverVersion(s string) string {
	versionRegex := regexp.MustCompile(`version:(.*)`)
	matches := versionRegex.FindStringSubmatch(s)

	if len(matches) != 2 {
		return ""
	}

	v := strings.TrimSpace(matches[1])
	return strings.TrimPrefix(v, version.VersionPrefix)
}

func setHyperKitPermissions(driverPath string) error {
	msg := fmt.Sprintf("A new hyperkit driver was installed. It needs elevated permissions to run. The following commands will be executed:\n\n    $ sudo chown root:wheel %s\n    $ sudo chmod u+s %s\n", driverPath, driverPath)
	out.T(out.Permissions, msg, out.V{})

	cmd := exec.Command("sudo", "chown", "root:wheel", driverPath)
	err := cmd.Run()
	if err != nil {
		return errors.Wrap(err, "chown root:wheel")
	}

	cmd = exec.Command("sudo", "chmod", "u+s", driverPath)
	err = cmd.Run()
	if err != nil {
		return errors.Wrap(err, "chmod u+s")
	}

	return nil
}
<|endoftext|>


<commit_before>package idtools

import (
	"bufio"
	"fmt"
	"os"
	"os/user"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/containers/storage/pkg/system"
	"github.com/pkg/errors"
)

// IDMap contains a single entry for user namespace range remapping. An array
// of IDMap entries represents the structure that will be provided to the Linux
// kernel for creating a user namespace.
type IDMap struct {
	ContainerID int `json:"container_id"`
	HostID      int `json:"host_id"`
	Size        int `json:"size"`
}

type subIDRange struct {
	Start  int
	Length int
}

type ranges []subIDRange

func (e ranges) Len() int           { return len(e) }
func (e ranges) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }

const (
	subuidFileName string = "/etc/subuid"
	subgidFileName string = "/etc/subgid"
)

// MkdirAllAs creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid.  If the directory already exists, this
// function will still change ownership to the requested uid/gid pair.
// Deprecated: Use MkdirAllAndChown
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
	return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
}

// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership
// Deprecated: Use MkdirAndChown with a IDPair
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
	return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
}

// MkdirAllAndChown creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid.  If the directory already exists, this
// function will still change ownership to the requested uid/gid pair.
func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error {
	return mkdirAs(path, mode, ids.UID, ids.GID, true, true)
}

// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership
func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error {
	return mkdirAs(path, mode, ids.UID, ids.GID, false, true)
}

// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
// ownership ONLY of newly created directories to the requested uid/gid. If the
// directories along the path exist, no change of ownership will be performed
func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error {
	return mkdirAs(path, mode, ids.UID, ids.GID, true, false)
}

// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
	uid, err := toHost(0, uidMap)
	if err != nil {
		return -1, -1, err
	}
	gid, err := toHost(0, gidMap)
	if err != nil {
		return -1, -1, err
	}
	return uid, gid, nil
}

// toContainer takes an id mapping, and uses it to translate a
// host ID to the remapped ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id
func toContainer(hostID int, idMap []IDMap) (int, error) {
	if idMap == nil {
		return hostID, nil
	}
	for _, m := range idMap {
		if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
			contID := m.ContainerID + (hostID - m.HostID)
			return contID, nil
		}
	}
	return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
}

// toHost takes an id mapping and a remapped ID, and translates the
// ID to the mapped host ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id #
func toHost(contID int, idMap []IDMap) (int, error) {
	if idMap == nil {
		return contID, nil
	}
	for _, m := range idMap {
		if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
			hostID := m.HostID + (contID - m.ContainerID)
			return hostID, nil
		}
	}
	return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
}

// IDPair is a UID and GID pair
type IDPair struct {
	UID int
	GID int
}

// IDMappings contains a mappings of UIDs and GIDs
type IDMappings struct {
	uids []IDMap
	gids []IDMap
}

// NewIDMappings takes a requested user and group name and
// using the data from /etc/sub{uid,gid} ranges, creates the
// proper uid and gid remapping ranges for that user/group pair
func NewIDMappings(username, groupname string) (*IDMappings, error) {
	subuidRanges, err := parseSubuid(username)
	if err != nil {
		return nil, err
	}
	subgidRanges, err := parseSubgid(groupname)
	if err != nil {
		return nil, err
	}
	if len(subuidRanges) == 0 {
		return nil, fmt.Errorf("No subuid ranges found for user %q in %s", username, subuidFileName)
	}
	if len(subgidRanges) == 0 {
		return nil, fmt.Errorf("No subgid ranges found for group %q in %s", groupname, subgidFileName)
	}

	return &IDMappings{
		uids: createIDMap(subuidRanges),
		gids: createIDMap(subgidRanges),
	}, nil
}

// NewIDMappingsFromMaps creates a new mapping from two slices
// Deprecated: this is a temporary shim while transitioning to IDMapping
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings {
	return &IDMappings{uids: uids, gids: gids}
}

// RootPair returns a uid and gid pair for the root user. The error is ignored
// because a root user always exists, and the defaults are correct when the uid
// and gid maps are empty.
func (i *IDMappings) RootPair() IDPair {
	uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
	return IDPair{UID: uid, GID: gid}
}

// ToHost returns the host UID and GID for the container uid, gid.
// Remapping is only performed if the ids aren't already the remapped root ids
func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) {
	var err error
	target := i.RootPair()

	if pair.UID != target.UID {
		target.UID, err = toHost(pair.UID, i.uids)
		if err != nil {
			return target, err
		}
	}

	if pair.GID != target.GID {
		target.GID, err = toHost(pair.GID, i.gids)
	}
	return target, err
}

// ToContainer returns the container UID and GID for the host uid and gid
func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) {
	uid, err := toContainer(pair.UID, i.uids)
	if err != nil {
		return -1, -1, err
	}
	gid, err := toContainer(pair.GID, i.gids)
	return uid, gid, err
}

// Empty returns true if there are no id mappings
func (i *IDMappings) Empty() bool {
	return len(i.uids) == 0 && len(i.gids) == 0
}

// UIDs return the UID mapping
// TODO: remove this once everything has been refactored to use pairs
func (i *IDMappings) UIDs() []IDMap {
	return i.uids
}

// GIDs return the UID mapping
// TODO: remove this once everything has been refactored to use pairs
func (i *IDMappings) GIDs() []IDMap {
	return i.gids
}

func createIDMap(subidRanges ranges) []IDMap {
	idMap := []IDMap{}

	// sort the ranges by lowest ID first
	sort.Sort(subidRanges)
	containerID := 0
	for _, idrange := range subidRanges {
		idMap = append(idMap, IDMap{
			ContainerID: containerID,
			HostID:      idrange.Start,
			Size:        idrange.Length,
		})
		containerID = containerID + idrange.Length
	}
	return idMap
}

func parseSubuid(username string) (ranges, error) {
	return parseSubidFile(subuidFileName, username)
}

func parseSubgid(username string) (ranges, error) {
	return parseSubidFile(subgidFileName, username)
}

// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
// and return all found ranges for a specified username. If the special value
// "ALL" is supplied for username, then all ranges in the file will be returned
func parseSubidFile(path, username string) (ranges, error) {
	var (
		rangeList ranges
		uidstr    string
	)
	if u, err := user.Lookup(username); err == nil {
		uidstr = u.Uid
	}

	subidFile, err := os.Open(path)
	if err != nil {
		return rangeList, err
	}
	defer subidFile.Close()

	s := bufio.NewScanner(subidFile)
	for s.Scan() {
		if err := s.Err(); err != nil {
			return rangeList, err
		}

		text := strings.TrimSpace(s.Text())
		if text == "" || strings.HasPrefix(text, "#") {
			continue
		}
		parts := strings.Split(text, ":")
		if len(parts) != 3 {
			return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
		}
		if parts[0] == username || username == "ALL" || (parts[0] == uidstr && parts[0] != "") {
			startid, err := strconv.Atoi(parts[1])
			if err != nil {
				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
			}
			length, err := strconv.Atoi(parts[2])
			if err != nil {
				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
			}
			rangeList = append(rangeList, subIDRange{startid, length})
		}
	}
	return rangeList, nil
}

func checkChownErr(err error, name string, uid, gid int) error {
	if e, ok := err.(*os.PathError); ok && e.Err == syscall.EINVAL {
		return errors.Wrapf(err, "there might not be enough IDs available in the namespace (requested %d:%d for %s)", uid, gid, name)
	}
	return err
}

func SafeChown(name string, uid, gid int) error {
	if stat, statErr := system.Stat(name); statErr == nil {
		if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
			return nil
		}
	}
	return checkChownErr(os.Chown(name, uid, gid), name, uid, gid)
}

func SafeLchown(name string, uid, gid int) error {
	if stat, statErr := system.Lstat(name); statErr == nil {
		if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
			return nil
		}
	}
	return checkChownErr(os.Lchown(name, uid, gid), name, uid, gid)
}
<commit_msg>Mention /etc/subuid and /etc/subgid files in error message<commit_after>package idtools

import (
	"bufio"
	"fmt"
	"os"
	"os/user"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/containers/storage/pkg/system"
	"github.com/pkg/errors"
)

// IDMap contains a single entry for user namespace range remapping. An array
// of IDMap entries represents the structure that will be provided to the Linux
// kernel for creating a user namespace.
type IDMap struct {
	ContainerID int `json:"container_id"`
	HostID      int `json:"host_id"`
	Size        int `json:"size"`
}

type subIDRange struct {
	Start  int
	Length int
}

type ranges []subIDRange

func (e ranges) Len() int           { return len(e) }
func (e ranges) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }

const (
	subuidFileName string = "/etc/subuid"
	subgidFileName string = "/etc/subgid"
)

// MkdirAllAs creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid.  If the directory already exists, this
// function will still change ownership to the requested uid/gid pair.
// Deprecated: Use MkdirAllAndChown
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
	return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
}

// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership
// Deprecated: Use MkdirAndChown with a IDPair
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
	return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
}

// MkdirAllAndChown creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid.  If the directory already exists, this
// function will still change ownership to the requested uid/gid pair.
func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error {
	return mkdirAs(path, mode, ids.UID, ids.GID, true, true)
}

// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership
func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error {
	return mkdirAs(path, mode, ids.UID, ids.GID, false, true)
}

// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
// ownership ONLY of newly created directories to the requested uid/gid. If the
// directories along the path exist, no change of ownership will be performed
func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error {
	return mkdirAs(path, mode, ids.UID, ids.GID, true, false)
}

// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
	uid, err := toHost(0, uidMap)
	if err != nil {
		return -1, -1, err
	}
	gid, err := toHost(0, gidMap)
	if err != nil {
		return -1, -1, err
	}
	return uid, gid, nil
}

// toContainer takes an id mapping, and uses it to translate a
// host ID to the remapped ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id
func toContainer(hostID int, idMap []IDMap) (int, error) {
	if idMap == nil {
		return hostID, nil
	}
	for _, m := range idMap {
		if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
			contID := m.ContainerID + (hostID - m.HostID)
			return contID, nil
		}
	}
	return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
}

// toHost takes an id mapping and a remapped ID, and translates the
// ID to the mapped host ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id #
func toHost(contID int, idMap []IDMap) (int, error) {
	if idMap == nil {
		return contID, nil
	}
	for _, m := range idMap {
		if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
			hostID := m.HostID + (contID - m.ContainerID)
			return hostID, nil
		}
	}
	return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
}

// IDPair is a UID and GID pair
type IDPair struct {
	UID int
	GID int
}

// IDMappings contains a mappings of UIDs and GIDs
type IDMappings struct {
	uids []IDMap
	gids []IDMap
}

// NewIDMappings takes a requested user and group name and
// using the data from /etc/sub{uid,gid} ranges, creates the
// proper uid and gid remapping ranges for that user/group pair
func NewIDMappings(username, groupname string) (*IDMappings, error) {
	subuidRanges, err := parseSubuid(username)
	if err != nil {
		return nil, err
	}
	subgidRanges, err := parseSubgid(groupname)
	if err != nil {
		return nil, err
	}
	if len(subuidRanges) == 0 {
		return nil, fmt.Errorf("No subuid ranges found for user %q in %s", username, subuidFileName)
	}
	if len(subgidRanges) == 0 {
		return nil, fmt.Errorf("No subgid ranges found for group %q in %s", groupname, subgidFileName)
	}

	return &IDMappings{
		uids: createIDMap(subuidRanges),
		gids: createIDMap(subgidRanges),
	}, nil
}

// NewIDMappingsFromMaps creates a new mapping from two slices
// Deprecated: this is a temporary shim while transitioning to IDMapping
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings {
	return &IDMappings{uids: uids, gids: gids}
}

// RootPair returns a uid and gid pair for the root user. The error is ignored
// because a root user always exists, and the defaults are correct when the uid
// and gid maps are empty.
func (i *IDMappings) RootPair() IDPair {
	uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
	return IDPair{UID: uid, GID: gid}
}

// ToHost returns the host UID and GID for the container uid, gid.
// Remapping is only performed if the ids aren't already the remapped root ids
func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) {
	var err error
	target := i.RootPair()

	if pair.UID != target.UID {
		target.UID, err = toHost(pair.UID, i.uids)
		if err != nil {
			return target, err
		}
	}

	if pair.GID != target.GID {
		target.GID, err = toHost(pair.GID, i.gids)
	}
	return target, err
}

// ToContainer returns the container UID and GID for the host uid and gid
func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) {
	uid, err := toContainer(pair.UID, i.uids)
	if err != nil {
		return -1, -1, err
	}
	gid, err := toContainer(pair.GID, i.gids)
	return uid, gid, err
}

// Empty returns true if there are no id mappings
func (i *IDMappings) Empty() bool {
	return len(i.uids) == 0 && len(i.gids) == 0
}

// UIDs return the UID mapping
// TODO: remove this once everything has been refactored to use pairs
func (i *IDMappings) UIDs() []IDMap {
	return i.uids
}

// GIDs return the UID mapping
// TODO: remove this once everything has been refactored to use pairs
func (i *IDMappings) GIDs() []IDMap {
	return i.gids
}

func createIDMap(subidRanges ranges) []IDMap {
	idMap := []IDMap{}

	// sort the ranges by lowest ID first
	sort.Sort(subidRanges)
	containerID := 0
	for _, idrange := range subidRanges {
		idMap = append(idMap, IDMap{
			ContainerID: containerID,
			HostID:      idrange.Start,
			Size:        idrange.Length,
		})
		containerID = containerID + idrange.Length
	}
	return idMap
}

func parseSubuid(username string) (ranges, error) {
	return parseSubidFile(subuidFileName, username)
}

func parseSubgid(username string) (ranges, error) {
	return parseSubidFile(subgidFileName, username)
}

// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
// and return all found ranges for a specified username. If the special value
// "ALL" is supplied for username, then all ranges in the file will be returned
func parseSubidFile(path, username string) (ranges, error) {
	var (
		rangeList ranges
		uidstr    string
	)
	if u, err := user.Lookup(username); err == nil {
		uidstr = u.Uid
	}

	subidFile, err := os.Open(path)
	if err != nil {
		return rangeList, err
	}
	defer subidFile.Close()

	s := bufio.NewScanner(subidFile)
	for s.Scan() {
		if err := s.Err(); err != nil {
			return rangeList, err
		}

		text := strings.TrimSpace(s.Text())
		if text == "" || strings.HasPrefix(text, "#") {
			continue
		}
		parts := strings.Split(text, ":")
		if len(parts) != 3 {
			return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
		}
		if parts[0] == username || username == "ALL" || (parts[0] == uidstr && parts[0] != "") {
			startid, err := strconv.Atoi(parts[1])
			if err != nil {
				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
			}
			length, err := strconv.Atoi(parts[2])
			if err != nil {
				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
			}
			rangeList = append(rangeList, subIDRange{startid, length})
		}
	}
	return rangeList, nil
}

func checkChownErr(err error, name string, uid, gid int) error {
	if e, ok := err.(*os.PathError); ok && e.Err == syscall.EINVAL {
		return errors.Wrapf(err, "potentially insufficient UIDs or GIDs available in user namespace (requested %d:%d for %s): Check /etc/subuid and /etc/subgid", uid, gid, name)
	}
	return err
}

func SafeChown(name string, uid, gid int) error {
	if stat, statErr := system.Stat(name); statErr == nil {
		if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
			return nil
		}
	}
	return checkChownErr(os.Chown(name, uid, gid), name, uid, gid)
}

func SafeLchown(name string, uid, gid int) error {
	if stat, statErr := system.Lstat(name); statErr == nil {
		if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
			return nil
		}
	}
	return checkChownErr(os.Lchown(name, uid, gid), name, uid, gid)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The cert-manager Authors.

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

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

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

// Package metrics contains global structures related to metrics collection
// cert-manager exposes the following metrics:
// certificate_expiration_timestamp_seconds{name, namespace}
// certificate_ready_status{name, namespace, condition}
// acme_client_request_count{"scheme", "host", "path", "method", "status"}
// acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"}
// controller_sync_call_count{"controller"}
package metrics

import (
	"context"
	"net"
	"net/http"
	"time"

	logf "github.com/jetstack/cert-manager/pkg/logs"

	"github.com/go-logr/logr"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	"github.com/jetstack/cert-manager/pkg/util/profiling"
)

const (
	// Namespace is the namespace for cert-manager metric names
	namespace                              = "certmanager"
	prometheusMetricsServerShutdownTimeout = 5 * time.Second
	prometheusMetricsServerReadTimeout     = 8 * time.Second
	prometheusMetricsServerWriteTimeout    = 8 * time.Second
	prometheusMetricsServerMaxHeaderBytes  = 1 << 20 // 1 MiB
)

// Metrics is designed to be a shared object for updating the metrics exposed
// by cert-manager
type Metrics struct {
	log      logr.Logger
	registry *prometheus.Registry

	certificateExpiryTimeSeconds     *prometheus.GaugeVec
	certificateReadyStatus           *prometheus.GaugeVec
	acmeClientRequestDurationSeconds *prometheus.SummaryVec
	acmeClientRequestCount           *prometheus.CounterVec
	controllerSyncCallCount          *prometheus.CounterVec
}

var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown}

func New(log logr.Logger) *Metrics {
	var (
		certificateExpiryTimeSeconds = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "certificate_expiration_timestamp_seconds",
				Help:      "The date after which the certificate expires. Expressed as a Unix Epoch Time.",
			},
			[]string{"name", "namespace"},
		)

		certificateReadyStatus = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "certificate_ready_status",
				Help:      "The ready status of the certificate.",
			},
			[]string{"name", "namespace", "condition"},
		)

		// acmeClientRequestCount is a Prometheus summary to collect the number of
		// requests made to each endpoint with the ACME client.
		acmeClientRequestCount = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "acme_client_request_count",
				Help:      "The number of requests made by the ACME client.",
				Subsystem: "http",
			},
			[]string{"scheme", "host", "path", "method", "status"},
		)

		// acmeClientRequestDurationSeconds is a Prometheus summary to collect request
		// times for the ACME client.
		acmeClientRequestDurationSeconds = prometheus.NewSummaryVec(
			prometheus.SummaryOpts{
				Namespace:  namespace,
				Name:       "acme_client_request_duration_seconds",
				Help:       "The HTTP request latencies in seconds for the ACME client.",
				Subsystem:  "http",
				Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
			},
			[]string{"scheme", "host", "path", "method", "status"},
		)

		controllerSyncCallCount = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "controller_sync_call_count",
				Help:      "The number of sync() calls made by a controller.",
			},
			[]string{"controller"},
		)
	)

	// Create server and register Prometheus metrics handler
	m := &Metrics{
		log:      log.WithName("metrics"),
		registry: prometheus.NewRegistry(),

		certificateExpiryTimeSeconds:     certificateExpiryTimeSeconds,
		certificateReadyStatus:           certificateReadyStatus,
		acmeClientRequestCount:           acmeClientRequestCount,
		acmeClientRequestDurationSeconds: acmeClientRequestDurationSeconds,
		controllerSyncCallCount:          controllerSyncCallCount,
	}

	return m
}

// Start will register the Prometheu metrics, and start the Prometheus server
func (m *Metrics) Start(listenAddress string, enablePprof bool) (*http.Server, error) {
	m.registry.MustRegister(m.certificateExpiryTimeSeconds)
	m.registry.MustRegister(m.certificateReadyStatus)
	m.registry.MustRegister(m.acmeClientRequestDurationSeconds)
	m.registry.MustRegister(m.acmeClientRequestCount)
	m.registry.MustRegister(m.controllerSyncCallCount)

	mux := http.NewServeMux()
	mux.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}))
	if enablePprof {
		profiling.Install(mux)
	}

	ln, err := net.Listen("tcp", listenAddress)
	if err != nil {
		return nil, err
	}

	server := &http.Server{
		Addr:           ln.Addr().String(),
		ReadTimeout:    prometheusMetricsServerReadTimeout,
		WriteTimeout:   prometheusMetricsServerWriteTimeout,
		MaxHeaderBytes: prometheusMetricsServerMaxHeaderBytes,
		Handler:        mux,
	}

	go func() {
		log := m.log.WithValues("address", ln.Addr())
		log.V(logf.InfoLevel).Info("listening for connections on")

		if err := server.Serve(ln); err != nil {
			log.Error(err, "error running prometheus metrics server")
			return
		}
	}()

	return server, nil
}

// IncrementSyncCallCount will increase the sync counter for that controller.
func (m *Metrics) IncrementSyncCallCount(controllerName string) {
	m.controllerSyncCallCount.WithLabelValues(controllerName).Inc()
}

func (m *Metrics) Shutdown(server *http.Server) {
	m.log.V(logf.InfoLevel).Info("stopping Prometheus metrics server...")

	ctx, cancel := context.WithTimeout(context.Background(), prometheusMetricsServerShutdownTimeout)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		m.log.Error(err, "prometheus metrics server shutdown failed", err)
		return
	}

	m.log.V(logf.InfoLevel).Info("prometheus metrics server gracefully stopped")
}
<commit_msg>spelling: prometheus<commit_after>/*
Copyright 2020 The cert-manager Authors.

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

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

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

// Package metrics contains global structures related to metrics collection
// cert-manager exposes the following metrics:
// certificate_expiration_timestamp_seconds{name, namespace}
// certificate_ready_status{name, namespace, condition}
// acme_client_request_count{"scheme", "host", "path", "method", "status"}
// acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"}
// controller_sync_call_count{"controller"}
package metrics

import (
	"context"
	"net"
	"net/http"
	"time"

	logf "github.com/jetstack/cert-manager/pkg/logs"

	"github.com/go-logr/logr"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	"github.com/jetstack/cert-manager/pkg/util/profiling"
)

const (
	// Namespace is the namespace for cert-manager metric names
	namespace                              = "certmanager"
	prometheusMetricsServerShutdownTimeout = 5 * time.Second
	prometheusMetricsServerReadTimeout     = 8 * time.Second
	prometheusMetricsServerWriteTimeout    = 8 * time.Second
	prometheusMetricsServerMaxHeaderBytes  = 1 << 20 // 1 MiB
)

// Metrics is designed to be a shared object for updating the metrics exposed
// by cert-manager
type Metrics struct {
	log      logr.Logger
	registry *prometheus.Registry

	certificateExpiryTimeSeconds     *prometheus.GaugeVec
	certificateReadyStatus           *prometheus.GaugeVec
	acmeClientRequestDurationSeconds *prometheus.SummaryVec
	acmeClientRequestCount           *prometheus.CounterVec
	controllerSyncCallCount          *prometheus.CounterVec
}

var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown}

func New(log logr.Logger) *Metrics {
	var (
		certificateExpiryTimeSeconds = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "certificate_expiration_timestamp_seconds",
				Help:      "The date after which the certificate expires. Expressed as a Unix Epoch Time.",
			},
			[]string{"name", "namespace"},
		)

		certificateReadyStatus = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "certificate_ready_status",
				Help:      "The ready status of the certificate.",
			},
			[]string{"name", "namespace", "condition"},
		)

		// acmeClientRequestCount is a Prometheus summary to collect the number of
		// requests made to each endpoint with the ACME client.
		acmeClientRequestCount = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "acme_client_request_count",
				Help:      "The number of requests made by the ACME client.",
				Subsystem: "http",
			},
			[]string{"scheme", "host", "path", "method", "status"},
		)

		// acmeClientRequestDurationSeconds is a Prometheus summary to collect request
		// times for the ACME client.
		acmeClientRequestDurationSeconds = prometheus.NewSummaryVec(
			prometheus.SummaryOpts{
				Namespace:  namespace,
				Name:       "acme_client_request_duration_seconds",
				Help:       "The HTTP request latencies in seconds for the ACME client.",
				Subsystem:  "http",
				Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
			},
			[]string{"scheme", "host", "path", "method", "status"},
		)

		controllerSyncCallCount = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "controller_sync_call_count",
				Help:      "The number of sync() calls made by a controller.",
			},
			[]string{"controller"},
		)
	)

	// Create server and register Prometheus metrics handler
	m := &Metrics{
		log:      log.WithName("metrics"),
		registry: prometheus.NewRegistry(),

		certificateExpiryTimeSeconds:     certificateExpiryTimeSeconds,
		certificateReadyStatus:           certificateReadyStatus,
		acmeClientRequestCount:           acmeClientRequestCount,
		acmeClientRequestDurationSeconds: acmeClientRequestDurationSeconds,
		controllerSyncCallCount:          controllerSyncCallCount,
	}

	return m
}

// Start will register the Prometheus metrics, and start the Prometheus server
func (m *Metrics) Start(listenAddress string, enablePprof bool) (*http.Server, error) {
	m.registry.MustRegister(m.certificateExpiryTimeSeconds)
	m.registry.MustRegister(m.certificateReadyStatus)
	m.registry.MustRegister(m.acmeClientRequestDurationSeconds)
	m.registry.MustRegister(m.acmeClientRequestCount)
	m.registry.MustRegister(m.controllerSyncCallCount)

	mux := http.NewServeMux()
	mux.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}))
	if enablePprof {
		profiling.Install(mux)
	}

	ln, err := net.Listen("tcp", listenAddress)
	if err != nil {
		return nil, err
	}

	server := &http.Server{
		Addr:           ln.Addr().String(),
		ReadTimeout:    prometheusMetricsServerReadTimeout,
		WriteTimeout:   prometheusMetricsServerWriteTimeout,
		MaxHeaderBytes: prometheusMetricsServerMaxHeaderBytes,
		Handler:        mux,
	}

	go func() {
		log := m.log.WithValues("address", ln.Addr())
		log.V(logf.InfoLevel).Info("listening for connections on")

		if err := server.Serve(ln); err != nil {
			log.Error(err, "error running prometheus metrics server")
			return
		}
	}()

	return server, nil
}

// IncrementSyncCallCount will increase the sync counter for that controller.
func (m *Metrics) IncrementSyncCallCount(controllerName string) {
	m.controllerSyncCallCount.WithLabelValues(controllerName).Inc()
}

func (m *Metrics) Shutdown(server *http.Server) {
	m.log.V(logf.InfoLevel).Info("stopping Prometheus metrics server...")

	ctx, cancel := context.WithTimeout(context.Background(), prometheusMetricsServerShutdownTimeout)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		m.log.Error(err, "prometheus metrics server shutdown failed", err)
		return
	}

	m.log.V(logf.InfoLevel).Info("prometheus metrics server gracefully stopped")
}
<|endoftext|>


<commit_before>package project

var (
	description string = "The azure-operator manages Kubernetes clusters on Azure."
	gitSHA             = "n/a"
	name        string = "azure-operator"
	source      string = "https://github.com/giantswarm/azure-operator"
	version            = "5.10.2"
)

func Description() string {
	return description
}

func GitSHA() string {
	return gitSHA
}

func Name() string {
	return name
}

func Source() string {
	return source
}

func Version() string {
	return version
}
<commit_msg>Bump version to 5.10.3-dev (#1578)<commit_after>package project

var (
	description string = "The azure-operator manages Kubernetes clusters on Azure."
	gitSHA             = "n/a"
	name        string = "azure-operator"
	source      string = "https://github.com/giantswarm/azure-operator"
	version            = "5.10.3-dev"
)

func Description() string {
	return description
}

func GitSHA() string {
	return gitSHA
}

func Name() string {
	return name
}

func Source() string {
	return source
}

func Version() string {
	return version
}
<|endoftext|>


<commit_before>package sampler

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

type Sample struct {
	StatusCode      int
	TimeStart       time.Time
	TimeToResolveIP time.Time
	TimeToConnect   time.Time
	TimeToFirstByte time.Time
	TimeEnd         time.Time
	ResponseHeaders http.Header
	LocalAddr       net.IP
	RemoteAddr      net.IP
}

// StatusCodeError is an error representing an HTTP Status code
// of 400 or greater.
type StatusCodeError struct {
	StatusCode int
}

func (e StatusCodeError) Error() string {
	return fmt.Sprintf(
		"recieved HTTP status %d",
		e.StatusCode,
	)
}

// Ping measures a given URL and returns a Sample
func Ping(target Target, timeout int) (sample Sample, err error) {
	// we require four pieces of information to make the request:
	// * hostname to connect to
	// * port to connect to
	// * ip address of the hostname
	// * Host header value

	hostname, port, err := hostnameAndPort(&target.URL)
	if err != nil {
		return
	}

	sample.TimeStart = time.Now()
	defer func() { sample.TimeEnd = time.Now() }()

	deadline := sample.TimeStart.Add(time.Duration(timeout) * time.Second)

	ip, err := resolveIPAddr(hostname, deadline)
	if err != nil {
		err = fmt.Errorf("resolving IP for %s: %s", hostname, err)
		return
	}

	sample.TimeToResolveIP = time.Now()
	sample.RemoteAddr = ip

	conn, err := dial(target.URL.Scheme, ip.String() + ":" + port, hostname, deadline, target.InsecureSkipVerify)
	if err != nil {
		err = fmt.Errorf("connecting: %s", err)
		return
	}
	defer conn.Close()
	conn.SetDeadline(deadline)

	sample.TimeToConnect = time.Now()
	sample.LocalAddr = conn.LocalAddr().(*net.TCPAddr).IP

	req, err := genRequest(target)
	if err != nil {
		return
	}
	fmt.Fprint(
		conn,
		req)

	r := bufio.NewReader(conn)

	sample.StatusCode, err = parseStatus(r)
	if err != nil {
		err = fmt.Errorf("parsing status: %s", err)
		return
	}

	sample.TimeToFirstByte = time.Now()

	sample.ResponseHeaders, err = parseHeaders(r)
	if err != nil {
		err = fmt.Errorf("parsing headers: %s", err)
		return
	}

	// if we have a Content-Length, go ahead and read the body
	val := sample.ResponseHeaders.Get("Content-Length")
	if val != "" {
		contentLength, err := strconv.Atoi(val)
		if err != nil {
			err = fmt.Errorf("parsing Content-Length: %s", err)
			return sample, err
		}
		n := 0
		buf := make([]byte, contentLength)
		for i := contentLength; i > 0; i = i - n {
			n, err = r.Read(buf)
			if err != nil {
				return sample, err
			}
		}
	}

	if sample.StatusCode >= 400 {
		err = &StatusCodeError{
			StatusCode: sample.StatusCode,
		}
	}

	return
}

func hostnameAndPort(u *JsonURL) (hostname string, port string, err error) {
	// @todo investigate net.SplitHostPort
	hostname = u.Host

 	if colonInd := strings.LastIndex(hostname, ":"); colonInd > 0 {
 		// there's a port in the hostname
 		port = hostname[colonInd+1:len(hostname)]

 		hostname = hostname[:colonInd]
 	} else {
		switch u.Scheme {
		case "http":
			port = "80"
		case "https":
			port = "443"
		default:
			err =  fmt.Errorf("unknown URL scheme '%s' and no port provided", u.Scheme)
		}
	}

	return
}
<commit_msg>Handle IPv6 IP addresses properly<commit_after>package sampler

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

type Sample struct {
	StatusCode      int
	TimeStart       time.Time
	TimeToResolveIP time.Time
	TimeToConnect   time.Time
	TimeToFirstByte time.Time
	TimeEnd         time.Time
	ResponseHeaders http.Header
	LocalAddr       net.IP
	RemoteAddr      net.IP
}

// StatusCodeError is an error representing an HTTP Status code
// of 400 or greater.
type StatusCodeError struct {
	StatusCode int
}

func (e StatusCodeError) Error() string {
	return fmt.Sprintf(
		"recieved HTTP status %d",
		e.StatusCode,
	)
}

// Ping measures a given URL and returns a Sample
func Ping(target Target, timeout int) (sample Sample, err error) {
	// we require four pieces of information to make the request:
	// * hostname to connect to
	// * port to connect to
	// * ip address of the hostname
	// * Host header value

	hostname, port, err := hostnameAndPort(&target.URL)
	if err != nil {
		return
	}

	sample.TimeStart = time.Now()
	defer func() { sample.TimeEnd = time.Now() }()

	deadline := sample.TimeStart.Add(time.Duration(timeout) * time.Second)

	ip, err := resolveIPAddr(hostname, deadline)
	if err != nil {
		err = fmt.Errorf("resolving IP for %s: %s", hostname, err)
		return
	}

	sample.TimeToResolveIP = time.Now()
	sample.RemoteAddr = ip

	// ipv6 addresses must be wrapped in brackets
	ipStr := ip.String()
	if ip.To4() == nil {
		ipStr = "[" + ipStr + "]"
	}

	conn, err := dial(target.URL.Scheme, ipStr + ":" + port, hostname, deadline, target.InsecureSkipVerify)
	if err != nil {
		err = fmt.Errorf("connecting: %s", err)
		return
	}
	defer conn.Close()
	conn.SetDeadline(deadline)

	sample.TimeToConnect = time.Now()
	sample.LocalAddr = conn.LocalAddr().(*net.TCPAddr).IP

	req, err := genRequest(target)
	if err != nil {
		return
	}

	fmt.Fprint(conn, req)

	r := bufio.NewReader(conn)

	sample.StatusCode, err = parseStatus(r)
	if err != nil {
		err = fmt.Errorf("parsing status: %s", err)
		return
	}

	sample.TimeToFirstByte = time.Now()

	sample.ResponseHeaders, err = parseHeaders(r)
	if err != nil {
		err = fmt.Errorf("parsing headers: %s", err)
		return
	}

	// if we have a Content-Length, go ahead and read the body
	val := sample.ResponseHeaders.Get("Content-Length")
	if val != "" {
		contentLength, err := strconv.Atoi(val)
		if err != nil {
			err = fmt.Errorf("parsing Content-Length: %s", err)
			return sample, err
		}
		n := 0
		buf := make([]byte, contentLength)
		for i := contentLength; i > 0; i = i - n {
			n, err = r.Read(buf)
			if err != nil {
				return sample, err
			}
		}
	}

	if sample.StatusCode >= 400 {
		err = &StatusCodeError{
			StatusCode: sample.StatusCode,
		}
	}

	return
}

func hostnameAndPort(u *JsonURL) (hostname string, port string, err error) {
	// @todo investigate net.SplitHostPort
	hostname = u.Host

 	if colonInd := strings.LastIndex(hostname, ":"); colonInd > 0 {
 		// there's a port in the hostname
 		port = hostname[colonInd+1:len(hostname)]

 		hostname = hostname[:colonInd]
 	} else {
		switch u.Scheme {
		case "http":
			port = "80"
		case "https":
			port = "443"
		default:
			err =  fmt.Errorf("unknown URL scheme '%s' and no port provided", u.Scheme)
		}
	}

	return
}
<|endoftext|>