<commit_before>package physics

import (
	"io"
	"math"
	"os"

	"chunkymonkey/proto"
	. "chunkymonkey/types"
)

const (
	// Guestimated gravity value. Unknown how accurate this is.
	gravityBlocksPerSecond2 = 3.0

	gravityBlocksPerTick2 = gravityBlocksPerSecond2 / TicksPerSecond

	// Air resistance, as a denominator of a velocity component
	airResistance = 5

	// Min velocity component before we clamp to zero
	minVel = 0.01

	objBlockDistance = 4.25 / PixelsPerBlock
)

type blockAxisMove byte

const (
	blockAxisMoveX = blockAxisMove(iota)
	blockAxisMoveY = blockAxisMove(iota)
	blockAxisMoveZ = blockAxisMove(iota)
)

type BlockQueryFn func(*BlockXyz) (isSolid bool, isWithinChunk bool)

type PointObject struct {
	// Used in knowing what to send as client updates
	LastSentPosition AbsIntXyz
	LastSentVelocity Velocity

	// TODO Add a counter so that a full teleport is sent every so often so that
	// positions don't drift out of sync.

	// Used in physical modelling
	position  AbsXyz
	velocity  AbsVelocity
	onGround  bool
	remainder TickTime
}

func (obj *PointObject) Position() *AbsXyz {
	return &obj.position
}

func (obj *PointObject) Init(position *AbsXyz, velocity *AbsVelocity) {
	obj.LastSentPosition = *position.ToAbsIntXyz()
	obj.LastSentVelocity = *velocity.ToVelocity()
	obj.position = *position
	obj.velocity = *velocity
	obj.onGround = false
}

// Generates any packets needed to update clients as to the position and
// velocity of the object.
// It assumes that the clients have either been sent packets via this method
// before, or that the previous position/velocity sent was generated from the
// LastSentPosition and LastSentVelocity attributes.
func (obj *PointObject) SendUpdate(writer io.Writer, entityId EntityId, look *LookBytes) (err os.Error) {
	curPosition := obj.position.ToAbsIntXyz()

	dx := curPosition.X - obj.LastSentPosition.X
	dy := curPosition.Y - obj.LastSentPosition.Y
	dz := curPosition.Z - obj.LastSentPosition.Z

	if dx != 0 || dy != 0 || dz != 0 {
		if dx >= -128 && dx <= 127 && dy >= -128 && dy <= 127 && dz >= -128 && dz <= 127 {
			err = proto.WriteEntityRelMove(
				writer, entityId,
				&RelMove{
					RelMoveCoord(dx),
					RelMoveCoord(dy),
					RelMoveCoord(dz),
				},
			)
		} else {
			err = proto.WriteEntityTeleport(
				writer, entityId,
				curPosition, look)
		}
		if err != nil {
			return
		}
		obj.LastSentPosition = *curPosition
	}

	curVelocity := obj.velocity.ToVelocity()
	if curVelocity.X != obj.LastSentVelocity.X || curVelocity.Y != obj.LastSentVelocity.Y || curVelocity.Z != obj.LastSentVelocity.Z {
		if err = proto.WriteEntityVelocity(writer, entityId, curVelocity); err != nil {
			return
		}
		obj.LastSentVelocity = *curVelocity
	}

	return
}

func (obj *PointObject) Tick(blockQuery BlockQueryFn) (leftBlock bool) {
	// TODO this algorithm can probably be sped up a bit, but initially trying
	// to keep things simple and more or less correct
	// TODO flowing water movement of items

	p := &obj.position
	v := &obj.velocity

	// FIXME note that if the block under the item should become non-solid,
	// then we need to turn off onGround to re-enable physics
	// TODO if the object has stopped moving (i.e is at rest on top of a solid
	// block and not inside a flowing block), take the object out of a
	// "physically active" list. Note that the object will have to be re-added
	// if any blocks it is adjacent to change in solidity or "flow"
	stopped := obj.updateVelocity()

	if stopped {
		// The object isn't moving, we're done
		obj.remainder = 0.0
		return
	}

	// Enforce max absolute velocity per dimension
	v.X.Constrain()
	v.Y.Constrain()
	v.Z.Constrain()

	// t0 = time at start of tick,
	// t1 = time at end of tick,
	// t = current time in tick (t0 <= t <= t1)
	var t0, t1, t TickTime

	// `Dt` and `dt` means delta-time, that is, time relative to `t`
	var nextBlockXdt, nextBlockYdt, nextBlockZdt TickTime

	var dt TickTime

	t0 = 0.0
	t1 = 1.0 + obj.remainder
	dt = 0

	var move blockAxisMove

	// Project the velocity in block space to see if we hit anything solid, and
	// stop the object's velocity component if so

	for t = t0; t < t1; t += dt {
		// How long after t0 is it that the object hits a block boundary on
		// each axis?
		nextBlockXdt = calcNextBlockDt(p.X, v.X)
		nextBlockYdt = calcNextBlockDt(p.Y, v.Y)
		nextBlockZdt = calcNextBlockDt(p.Z, v.Z)

		// In the axis of which block are we moving? In X, Y or Z axis?
		move, dt = getBlockAxisMove(nextBlockXdt, nextBlockYdt, nextBlockZdt)

		// Don't calculate beyond 1 tick of time
		if t+dt >= t1 {
			// It will be after the end of this tick when the object crosses a
			// block boundary
			dt = t1 - t
			p.ApplyVelocity(dt, v)

			// We're all done
			break
		} else {

			// Examine the block being entered
			blockLoc := obj.nextBlockToEnter(move)
			// FIXME deal better with the case where the block goes over the
			// top (Y > 128) - BlockYCoord is an int8, so it'll overflow
			if blockLoc.Y < 0 {
				break
			}

			// Is it solid?
			isSolid, isWithinChunk := blockQuery(blockLoc)
			if isSolid {
				// Collision - cancel axis movement
				switch move {
				case blockAxisMoveX:
					applyCollision(&p.X, &v.X)
				case blockAxisMoveY:
					applyCollision(&p.Y, &v.Y)
					obj.onGround = true
				case blockAxisMoveZ:
					applyCollision(&p.Z, &v.Z)
				}

				// Move the object up to the block boundary
				p.ApplyVelocity(dt, v)
			} else {
				// No collision, continue as normal
				// HACK: We add 1e-4 to dt to "break past" the block boundary,
				// otherwise we end up at rest on it in an infinite loop. dt
				// would otherwise be *approximately* sufficient to reach the
				// block boundary.
				p.ApplyVelocity(dt+1e-4, v)
				if !isWithinChunk {
					// Object has left the chunk, finish early
					break
				}
			}
		}
	}

	if p.Y < 0 {
		leftBlock = true
	}
	obj.remainder = t1 - t
	return
}

func (obj *PointObject) updateVelocity() (stopped bool) {
	v := &obj.velocity

	if !obj.onGround {
		v.Y -= gravityBlocksPerTick2 * AbsVelocityCoord(1.0+float64(obj.remainder))
	}

	stopped = true

	if v.X > -minVel && v.X < minVel {
		v.X = 0
	} else {
		v.X -= v.X / airResistance
		stopped = false
	}
	if v.Y > -minVel && v.Y < minVel {
		v.Y = 0
	} else {
		v.Y -= v.Y / airResistance
		stopped = false
	}
	if v.Z > -minVel && v.Z < minVel {
		v.Z = 0
	} else {
		v.Z -= v.Z / airResistance
		stopped = false
	}

	return
}

func (obj *PointObject) nextBlockToEnter(move blockAxisMove) *BlockXyz {
	p := &obj.position
	v := &obj.velocity

	block := p.ToBlockXyz()

	switch move {
	case blockAxisMoveX:
		if v.X > 0 {
			block.X += 1
		} else {
			block.X -= 1
		}
	case blockAxisMoveY:
		if v.Y > 0 {
			block.Y += 1
		} else {
			block.Y -= 1
		}
	case blockAxisMoveZ:
		if v.Z > 0 {
			block.Z += 1
		} else {
			block.Z -= 1
		}
	}

	return block
}

// In one dimension, calculates time taken for movement from position `p` with
// velocity `v` until intersection with a block boundary. Note that if v is
// small enough or zero then math.MaxFloat64 is returned.
func calcNextBlockDt(p AbsCoord, v AbsVelocityCoord) TickTime {
	if v > -1e-20 && v < 1e-20 {
		return math.MaxFloat64
	}

	if p < 0 {
		p = -p
		v = -v
	}

	var p_prime AbsCoord
	if v > 0 {
		p_prime = AbsCoord(math.Floor(float64(p + 1.0)))
	} else {
		p_prime = AbsCoord(math.Floor(float64(p)))
	}

	return TickTime(float64(p_prime-p) / float64(v))
}

// Given 3 time deltas, it returns the axis that the smallest was on, and the
// value of the smallest time delta. This is used to know on which axis and how
// long until the next block transition is. Only +ve numbers should be passed
// in for it to be sensible.
func getBlockAxisMove(xDt, yDt, zDt TickTime) (move blockAxisMove, dt TickTime) {
	if xDt <= yDt {
		if xDt <= zDt {
			return blockAxisMoveX, xDt
		} else {
			return blockAxisMoveZ, zDt
		}
	} else {
		if yDt <= zDt {
			return blockAxisMoveY, yDt
		} else {
			return blockAxisMoveZ, zDt
		}
	}
	return
}

func applyCollision(p *AbsCoord, v *AbsVelocityCoord) {
	if *v > 0 {
		*p = AbsCoord(math.Ceil(float64(*p)) - objBlockDistance)
	} else {
		*p = AbsCoord(math.Floor(float64(*p)) + objBlockDistance)
	}
	*v = 0
}
<commit_msg>Realised that the system is already correct, removing TODO.<commit_after>package physics

import (
	"io"
	"math"
	"os"

	"chunkymonkey/proto"
	. "chunkymonkey/types"
)

const (
	// Guestimated gravity value. Unknown how accurate this is.
	gravityBlocksPerSecond2 = 3.0

	gravityBlocksPerTick2 = gravityBlocksPerSecond2 / TicksPerSecond

	// Air resistance, as a denominator of a velocity component
	airResistance = 5

	// Min velocity component before we clamp to zero
	minVel = 0.01

	objBlockDistance = 4.25 / PixelsPerBlock
)

type blockAxisMove byte

const (
	blockAxisMoveX = blockAxisMove(iota)
	blockAxisMoveY = blockAxisMove(iota)
	blockAxisMoveZ = blockAxisMove(iota)
)

type BlockQueryFn func(*BlockXyz) (isSolid bool, isWithinChunk bool)

type PointObject struct {
	// Used in knowing what to send as client updates
	LastSentPosition AbsIntXyz
	LastSentVelocity Velocity

	// Used in physical modelling
	position  AbsXyz
	velocity  AbsVelocity
	onGround  bool
	remainder TickTime
}

func (obj *PointObject) Position() *AbsXyz {
	return &obj.position
}

func (obj *PointObject) Init(position *AbsXyz, velocity *AbsVelocity) {
	obj.LastSentPosition = *position.ToAbsIntXyz()
	obj.LastSentVelocity = *velocity.ToVelocity()
	obj.position = *position
	obj.velocity = *velocity
	obj.onGround = false
}

// Generates any packets needed to update clients as to the position and
// velocity of the object.
// It assumes that the clients have either been sent packets via this method
// before, or that the previous position/velocity sent was generated from the
// LastSentPosition and LastSentVelocity attributes.
func (obj *PointObject) SendUpdate(writer io.Writer, entityId EntityId, look *LookBytes) (err os.Error) {
	curPosition := obj.position.ToAbsIntXyz()

	dx := curPosition.X - obj.LastSentPosition.X
	dy := curPosition.Y - obj.LastSentPosition.Y
	dz := curPosition.Z - obj.LastSentPosition.Z

	if dx != 0 || dy != 0 || dz != 0 {
		if dx >= -128 && dx <= 127 && dy >= -128 && dy <= 127 && dz >= -128 && dz <= 127 {
			err = proto.WriteEntityRelMove(
				writer, entityId,
				&RelMove{
					RelMoveCoord(dx),
					RelMoveCoord(dy),
					RelMoveCoord(dz),
				},
			)
		} else {
			err = proto.WriteEntityTeleport(
				writer, entityId,
				curPosition, look)
		}
		if err != nil {
			return
		}
		obj.LastSentPosition = *curPosition
	}

	curVelocity := obj.velocity.ToVelocity()
	if curVelocity.X != obj.LastSentVelocity.X || curVelocity.Y != obj.LastSentVelocity.Y || curVelocity.Z != obj.LastSentVelocity.Z {
		if err = proto.WriteEntityVelocity(writer, entityId, curVelocity); err != nil {
			return
		}
		obj.LastSentVelocity = *curVelocity
	}

	return
}

func (obj *PointObject) Tick(blockQuery BlockQueryFn) (leftBlock bool) {
	// TODO this algorithm can probably be sped up a bit, but initially trying
	// to keep things simple and more or less correct
	// TODO flowing water movement of items

	p := &obj.position
	v := &obj.velocity

	// FIXME note that if the block under the item should become non-solid,
	// then we need to turn off onGround to re-enable physics
	// TODO if the object has stopped moving (i.e is at rest on top of a solid
	// block and not inside a flowing block), take the object out of a
	// "physically active" list. Note that the object will have to be re-added
	// if any blocks it is adjacent to change in solidity or "flow"
	stopped := obj.updateVelocity()

	if stopped {
		// The object isn't moving, we're done
		obj.remainder = 0.0
		return
	}

	// Enforce max absolute velocity per dimension
	v.X.Constrain()
	v.Y.Constrain()
	v.Z.Constrain()

	// t0 = time at start of tick,
	// t1 = time at end of tick,
	// t = current time in tick (t0 <= t <= t1)
	var t0, t1, t TickTime

	// `Dt` and `dt` means delta-time, that is, time relative to `t`
	var nextBlockXdt, nextBlockYdt, nextBlockZdt TickTime

	var dt TickTime

	t0 = 0.0
	t1 = 1.0 + obj.remainder
	dt = 0

	var move blockAxisMove

	// Project the velocity in block space to see if we hit anything solid, and
	// stop the object's velocity component if so

	for t = t0; t < t1; t += dt {
		// How long after t0 is it that the object hits a block boundary on
		// each axis?
		nextBlockXdt = calcNextBlockDt(p.X, v.X)
		nextBlockYdt = calcNextBlockDt(p.Y, v.Y)
		nextBlockZdt = calcNextBlockDt(p.Z, v.Z)

		// In the axis of which block are we moving? In X, Y or Z axis?
		move, dt = getBlockAxisMove(nextBlockXdt, nextBlockYdt, nextBlockZdt)

		// Don't calculate beyond 1 tick of time
		if t+dt >= t1 {
			// It will be after the end of this tick when the object crosses a
			// block boundary
			dt = t1 - t
			p.ApplyVelocity(dt, v)

			// We're all done
			break
		} else {

			// Examine the block being entered
			blockLoc := obj.nextBlockToEnter(move)
			// FIXME deal better with the case where the block goes over the
			// top (Y > 128) - BlockYCoord is an int8, so it'll overflow
			if blockLoc.Y < 0 {
				break
			}

			// Is it solid?
			isSolid, isWithinChunk := blockQuery(blockLoc)
			if isSolid {
				// Collision - cancel axis movement
				switch move {
				case blockAxisMoveX:
					applyCollision(&p.X, &v.X)
				case blockAxisMoveY:
					applyCollision(&p.Y, &v.Y)
					obj.onGround = true
				case blockAxisMoveZ:
					applyCollision(&p.Z, &v.Z)
				}

				// Move the object up to the block boundary
				p.ApplyVelocity(dt, v)
			} else {
				// No collision, continue as normal
				// HACK: We add 1e-4 to dt to "break past" the block boundary,
				// otherwise we end up at rest on it in an infinite loop. dt
				// would otherwise be *approximately* sufficient to reach the
				// block boundary.
				p.ApplyVelocity(dt+1e-4, v)
				if !isWithinChunk {
					// Object has left the chunk, finish early
					break
				}
			}
		}
	}

	if p.Y < 0 {
		leftBlock = true
	}
	obj.remainder = t1 - t
	return
}

func (obj *PointObject) updateVelocity() (stopped bool) {
	v := &obj.velocity

	if !obj.onGround {
		v.Y -= gravityBlocksPerTick2 * AbsVelocityCoord(1.0+float64(obj.remainder))
	}

	stopped = true

	if v.X > -minVel && v.X < minVel {
		v.X = 0
	} else {
		v.X -= v.X / airResistance
		stopped = false
	}
	if v.Y > -minVel && v.Y < minVel {
		v.Y = 0
	} else {
		v.Y -= v.Y / airResistance
		stopped = false
	}
	if v.Z > -minVel && v.Z < minVel {
		v.Z = 0
	} else {
		v.Z -= v.Z / airResistance
		stopped = false
	}

	return
}

func (obj *PointObject) nextBlockToEnter(move blockAxisMove) *BlockXyz {
	p := &obj.position
	v := &obj.velocity

	block := p.ToBlockXyz()

	switch move {
	case blockAxisMoveX:
		if v.X > 0 {
			block.X += 1
		} else {
			block.X -= 1
		}
	case blockAxisMoveY:
		if v.Y > 0 {
			block.Y += 1
		} else {
			block.Y -= 1
		}
	case blockAxisMoveZ:
		if v.Z > 0 {
			block.Z += 1
		} else {
			block.Z -= 1
		}
	}

	return block
}

// In one dimension, calculates time taken for movement from position `p` with
// velocity `v` until intersection with a block boundary. Note that if v is
// small enough or zero then math.MaxFloat64 is returned.
func calcNextBlockDt(p AbsCoord, v AbsVelocityCoord) TickTime {
	if v > -1e-20 && v < 1e-20 {
		return math.MaxFloat64
	}

	if p < 0 {
		p = -p
		v = -v
	}

	var p_prime AbsCoord
	if v > 0 {
		p_prime = AbsCoord(math.Floor(float64(p + 1.0)))
	} else {
		p_prime = AbsCoord(math.Floor(float64(p)))
	}

	return TickTime(float64(p_prime-p) / float64(v))
}

// Given 3 time deltas, it returns the axis that the smallest was on, and the
// value of the smallest time delta. This is used to know on which axis and how
// long until the next block transition is. Only +ve numbers should be passed
// in for it to be sensible.
func getBlockAxisMove(xDt, yDt, zDt TickTime) (move blockAxisMove, dt TickTime) {
	if xDt <= yDt {
		if xDt <= zDt {
			return blockAxisMoveX, xDt
		} else {
			return blockAxisMoveZ, zDt
		}
	} else {
		if yDt <= zDt {
			return blockAxisMoveY, yDt
		} else {
			return blockAxisMoveZ, zDt
		}
	}
	return
}

func applyCollision(p *AbsCoord, v *AbsVelocityCoord) {
	if *v > 0 {
		*p = AbsCoord(math.Ceil(float64(*p)) - objBlockDistance)
	} else {
		*p = AbsCoord(math.Floor(float64(*p)) + objBlockDistance)
	}
	*v = 0
}
<|endoftext|>


<commit_before>// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT.

package skyerr

import "strconv"

const (
	_ErrorCode_name_0 = "NotAuthenticatedPermissionDeniedAccessKeyNotAcceptedAccessTokenNotAcceptedInvalidCredentialsInvalidSignatureBadRequestInvalidArgumentDuplicatedResourceNotFoundNotSupportedNotImplementedConstraintViolatedIncompatibleSchemaAtomicOperationFailurePartialOperationFailureUndefinedOperationPluginUnavailablePluginTimeoutRecordQueryInvalidPluginInitializingResponseTimeoutDeniedArgumentRecordQueryDeniedNotConfiguredPasswordPolicyViolatedUserDisabledVerificationRequiredAssetSizeTooLargeHookTimeOut"
	_ErrorCode_name_1 = "UnexpectedErrorUnexpectedAuthInfoNotFoundUnexpectedUnableToOpenDatabaseUnexpectedPushNotificationNotConfiguredInternalQueryInvalidUnexpectedUserNotFound"
)

var (
	_ErrorCode_index_0 = [...]uint16{0, 16, 32, 52, 74, 92, 108, 118, 133, 143, 159, 171, 185, 203, 221, 243, 266, 284, 301, 314, 332, 350, 365, 379, 396, 409, 431, 443, 463, 480, 491}
	_ErrorCode_index_1 = [...]uint8{0, 15, 41, 71, 110, 130, 152}
)

func (i ErrorCode) String() string {
	switch {
	case 101 <= i && i <= 130:
		i -= 101
		return _ErrorCode_name_0[_ErrorCode_index_0[i]:_ErrorCode_index_0[i+1]]
	case 10000 <= i && i <= 10005:
		i -= 10000
		return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
	default:
		return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
	}
}
<commit_msg>Re-run `make generate`<commit_after>// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT.

package skyerr

import "strconv"

func _() {
	// An "invalid array index" compiler error signifies that the constant values have changed.
	// Re-run the stringer command to generate them again.
	var x [1]struct{}
	_ = x[NotAuthenticated-101]
	_ = x[PermissionDenied-102]
	_ = x[AccessKeyNotAccepted-103]
	_ = x[AccessTokenNotAccepted-104]
	_ = x[InvalidCredentials-105]
	_ = x[InvalidSignature-106]
	_ = x[BadRequest-107]
	_ = x[InvalidArgument-108]
	_ = x[Duplicated-109]
	_ = x[ResourceNotFound-110]
	_ = x[NotSupported-111]
	_ = x[NotImplemented-112]
	_ = x[ConstraintViolated-113]
	_ = x[IncompatibleSchema-114]
	_ = x[AtomicOperationFailure-115]
	_ = x[PartialOperationFailure-116]
	_ = x[UndefinedOperation-117]
	_ = x[PluginUnavailable-118]
	_ = x[PluginTimeout-119]
	_ = x[RecordQueryInvalid-120]
	_ = x[PluginInitializing-121]
	_ = x[ResponseTimeout-122]
	_ = x[DeniedArgument-123]
	_ = x[RecordQueryDenied-124]
	_ = x[NotConfigured-125]
	_ = x[PasswordPolicyViolated-126]
	_ = x[UserDisabled-127]
	_ = x[VerificationRequired-128]
	_ = x[AssetSizeTooLarge-129]
	_ = x[HookTimeOut-130]
	_ = x[UnexpectedError-10000]
	_ = x[UnexpectedAuthInfoNotFound-10001]
	_ = x[UnexpectedUnableToOpenDatabase-10002]
	_ = x[UnexpectedPushNotificationNotConfigured-10003]
	_ = x[InternalQueryInvalid-10004]
	_ = x[UnexpectedUserNotFound-10005]
}

const (
	_ErrorCode_name_0 = "NotAuthenticatedPermissionDeniedAccessKeyNotAcceptedAccessTokenNotAcceptedInvalidCredentialsInvalidSignatureBadRequestInvalidArgumentDuplicatedResourceNotFoundNotSupportedNotImplementedConstraintViolatedIncompatibleSchemaAtomicOperationFailurePartialOperationFailureUndefinedOperationPluginUnavailablePluginTimeoutRecordQueryInvalidPluginInitializingResponseTimeoutDeniedArgumentRecordQueryDeniedNotConfiguredPasswordPolicyViolatedUserDisabledVerificationRequiredAssetSizeTooLargeHookTimeOut"
	_ErrorCode_name_1 = "UnexpectedErrorUnexpectedAuthInfoNotFoundUnexpectedUnableToOpenDatabaseUnexpectedPushNotificationNotConfiguredInternalQueryInvalidUnexpectedUserNotFound"
)

var (
	_ErrorCode_index_0 = [...]uint16{0, 16, 32, 52, 74, 92, 108, 118, 133, 143, 159, 171, 185, 203, 221, 243, 266, 284, 301, 314, 332, 350, 365, 379, 396, 409, 431, 443, 463, 480, 491}
	_ErrorCode_index_1 = [...]uint8{0, 15, 41, 71, 110, 130, 152}
)

func (i ErrorCode) String() string {
	switch {
	case 101 <= i && i <= 130:
		i -= 101
		return _ErrorCode_name_0[_ErrorCode_index_0[i]:_ErrorCode_index_0[i+1]]
	case 10000 <= i && i <= 10005:
		i -= 10000
		return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
	default:
		return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
	}
}
<|endoftext|>


<commit_before>package helm

import (
	"fmt"
	"io"
	"time"

	"github.com/flant/kubedog/pkg/kube"
	"github.com/flant/kubedog/pkg/tracker"
	"github.com/flant/kubedog/pkg/trackers/rollout"
	"github.com/flant/kubedog/pkg/trackers/rollout/multitrack"
	"github.com/flant/logboek"
	appsv1 "k8s.io/api/apps/v1"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	appsv1beta2 "k8s.io/api/apps/v1beta2"
	v1 "k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes/scheme"
	helmKube "k8s.io/helm/pkg/kube"
)

type ResourcesWaiter struct {
	Client       *helmKube.Client
	LogsFromTime time.Time
}

func (waiter *ResourcesWaiter) WaitForResources(timeout time.Duration, created helmKube.Result) error {
	specs := multitrack.MultitrackSpecs{}

	for _, v := range created {
		switch value := asVersioned(v).(type) {
		case *v1.Pod:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Pods = append(specs.Pods, *spec)
			}
		case *appsv1.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *appsv1beta1.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *appsv1beta2.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *extensions.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *extensions.DaemonSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.DaemonSets = append(specs.DaemonSets, *spec)
			}
		case *appsv1.DaemonSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.DaemonSets = append(specs.DaemonSets, *spec)
			}
		case *appsv1beta2.DaemonSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.DaemonSets = append(specs.DaemonSets, *spec)
			}

		case *appsv1.StatefulSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.StatefulSets = append(specs.StatefulSets, *spec)
			}
		case *appsv1beta1.StatefulSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.StatefulSets = append(specs.StatefulSets, *spec)
			}
		case *appsv1beta2.StatefulSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.StatefulSets = append(specs.StatefulSets, *spec)
			}
		case *v1.ReplicationController:
		case *extensions.ReplicaSet:
		case *appsv1beta2.ReplicaSet:
		case *appsv1.ReplicaSet:
		case *v1.PersistentVolumeClaim:
		case *v1.Service:
		}
	}

	return logboek.LogSecondaryProcess("Waiting for release resources to become ready", logboek.LogProcessOptions{}, func() error {
		return multitrack.Multitrack(kube.Kubernetes, specs, multitrack.MultitrackOptions{
			Options: tracker.Options{
				Timeout:      timeout,
				LogsFromTime: waiter.LogsFromTime,
			},
		})
	})
}

func makeMultitrackSpec(objMeta *metav1.ObjectMeta) (*multitrack.MultitrackSpec, error) {
	if objMeta.Annotations[TrackAnnoName] == string(TrackDisabled) {
		return nil, nil
	}

	return &multitrack.MultitrackSpec{
		ResourceName: objMeta.Name,
		Namespace:    objMeta.Namespace,
	}, nil
}

func (waiter *ResourcesWaiter) WatchUntilReady(namespace string, reader io.Reader, timeout time.Duration) error {
	watchStartTime := time.Now()

	infos, err := waiter.Client.BuildUnstructured(namespace, reader)
	if err != nil {
		return err
	}

	for _, info := range infos {
		name := info.Name
		namespace := info.Namespace
		kind := info.Mapping.GroupVersionKind.Kind

		switch kind {
		case "Job":
			loggerProcessMsg := fmt.Sprintf("Waiting for helm hook job/%s termination", name)
			if err := logboek.LogSecondaryProcess(loggerProcessMsg, logboek.LogProcessOptions{}, func() error {
				return logboek.WithFittedStreamsOutputOn(func() error {
					return rollout.TrackJobTillDone(name, namespace, kube.Kubernetes, tracker.Options{Timeout: timeout, LogsFromTime: watchStartTime})
				})
			}); err != nil {
				return err
			}

		default:
			return fmt.Errorf("helm hook kind '%s' not supported yet, only Job hooks can be used for now", kind)
		}
	}

	return nil
}

func asVersioned(info *resource.Info) runtime.Object {
	converter := runtime.ObjectConvertor(scheme.Scheme)
	groupVersioner := runtime.GroupVersioner(schema.GroupVersions(scheme.Scheme.PrioritizedVersionsAllGroups()))
	if info.Mapping != nil {
		groupVersioner = info.Mapping.GroupVersionKind.GroupVersion()
	}

	if obj, err := converter.ConvertToVersion(info.Object, groupVersioner); err == nil {
		return obj
	}
	return info.Object
}
<commit_msg>Enable werf.io/track=false annotation for helm hooks (Job only for now)<commit_after>package helm

import (
	"fmt"
	"io"
	"time"

	"github.com/flant/kubedog/pkg/kube"
	"github.com/flant/kubedog/pkg/tracker"
	"github.com/flant/kubedog/pkg/trackers/rollout"
	"github.com/flant/kubedog/pkg/trackers/rollout/multitrack"
	"github.com/flant/logboek"
	appsv1 "k8s.io/api/apps/v1"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	appsv1beta2 "k8s.io/api/apps/v1beta2"
	batchv1 "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	extensions "k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/resource"
	"k8s.io/client-go/kubernetes/scheme"
	helmKube "k8s.io/helm/pkg/kube"
)

type ResourcesWaiter struct {
	Client       *helmKube.Client
	LogsFromTime time.Time
}

func (waiter *ResourcesWaiter) WaitForResources(timeout time.Duration, created helmKube.Result) error {
	specs := multitrack.MultitrackSpecs{}

	for _, v := range created {
		switch value := asVersioned(v).(type) {
		case *v1.Pod:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Pods = append(specs.Pods, *spec)
			}
		case *appsv1.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *appsv1beta1.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *appsv1beta2.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *extensions.Deployment:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.Deployments = append(specs.Deployments, *spec)
			}
		case *extensions.DaemonSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.DaemonSets = append(specs.DaemonSets, *spec)
			}
		case *appsv1.DaemonSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.DaemonSets = append(specs.DaemonSets, *spec)
			}
		case *appsv1beta2.DaemonSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.DaemonSets = append(specs.DaemonSets, *spec)
			}

		case *appsv1.StatefulSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.StatefulSets = append(specs.StatefulSets, *spec)
			}
		case *appsv1beta1.StatefulSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.StatefulSets = append(specs.StatefulSets, *spec)
			}
		case *appsv1beta2.StatefulSet:
			spec, err := makeMultitrackSpec(&value.ObjectMeta)
			if err != nil {
				return fmt.Errorf("cannot track %s %s: %s", value.Kind, value.Name, err)
			}
			if spec != nil {
				specs.StatefulSets = append(specs.StatefulSets, *spec)
			}
		case *v1.ReplicationController:
		case *extensions.ReplicaSet:
		case *appsv1beta2.ReplicaSet:
		case *appsv1.ReplicaSet:
		case *v1.PersistentVolumeClaim:
		case *v1.Service:
		}
	}

	return logboek.LogSecondaryProcess("Waiting for release resources to become ready", logboek.LogProcessOptions{}, func() error {
		return multitrack.Multitrack(kube.Kubernetes, specs, multitrack.MultitrackOptions{
			Options: tracker.Options{
				Timeout:      timeout,
				LogsFromTime: waiter.LogsFromTime,
			},
		})
	})
}

func makeMultitrackSpec(objMeta *metav1.ObjectMeta) (*multitrack.MultitrackSpec, error) {
	if objMeta.Annotations[TrackAnnoName] == string(TrackDisabled) {
		return nil, nil
	}

	return &multitrack.MultitrackSpec{
		ResourceName: objMeta.Name,
		Namespace:    objMeta.Namespace,
	}, nil
}

func (waiter *ResourcesWaiter) WatchUntilReady(namespace string, reader io.Reader, timeout time.Duration) error {
	watchStartTime := time.Now()

	infos, err := waiter.Client.BuildUnstructured(namespace, reader)
	if err != nil {
		return err
	}

TrackHooks:
	for _, info := range infos {
		name := info.Name
		namespace := info.Namespace
		kind := info.Mapping.GroupVersionKind.Kind

		switch value := asVersioned(info).(type) {
		case *batchv1.Job:
			if value.ObjectMeta.Annotations[TrackAnnoName] == string(TrackDisabled) {
				continue TrackHooks
			}

			loggerProcessMsg := fmt.Sprintf("Waiting for helm hook job/%s termination", name)
			if err := logboek.LogSecondaryProcess(loggerProcessMsg, logboek.LogProcessOptions{}, func() error {
				return logboek.WithFittedStreamsOutputOn(func() error {
					return rollout.TrackJobTillDone(name, namespace, kube.Kubernetes, tracker.Options{Timeout: timeout, LogsFromTime: watchStartTime})
				})
			}); err != nil {
				return err
			}

		default:
			return fmt.Errorf("helm hook kind '%s' not supported yet, only Job hooks can be used for now", kind)
		}
	}

	return nil
}

func asVersioned(info *resource.Info) runtime.Object {
	converter := runtime.ObjectConvertor(scheme.Scheme)
	groupVersioner := runtime.GroupVersioner(schema.GroupVersions(scheme.Scheme.PrioritizedVersionsAllGroups()))
	if info.Mapping != nil {
		groupVersioner = info.Mapping.GroupVersionKind.GroupVersion()
	}

	if obj, err := converter.ConvertToVersion(info.Object, groupVersioner); err == nil {
		return obj
	}
	return info.Object
}
<|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 translate

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path"
	"strings"

	"github.com/cloudfoundry-attic/jibber_jabber"
	"golang.org/x/text/language"

	"k8s.io/klog/v2"
)

var (
	// preferredLanguage is the default language messages will be output in
	preferredLanguage = language.AmericanEnglish
	// our default language
	defaultLanguage = language.AmericanEnglish

	// Translations is a translation map from strings that can be output to console
	// to its translation in the user's system locale.
	Translations map[string]interface{}
)

// T translates the given string to the preferred language.
func T(s string) string {
	if preferredLanguage == defaultLanguage {
		return s
	}

	if len(Translations) == 0 {
		return s
	}

	if t, ok := Translations[s]; ok {
		if len(t.(string)) > 0 && t.(string) != " " {
			return t.(string)
		}
	}

	return s
}

// DetermineLocale finds the system locale and sets the preferred language for output appropriately.
func DetermineLocale() {
	locale, err := jibber_jabber.DetectIETF()
	if err != nil {
		klog.V(1).Infof("Getting system locale failed: %v", err)
		locale = ""
	}
	SetPreferredLanguage(locale)

	// Load translations for preferred language into memory.
	p := preferredLanguage.String()
	translationFile := path.Join("translations", fmt.Sprintf("%s.json", p))
	t, err := Asset(translationFile)
	if err != nil {
		// Attempt to find a more broad locale, e.g. fr instead of fr-FR.
		if strings.Contains(p, "-") {
			p = strings.Split(p, "-")[0]
			translationFile := path.Join("translations", fmt.Sprintf("%s.json", p))
			t, err = Asset(translationFile)
			if err != nil {
				klog.V(1).Infof("Failed to load translation file for %s: %v", p, err)
				return
			}
		} else {
			klog.V(1).Infof("Failed to load translation file for %s: %v", preferredLanguage.String(), err)
			return
		}
	}

	err = json.Unmarshal(t, &Translations)
	if err != nil {
		klog.V(1).Infof("Failed to populate translation map: %v", err)
	}

}

// setPreferredLanguageTag configures which language future messages should use.
func setPreferredLanguageTag(l language.Tag) {
	// output message only if verbosity level is set and we still haven't got all the flags parsed in main()
	klog.V(1).Infof("Setting Language to %s ...", l)
	preferredLanguage = l
}

// SetPreferredLanguage configures which language future messages should use, based on a LANG string.
func SetPreferredLanguage(s string) {
	// "C" is commonly used to denote a neutral POSIX locale. See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02
	if s == "" || s == "C" {
		setPreferredLanguageTag(defaultLanguage)
		return
	}
	// Handles "de_DE" or "de_DE.utf8"
	// We don't process encodings, since Rob Pike invented utf8 and we're mostly stuck with it.
	// Fallback to the default language if not detected
	parts := strings.Split(s, ".")
	l, err := language.Parse(parts[0])
	if err != nil {
		setPreferredLanguageTag(defaultLanguage)
		return
	}
	setPreferredLanguageTag(l)
}

// GetPreferredLanguage returns the preferred language tag.
func GetPreferredLanguage() language.Tag {
	return preferredLanguage
}
<commit_msg>remove unused package<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 translate

import (
	"encoding/json"
	"fmt"
	"path"
	"strings"

	"github.com/cloudfoundry-attic/jibber_jabber"
	"golang.org/x/text/language"

	"k8s.io/klog/v2"
)

var (
	// preferredLanguage is the default language messages will be output in
	preferredLanguage = language.AmericanEnglish
	// our default language
	defaultLanguage = language.AmericanEnglish

	// Translations is a translation map from strings that can be output to console
	// to its translation in the user's system locale.
	Translations map[string]interface{}
)

// T translates the given string to the preferred language.
func T(s string) string {
	if preferredLanguage == defaultLanguage {
		return s
	}

	if len(Translations) == 0 {
		return s
	}

	if t, ok := Translations[s]; ok {
		if len(t.(string)) > 0 && t.(string) != " " {
			return t.(string)
		}
	}

	return s
}

// DetermineLocale finds the system locale and sets the preferred language for output appropriately.
func DetermineLocale() {
	locale, err := jibber_jabber.DetectIETF()
	if err != nil {
		klog.V(1).Infof("Getting system locale failed: %v", err)
		locale = ""
	}
	SetPreferredLanguage(locale)

	// Load translations for preferred language into memory.
	p := preferredLanguage.String()
	translationFile := path.Join("translations", fmt.Sprintf("%s.json", p))
	t, err := Asset(translationFile)
	if err != nil {
		// Attempt to find a more broad locale, e.g. fr instead of fr-FR.
		if strings.Contains(p, "-") {
			p = strings.Split(p, "-")[0]
			translationFile := path.Join("translations", fmt.Sprintf("%s.json", p))
			t, err = Asset(translationFile)
			if err != nil {
				klog.V(1).Infof("Failed to load translation file for %s: %v", p, err)
				return
			}
		} else {
			klog.V(1).Infof("Failed to load translation file for %s: %v", preferredLanguage.String(), err)
			return
		}
	}

	err = json.Unmarshal(t, &Translations)
	if err != nil {
		klog.V(1).Infof("Failed to populate translation map: %v", err)
	}

}

// setPreferredLanguageTag configures which language future messages should use.
func setPreferredLanguageTag(l language.Tag) {
	// output message only if verbosity level is set and we still haven't got all the flags parsed in main()
	klog.V(1).Infof("Setting Language to %s ...", l)
	preferredLanguage = l
}

// SetPreferredLanguage configures which language future messages should use, based on a LANG string.
func SetPreferredLanguage(s string) {
	// "C" is commonly used to denote a neutral POSIX locale. See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02
	if s == "" || s == "C" {
		setPreferredLanguageTag(defaultLanguage)
		return
	}
	// Handles "de_DE" or "de_DE.utf8"
	// We don't process encodings, since Rob Pike invented utf8 and we're mostly stuck with it.
	// Fallback to the default language if not detected
	parts := strings.Split(s, ".")
	l, err := language.Parse(parts[0])
	if err != nil {
		setPreferredLanguageTag(defaultLanguage)
		return
	}
	setPreferredLanguageTag(l)
}

// GetPreferredLanguage returns the preferred language tag.
func GetPreferredLanguage() language.Tag {
	return preferredLanguage
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 kic

import (
	"fmt"
	"os/exec"

	"github.com/phayes/freeport"

	v1 "k8s.io/api/core/v1"
)

type sshConn struct {
	name    string
	service string
	cmd     *exec.Cmd
	ports   []int
}

func createSSHConn(name, sshPort, sshKey string, svc *v1.Service) *sshConn {
	// extract sshArgs
	sshArgs := []string{
		// TODO: document the options here
		"-o", "UserKnownHostsFile=/dev/null",
		"-o", "StrictHostKeyChecking no",
		"-N",
		"docker@127.0.0.1",
		"-p", sshPort,
		"-i", sshKey,
	}

	askForSudo := false
	var privilegedPorts []int32
	for _, port := range svc.Spec.Ports {
		arg := fmt.Sprintf(
			"-L %d:%s:%d",
			port.Port,
			svc.Spec.ClusterIP,
			port.Port,
		)

		// check if any port is privileged
		if port.Port < 1024 {
			privilegedPorts = append(privilegedPorts, port.Port)
			askForSudo = true
		}

		sshArgs = append(sshArgs, arg)
	}

	command := "ssh"

	if askForSudo {
		// TODO: use out package
		fmt.Printf("The service %s requires priviledged ports to be exposed: %v\n", svc.Name, privilegedPorts)
		fmt.Printf("sudo permission will be asked for it.\n")

		command = "sudo"
		sshArgs = append([]string{"ssh"}, sshArgs...)
	}

	cmd := exec.Command(command, sshArgs...)

	return &sshConn{
		name:    name,
		service: svc.Name,
		cmd:     cmd,
	}
}

func createSSHConnWithRandomPorts(name, sshPort, sshKey string, svc *v1.Service) (*sshConn, error) {
	// extract sshArgs
	sshArgs := []string{
		// TODO: document the options here
		"-o", "UserKnownHostsFile=/dev/null",
		"-o", "StrictHostKeyChecking no",
		"-N",
		"docker@127.0.0.1",
		"-p", sshPort,
		"-i", sshKey,
	}

	usedPorts := make([]int, 0, len(svc.Spec.Ports))

	for _, port := range svc.Spec.Ports {
		freeport, err := freeport.GetFreePort()
		if err != nil {
			return nil, err
		}

		arg := fmt.Sprintf(
			"-L %d:%s:%d",
			freeport,
			svc.Spec.ClusterIP,
			port.Port,
		)

		sshArgs = append(sshArgs, arg)
		usedPorts = append(usedPorts, freeport)
	}

	cmd := exec.Command("ssh", sshArgs...)

	return &sshConn{
		name:    name,
		service: svc.Name,
		cmd:     cmd,
		ports:   usedPorts,
	}, nil
}

func (c *sshConn) startAndWait() error {
	fmt.Printf("starting tunnel for %s\n", c.service)
	err := c.cmd.Start()
	if err != nil {
		return err
	}

	// we ignore wait error because the process will be killed
	_ = c.cmd.Wait()

	return nil
}

func (c *sshConn) stop() error {
	fmt.Printf("stopping tunnel for %s\n", c.service)
	return c.cmd.Process.Kill()
}
<commit_msg>refactor kic tunnel to use out pkg<commit_after>/*
Copyright 2020 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 kic

import (
	"fmt"
	"os/exec"

	"github.com/phayes/freeport"
	v1 "k8s.io/api/core/v1"

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

type sshConn struct {
	name    string
	service string
	cmd     *exec.Cmd
	ports   []int
}

func createSSHConn(name, sshPort, sshKey string, svc *v1.Service) *sshConn {
	// extract sshArgs
	sshArgs := []string{
		// TODO: document the options here
		"-o", "UserKnownHostsFile=/dev/null",
		"-o", "StrictHostKeyChecking no",
		"-N",
		"docker@127.0.0.1",
		"-p", sshPort,
		"-i", sshKey,
	}

	askForSudo := false
	var privilegedPorts []int32
	for _, port := range svc.Spec.Ports {
		arg := fmt.Sprintf(
			"-L %d:%s:%d",
			port.Port,
			svc.Spec.ClusterIP,
			port.Port,
		)

		// check if any port is privileged
		if port.Port < 1024 {
			privilegedPorts = append(privilegedPorts, port.Port)
			askForSudo = true
		}

		sshArgs = append(sshArgs, arg)
	}

	command := "ssh"

	if askForSudo {
		out.T(
			out.WarningType,
			"The service {{.service}} requires privileged ports to be exposed: {{.ports}}",
			out.V{"service": svc.Name, "ports": fmt.Sprintf("%v", privilegedPorts)},
		)

		out.T(out.Permissions, "sudo permission will be asked for it.")

		command = "sudo"
		sshArgs = append([]string{"ssh"}, sshArgs...)
	}

	cmd := exec.Command(command, sshArgs...)

	return &sshConn{
		name:    name,
		service: svc.Name,
		cmd:     cmd,
	}
}

func createSSHConnWithRandomPorts(name, sshPort, sshKey string, svc *v1.Service) (*sshConn, error) {
	// extract sshArgs
	sshArgs := []string{
		// TODO: document the options here
		"-o", "UserKnownHostsFile=/dev/null",
		"-o", "StrictHostKeyChecking no",
		"-N",
		"docker@127.0.0.1",
		"-p", sshPort,
		"-i", sshKey,
	}

	usedPorts := make([]int, 0, len(svc.Spec.Ports))

	for _, port := range svc.Spec.Ports {
		freeport, err := freeport.GetFreePort()
		if err != nil {
			return nil, err
		}

		arg := fmt.Sprintf(
			"-L %d:%s:%d",
			freeport,
			svc.Spec.ClusterIP,
			port.Port,
		)

		sshArgs = append(sshArgs, arg)
		usedPorts = append(usedPorts, freeport)
	}

	cmd := exec.Command("ssh", sshArgs...)

	return &sshConn{
		name:    name,
		service: svc.Name,
		cmd:     cmd,
		ports:   usedPorts,
	}, nil
}

func (c *sshConn) startAndWait() error {
	out.T(out.Running, "Starting tunnel for service {{.service}}.", out.V{"service": c.service})

	err := c.cmd.Start()
	if err != nil {
		return err
	}

	// we ignore wait error because the process will be killed
	_ = c.cmd.Wait()

	return nil
}

func (c *sshConn) stop() error {
	out.T(out.Stopping, "Stopping tunnel for service {{.service}}.", out.V{"service": c.service})

	return c.cmd.Process.Kill()
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 config

import (
	"reflect"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestLabels(t *testing.T) {
	tests := []struct {
		description    string
		options        SkaffoldOptions
		expectedLabels map[string]string
	}{
		{
			description:    "empty",
			options:        SkaffoldOptions{},
			expectedLabels: map[string]string{},
		},
		{
			description: "cleanup",
			options:     SkaffoldOptions{Cleanup: true},
			expectedLabels: map[string]string{
				"skaffold.dev/cleanup": "true",
			},
		},
		{
			description: "namespace",
			options:     SkaffoldOptions{Namespace: "NS"},
			expectedLabels: map[string]string{
				"skaffold.dev/namespace": "NS",
			},
		},
		{
			description: "profile",
			options:     SkaffoldOptions{Profiles: []string{"profile"}},
			expectedLabels: map[string]string{
				"skaffold.dev/profiles": "profile",
			},
		},
		{
			description: "profiles",
			options:     SkaffoldOptions{Profiles: []string{"profile1", "profile2"}},
			expectedLabels: map[string]string{
				"skaffold.dev/profiles": "profile1__profile2",
			},
		},
		{
			description: "all labels",
			options: SkaffoldOptions{
				Cleanup:   true,
				Namespace: "namespace",
				Profiles:  []string{"p1", "p2"},
			},
			expectedLabels: map[string]string{
				"skaffold.dev/cleanup":   "true",
				"skaffold.dev/namespace": "namespace",
				"skaffold.dev/profiles":  "p1__p2",
			},
		},
		{
			description: "custom labels",
			options: SkaffoldOptions{
				Cleanup: true,
				CustomLabels: []string{
					"one=first",
					"two=second",
					"three=",
					"four",
				},
			},
			expectedLabels: map[string]string{
				"skaffold.dev/cleanup": "true",
				"one":                  "first",
				"two":                  "second",
				"three":                "",
				"four":                 "",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			labels := test.options.Labels()

			if !reflect.DeepEqual(test.expectedLabels, labels) {
				t.Errorf("Wrong labels. Expected %v. Got %v", test.expectedLabels, labels)
			}
		})
	}
}

func TestIsTargetImage(t *testing.T) {
	var tests = []struct {
		description   string
		targetImages  []string
		expectedMatch bool
	}{
		{
			description:   "match all",
			targetImages:  nil,
			expectedMatch: true,
		},
		{
			description:   "match full name",
			targetImages:  []string{"domain/image"},
			expectedMatch: true,
		},
		{
			description:   "match partial name",
			targetImages:  []string{"image"},
			expectedMatch: true,
		},
		{
			description:   "match any",
			targetImages:  []string{"other", "image"},
			expectedMatch: true,
		},
		{
			description:   "no match",
			targetImages:  []string{"other"},
			expectedMatch: false,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			opts := &SkaffoldOptions{
				TargetImages: test.targetImages,
			}

			match := opts.IsTargetImage(&latest.Artifact{
				ImageName: "domain/image",
			})

			testutil.CheckDeepEqual(t, test.expectedMatch, match)
		})
	}
}
<commit_msg>Add missing test<commit_after>/*
Copyright 2019 The Skaffold 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 config

import (
	"reflect"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestLabels(t *testing.T) {
	tests := []struct {
		description    string
		options        SkaffoldOptions
		expectedLabels map[string]string
	}{
		{
			description:    "empty",
			options:        SkaffoldOptions{},
			expectedLabels: map[string]string{},
		},
		{
			description: "cleanup",
			options:     SkaffoldOptions{Cleanup: true},
			expectedLabels: map[string]string{
				"skaffold.dev/cleanup": "true",
			},
		},
		{
			description: "namespace",
			options:     SkaffoldOptions{Namespace: "NS"},
			expectedLabels: map[string]string{
				"skaffold.dev/namespace": "NS",
			},
		},
		{
			description: "profile",
			options:     SkaffoldOptions{Profiles: []string{"profile"}},
			expectedLabels: map[string]string{
				"skaffold.dev/profiles": "profile",
			},
		},
		{
			description: "profiles",
			options:     SkaffoldOptions{Profiles: []string{"profile1", "profile2"}},
			expectedLabels: map[string]string{
				"skaffold.dev/profiles": "profile1__profile2",
			},
		},
		{
			description: "tail",
			options:     SkaffoldOptions{Tail: true},
			expectedLabels: map[string]string{
				"skaffold.dev/tail": "true",
			},
		},
		{
			description: "tail dev",
			options:     SkaffoldOptions{TailDev: true},
			expectedLabels: map[string]string{
				"skaffold.dev/tail": "true",
			},
		},
		{
			description: "all labels",
			options: SkaffoldOptions{
				Cleanup:   true,
				Namespace: "namespace",
				Profiles:  []string{"p1", "p2"},
			},
			expectedLabels: map[string]string{
				"skaffold.dev/cleanup":   "true",
				"skaffold.dev/namespace": "namespace",
				"skaffold.dev/profiles":  "p1__p2",
			},
		},
		{
			description: "custom labels",
			options: SkaffoldOptions{
				Cleanup: true,
				CustomLabels: []string{
					"one=first",
					"two=second",
					"three=",
					"four",
				},
			},
			expectedLabels: map[string]string{
				"skaffold.dev/cleanup": "true",
				"one":                  "first",
				"two":                  "second",
				"three":                "",
				"four":                 "",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			labels := test.options.Labels()

			if !reflect.DeepEqual(test.expectedLabels, labels) {
				t.Errorf("Wrong labels. Expected %v. Got %v", test.expectedLabels, labels)
			}
		})
	}
}

func TestIsTargetImage(t *testing.T) {
	var tests = []struct {
		description   string
		targetImages  []string
		expectedMatch bool
	}{
		{
			description:   "match all",
			targetImages:  nil,
			expectedMatch: true,
		},
		{
			description:   "match full name",
			targetImages:  []string{"domain/image"},
			expectedMatch: true,
		},
		{
			description:   "match partial name",
			targetImages:  []string{"image"},
			expectedMatch: true,
		},
		{
			description:   "match any",
			targetImages:  []string{"other", "image"},
			expectedMatch: true,
		},
		{
			description:   "no match",
			targetImages:  []string{"other"},
			expectedMatch: false,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			opts := &SkaffoldOptions{
				TargetImages: test.targetImages,
			}

			match := opts.IsTargetImage(&latest.Artifact{
				ImageName: "domain/image",
			})

			testutil.CheckDeepEqual(t, test.expectedMatch, match)
		})
	}
}
<|endoftext|>


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

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

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

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

package fielderrors

import (
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/util/errors"

	"github.com/davecgh/go-spew/spew"
	"github.com/golang/glog"
)

// ValidationErrorType is a machine readable value providing more detail about why
// a field is invalid.  These values are expected to match 1-1 with
// CauseType in api/types.go.
type ValidationErrorType string

// TODO: These values are duplicated in api/types.go, but there's a circular dep.  Fix it.
const (
	// ValidationErrorTypeNotFound is used to report failure to find a requested value
	// (e.g. looking up an ID).
	ValidationErrorTypeNotFound ValidationErrorType = "FieldValueNotFound"
	// ValidationErrorTypeRequired is used to report required values that are not
	// provided (e.g. empty strings, null values, or empty arrays).
	ValidationErrorTypeRequired ValidationErrorType = "FieldValueRequired"
	// ValidationErrorTypeDuplicate is used to report collisions of values that must be
	// unique (e.g. unique IDs).
	ValidationErrorTypeDuplicate ValidationErrorType = "FieldValueDuplicate"
	// ValidationErrorTypeInvalid is used to report malformed values (e.g. failed regex
	// match).
	ValidationErrorTypeInvalid ValidationErrorType = "FieldValueInvalid"
	// ValidationErrorTypeNotSupported is used to report valid (as per formatting rules)
	// values that can not be handled (e.g. an enumerated string).
	ValidationErrorTypeNotSupported ValidationErrorType = "FieldValueNotSupported"
	// ValidationErrorTypeForbidden is used to report valid (as per formatting rules)
	// values which would be accepted by some api instances, but which would invoke behavior
	// not permitted by this api instance (such as due to stricter security policy).
	ValidationErrorTypeForbidden ValidationErrorType = "FieldValueForbidden"
	// ValidationErrorTypeTooLong is used to report that given value is too long.
	ValidationErrorTypeTooLong ValidationErrorType = "FieldValueTooLong"
)

// String converts a ValidationErrorType into its corresponding error message.
func (t ValidationErrorType) String() string {
	switch t {
	case ValidationErrorTypeNotFound:
		return "not found"
	case ValidationErrorTypeRequired:
		return "required value"
	case ValidationErrorTypeDuplicate:
		return "duplicate value"
	case ValidationErrorTypeInvalid:
		return "invalid value"
	case ValidationErrorTypeNotSupported:
		return "unsupported value"
	case ValidationErrorTypeForbidden:
		return "forbidden"
	case ValidationErrorTypeTooLong:
		return "too long"
	default:
		glog.Errorf("unrecognized validation type: %#v", t)
		return ""
	}
}

// ValidationError is an implementation of the 'error' interface, which represents an error of validation.
type ValidationError struct {
	Type     ValidationErrorType
	Field    string
	BadValue interface{}
	Detail   string
}

var _ error = &ValidationError{}

func (v *ValidationError) Error() string {
	return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
}

func (v *ValidationError) ErrorBody() string {
	var s string
	switch v.Type {
	case ValidationErrorTypeRequired, ValidationErrorTypeTooLong:
		s = spew.Sprintf("%s", v.Type)
	default:
		s = spew.Sprintf("%s '%+v'", v.Type, v.BadValue)
	}
	if len(v.Detail) != 0 {
		s += fmt.Sprintf(", Details: %s", v.Detail)
	}
	return s
}

// NewFieldRequired returns a *ValidationError indicating "value required"
func NewFieldRequired(field string) *ValidationError {
	return &ValidationError{ValidationErrorTypeRequired, field, "", ""}
}

// NewFieldInvalid returns a *ValidationError indicating "invalid value"
func NewFieldInvalid(field string, value interface{}, detail string) *ValidationError {
	return &ValidationError{ValidationErrorTypeInvalid, field, value, detail}
}

// NewFieldValueNotSupported returns a *ValidationError indicating "unsupported value"
func NewFieldValueNotSupported(field string, value interface{}, validValues []string) *ValidationError {
	detail := ""
	if validValues != nil && len(validValues) > 0 {
		detail = "supported values: " + strings.Join(validValues, ", ")
	}
	return &ValidationError{ValidationErrorTypeNotSupported, field, value, detail}
}

// NewFieldForbidden returns a *ValidationError indicating "forbidden"
func NewFieldForbidden(field string, value interface{}) *ValidationError {
	return &ValidationError{ValidationErrorTypeForbidden, field, value, ""}
}

// NewFieldDuplicate returns a *ValidationError indicating "duplicate value"
func NewFieldDuplicate(field string, value interface{}) *ValidationError {
	return &ValidationError{ValidationErrorTypeDuplicate, field, value, ""}
}

// NewFieldNotFound returns a *ValidationError indicating "value not found"
func NewFieldNotFound(field string, value interface{}) *ValidationError {
	return &ValidationError{ValidationErrorTypeNotFound, field, value, ""}
}

func NewFieldTooLong(field string, value interface{}, maxLength int) *ValidationError {
	return &ValidationError{ValidationErrorTypeTooLong, field, value, fmt.Sprintf("must have at most %d characters", maxLength)}
}

type ValidationErrorList []error

// Prefix adds a prefix to the Field of every ValidationError in the list.
// Returns the list for convenience.
func (list ValidationErrorList) Prefix(prefix string) ValidationErrorList {
	for i := range list {
		if err, ok := list[i].(*ValidationError); ok {
			if strings.HasPrefix(err.Field, "[") {
				err.Field = prefix + err.Field
			} else if len(err.Field) != 0 {
				err.Field = prefix + "." + err.Field
			} else {
				err.Field = prefix
			}
			list[i] = err
		} else {
			glog.Warningf("Programmer error: ValidationErrorList holds non-ValidationError: %#v", list[i])
		}
	}
	return list
}

// PrefixIndex adds an index to the Field of every ValidationError in the list.
// Returns the list for convenience.
func (list ValidationErrorList) PrefixIndex(index int) ValidationErrorList {
	return list.Prefix(fmt.Sprintf("[%d]", index))
}

// NewValidationErrorFieldPrefixMatcher returns an errors.Matcher that returns true
// if the provided error is a ValidationError and has the provided ValidationErrorType.
func NewValidationErrorTypeMatcher(t ValidationErrorType) errors.Matcher {
	return func(err error) bool {
		if e, ok := err.(*ValidationError); ok {
			return e.Type == t
		}
		return false
	}
}

// NewValidationErrorFieldPrefixMatcher returns an errors.Matcher that returns true
// if the provided error is a ValidationError and has a field with the provided
// prefix.
func NewValidationErrorFieldPrefixMatcher(prefix string) errors.Matcher {
	return func(err error) bool {
		if e, ok := err.(*ValidationError); ok {
			return strings.HasPrefix(e.Field, prefix)
		}
		return false
	}
}

// Filter removes items from the ValidationErrorList that match the provided fns.
func (list ValidationErrorList) Filter(fns ...errors.Matcher) ValidationErrorList {
	err := errors.FilterOut(errors.NewAggregate(list), fns...)
	if err == nil {
		return nil
	}
	// FilterOut that takes an Aggregate returns an Aggregate
	agg := err.(errors.Aggregate)
	return ValidationErrorList(agg.Errors())
}
<commit_msg>Remove glog dependency from fielderrors<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package fielderrors

import (
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/util/errors"

	"github.com/davecgh/go-spew/spew"
)

// ValidationErrorType is a machine readable value providing more detail about why
// a field is invalid.  These values are expected to match 1-1 with
// CauseType in api/types.go.
type ValidationErrorType string

// TODO: These values are duplicated in api/types.go, but there's a circular dep.  Fix it.
const (
	// ValidationErrorTypeNotFound is used to report failure to find a requested value
	// (e.g. looking up an ID).
	ValidationErrorTypeNotFound ValidationErrorType = "FieldValueNotFound"
	// ValidationErrorTypeRequired is used to report required values that are not
	// provided (e.g. empty strings, null values, or empty arrays).
	ValidationErrorTypeRequired ValidationErrorType = "FieldValueRequired"
	// ValidationErrorTypeDuplicate is used to report collisions of values that must be
	// unique (e.g. unique IDs).
	ValidationErrorTypeDuplicate ValidationErrorType = "FieldValueDuplicate"
	// ValidationErrorTypeInvalid is used to report malformed values (e.g. failed regex
	// match).
	ValidationErrorTypeInvalid ValidationErrorType = "FieldValueInvalid"
	// ValidationErrorTypeNotSupported is used to report valid (as per formatting rules)
	// values that can not be handled (e.g. an enumerated string).
	ValidationErrorTypeNotSupported ValidationErrorType = "FieldValueNotSupported"
	// ValidationErrorTypeForbidden is used to report valid (as per formatting rules)
	// values which would be accepted by some api instances, but which would invoke behavior
	// not permitted by this api instance (such as due to stricter security policy).
	ValidationErrorTypeForbidden ValidationErrorType = "FieldValueForbidden"
	// ValidationErrorTypeTooLong is used to report that given value is too long.
	ValidationErrorTypeTooLong ValidationErrorType = "FieldValueTooLong"
)

// String converts a ValidationErrorType into its corresponding error message.
func (t ValidationErrorType) String() string {
	switch t {
	case ValidationErrorTypeNotFound:
		return "not found"
	case ValidationErrorTypeRequired:
		return "required value"
	case ValidationErrorTypeDuplicate:
		return "duplicate value"
	case ValidationErrorTypeInvalid:
		return "invalid value"
	case ValidationErrorTypeNotSupported:
		return "unsupported value"
	case ValidationErrorTypeForbidden:
		return "forbidden"
	case ValidationErrorTypeTooLong:
		return "too long"
	default:
		panic(fmt.Sprintf("unrecognized validation type: %#v", t))
		return ""
	}
}

// ValidationError is an implementation of the 'error' interface, which represents an error of validation.
type ValidationError struct {
	Type     ValidationErrorType
	Field    string
	BadValue interface{}
	Detail   string
}

var _ error = &ValidationError{}

func (v *ValidationError) Error() string {
	return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
}

func (v *ValidationError) ErrorBody() string {
	var s string
	switch v.Type {
	case ValidationErrorTypeRequired, ValidationErrorTypeTooLong:
		s = spew.Sprintf("%s", v.Type)
	default:
		s = spew.Sprintf("%s '%+v'", v.Type, v.BadValue)
	}
	if len(v.Detail) != 0 {
		s += fmt.Sprintf(", Details: %s", v.Detail)
	}
	return s
}

// NewFieldRequired returns a *ValidationError indicating "value required"
func NewFieldRequired(field string) *ValidationError {
	return &ValidationError{ValidationErrorTypeRequired, field, "", ""}
}

// NewFieldInvalid returns a *ValidationError indicating "invalid value"
func NewFieldInvalid(field string, value interface{}, detail string) *ValidationError {
	return &ValidationError{ValidationErrorTypeInvalid, field, value, detail}
}

// NewFieldValueNotSupported returns a *ValidationError indicating "unsupported value"
func NewFieldValueNotSupported(field string, value interface{}, validValues []string) *ValidationError {
	detail := ""
	if validValues != nil && len(validValues) > 0 {
		detail = "supported values: " + strings.Join(validValues, ", ")
	}
	return &ValidationError{ValidationErrorTypeNotSupported, field, value, detail}
}

// NewFieldForbidden returns a *ValidationError indicating "forbidden"
func NewFieldForbidden(field string, value interface{}) *ValidationError {
	return &ValidationError{ValidationErrorTypeForbidden, field, value, ""}
}

// NewFieldDuplicate returns a *ValidationError indicating "duplicate value"
func NewFieldDuplicate(field string, value interface{}) *ValidationError {
	return &ValidationError{ValidationErrorTypeDuplicate, field, value, ""}
}

// NewFieldNotFound returns a *ValidationError indicating "value not found"
func NewFieldNotFound(field string, value interface{}) *ValidationError {
	return &ValidationError{ValidationErrorTypeNotFound, field, value, ""}
}

func NewFieldTooLong(field string, value interface{}, maxLength int) *ValidationError {
	return &ValidationError{ValidationErrorTypeTooLong, field, value, fmt.Sprintf("must have at most %d characters", maxLength)}
}

type ValidationErrorList []error

// Prefix adds a prefix to the Field of every ValidationError in the list.
// Returns the list for convenience.
func (list ValidationErrorList) Prefix(prefix string) ValidationErrorList {
	for i := range list {
		if err, ok := list[i].(*ValidationError); ok {
			if strings.HasPrefix(err.Field, "[") {
				err.Field = prefix + err.Field
			} else if len(err.Field) != 0 {
				err.Field = prefix + "." + err.Field
			} else {
				err.Field = prefix
			}
			list[i] = err
		} else {
			panic(fmt.Sprintf("Programmer error: ValidationErrorList holds non-ValidationError: %#v", list[i]))
		}
	}
	return list
}

// PrefixIndex adds an index to the Field of every ValidationError in the list.
// Returns the list for convenience.
func (list ValidationErrorList) PrefixIndex(index int) ValidationErrorList {
	return list.Prefix(fmt.Sprintf("[%d]", index))
}

// NewValidationErrorFieldPrefixMatcher returns an errors.Matcher that returns true
// if the provided error is a ValidationError and has the provided ValidationErrorType.
func NewValidationErrorTypeMatcher(t ValidationErrorType) errors.Matcher {
	return func(err error) bool {
		if e, ok := err.(*ValidationError); ok {
			return e.Type == t
		}
		return false
	}
}

// NewValidationErrorFieldPrefixMatcher returns an errors.Matcher that returns true
// if the provided error is a ValidationError and has a field with the provided
// prefix.
func NewValidationErrorFieldPrefixMatcher(prefix string) errors.Matcher {
	return func(err error) bool {
		if e, ok := err.(*ValidationError); ok {
			return strings.HasPrefix(e.Field, prefix)
		}
		return false
	}
}

// Filter removes items from the ValidationErrorList that match the provided fns.
func (list ValidationErrorList) Filter(fns ...errors.Matcher) ValidationErrorList {
	err := errors.FilterOut(errors.NewAggregate(list), fns...)
	if err == nil {
		return nil
	}
	// FilterOut that takes an Aggregate returns an Aggregate
	agg := err.(errors.Aggregate)
	return ValidationErrorList(agg.Errors())
}
<|endoftext|>


<commit_before>package termite

import (
	"errors"
	"fmt"
	"github.com/hanwen/termite/attr"
	"log"
	"net"
	"net/rpc"
	"sync"
	"time"
)

// State associated with one master.
type Mirror struct {
	worker       *Worker
	rpcConn      net.Conn
	contentConn  net.Conn
	revContentConn net.Conn

	rpcFs        *RpcFs
	writableRoot string

	// key in Worker's map.
	key string

	maxJobCount int

	fsMutex    sync.Mutex
	cond       *sync.Cond
	waiting    int
	nextFsId   int
	activeFses map[*workerFuseFs]bool
	accepting  bool
	killed     bool
}

func NewMirror(worker *Worker, rpcConn, revConn, contentConn, revContentConn net.Conn) *Mirror {
	log.Println("Mirror for", rpcConn)

	mirror := &Mirror{
		activeFses: map[*workerFuseFs]bool{},
		rpcConn:    rpcConn,
		contentConn: contentConn,
		revContentConn: revContentConn,
		worker:     worker,
		accepting:  true,
	}

	mirror.cond = sync.NewCond(&mirror.fsMutex)
	mirror.rpcFs = NewRpcFs(rpc.NewClient(revConn), worker.contentCache, revContentConn)

	_, portString, _ := net.SplitHostPort(worker.listener.Addr().String())

	mirror.rpcFs.id = Hostname + ":" + portString
	mirror.rpcFs.attr.Paranoia = worker.options.Paranoia
	mirror.rpcFs.localRoots = []string{"/lib", "/usr"}

	go mirror.serveRpc()
	return mirror
}

func (me *Mirror) serveRpc() {
	server := rpc.NewServer()
	server.Register(me)
	done := make(chan int, 2)
	go func() {
		server.ServeConn(me.rpcConn)
		done <- 1
	}()
	go func() {
		me.worker.contentCache.ServeConn(me.contentConn)
		done <- 1
	}()
	<-done
	me.Shutdown(true)
	me.worker.DropMirror(me)
}

func (me *Mirror) Shutdown(aggressive bool) {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()
	me.accepting = false
	if aggressive {
		me.killed = true
	}

	for fs := range me.activeFses {
		if len(fs.tasks) == 0 {
			fs.Stop()
			delete(me.activeFses, fs)
		}
	}

	if aggressive {
		me.rpcFs.Close()
		for fs := range me.activeFses {
			for t := range fs.tasks {
				t.Kill()
			}
		}
	}
	for len(me.activeFses) > 0 {
		me.cond.Wait()
	}

	me.rpcConn.Close()
	me.contentConn.Close()
}

func (me *Mirror) runningCount() int {
	r := 0
	for fs := range me.activeFses {
		r += len(fs.tasks)
	}
	return r
}

func (me *Mirror) newFs(t *WorkerTask) (fs *workerFuseFs, err error) {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()

	me.waiting++
	for me.accepting && me.runningCount() >= me.maxJobCount {
		me.cond.Wait()
	}
	me.waiting--

	if !me.accepting {
		return nil, errors.New("shutting down")
	}

	for fs := range me.activeFses {
		if !fs.reaping && len(fs.taskIds) < me.worker.options.ReapCount {
			fs.addTask(t)
			return fs, nil
		}
	}
	fs, err = me.newWorkerFuseFs()
	if err != nil {
		return nil, err
	}

	me.prepareFs(fs)
	fs.addTask(t)
	me.activeFses[fs] = true
	return fs, nil
}

// Must hold lock.
func (me *Mirror) prepareFs(fs *workerFuseFs) {
	fs.reaping = false
	fs.taskIds = make([]int, 0, me.worker.options.ReapCount)
}

func (me *Mirror) considerReap(fs *workerFuseFs, task *WorkerTask) bool {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()
	delete(fs.tasks, task)
	if len(fs.tasks) == 0 {
		fs.reaping = true
	}
	me.cond.Broadcast()
	return fs.reaping
}

func (me *Mirror) reapFuse(fs *workerFuseFs) (results *attr.FileSet, taskIds []int) {
	log.Printf("Reaping fuse FS %v", fs.id)
	results = me.fillReply(fs.unionFs)
	fs.unionFs.Reset()

	return results, fs.taskIds[:]
}

func (me *Mirror) returnFs(fs *workerFuseFs) {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()

	if fs.reaping {
		me.prepareFs(fs)
	}

	fs.SetDebug(false)
	if !me.accepting {
		fs.Stop()
		delete(me.activeFses, fs)
		me.cond.Broadcast()
	}
}

func (me *Mirror) Update(req *UpdateRequest, rep *UpdateResponse) error {
	me.updateFiles(req.Files)
	return nil
}

func (me *Mirror) updateFiles(attrs []*attr.FileAttr) {
	me.rpcFs.updateFiles(attrs)

	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()

	for fs := range me.activeFses {
		fs.update(attrs)
	}
}

func (me *Mirror) Run(req *WorkRequest, rep *WorkResponse) error {
	me.worker.stats.Enter("run")
	log.Print("Received request", req)

	// Don't run me.updateFiles() as we don't want to issue
	// unneeded cache invalidations.
	task, err := me.newWorkerTask(req, rep)
	if err != nil {
		return err
	}

	err = task.Run()
	if err != nil {
		log.Println("task.Run:", err)
		return err
	}

	log.Println(rep)
	rep.WorkerId = fmt.Sprintf("%s: %s", Hostname, me.worker.listener.Addr().String())
	me.worker.stats.Exit("run")

	if me.killed {
		return fmt.Errorf("killed worker %s", me.worker.listener.Addr().String())
	}
	return nil
}

const _DELETIONS = "DELETIONS"

func (me *Mirror) newWorkerFuseFs() (*workerFuseFs, error) {
	f, err := newWorkerFuseFs(me.worker.options.TempDir, me.rpcFs, me.writableRoot,
		me.worker.options.User)
	if err != nil {
		return nil, err
	}
	f.id = fmt.Sprintf("%d", me.nextFsId)
	me.nextFsId++

	return f, err
}

func (me *Mirror) newWorkerTask(req *WorkRequest, rep *WorkResponse) (*WorkerTask, error) {
	var stdin net.Conn
	if req.StdinId != "" {
		stdin = me.worker.pending.WaitConnection(req.StdinId)
	}
	task := &WorkerTask{
		req:       req,
		rep:       rep,
		stdinConn: stdin,
		mirror:    me,
		taskInfo:  fmt.Sprintf("%v, dir %v", req.Argv, req.Dir),
	}
	return task, nil
}

<commit_msg>Compile fix.<commit_after>package termite

import (
	"errors"
	"fmt"
	"github.com/hanwen/termite/attr"
	"log"
	"net"
	"net/rpc"
	"sync"
)

// State associated with one master.
type Mirror struct {
	worker       *Worker
	rpcConn      net.Conn
	contentConn  net.Conn
	revContentConn net.Conn

	rpcFs        *RpcFs
	writableRoot string

	// key in Worker's map.
	key string

	maxJobCount int

	fsMutex    sync.Mutex
	cond       *sync.Cond
	waiting    int
	nextFsId   int
	activeFses map[*workerFuseFs]bool
	accepting  bool
	killed     bool
}

func NewMirror(worker *Worker, rpcConn, revConn, contentConn, revContentConn net.Conn) *Mirror {
	log.Println("Mirror for", rpcConn)

	mirror := &Mirror{
		activeFses: map[*workerFuseFs]bool{},
		rpcConn:    rpcConn,
		contentConn: contentConn,
		revContentConn: revContentConn,
		worker:     worker,
		accepting:  true,
	}

	mirror.cond = sync.NewCond(&mirror.fsMutex)
	mirror.rpcFs = NewRpcFs(rpc.NewClient(revConn), worker.contentCache, revContentConn)

	_, portString, _ := net.SplitHostPort(worker.listener.Addr().String())

	mirror.rpcFs.id = Hostname + ":" + portString
	mirror.rpcFs.attr.Paranoia = worker.options.Paranoia
	mirror.rpcFs.localRoots = []string{"/lib", "/usr"}

	go mirror.serveRpc()
	return mirror
}

func (me *Mirror) serveRpc() {
	server := rpc.NewServer()
	server.Register(me)
	done := make(chan int, 2)
	go func() {
		server.ServeConn(me.rpcConn)
		done <- 1
	}()
	go func() {
		me.worker.contentCache.ServeConn(me.contentConn)
		done <- 1
	}()
	<-done
	me.Shutdown(true)
	me.worker.DropMirror(me)
}

func (me *Mirror) Shutdown(aggressive bool) {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()
	me.accepting = false
	if aggressive {
		me.killed = true
	}

	for fs := range me.activeFses {
		if len(fs.tasks) == 0 {
			fs.Stop()
			delete(me.activeFses, fs)
		}
	}

	if aggressive {
		me.rpcFs.Close()
		for fs := range me.activeFses {
			for t := range fs.tasks {
				t.Kill()
			}
		}
	}
	for len(me.activeFses) > 0 {
		me.cond.Wait()
	}

	me.rpcConn.Close()
	me.contentConn.Close()
}

func (me *Mirror) runningCount() int {
	r := 0
	for fs := range me.activeFses {
		r += len(fs.tasks)
	}
	return r
}

func (me *Mirror) newFs(t *WorkerTask) (fs *workerFuseFs, err error) {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()

	me.waiting++
	for me.accepting && me.runningCount() >= me.maxJobCount {
		me.cond.Wait()
	}
	me.waiting--

	if !me.accepting {
		return nil, errors.New("shutting down")
	}

	for fs := range me.activeFses {
		if !fs.reaping && len(fs.taskIds) < me.worker.options.ReapCount {
			fs.addTask(t)
			return fs, nil
		}
	}
	fs, err = me.newWorkerFuseFs()
	if err != nil {
		return nil, err
	}

	me.prepareFs(fs)
	fs.addTask(t)
	me.activeFses[fs] = true
	return fs, nil
}

// Must hold lock.
func (me *Mirror) prepareFs(fs *workerFuseFs) {
	fs.reaping = false
	fs.taskIds = make([]int, 0, me.worker.options.ReapCount)
}

func (me *Mirror) considerReap(fs *workerFuseFs, task *WorkerTask) bool {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()
	delete(fs.tasks, task)
	if len(fs.tasks) == 0 {
		fs.reaping = true
	}
	me.cond.Broadcast()
	return fs.reaping
}

func (me *Mirror) reapFuse(fs *workerFuseFs) (results *attr.FileSet, taskIds []int) {
	log.Printf("Reaping fuse FS %v", fs.id)
	results = me.fillReply(fs.unionFs)
	fs.unionFs.Reset()

	return results, fs.taskIds[:]
}

func (me *Mirror) returnFs(fs *workerFuseFs) {
	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()

	if fs.reaping {
		me.prepareFs(fs)
	}

	fs.SetDebug(false)
	if !me.accepting {
		fs.Stop()
		delete(me.activeFses, fs)
		me.cond.Broadcast()
	}
}

func (me *Mirror) Update(req *UpdateRequest, rep *UpdateResponse) error {
	me.updateFiles(req.Files)
	return nil
}

func (me *Mirror) updateFiles(attrs []*attr.FileAttr) {
	me.rpcFs.updateFiles(attrs)

	me.fsMutex.Lock()
	defer me.fsMutex.Unlock()

	for fs := range me.activeFses {
		fs.update(attrs)
	}
}

func (me *Mirror) Run(req *WorkRequest, rep *WorkResponse) error {
	me.worker.stats.Enter("run")
	log.Print("Received request", req)

	// Don't run me.updateFiles() as we don't want to issue
	// unneeded cache invalidations.
	task, err := me.newWorkerTask(req, rep)
	if err != nil {
		return err
	}

	err = task.Run()
	if err != nil {
		log.Println("task.Run:", err)
		return err
	}

	log.Println(rep)
	rep.WorkerId = fmt.Sprintf("%s: %s", Hostname, me.worker.listener.Addr().String())
	me.worker.stats.Exit("run")

	if me.killed {
		return fmt.Errorf("killed worker %s", me.worker.listener.Addr().String())
	}
	return nil
}

const _DELETIONS = "DELETIONS"

func (me *Mirror) newWorkerFuseFs() (*workerFuseFs, error) {
	f, err := newWorkerFuseFs(me.worker.options.TempDir, me.rpcFs, me.writableRoot,
		me.worker.options.User)
	if err != nil {
		return nil, err
	}
	f.id = fmt.Sprintf("%d", me.nextFsId)
	me.nextFsId++

	return f, err
}

func (me *Mirror) newWorkerTask(req *WorkRequest, rep *WorkResponse) (*WorkerTask, error) {
	var stdin net.Conn
	if req.StdinId != "" {
		stdin = me.worker.pending.WaitConnection(req.StdinId)
	}
	task := &WorkerTask{
		req:       req,
		rep:       rep,
		stdinConn: stdin,
		mirror:    me,
		taskInfo:  fmt.Sprintf("%v, dir %v", req.Argv, req.Dir),
	}
	return task, nil
}

<|endoftext|>


<commit_before>// Copyright (c) 2014 Couchbase, Inc.
// 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 indexer

import (
	"encoding/json"
	"github.com/couchbase/indexing/secondary/common"
	"net/http"
)

type statsManager struct {
	supvCmdch MsgChannel
	supvMsgch MsgChannel
}

func NewStatsManager(supvCmdch MsgChannel,
	supvMsgch MsgChannel, config common.Config) (statsManager, Message) {
	s := statsManager{
		supvCmdch: supvCmdch,
		supvMsgch: supvMsgch,
	}

	http.HandleFunc("/stats", s.handleStatsReq)
	return s, &MsgSuccess{}
}

func (s *statsManager) handleStatsReq(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" || r.Method == "GET" {
		statsMap := make(map[string]string)
		stats_list := []MsgType{STORAGE_STATS, SCAN_STATS, INDEX_PROGRESS_STATS}
		for _, t := range stats_list {
			ch := make(chan map[string]string)
			msg := &MsgStatsRequest{
				mType:  t,
				respch: ch,
			}

			s.supvMsgch <- msg
			for k, v := range <-ch {
				statsMap[k] = v
			}
		}

		bytes, _ := json.Marshal(statsMap)
		w.WriteHeader(200)
		w.Write(bytes)
	} else {
		w.WriteHeader(400)
		w.Write([]byte("Unsupported method"))
	}
}

func (s *statsManager) run() {
loop:
	for {
		select {
		case cmd, ok := <-s.supvCmdch:
			if ok {
				if cmd.GetMsgType() == STORAGE_MGR_SHUTDOWN {
					common.Infof("SettingsManager::run Shutting Down")
					s.supvCmdch <- &MsgSuccess{}
					break loop
				}
			} else {
				break loop
			}
		}
	}
}
<commit_msg>indexer/stats: Add /stats/mem endpoint<commit_after>// Copyright (c) 2014 Couchbase, Inc.
// 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 indexer

import (
	"encoding/json"
	"github.com/couchbase/indexing/secondary/common"
	"net/http"
	"runtime"
)

type statsManager struct {
	supvCmdch MsgChannel
	supvMsgch MsgChannel
}

func NewStatsManager(supvCmdch MsgChannel,
	supvMsgch MsgChannel, config common.Config) (statsManager, Message) {
	s := statsManager{
		supvCmdch: supvCmdch,
		supvMsgch: supvMsgch,
	}

	http.HandleFunc("/stats", s.handleStatsReq)
	http.HandleFunc("/stats/mem", s.handleMemStatsReq)
	return s, &MsgSuccess{}
}

func (s *statsManager) handleStatsReq(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" || r.Method == "GET" {
		statsMap := make(map[string]string)
		stats_list := []MsgType{STORAGE_STATS, SCAN_STATS, INDEX_PROGRESS_STATS}
		for _, t := range stats_list {
			ch := make(chan map[string]string)
			msg := &MsgStatsRequest{
				mType:  t,
				respch: ch,
			}

			s.supvMsgch <- msg
			for k, v := range <-ch {
				statsMap[k] = v
			}
		}

		bytes, _ := json.Marshal(statsMap)
		w.WriteHeader(200)
		w.Write(bytes)
	} else {
		w.WriteHeader(400)
		w.Write([]byte("Unsupported method"))
	}
}

func (s *statsManager) handleMemStatsReq(w http.ResponseWriter, r *http.Request) {
	stats := new(runtime.MemStats)
	if r.Method == "POST" || r.Method == "GET" {
		runtime.ReadMemStats(stats)
		bytes, _ := json.Marshal(stats)
		w.WriteHeader(200)
		w.Write(bytes)
	} else {
		w.WriteHeader(400)
		w.Write([]byte("Unsupported method"))
	}
}

func (s *statsManager) run() {
loop:
	for {
		select {
		case cmd, ok := <-s.supvCmdch:
			if ok {
				if cmd.GetMsgType() == STORAGE_MGR_SHUTDOWN {
					common.Infof("SettingsManager::run Shutting Down")
					s.supvCmdch <- &MsgSuccess{}
					break loop
				}
			} else {
				break loop
			}
		}
	}
}
<|endoftext|>


<commit_before>package resolver

import (
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/containerd/containerd/remotes"
	"github.com/containerd/containerd/remotes/docker"
	"github.com/moby/buildkit/cmd/buildkitd/config"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/session/auth"
	"github.com/moby/buildkit/util/tracing"
	"github.com/pkg/errors"
)

func fillInsecureOpts(host string, c config.RegistryConfig, h docker.RegistryHost) ([]docker.RegistryHost, error) {
	var hosts []docker.RegistryHost

	tc, err := loadTLSConfig(c)
	if err != nil {
		return nil, err
	}
	var isHTTP bool

	if c.PlainHTTP != nil && *c.PlainHTTP {
		isHTTP = true
	}
	if c.PlainHTTP == nil {
		if ok, _ := docker.MatchLocalhost(host); ok {
			isHTTP = true
		}
	}

	if isHTTP {
		h2 := h
		h2.Scheme = "http"
		hosts = append(hosts, h2)
	}
	if c.Insecure != nil && *c.Insecure {
		h2 := h
		transport := newDefaultTransport()
		transport.TLSClientConfig = tc
		h2.Client = &http.Client{
			Transport: tracing.NewTransport(transport),
		}
		tc.InsecureSkipVerify = true
		hosts = append(hosts, h2)
	}

	if len(hosts) == 0 {
		transport := newDefaultTransport()
		transport.TLSClientConfig = tc

		h.Client = &http.Client{
			Transport: tracing.NewTransport(transport),
		}
		hosts = append(hosts, h)
	}

	return hosts, nil
}

func loadTLSConfig(c config.RegistryConfig) (*tls.Config, error) {
	for _, d := range c.TLSConfigDir {
		fs, err := ioutil.ReadDir(d)
		if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) {
			return nil, errors.WithStack(err)
		}
		for _, f := range fs {
			if strings.HasSuffix(f.Name(), ".crt") {
				c.RootCAs = append(c.RootCAs, filepath.Join(d, f.Name()))
			}
			if strings.HasSuffix(f.Name(), ".cert") {
				c.KeyPairs = append(c.KeyPairs, config.TLSKeyPair{
					Certificate: filepath.Join(d, f.Name()),
					Key:         filepath.Join(d, strings.TrimSuffix(f.Name(), ".cert")+".key"),
				})
			}
		}
	}

	tc := &tls.Config{}
	if len(c.RootCAs) > 0 {
		systemPool, err := x509.SystemCertPool()
		if err != nil {
			if runtime.GOOS == "windows" {
				systemPool = x509.NewCertPool()
			} else {
				return nil, errors.Wrapf(err, "unable to get system cert pool")
			}
		}
		tc.RootCAs = systemPool
	}

	for _, p := range c.RootCAs {
		dt, err := ioutil.ReadFile(p)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to read %s", p)
		}
		tc.RootCAs.AppendCertsFromPEM(dt)
	}

	for _, kp := range c.KeyPairs {
		cert, err := tls.LoadX509KeyPair(kp.Certificate, kp.Key)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to load keypair for %s", kp.Certificate)
		}
		tc.Certificates = append(tc.Certificates, cert)
	}
	return tc, nil
}

func NewRegistryConfig(m map[string]config.RegistryConfig) docker.RegistryHosts {
	return docker.Registries(
		func(host string) ([]docker.RegistryHost, error) {
			c, ok := m[host]
			if !ok {
				return nil, nil
			}

			var out []docker.RegistryHost

			for _, mirror := range c.Mirrors {
				h := docker.RegistryHost{
					Scheme:       "https",
					Client:       newDefaultClient(),
					Host:         mirror,
					Path:         "/v2",
					Capabilities: docker.HostCapabilityPull | docker.HostCapabilityResolve,
				}

				hosts, err := fillInsecureOpts(mirror, m[mirror], h)
				if err != nil {
					return nil, err
				}

				out = append(out, hosts...)
			}

			if host == "docker.io" {
				host = "registry-1.docker.io"
			}

			h := docker.RegistryHost{
				Scheme:       "https",
				Client:       newDefaultClient(),
				Host:         host,
				Path:         "/v2",
				Capabilities: docker.HostCapabilityPush | docker.HostCapabilityPull | docker.HostCapabilityResolve,
			}

			hosts, err := fillInsecureOpts(host, c, h)
			if err != nil {
				return nil, err
			}

			out = append(out, hosts...)
			return out, nil
		},
		docker.ConfigureDefaultRegistries(
			docker.WithClient(newDefaultClient()),
			docker.WithPlainHTTP(docker.MatchLocalhost),
		),
	)
}

type SessionAuthenticator struct {
	sm      *session.Manager
	groups  []session.Group
	mu      sync.RWMutex
	cache   map[string]credentials
	cacheMu sync.RWMutex
}

type credentials struct {
	user    string
	secret  string
	created time.Time
}

func NewSessionAuthenticator(sm *session.Manager, g session.Group) *SessionAuthenticator {
	return &SessionAuthenticator{sm: sm, groups: []session.Group{g}, cache: map[string]credentials{}}
}

func (a *SessionAuthenticator) credentials(h string) (string, string, error) {
	const credentialsTimeout = time.Minute

	a.cacheMu.RLock()
	c, ok := a.cache[h]
	if ok && time.Since(c.created) < credentialsTimeout {
		a.cacheMu.RUnlock()
		return c.user, c.secret, nil
	}
	a.cacheMu.RUnlock()

	a.mu.RLock()
	defer a.mu.RUnlock()

	var err error
	for i := len(a.groups) - 1; i >= 0; i-- {
		var user, secret string
		user, secret, err = auth.CredentialsFunc(a.sm, a.groups[i])(h)
		if err != nil {
			continue
		}
		a.cacheMu.Lock()
		a.cache[h] = credentials{user: user, secret: secret, created: time.Now()}
		a.cacheMu.Unlock()
		return user, secret, nil
	}
	return "", "", err
}

func (a *SessionAuthenticator) AddSession(g session.Group) {
	a.mu.Lock()
	a.groups = append(a.groups, g)
	a.mu.Unlock()
}

func New(hosts docker.RegistryHosts, auth *SessionAuthenticator) remotes.Resolver {
	return docker.NewResolver(docker.ResolverOptions{
		Hosts: hostsWithCredentials(hosts, auth),
	})
}

func hostsWithCredentials(hosts docker.RegistryHosts, auth *SessionAuthenticator) docker.RegistryHosts {
	if hosts == nil {
		return nil
	}
	return func(domain string) ([]docker.RegistryHost, error) {
		res, err := hosts(domain)
		if err != nil {
			return nil, err
		}
		if len(res) == 0 {
			return nil, nil
		}

		a := docker.NewDockerAuthorizer(
			docker.WithAuthClient(res[0].Client),
			docker.WithAuthCreds(auth.credentials),
		)
		for i := range res {
			res[i].Authorizer = a
		}
		return res, nil
	}
}

func newDefaultClient() *http.Client {
	return &http.Client{
		Transport: tracing.NewTransport(newDefaultTransport()),
	}
}

// newDefaultTransport is for pull or push client
//
// NOTE: For push, there must disable http2 for https because the flow control
// will limit data transfer. The net/http package doesn't provide http2 tunable
// settings which limits push performance.
//
// REF: https://github.com/golang/go/issues/14077
func newDefaultTransport() *http.Transport {
	return &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
			DualStack: true,
		}).DialContext,
		MaxIdleConns:          10,
		IdleConnTimeout:       30 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 5 * time.Second,
		DisableKeepAlives:     true,
		TLSNextProto:          make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
	}
}
<commit_msg>resolver: make sure authorizer is not regenerated<commit_after>package resolver

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/containerd/containerd/remotes"
	"github.com/containerd/containerd/remotes/docker"
	"github.com/moby/buildkit/cmd/buildkitd/config"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/session/auth"
	"github.com/moby/buildkit/util/flightcontrol"
	"github.com/moby/buildkit/util/tracing"
	"github.com/pkg/errors"
)

func fillInsecureOpts(host string, c config.RegistryConfig, h docker.RegistryHost) ([]docker.RegistryHost, error) {
	var hosts []docker.RegistryHost

	tc, err := loadTLSConfig(c)
	if err != nil {
		return nil, err
	}
	var isHTTP bool

	if c.PlainHTTP != nil && *c.PlainHTTP {
		isHTTP = true
	}
	if c.PlainHTTP == nil {
		if ok, _ := docker.MatchLocalhost(host); ok {
			isHTTP = true
		}
	}

	if isHTTP {
		h2 := h
		h2.Scheme = "http"
		hosts = append(hosts, h2)
	}
	if c.Insecure != nil && *c.Insecure {
		h2 := h
		transport := newDefaultTransport()
		transport.TLSClientConfig = tc
		h2.Client = &http.Client{
			Transport: tracing.NewTransport(transport),
		}
		tc.InsecureSkipVerify = true
		hosts = append(hosts, h2)
	}

	if len(hosts) == 0 {
		transport := newDefaultTransport()
		transport.TLSClientConfig = tc

		h.Client = &http.Client{
			Transport: tracing.NewTransport(transport),
		}
		hosts = append(hosts, h)
	}

	return hosts, nil
}

func loadTLSConfig(c config.RegistryConfig) (*tls.Config, error) {
	for _, d := range c.TLSConfigDir {
		fs, err := ioutil.ReadDir(d)
		if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) {
			return nil, errors.WithStack(err)
		}
		for _, f := range fs {
			if strings.HasSuffix(f.Name(), ".crt") {
				c.RootCAs = append(c.RootCAs, filepath.Join(d, f.Name()))
			}
			if strings.HasSuffix(f.Name(), ".cert") {
				c.KeyPairs = append(c.KeyPairs, config.TLSKeyPair{
					Certificate: filepath.Join(d, f.Name()),
					Key:         filepath.Join(d, strings.TrimSuffix(f.Name(), ".cert")+".key"),
				})
			}
		}
	}

	tc := &tls.Config{}
	if len(c.RootCAs) > 0 {
		systemPool, err := x509.SystemCertPool()
		if err != nil {
			if runtime.GOOS == "windows" {
				systemPool = x509.NewCertPool()
			} else {
				return nil, errors.Wrapf(err, "unable to get system cert pool")
			}
		}
		tc.RootCAs = systemPool
	}

	for _, p := range c.RootCAs {
		dt, err := ioutil.ReadFile(p)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to read %s", p)
		}
		tc.RootCAs.AppendCertsFromPEM(dt)
	}

	for _, kp := range c.KeyPairs {
		cert, err := tls.LoadX509KeyPair(kp.Certificate, kp.Key)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to load keypair for %s", kp.Certificate)
		}
		tc.Certificates = append(tc.Certificates, cert)
	}
	return tc, nil
}

func NewRegistryConfig(m map[string]config.RegistryConfig) docker.RegistryHosts {
	return docker.Registries(
		func(host string) ([]docker.RegistryHost, error) {
			c, ok := m[host]
			if !ok {
				return nil, nil
			}

			var out []docker.RegistryHost

			for _, mirror := range c.Mirrors {
				h := docker.RegistryHost{
					Scheme:       "https",
					Client:       newDefaultClient(),
					Host:         mirror,
					Path:         "/v2",
					Capabilities: docker.HostCapabilityPull | docker.HostCapabilityResolve,
				}

				hosts, err := fillInsecureOpts(mirror, m[mirror], h)
				if err != nil {
					return nil, err
				}

				out = append(out, hosts...)
			}

			if host == "docker.io" {
				host = "registry-1.docker.io"
			}

			h := docker.RegistryHost{
				Scheme:       "https",
				Client:       newDefaultClient(),
				Host:         host,
				Path:         "/v2",
				Capabilities: docker.HostCapabilityPush | docker.HostCapabilityPull | docker.HostCapabilityResolve,
			}

			hosts, err := fillInsecureOpts(host, c, h)
			if err != nil {
				return nil, err
			}

			out = append(out, hosts...)
			return out, nil
		},
		docker.ConfigureDefaultRegistries(
			docker.WithClient(newDefaultClient()),
			docker.WithPlainHTTP(docker.MatchLocalhost),
		),
	)
}

type SessionAuthenticator struct {
	sm      *session.Manager
	groups  []session.Group
	mu      sync.RWMutex
	cache   map[string]credentials
	cacheMu sync.RWMutex
}

type credentials struct {
	user    string
	secret  string
	created time.Time
}

func NewSessionAuthenticator(sm *session.Manager, g session.Group) *SessionAuthenticator {
	return &SessionAuthenticator{sm: sm, groups: []session.Group{g}, cache: map[string]credentials{}}
}

func (a *SessionAuthenticator) credentials(h string) (string, string, error) {
	const credentialsTimeout = time.Minute

	a.cacheMu.RLock()
	c, ok := a.cache[h]
	if ok && time.Since(c.created) < credentialsTimeout {
		a.cacheMu.RUnlock()
		return c.user, c.secret, nil
	}
	a.cacheMu.RUnlock()

	a.mu.RLock()
	defer a.mu.RUnlock()

	var err error
	for i := len(a.groups) - 1; i >= 0; i-- {
		var user, secret string
		user, secret, err = auth.CredentialsFunc(a.sm, a.groups[i])(h)
		if err != nil {
			continue
		}
		a.cacheMu.Lock()
		a.cache[h] = credentials{user: user, secret: secret, created: time.Now()}
		a.cacheMu.Unlock()
		return user, secret, nil
	}
	return "", "", err
}

func (a *SessionAuthenticator) AddSession(g session.Group) {
	a.mu.Lock()
	a.groups = append(a.groups, g)
	a.mu.Unlock()
}

func New(hosts docker.RegistryHosts, auth *SessionAuthenticator) remotes.Resolver {
	return docker.NewResolver(docker.ResolverOptions{
		Hosts: hostsWithCredentials(hosts, auth),
	})
}

func hostsWithCredentials(hosts docker.RegistryHosts, auth *SessionAuthenticator) docker.RegistryHosts {
	if hosts == nil {
		return nil
	}
	cache := map[string][]docker.RegistryHost{}
	var mu sync.Mutex
	var g flightcontrol.Group
	return func(domain string) ([]docker.RegistryHost, error) {
		v, err := g.Do(context.TODO(), domain, func(ctx context.Context) (interface{}, error) {
			mu.Lock()
			v, ok := cache[domain]
			mu.Unlock()
			if ok {
				return v, nil
			}
			res, err := hosts(domain)
			if err != nil {
				return nil, err
			}
			if len(res) == 0 {
				return nil, nil
			}

			a := docker.NewDockerAuthorizer(
				docker.WithAuthClient(res[0].Client),
				docker.WithAuthCreds(auth.credentials),
			)
			for i := range res {
				res[i].Authorizer = a
			}
			mu.Lock()
			cache[domain] = res
			mu.Unlock()
			return res, nil
		})
		if err != nil {
			return nil, err
		}
		if v == nil {
			return nil, nil
		}
		return v.([]docker.RegistryHost), nil
	}
}

func newDefaultClient() *http.Client {
	return &http.Client{
		Transport: tracing.NewTransport(newDefaultTransport()),
	}
}

// newDefaultTransport is for pull or push client
//
// NOTE: For push, there must disable http2 for https because the flow control
// will limit data transfer. The net/http package doesn't provide http2 tunable
// settings which limits push performance.
//
// REF: https://github.com/golang/go/issues/14077
func newDefaultTransport() *http.Transport {
	return &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
			DualStack: true,
		}).DialContext,
		MaxIdleConns:          10,
		IdleConnTimeout:       30 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 5 * time.Second,
		DisableKeepAlives:     true,
		TLSNextProto:          make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
	}
}
<|endoftext|>