<commit_before>// Copyright 2018 Google LLC
//
// 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 functions

import (
	"github.com/google/cel-go/common/operators"
	"github.com/google/cel-go/common/overloads"
	"github.com/google/cel-go/common/types"
	"github.com/google/cel-go/common/types/ref"
	"github.com/google/cel-go/common/types/traits"
)

// StandardOverloads returns the definitions of the built-in overloads.
func StandardOverloads() []*Overload {
	return []*Overload{
		// Logical not (!a)
		{
			Operator:     operators.LogicalNot,
			OperandTrait: traits.NegatorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Negater).Negate()
			}},
		// Logical and (a && b)
		{
			Operator: operators.LogicalAnd,
			Binary:   logicalAnd},
		// Logical or (a || b)
		{
			Operator: operators.LogicalOr,
			Binary:   logicalOr},
		// Conditional operator (a ? b : c)
		{
			Operator: operators.Conditional,
			Function: conditional},

		// Equality overloads
		{Operator: operators.Equals,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.Equal(rhs)
			}},

		{Operator: operators.NotEquals,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				eq := lhs.Equal(rhs)
				if types.IsBool(eq) {
					return !eq.(types.Bool)
				}
				return eq
			}},

		// Less than operator
		{Operator: operators.Less,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntNegOne {
					return types.True
				}
				if cmp == types.IntOne || cmp == types.IntZero {
					return types.False
				}
				return cmp
			}},

		// Less than or equal operator
		{Operator: operators.LessEquals,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntNegOne || cmp == types.IntZero {
					return types.True
				}
				if cmp == types.IntOne {
					return types.False
				}
				return cmp
			}},

		// Greater than operator
		{Operator: operators.Greater,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntOne {
					return types.True
				}
				if cmp == types.IntNegOne || cmp == types.IntZero {
					return types.False
				}
				return cmp
			}},

		// Greater than equal operators
		{Operator: operators.GreaterEquals,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntOne || cmp == types.IntZero {
					return types.True
				}
				if cmp == types.IntNegOne {
					return types.False
				}
				return cmp
			}},

		// TODO: Verify overflow, NaN, underflow cases for numeric values.

		// Add operator
		{Operator: operators.Add,
			OperandTrait: traits.AdderType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Adder).Add(rhs)
			}},

		// Subtract operators
		{Operator: operators.Subtract,
			OperandTrait: traits.SubtractorType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Subtractor).Subtract(rhs)
			}},

		// Multiply operator
		{Operator: operators.Multiply,
			OperandTrait: traits.MultiplierType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Multiplier).Multiply(rhs)
			}},

		// Divide operator
		{Operator: operators.Divide,
			OperandTrait: traits.DividerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Divider).Divide(rhs)
			}},

		// Modulo operator
		{Operator: operators.Modulo,
			OperandTrait: traits.ModderType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Modder).Modulo(rhs)
			}},

		// Negate operator
		{Operator: operators.Negate,
			OperandTrait: traits.NegatorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Negater).Negate()
			}},

		// Index operator
		{Operator: operators.Index,
			OperandTrait: traits.IndexerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Indexer).Get(rhs)
			}},

		// Size function
		{Operator: overloads.Size,
			OperandTrait: traits.SizerType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Sizer).Size()
			}},

		// In operator
		{Operator: operators.In,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				if rhs.Type().HasTrait(traits.ContainerType) {
					return rhs.(traits.Container).Contains(lhs)
				}
				return types.NewErr("no such overload")
			}},

		// Matches function
		{Operator: overloads.MatchString,
			OperandTrait: traits.MatcherType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Matcher).Match(rhs)
			}},

		// Type conversion functions
		// TODO: verify type conversion safety of numeric values.

		// Int conversions.
		{Operator: overloads.TypeConvertInt,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.IntType)
			}},

		// Uint conversions.
		{Operator: overloads.TypeConvertUint,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.UintType)
			}},

		// Double conversions.
		{Operator: overloads.TypeConvertDouble,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.DoubleType)
			}},

		// Bool conversions.
		{Operator: overloads.TypeConvertBool,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.BoolType)
			}},

		// Bytes conversions.
		{Operator: overloads.TypeConvertBytes,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.BytesType)
			}},

		// String conversions.
		{Operator: overloads.TypeConvertString,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.StringType)
			}},

		// Timestamp conversions.
		{Operator: overloads.TypeConvertTimestamp,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.TimestampType)
			}},

		// Duration conversions.
		{Operator: overloads.TypeConvertDuration,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.DurationType)
			}},

		// Type operations.
		{Operator: overloads.TypeConvertType,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.TypeType)
			}},

		{Operator: overloads.Iterator,
			OperandTrait: traits.IterableType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Iterable).Iterator()
			}},

		{Operator: overloads.HasNext,
			OperandTrait: traits.IteratorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Iterator).HasNext()
			}},

		{Operator: overloads.Next,
			OperandTrait: traits.IteratorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Iterator).Next()
			}},
	}

}

func logicalAnd(lhs ref.Value, rhs ref.Value) ref.Value {
	lhsIsBool := types.Bool(types.IsBool(lhs))
	rhsIsBool := types.Bool(types.IsBool(rhs))
	// both are boolean use natural logic.
	if lhsIsBool && rhsIsBool {
		return lhs.(types.Bool) && rhs.(types.Bool)
	}
	// one or the other is boolean and false, return false.
	if lhsIsBool && !lhs.(types.Bool) ||
		rhsIsBool && !rhs.(types.Bool) {
		return types.False
	}

	if types.IsUnknown(lhs) {
		return lhs
	}

	if types.IsUnknown(rhs) {
		return rhs
	}

	// if the left-hand side is non-boolean return it as the error.
	if !lhsIsBool {
		return types.NewErr("Got '%v', expected argument of type 'bool'", lhs)
	}
	return types.NewErr("Got '%v', expected argument of type 'bool'", rhs)
}

func logicalOr(lhs ref.Value, rhs ref.Value) ref.Value {
	lhsIsBool := types.Bool(types.IsBool(lhs.Type()))
	rhsIsBool := types.Bool(types.IsBool(rhs.Type()))
	// both are boolean, use natural logic.
	if lhsIsBool && rhsIsBool {
		return lhs.(types.Bool) || rhs.(types.Bool)
	}
	// one or the other is boolean and true, return true
	if lhsIsBool && lhs.(types.Bool) ||
		rhsIsBool && rhs.(types.Bool) {
		return types.True
	}

	if types.IsUnknown(lhs) {
		return lhs
	}

	if types.IsUnknown(rhs) {
		return rhs
	}

	// if the left-hand side is non-boolean return it as the error.
	if !lhsIsBool {
		return types.NewErr("Got '%v', expected argument of type 'bool'", lhs)
	}
	return types.NewErr("Got '%v', expected argument of type 'bool'", rhs)
}

func conditional(values ...ref.Value) ref.Value {
	if len(values) != 3 {
		return types.NewErr("no such overload")
	}
	cond := values[0]
	condType := cond.Type()
	if types.IsBool(condType) {
		if cond == types.True {
			return values[1]
		}
		return values[2]
	} else if types.IsError(condType) || types.IsUnknown(condType) {
		return cond
	} else {
		return types.NewErr("no such overload")
	}
}
<commit_msg>use correct constant for defining matches() std fn (#105)<commit_after>// Copyright 2018 Google LLC
//
// 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 functions

import (
	"github.com/google/cel-go/common/operators"
	"github.com/google/cel-go/common/overloads"
	"github.com/google/cel-go/common/types"
	"github.com/google/cel-go/common/types/ref"
	"github.com/google/cel-go/common/types/traits"
)

// StandardOverloads returns the definitions of the built-in overloads.
func StandardOverloads() []*Overload {
	return []*Overload{
		// Logical not (!a)
		{
			Operator:     operators.LogicalNot,
			OperandTrait: traits.NegatorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Negater).Negate()
			}},
		// Logical and (a && b)
		{
			Operator: operators.LogicalAnd,
			Binary:   logicalAnd},
		// Logical or (a || b)
		{
			Operator: operators.LogicalOr,
			Binary:   logicalOr},
		// Conditional operator (a ? b : c)
		{
			Operator: operators.Conditional,
			Function: conditional},

		// Equality overloads
		{Operator: operators.Equals,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.Equal(rhs)
			}},

		{Operator: operators.NotEquals,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				eq := lhs.Equal(rhs)
				if types.IsBool(eq) {
					return !eq.(types.Bool)
				}
				return eq
			}},

		// Less than operator
		{Operator: operators.Less,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntNegOne {
					return types.True
				}
				if cmp == types.IntOne || cmp == types.IntZero {
					return types.False
				}
				return cmp
			}},

		// Less than or equal operator
		{Operator: operators.LessEquals,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntNegOne || cmp == types.IntZero {
					return types.True
				}
				if cmp == types.IntOne {
					return types.False
				}
				return cmp
			}},

		// Greater than operator
		{Operator: operators.Greater,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntOne {
					return types.True
				}
				if cmp == types.IntNegOne || cmp == types.IntZero {
					return types.False
				}
				return cmp
			}},

		// Greater than equal operators
		{Operator: operators.GreaterEquals,
			OperandTrait: traits.ComparerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				cmp := lhs.(traits.Comparer).Compare(rhs)
				if cmp == types.IntOne || cmp == types.IntZero {
					return types.True
				}
				if cmp == types.IntNegOne {
					return types.False
				}
				return cmp
			}},

		// TODO: Verify overflow, NaN, underflow cases for numeric values.

		// Add operator
		{Operator: operators.Add,
			OperandTrait: traits.AdderType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Adder).Add(rhs)
			}},

		// Subtract operators
		{Operator: operators.Subtract,
			OperandTrait: traits.SubtractorType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Subtractor).Subtract(rhs)
			}},

		// Multiply operator
		{Operator: operators.Multiply,
			OperandTrait: traits.MultiplierType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Multiplier).Multiply(rhs)
			}},

		// Divide operator
		{Operator: operators.Divide,
			OperandTrait: traits.DividerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Divider).Divide(rhs)
			}},

		// Modulo operator
		{Operator: operators.Modulo,
			OperandTrait: traits.ModderType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Modder).Modulo(rhs)
			}},

		// Negate operator
		{Operator: operators.Negate,
			OperandTrait: traits.NegatorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Negater).Negate()
			}},

		// Index operator
		{Operator: operators.Index,
			OperandTrait: traits.IndexerType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Indexer).Get(rhs)
			}},

		// Size function
		{Operator: overloads.Size,
			OperandTrait: traits.SizerType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Sizer).Size()
			}},

		// In operator
		{Operator: operators.In,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				if rhs.Type().HasTrait(traits.ContainerType) {
					return rhs.(traits.Container).Contains(lhs)
				}
				return types.NewErr("no such overload")
			}},

		// Matches function
		{Operator: overloads.Matches,
			OperandTrait: traits.MatcherType,
			Binary: func(lhs ref.Value, rhs ref.Value) ref.Value {
				return lhs.(traits.Matcher).Match(rhs)
			}},

		// Type conversion functions
		// TODO: verify type conversion safety of numeric values.

		// Int conversions.
		{Operator: overloads.TypeConvertInt,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.IntType)
			}},

		// Uint conversions.
		{Operator: overloads.TypeConvertUint,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.UintType)
			}},

		// Double conversions.
		{Operator: overloads.TypeConvertDouble,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.DoubleType)
			}},

		// Bool conversions.
		{Operator: overloads.TypeConvertBool,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.BoolType)
			}},

		// Bytes conversions.
		{Operator: overloads.TypeConvertBytes,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.BytesType)
			}},

		// String conversions.
		{Operator: overloads.TypeConvertString,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.StringType)
			}},

		// Timestamp conversions.
		{Operator: overloads.TypeConvertTimestamp,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.TimestampType)
			}},

		// Duration conversions.
		{Operator: overloads.TypeConvertDuration,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.DurationType)
			}},

		// Type operations.
		{Operator: overloads.TypeConvertType,
			Unary: func(value ref.Value) ref.Value {
				return value.ConvertToType(types.TypeType)
			}},

		{Operator: overloads.Iterator,
			OperandTrait: traits.IterableType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Iterable).Iterator()
			}},

		{Operator: overloads.HasNext,
			OperandTrait: traits.IteratorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Iterator).HasNext()
			}},

		{Operator: overloads.Next,
			OperandTrait: traits.IteratorType,
			Unary: func(value ref.Value) ref.Value {
				return value.(traits.Iterator).Next()
			}},
	}

}

func logicalAnd(lhs ref.Value, rhs ref.Value) ref.Value {
	lhsIsBool := types.Bool(types.IsBool(lhs))
	rhsIsBool := types.Bool(types.IsBool(rhs))
	// both are boolean use natural logic.
	if lhsIsBool && rhsIsBool {
		return lhs.(types.Bool) && rhs.(types.Bool)
	}
	// one or the other is boolean and false, return false.
	if lhsIsBool && !lhs.(types.Bool) ||
		rhsIsBool && !rhs.(types.Bool) {
		return types.False
	}

	if types.IsUnknown(lhs) {
		return lhs
	}

	if types.IsUnknown(rhs) {
		return rhs
	}

	// if the left-hand side is non-boolean return it as the error.
	if !lhsIsBool {
		return types.NewErr("Got '%v', expected argument of type 'bool'", lhs)
	}
	return types.NewErr("Got '%v', expected argument of type 'bool'", rhs)
}

func logicalOr(lhs ref.Value, rhs ref.Value) ref.Value {
	lhsIsBool := types.Bool(types.IsBool(lhs.Type()))
	rhsIsBool := types.Bool(types.IsBool(rhs.Type()))
	// both are boolean, use natural logic.
	if lhsIsBool && rhsIsBool {
		return lhs.(types.Bool) || rhs.(types.Bool)
	}
	// one or the other is boolean and true, return true
	if lhsIsBool && lhs.(types.Bool) ||
		rhsIsBool && rhs.(types.Bool) {
		return types.True
	}

	if types.IsUnknown(lhs) {
		return lhs
	}

	if types.IsUnknown(rhs) {
		return rhs
	}

	// if the left-hand side is non-boolean return it as the error.
	if !lhsIsBool {
		return types.NewErr("Got '%v', expected argument of type 'bool'", lhs)
	}
	return types.NewErr("Got '%v', expected argument of type 'bool'", rhs)
}

func conditional(values ...ref.Value) ref.Value {
	if len(values) != 3 {
		return types.NewErr("no such overload")
	}
	cond := values[0]
	condType := cond.Type()
	if types.IsBool(condType) {
		if cond == types.True {
			return values[1]
		}
		return values[2]
	} else if types.IsError(condType) || types.IsUnknown(condType) {
		return cond
	} else {
		return types.NewErr("no such overload")
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Copyright 2012 The g Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// IsTerminal and Restore are based on functions in
// golang.org/x/crypto/ssh/terminal/util.go,
// and DisableFlags is derived from MakeRaw().

package terminal

import (
	"golang.org/x/sys/unix"
	"unsafe"
)

const (
	termInFilename  = "/dev/tty"
	termOutFilename = "/dev/tty"
)

// These constants are declared here, rather than importing
// them from the syscall package as some syscall/unix packages, even
// on linux, for example gccgo, do not declare them.
const ioctlReadTermios = 0x5401  // unix.TCGETS
const ioctlWriteTermios = 0x5402 // unix.TCSETS

// State contains the state of a terminal.
type State struct {
	termios unix.Termios
}

type FileDescriptor interface {
	Fd() uintptr
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd FileDescriptor) bool {
	var termios unix.Termios
	_, _, err := unix.Syscall6(unix.SYS_IOCTL, fd.Fd(), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
	return err == 0
}

type Flag uint32

const (
	LineFlag   Flag = unix.ICANON
	EchoFlag   Flag = unix.ECHO
	SignalFlag Flag = unix.ISIG
)

// DisableFlags disables the terminal flags specified in the function
// argument 'flags', and returns the previous state of the terminal
// so that it can be restored.
func DisableFlags(fd FileDescriptor, flags Flag) (*State, error) {
	var oldState State
	if _, _, err := unix.Syscall6(unix.SYS_IOCTL, fd.Fd(), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
		return nil, err
	}

	newState := oldState.termios
	newState.Lflag &^= uint32(flags)
	if _, _, err := unix.Syscall6(unix.SYS_IOCTL, fd.Fd(), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
		return nil, err
	}

	return &oldState, nil
}

// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd FileDescriptor, state *State) error {
	_, _, err := unix.Syscall6(unix.SYS_IOCTL, fd.Fd(), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
	return err
}
<commit_msg>ioutil/terminal: linux: use golang.org/x/sys/unix ioctl functions, avoid importing "unsafe"<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Copyright 2012 The g Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// IsTerminal and Restore are based on functions in
// golang.org/x/crypto/ssh/terminal/util.go,
// and DisableFlags is derived from MakeRaw().

package terminal

import (
	"golang.org/x/sys/unix"
)

const (
	termInFilename  = "/dev/tty"
	termOutFilename = "/dev/tty"
)

// State contains the state of a terminal.
type State struct {
	termios unix.Termios
}

type FileDescriptor interface {
	Fd() uintptr
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(f FileDescriptor) bool {
	_, err := unix.IoctlGetTermios(int(f.Fd()), unix.TCGETS)
	return err == nil
}

type Flag uint32

const (
	LineFlag   Flag = unix.ICANON
	EchoFlag   Flag = unix.ECHO
	SignalFlag Flag = unix.ISIG
)

// DisableFlags disables the terminal flags specified in the function
// argument 'flags', and returns the previous state of the terminal
// so that it can be restored.
func DisableFlags(f FileDescriptor, flags Flag) (*State, error) {
	var old State

	fd := int(f.Fd())
	t, err := unix.IoctlGetTermios(fd, unix.TCGETS)
	if err != nil {
		return nil, err
	}
	old.termios = *t

	t.Lflag &^= uint32(flags)
	err = unix.IoctlSetTermios(fd, unix.TCSETS, t)
	if err != nil {
		return nil, err
	}

	return &old, nil
}

// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(f FileDescriptor, state *State) error {
	return unix.IoctlSetTermios(int(f.Fd()), unix.TCSETS, &state.termios)
}
<|endoftext|>


<commit_before>// Copyright (c) Facebook, Inc. and its affiliates.
//
// 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 schema

import (
	"encoding/json"
	"fmt"
)

// based on this specification
// https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html-single/red_hat_security_data_api/index#cve_format

type CVEList []struct {
	CVE string `json:"cve"`
	// don't need the rest
}

type CVE struct {
	Name           string `json:"name,omitempty"`
	ThreatSeverity string `json:"threat_severity,omitempty"`
	PublicDate     string `json:"public_date,omitempty"`
	Bugzilla       *struct {
		Description string `json:"description,omitempty"`
		ID          string `json:"id,omitempty"`
		URL         string `json:"url,omitempty"`
	} `json:"bugzilla,omitempty"`
	CVSS *struct {
		BaseScore string `json:"cvss_base_score,omitempty"`
		Vector    string `json:"cvss_scoring_vector,omitempty"`
		Status    string `json:"status,omitempty"`
	} `json:"CVSS,omitempty"`
	CVSS3 *struct {
		BaseScore string `json:"cvss3_base_score,omitempty"`
		Vector    string `json:"cvss3_scoring_vector,omitempty"`
		Status    string `json:"status,omitempty"`
	} `json:"CVSS3,omitempty"`
	CWE             string   `json:"cwe,omitempty"`
	Details         []string `json:"details,omitempty"`
	Statement       string   `json:"statement,omitempty"`
	References      []string `json:"references,omitempty"`
	Acknowledgement string   `json:"acknowledgement,omitempty"`
	Mitigation      string   `json:"mitigation,omitempty"`
	UpstreamFix     string   `json:"upstream_fix,omitempty"`

	// redhat uses a single object instead of an array when there's a single instance of that entity
	// that's why we need to do it manually
	// the types of these are just helper types

	AffectedRelease AffectedReleases `json:"affected_release,omitempty"`
	PackageState    PackageStates    `json:"package_state,omitempty"`
}

type AffectedRelease struct {
	ProductName string `json:"product_name,omitempty"`
	ReleaseDate string `json:"release_date,omitempty"`
	Advisory    string `json:"advisory,omitempty"`
	Package     string `json:"package,omitempty"`
	CPE         string `json:"cpe,omitempty"`
}

type PackageState struct {
	ProductName string `json:"product_name,omitempty"`
	FixState    string `json:"fix_state,omitempty"`
	PackageName string `json:"package_name,omitempty"`
	CPE         string `json:"cpe,omitempty"`
}

// // functions bellow are used to "fix" redhat feed
// // for some parts of the struct, they can either send an object X, or an array of X's when there's multiple of those
// // I try to first decode it as an array. If that fails, try to decode it as an single entity.

// these implement the UnmarshalJSON function, which gets called when we do unmarshal or decode

type AffectedReleases []*AffectedRelease

func (ars *AffectedReleases) UnmarshalJSON(b []byte) error {
	// try to parse it as an array
	var array []*AffectedRelease
	if err := json.Unmarshal(b, &array); err == nil {
		*ars = array
		return nil
	}

	// try to parse it as a single object
	var object AffectedRelease
	if err := json.Unmarshal(b, &object); err == nil {
		*ars = []*AffectedRelease{&object}
		return nil
	}

	return fmt.Errorf("unable to decode affected release as an array nor as a single object")
}

type PackageStates []*PackageState

func (pss *PackageStates) UnmarshalJSON(b []byte) error {
	// try to parse it as an array
	var array []*PackageState
	if err := json.Unmarshal(b, &array); err == nil {
		*pss = array
		return nil
	}

	// try to parse it as a single object
	var object PackageState
	if err := json.Unmarshal(b, &object); err == nil {
		*pss = []*PackageState{&object}
		return nil
	}

	return fmt.Errorf("unable to decode package state as an array nor as a single object")
}
<commit_msg>[redhat] Fix unmarshalling of the mitigation field<commit_after>// Copyright (c) Facebook, Inc. and its affiliates.
//
// 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 schema

import (
	"encoding/json"
	"fmt"
)

// based on this specification
// https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html-single/red_hat_security_data_api/index#cve_format

type CVEList []struct {
	CVE string `json:"cve"`
	// don't need the rest
}

type CVE struct {
	Name           string `json:"name,omitempty"`
	ThreatSeverity string `json:"threat_severity,omitempty"`
	PublicDate     string `json:"public_date,omitempty"`
	Bugzilla       *struct {
		Description string `json:"description,omitempty"`
		ID          string `json:"id,omitempty"`
		URL         string `json:"url,omitempty"`
	} `json:"bugzilla,omitempty"`
	CVSS *struct {
		BaseScore string `json:"cvss_base_score,omitempty"`
		Vector    string `json:"cvss_scoring_vector,omitempty"`
		Status    string `json:"status,omitempty"`
	} `json:"CVSS,omitempty"`
	CVSS3 *struct {
		BaseScore string `json:"cvss3_base_score,omitempty"`
		Vector    string `json:"cvss3_scoring_vector,omitempty"`
		Status    string `json:"status,omitempty"`
	} `json:"CVSS3,omitempty"`
	CWE             string   `json:"cwe,omitempty"`
	Details         []string `json:"details,omitempty"`
	Statement       string   `json:"statement,omitempty"`
	References      []string `json:"references,omitempty"`
	Acknowledgement string   `json:"acknowledgement,omitempty"`
	Mitigation      *struct {
		Value string `json:"value"`
		Lang  string `json:"lang"`
	} `json:"mitigation,omitempty"`
	UpstreamFix string `json:"upstream_fix,omitempty"`

	// redhat uses a single object instead of an array when there's a single instance of that entity
	// that's why we need to do it manually
	// the types of these are just helper types

	AffectedRelease AffectedReleases `json:"affected_release,omitempty"`
	PackageState    PackageStates    `json:"package_state,omitempty"`
}

type AffectedRelease struct {
	ProductName string `json:"product_name,omitempty"`
	ReleaseDate string `json:"release_date,omitempty"`
	Advisory    string `json:"advisory,omitempty"`
	Package     string `json:"package,omitempty"`
	CPE         string `json:"cpe,omitempty"`
}

type PackageState struct {
	ProductName string `json:"product_name,omitempty"`
	FixState    string `json:"fix_state,omitempty"`
	PackageName string `json:"package_name,omitempty"`
	CPE         string `json:"cpe,omitempty"`
}

// // functions bellow are used to "fix" redhat feed
// // for some parts of the struct, they can either send an object X, or an array of X's when there's multiple of those
// // I try to first decode it as an array. If that fails, try to decode it as an single entity.

// these implement the UnmarshalJSON function, which gets called when we do unmarshal or decode

type AffectedReleases []*AffectedRelease

func (ars *AffectedReleases) UnmarshalJSON(b []byte) error {
	// try to parse it as an array
	var array []*AffectedRelease
	if err := json.Unmarshal(b, &array); err == nil {
		*ars = array
		return nil
	}

	// try to parse it as a single object
	var object AffectedRelease
	if err := json.Unmarshal(b, &object); err == nil {
		*ars = []*AffectedRelease{&object}
		return nil
	}

	return fmt.Errorf("unable to decode affected release as an array nor as a single object")
}

type PackageStates []*PackageState

func (pss *PackageStates) UnmarshalJSON(b []byte) error {
	// try to parse it as an array
	var array []*PackageState
	if err := json.Unmarshal(b, &array); err == nil {
		*pss = array
		return nil
	}

	// try to parse it as a single object
	var object PackageState
	if err := json.Unmarshal(b, &object); err == nil {
		*pss = []*PackageState{&object}
		return nil
	}

	return fmt.Errorf("unable to decode package state as an array nor as a single object")
}
<|endoftext|>


<commit_before>// Basic commands for Cookoo.
package cookoo

import (
	"log"
)

// Print a message to the log.
//
// Params:
// - msg: The message to print
func LogMessage(cxt Context, params *Params) (interface{}, Interrupt) {
	msg := params.Get("msg", "tick")
	log.Print(msg)
	return nil, nil
}

// Add all of the param name/value pairs into the context.
//
// Params:
// - Any params will be added into the context.
func AddToContext(cxt Context, params *Params) (interface{}, Interrupt) {
	p := params.AsMap()
	for k, v := range p {
		cxt.Add(k, v)
	}
	return true, nil
}

// Forward to the given route name.
//
// To prevent possible loops or problematic re-routes, use ignoreRoutes.
//
// Params:
// - route: The route to forward to. This is required.
// - ignoreRoutes: Route names that should be ignored (generate recoverable errors).
func ForwardTo(cxt Context, params *Params) (interface{}, Interrupt) {
	ok, _ := params.Requires("route")

	if !ok {
		return nil, &FatalError{"Expected a 'route'"}
	}

	route := params.Get("route", "default").(string)

	stoplist := params.Get("ignoreRoutes", []string{}).([]string)
	if len(stoplist) > 0 {
		for _, stop := range stoplist {
			if stop == route {
				return nil, &RecoverableError{"Ignored route " + route}
			}
		}
	}

	return nil, &Reroute{route}
}
<commit_msg>Switched to built-in logger.<commit_after>// Basic commands for Cookoo.
package cookoo

import (
)

// Print a message to the log.
//
// Params:
// - msg: The message to print
// - level: The log level (default: "info")
func LogMessage(cxt Context, params *Params) (interface{}, Interrupt) {
	msg := params.Get("msg", "tick")
	level := params.Get("level", "info").(string)
	cxt.Log(level, msg)
	return nil, nil
}

// Add all of the param name/value pairs into the context.
//
// Params:
// - Any params will be added into the context.
func AddToContext(cxt Context, params *Params) (interface{}, Interrupt) {
	p := params.AsMap()
	for k, v := range p {
		cxt.Add(k, v)
	}
	return true, nil
}

// Forward to the given route name.
//
// To prevent possible loops or problematic re-routes, use ignoreRoutes.
//
// Params:
// - route: The route to forward to. This is required.
// - ignoreRoutes: Route names that should be ignored (generate recoverable errors).
func ForwardTo(cxt Context, params *Params) (interface{}, Interrupt) {
	ok, _ := params.Requires("route")

	if !ok {
		return nil, &FatalError{"Expected a 'route'"}
	}

	route := params.Get("route", "default").(string)

	stoplist := params.Get("ignoreRoutes", []string{}).([]string)
	if len(stoplist) > 0 {
		for _, stop := range stoplist {
			if stop == route {
				return nil, &RecoverableError{"Ignored route " + route}
			}
		}
	}

	return nil, &Reroute{route}
}
<|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package lxc

import (
	"bytes"
	"github.com/globocom/commandmocker"
	"github.com/globocom/config"
	etesting "github.com/globocom/tsuru/exec/testing"
	fstesting "github.com/globocom/tsuru/fs/testing"
	"github.com/globocom/tsuru/provision"
	rtesting "github.com/globocom/tsuru/router/testing"
	"github.com/globocom/tsuru/testing"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"launchpad.net/gocheck"
	"net"
	"os"
	"runtime"
	"time"
)

func (s *S) TestShouldBeRegistered(c *gocheck.C) {
	p, err := provision.Get("lxc")
	c.Assert(err, gocheck.IsNil)
	c.Assert(p, gocheck.FitsTypeOf, &LXCProvisioner{})
}

func (s *S) TestProvisionerProvision(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	ln, err := net.Listen("tcp", ":2222")
	c.Assert(err, gocheck.IsNil)
	defer ln.Close()
	formulasPath := "/home/ubuntu/formulas"
	config.Set("lxc:formulas-path", formulasPath)
	config.Set("lxc:ip-timeout", 5)
	config.Set("lxc:ssh-port", 2222)
	config.Set("lxc:authorized-key-path", "somepath")
	rfs := &fstesting.RecordingFs{}
	fsystem = rfs
	defer func() {
		fsystem = nil
	}()
	f, _ := os.Open("testdata/dnsmasq.leases")
	data, err := ioutil.ReadAll(f)
	c.Assert(err, gocheck.IsNil)
	file, err := rfs.Create("/var/lib/misc/dnsmasq.leases")
	c.Assert(err, gocheck.IsNil)
	_, err = file.Write(data)
	c.Assert(err, gocheck.IsNil)
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	defer p.collection().Remove(bson.M{"name": "myapp"})
	c.Assert(p.Provision(app), gocheck.IsNil)
	ok := make(chan bool, 1)
	go func() {
		for {
			coll := s.conn.Collection(s.collName)
			ct, err := coll.Find(bson.M{"name": "myapp", "status": provision.StatusStarted}).Count()
			if err != nil {
				c.Fatal(err)
			}
			if ct > 0 {
				ok <- true
				return
			}
			runtime.Gosched()
		}
	}()
	select {
	case <-ok:
	case <-time.After(10e9):
		c.Fatal("Timed out waiting for the container to be provisioned (10 seconds)")
	}
	args := []string{"lxc-create", "-t", "ubuntu-cloud", "-n", "myapp", "--", "-S", "somepath"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	args = []string{"lxc-start", "--daemon", "-n", "myapp"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	r, err := p.router()
	c.Assert(err, gocheck.IsNil)
	fk := r.(*rtesting.FakeRouter)
	c.Assert(fk.HasRoute("myapp"), gocheck.Equals, true)
	var unit provision.Unit
	err = s.conn.Collection(s.collName).Find(bson.M{"name": "myapp"}).One(&unit)
	c.Assert(err, gocheck.IsNil)
	c.Assert(unit.Ip, gocheck.Equals, "127.0.0.1")
}

func (s *S) TestProvisionerRestart(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	var p LXCProvisioner
	app := testing.NewFakeApp("almah", "static", 1)
	err := p.Restart(app)
	c.Assert(err, gocheck.IsNil)
	args := []string{"-l", "ubuntu", "-q", "-o", "StrictHostKeyChecking no", "10.10.10.1", "/var/lib/tsuru/hooks/restart"}
	c.Assert(fexec.ExecutedCmd("ssh", args), gocheck.Equals, true)
}

func (s *S) TestDeploy(c *gocheck.C) {
	tmpdir, err := commandmocker.Add("ssh", "")
	c.Assert(err, gocheck.IsNil)
	defer commandmocker.Remove(tmpdir)
	config.Set("git:unit-repo", "test/dir")
	config.Set("git:host", "gandalf.com")
	defer func() {
		config.Unset("git:unit-repo")
		config.Unset("git:host")
	}()
	app := testing.NewFakeApp("cribcaged", "python", 1)
	w := &bytes.Buffer{}
	p := LXCProvisioner{}
	err = p.Deploy(app, w)
	c.Assert(err, gocheck.IsNil)
}

func (s *S) TestProvisionerDestroy(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 1)
	u := provision.Unit{
		Name:   "myapp",
		Status: provision.StatusStarted,
	}
	err := s.conn.Collection(s.collName).Insert(&u)
	c.Assert(err, gocheck.IsNil)
	c.Assert(p.Destroy(app), gocheck.IsNil)
	ok := make(chan bool, 1)
	go func() {
		for {
			coll := s.conn.Collection(s.collName)
			ct, err := coll.Find(bson.M{"name": "myapp", "status": provision.StatusStarted}).Count()
			if err != nil {
				c.Fatal(err)
			}
			if ct == 0 {
				ok <- true
				return
			}
			runtime.Gosched()
		}
	}()
	select {
	case <-ok:
	case <-time.After(10e9):
		c.Fatal("Timed out waiting for the container to be provisioned (10 seconds)")
	}
	c.Assert(err, gocheck.IsNil)
	args := []string{"lxc-stop", "-n", "myapp"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	args = []string{"lxc-destroy", "-n", "myapp"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	length, err := p.collection().Find(bson.M{"name": "myapp"}).Count()
	c.Assert(err, gocheck.IsNil)
	c.Assert(length, gocheck.Equals, 0)
}

func (s *S) TestProvisionerAddr(c *gocheck.C) {
	config.Set("router", "fake")
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 1)
	r, err := p.router()
	c.Assert(err, gocheck.IsNil)
	r.AddRoute("myapp", "http://10.10.10.10")
	a, err := r.Addr(app.GetName())
	c.Assert(err, gocheck.IsNil)
	addr, err := p.Addr(app)
	c.Assert(err, gocheck.IsNil)
	c.Assert(addr, gocheck.Equals, a)
}

func (s *S) TestProvisionerAddUnits(c *gocheck.C) {
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	units, err := p.AddUnits(app, 2)
	c.Assert(err, gocheck.IsNil)
	c.Assert(units, gocheck.DeepEquals, []provision.Unit{})
}

func (s *S) TestProvisionerRemoveUnit(c *gocheck.C) {
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	err := p.RemoveUnit(app, "")
	c.Assert(err, gocheck.IsNil)
}

func (s *S) TestProvisionerInstallDepsExecutesHook(c *gocheck.C) {
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	w := &bytes.Buffer{}
	err := p.InstallDeps(app, w)
	c.Assert(err, gocheck.IsNil)
	expected := []string{"ran /var/lib/tsuru/hooks/dependencies"}
	c.Assert(app.Commands, gocheck.DeepEquals, expected)
}

func (s *S) TestProvisionerExecuteCommand(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	var p LXCProvisioner
	var buf bytes.Buffer
	app := testing.NewFakeApp("almah", "static", 2)
	err := p.ExecuteCommand(&buf, &buf, app, "ls", "-lh")
	c.Assert(err, gocheck.IsNil)
	args := []string{"-l", "ubuntu", "-q", "-o", "StrictHostKeyChecking no", "10.10.10.1", "ls", "-lh"}
	c.Assert(fexec.ExecutedCmd("ssh", args), gocheck.Equals, true)
}

func (s *S) TestCollectStatus(c *gocheck.C) {
	var p LXCProvisioner
	expected := []provision.Unit{
		{
			Name:       "vm1",
			AppName:    "vm1",
			Type:       "django",
			Machine:    0,
			InstanceId: "vm1",
			Ip:         "10.10.10.9",
			Status:     provision.StatusStarted,
		},
		{
			Name:       "vm2",
			AppName:    "vm2",
			Type:       "gunicorn",
			Machine:    0,
			InstanceId: "vm2",
			Ip:         "10.10.10.10",
			Status:     provision.StatusInstalling,
		},
	}
	for _, u := range expected {
		err := p.collection().Insert(u)
		c.Assert(err, gocheck.IsNil)
	}
	units, err := p.CollectStatus()
	c.Assert(err, gocheck.IsNil)
	c.Assert(units, gocheck.DeepEquals, expected)
}

func (s *S) TestProvisionCollection(c *gocheck.C) {
	var p LXCProvisioner
	collection := p.collection()
	c.Assert(collection.Name, gocheck.Equals, s.collName)
}

func (s *S) TestProvisionInstall(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	p := LXCProvisioner{}
	err := p.install("10.10.10.10")
	c.Assert(err, gocheck.IsNil)
	cmd := "ssh"
	args := []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo /var/lib/tsuru/hooks/install",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
}

func (s *S) TestProvisionStart(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	p := LXCProvisioner{}
	err := p.start("10.10.10.10")
	c.Assert(err, gocheck.IsNil)
	cmd := "ssh"
	args := []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo /var/lib/tsuru/hooks/start",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
}

func (s *S) TestProvisionSetup(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	p := LXCProvisioner{}
	formulasPath := "/home/ubuntu/formulas"
	config.Set("lxc:formulas-path", formulasPath)
	err := p.setup("10.10.10.10", "static")
	c.Assert(err, gocheck.IsNil)
	cmd := "scp"
	args := []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-r",
		formulasPath + "/static/hooks",
		"ubuntu@10.10.10.10:/var/lib/tsuru",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
	cmd = "ssh"
	args = []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo mkdir -p /var/lib/tsuru/hooks",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
	args = []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo chown -R ubuntu /var/lib/tsuru/hooks",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
}
<commit_msg>provixion/lxc: fix tests<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package lxc

import (
	"bytes"
	"github.com/globocom/commandmocker"
	"github.com/globocom/config"
	etesting "github.com/globocom/tsuru/exec/testing"
	fstesting "github.com/globocom/tsuru/fs/testing"
	"github.com/globocom/tsuru/provision"
	rtesting "github.com/globocom/tsuru/router/testing"
	"github.com/globocom/tsuru/testing"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"launchpad.net/gocheck"
	"net"
	"os"
	"runtime"
	"time"
)

func (s *S) TestShouldBeRegistered(c *gocheck.C) {
	p, err := provision.Get("lxc")
	c.Assert(err, gocheck.IsNil)
	c.Assert(p, gocheck.FitsTypeOf, &LXCProvisioner{})
}

func (s *S) TestProvisionerProvision(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	ln, err := net.Listen("tcp", ":2222")
	c.Assert(err, gocheck.IsNil)
	defer ln.Close()
	formulasPath := "/home/ubuntu/formulas"
	config.Set("lxc:formulas-path", formulasPath)
	config.Set("lxc:ip-timeout", 5)
	config.Set("lxc:ssh-port", 2222)
	config.Set("lxc:authorized-key-path", "somepath")
	rfs := &fstesting.RecordingFs{}
	fsystem = rfs
	defer func() {
		fsystem = nil
	}()
	f, _ := os.Open("testdata/dnsmasq.leases")
	data, err := ioutil.ReadAll(f)
	c.Assert(err, gocheck.IsNil)
	file, err := rfs.Create("/var/lib/misc/dnsmasq.leases")
	c.Assert(err, gocheck.IsNil)
	_, err = file.Write(data)
	c.Assert(err, gocheck.IsNil)
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	defer p.collection().Remove(bson.M{"name": "myapp"})
	c.Assert(p.Provision(app), gocheck.IsNil)
	ok := make(chan bool, 1)
	go func() {
		for {
			coll := s.conn.Collection(s.collName)
			ct, err := coll.Find(bson.M{"name": "myapp", "status": provision.StatusStarted}).Count()
			if err != nil {
				c.Fatal(err)
			}
			if ct > 0 {
				ok <- true
				return
			}
			runtime.Gosched()
		}
	}()
	select {
	case <-ok:
	case <-time.After(10e9):
		c.Fatal("Timed out waiting for the container to be provisioned (10 seconds)")
	}
	args := []string{"lxc-create", "-t", "ubuntu-cloud", "-n", "myapp", "--", "-S", "somepath"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	args = []string{"lxc-start", "--daemon", "-n", "myapp"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	r, err := p.router()
	c.Assert(err, gocheck.IsNil)
	fk := r.(*rtesting.FakeRouter)
	c.Assert(fk.HasRoute("myapp"), gocheck.Equals, true)
	var unit provision.Unit
	err = s.conn.Collection(s.collName).Find(bson.M{"name": "myapp"}).One(&unit)
	c.Assert(err, gocheck.IsNil)
	c.Assert(unit.Ip, gocheck.Equals, "127.0.0.1")
}

func (s *S) TestProvisionerRestart(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	var p LXCProvisioner
	app := testing.NewFakeApp("almah", "static", 1)
	err := p.Restart(app)
	c.Assert(err, gocheck.IsNil)
	args := []string{"-l", "ubuntu", "-q", "-o", "StrictHostKeyChecking no", "10.10.10.1", "/var/lib/tsuru/hooks/restart"}
	c.Assert(fexec.ExecutedCmd("ssh", args), gocheck.Equals, true)
}

func (s *S) TestDeploy(c *gocheck.C) {
	tmpdir, err := commandmocker.Add("ssh", "")
	c.Assert(err, gocheck.IsNil)
	defer commandmocker.Remove(tmpdir)
	config.Set("git:unit-repo", "test/dir")
	config.Set("git:ro-host", "gandalf.com")
	defer func() {
		config.Unset("git:unit-repo")
		config.Unset("git:ro-host")
	}()
	app := testing.NewFakeApp("cribcaged", "python", 1)
	w := &bytes.Buffer{}
	p := LXCProvisioner{}
	err = p.Deploy(app, w)
	c.Assert(err, gocheck.IsNil)
}

func (s *S) TestProvisionerDestroy(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 1)
	u := provision.Unit{
		Name:   "myapp",
		Status: provision.StatusStarted,
	}
	err := s.conn.Collection(s.collName).Insert(&u)
	c.Assert(err, gocheck.IsNil)
	c.Assert(p.Destroy(app), gocheck.IsNil)
	ok := make(chan bool, 1)
	go func() {
		for {
			coll := s.conn.Collection(s.collName)
			ct, err := coll.Find(bson.M{"name": "myapp", "status": provision.StatusStarted}).Count()
			if err != nil {
				c.Fatal(err)
			}
			if ct == 0 {
				ok <- true
				return
			}
			runtime.Gosched()
		}
	}()
	select {
	case <-ok:
	case <-time.After(10e9):
		c.Fatal("Timed out waiting for the container to be provisioned (10 seconds)")
	}
	c.Assert(err, gocheck.IsNil)
	args := []string{"lxc-stop", "-n", "myapp"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	args = []string{"lxc-destroy", "-n", "myapp"}
	c.Assert(fexec.ExecutedCmd("sudo", args), gocheck.Equals, true)
	length, err := p.collection().Find(bson.M{"name": "myapp"}).Count()
	c.Assert(err, gocheck.IsNil)
	c.Assert(length, gocheck.Equals, 0)
}

func (s *S) TestProvisionerAddr(c *gocheck.C) {
	config.Set("router", "fake")
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 1)
	r, err := p.router()
	c.Assert(err, gocheck.IsNil)
	r.AddRoute("myapp", "http://10.10.10.10")
	a, err := r.Addr(app.GetName())
	c.Assert(err, gocheck.IsNil)
	addr, err := p.Addr(app)
	c.Assert(err, gocheck.IsNil)
	c.Assert(addr, gocheck.Equals, a)
}

func (s *S) TestProvisionerAddUnits(c *gocheck.C) {
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	units, err := p.AddUnits(app, 2)
	c.Assert(err, gocheck.IsNil)
	c.Assert(units, gocheck.DeepEquals, []provision.Unit{})
}

func (s *S) TestProvisionerRemoveUnit(c *gocheck.C) {
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	err := p.RemoveUnit(app, "")
	c.Assert(err, gocheck.IsNil)
}

func (s *S) TestProvisionerInstallDepsExecutesHook(c *gocheck.C) {
	var p LXCProvisioner
	app := testing.NewFakeApp("myapp", "python", 0)
	w := &bytes.Buffer{}
	err := p.InstallDeps(app, w)
	c.Assert(err, gocheck.IsNil)
	expected := []string{"ran /var/lib/tsuru/hooks/dependencies"}
	c.Assert(app.Commands, gocheck.DeepEquals, expected)
}

func (s *S) TestProvisionerExecuteCommand(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	var p LXCProvisioner
	var buf bytes.Buffer
	app := testing.NewFakeApp("almah", "static", 2)
	err := p.ExecuteCommand(&buf, &buf, app, "ls", "-lh")
	c.Assert(err, gocheck.IsNil)
	args := []string{"-l", "ubuntu", "-q", "-o", "StrictHostKeyChecking no", "10.10.10.1", "ls", "-lh"}
	c.Assert(fexec.ExecutedCmd("ssh", args), gocheck.Equals, true)
}

func (s *S) TestCollectStatus(c *gocheck.C) {
	var p LXCProvisioner
	expected := []provision.Unit{
		{
			Name:       "vm1",
			AppName:    "vm1",
			Type:       "django",
			Machine:    0,
			InstanceId: "vm1",
			Ip:         "10.10.10.9",
			Status:     provision.StatusStarted,
		},
		{
			Name:       "vm2",
			AppName:    "vm2",
			Type:       "gunicorn",
			Machine:    0,
			InstanceId: "vm2",
			Ip:         "10.10.10.10",
			Status:     provision.StatusInstalling,
		},
	}
	for _, u := range expected {
		err := p.collection().Insert(u)
		c.Assert(err, gocheck.IsNil)
	}
	units, err := p.CollectStatus()
	c.Assert(err, gocheck.IsNil)
	c.Assert(units, gocheck.DeepEquals, expected)
}

func (s *S) TestProvisionCollection(c *gocheck.C) {
	var p LXCProvisioner
	collection := p.collection()
	c.Assert(collection.Name, gocheck.Equals, s.collName)
}

func (s *S) TestProvisionInstall(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	p := LXCProvisioner{}
	err := p.install("10.10.10.10")
	c.Assert(err, gocheck.IsNil)
	cmd := "ssh"
	args := []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo /var/lib/tsuru/hooks/install",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
}

func (s *S) TestProvisionStart(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	p := LXCProvisioner{}
	err := p.start("10.10.10.10")
	c.Assert(err, gocheck.IsNil)
	cmd := "ssh"
	args := []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo /var/lib/tsuru/hooks/start",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
}

func (s *S) TestProvisionSetup(c *gocheck.C) {
	fexec := &etesting.FakeExecutor{}
	execut = fexec
	defer func() {
		execut = nil
	}()
	p := LXCProvisioner{}
	formulasPath := "/home/ubuntu/formulas"
	config.Set("lxc:formulas-path", formulasPath)
	err := p.setup("10.10.10.10", "static")
	c.Assert(err, gocheck.IsNil)
	cmd := "scp"
	args := []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-r",
		formulasPath + "/static/hooks",
		"ubuntu@10.10.10.10:/var/lib/tsuru",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
	cmd = "ssh"
	args = []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo mkdir -p /var/lib/tsuru/hooks",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
	args = []string{
		"-q",
		"-o",
		"StrictHostKeyChecking no",
		"-l",
		"ubuntu",
		"10.10.10.10",
		"sudo chown -R ubuntu /var/lib/tsuru/hooks",
	}
	c.Assert(fexec.ExecutedCmd(cmd, args), gocheck.Equals, true)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/mitchellh/go-homedir"
	"github.com/olekukonko/tablewriter"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1beta1"
	compute "google.golang.org/api/compute/v1"
)

var commands = []cli.Command{
	commandCache,
	commandProject,
	commandSSH,
	commandCurrentProject,
}

var commandCache = cli.Command{
	Name:  "cache",
	Usage: "",
	Description: `
`,
	Action: doCache,
}

var commandProject = cli.Command{
	Name:  "project",
	Usage: "",
	Description: `
`,
	Action: doProject,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name: "zsh-widget, z",
		},
	},
}

var commandSSH = cli.Command{
	Name:  "ssh",
	Usage: "",
	Description: `
`,
	Action: doSSH,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name: "zsh-widget, z",
		},
	},
}

var commandCurrentProject = cli.Command{
	Name:  "current",
	Usage: "",
	Description: `
`,
	Action: doCurrentProject,
}

var maxParallelAPICalls = 10

// JSON struct
// config
type configCore struct {
	Project string `json:"project"`
	Account string `json:"account"`
}
type configRoot struct {
	Core configCore `json:"core"`
}

// cache
type cache struct {
	CacheDir  string
	Instances []*compute.Instance
	Projects  []*cloudresourcemanager.Project
}

func loadCache() (*cache, error) {
	cachedir, err := homedir.Expand("~/.cache/geco/")
	if err != nil {
		return nil, err
	}
	c := cache{CacheDir: cachedir}
	_, err = os.Stat(c.CacheDir)
	if os.IsNotExist(err) {
		os.MkdirAll(c.CacheDir, 0700)
	}
	fileinfos, _ := ioutil.ReadDir(c.CacheDir)
	for _, fileinfo := range fileinfos {
		if fileinfo.IsDir() {
			continue
		}
		if fileinfo.Name() == "projects.json" {
			projectsJSON, err := ioutil.ReadFile(filepath.Join(c.CacheDir, fileinfo.Name()))
			if err != nil {
				return nil, err
			}
			err = json.Unmarshal(projectsJSON, &c.Projects)
			if err != nil {
				return nil, err
			}
		}
		if fileinfo.Name() == "instances.json" {
			instancesJSON, err := ioutil.ReadFile(filepath.Join(c.CacheDir, fileinfo.Name()))
			if err != nil {
				return nil, err
			}
			err = json.Unmarshal(instancesJSON, &c.Instances)
			if err != nil {
				return nil, err
			}
		}
	}

	return &c, nil
}

func saveCache(c *cache) error {
	projectsJSON, _ := json.Marshal(c.Projects)
	err := ioutil.WriteFile(filepath.Join(c.CacheDir, "projects.json"), projectsJSON, 0600)
	if err != nil {
		return err
	}
	instancesJSON, _ := json.Marshal(c.Instances)
	err = ioutil.WriteFile(filepath.Join(c.CacheDir, "instances.json"), instancesJSON, 0600)
	if err != nil {
		return err
	}
	return nil
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func doCache(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}

	c, err := loadCache()
	if err != nil {
		panic(err)
	}
	c.Projects = []*cloudresourcemanager.Project{}
	c.Instances = []*compute.Instance{}

	ctx := oauth2.NoContext
	scopes := []string{compute.ComputeReadonlyScope}
	client, err := google.DefaultClient(ctx, scopes...)
	if err != nil {
		panic(err)
	}

	// gcloud beta projects list
	log.Println("loading projects...")
	service, err := cloudresourcemanager.New(client)
	if err != nil {
		panic(err)
	}

	projectsListCall := service.Projects.List()
	for {
		res, err := projectsListCall.Do()

		if err != nil {
			panic(err)
		}

		c.Projects = append(c.Projects, res.Projects...)

		if res.NextPageToken != "" {
			log.Printf("loading more projects with nextPageToken ...")
			projectsListCall.PageToken(res.NextPageToken)
		} else {
			break
		}
	}
	log.Printf("loaded projects, %d projects found.\n", len(c.Projects))

	semaphore := make(chan int, maxParallelAPICalls)
	notify := make(chan []*compute.Instance)

	// gcloud compute instances list (in parallel)
	for _, project := range c.Projects {
		go func(project *cloudresourcemanager.Project, notify chan<- []*compute.Instance) {
			semaphore <- 0
			var instances []*compute.Instance

			log.Printf("loading instances in %s (%s)...\n", project.Name, project.ProjectId)
			service, err := compute.New(client)
			if err != nil {
				log.Printf("error on loading instances in %s (%s), ignored: %s\n", project.Name, project.ProjectId, err)
				notify <- nil
				<-semaphore
				return
			}

			aggregatedListCall := service.Instances.AggregatedList(project.ProjectId)
			// aggregated_list_call.MaxResults(10)
			for {
				res, err := aggregatedListCall.Do()

				if err != nil {
					log.Printf("error on loading instances in %s (%s), ignored: %s\n", project.Name, project.ProjectId, err)
					notify <- nil
					<-semaphore
					return
				}

				for _, instancesScopedList := range res.Items {
					instances = append(instances, instancesScopedList.Instances...)
				}

				if res.NextPageToken != "" {
					log.Printf("loading more instances with nextPageToken in %s (%s) ...", project.Name, project.ProjectId)
					aggregatedListCall.PageToken(res.NextPageToken)
				} else {
					break
				}
			}

			<-semaphore
			notify <- instances

			log.Printf("loaded instances in %s (%s), %d instances found.\n", project.Name, project.ProjectId, len(instances))
		}(project, notify)
	}
	for _ = range c.Projects {
		instances, _ := <-notify
		if instances != nil {
			c.Instances = append(c.Instances, instances...)
		}
	}

	// sort projects, instances
	sort.Slice(c.Projects, func(i, j int) bool {
		return c.Projects[i].ProjectId < c.Projects[j].ProjectId
	})
	sort.Slice(c.Instances, func(i, j int) bool {
		return c.Instances[i].SelfLink < c.Instances[j].SelfLink
	})

	saveCache(c)
	log.Println("saved cache.")
}

func doProject(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}

	cache, err := loadCache()
	if err != nil {
		panic(err)
	}

	buf := renderProjectTable(cache.Projects)
	out := pecoCommand(buf)
	projectLine := strings.Fields(out)
	if len(projectLine) == 0 {
		os.Exit(1)
	}

	projectID := projectLine[1]

	cmd := []string{"gcloud", "config", "set", "project", projectID}

	if cliCtx.Bool("zsh-widget") {
		fmt.Println(strings.Join(cmd, " "))
	} else {
		log.Println(strings.Join(cmd, " "))
		exec.Command(cmd[0], cmd[1:]...).Output()
	}
}

func doSSH(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}
	cache, err := loadCache()
	if err != nil {
		panic(err)
	}

	config := loadConfig()
	project := config.Core.Project

	buf := renderInstanceTable(project, cache.Instances)
	out := pecoCommand(buf)
	instanceLine := strings.Fields(out)

	if len(instanceLine) == 0 {
		os.Exit(1)
	}

	var instance, zone string
	if project == "" {
		project = instanceLine[1]
		instance = instanceLine[3]
		zone = instanceLine[5]
	} else {
		instance = instanceLine[1]
		zone = instanceLine[3]
	}

	cmd := []string{"gcloud", "compute", "ssh", "--project=" + project, "--zone=" + zone, instance}

	if cliCtx.Bool("zsh-widget") {
		fmt.Println(strings.Join(cmd, " "))
	} else {
		log.Println(strings.Join(cmd, " "))
		sshCom := exec.Command(cmd[0], cmd[1:]...)
		sshCom.Stdout = os.Stdout
		sshCom.Stderr = os.Stderr
		sshCom.Stdin = os.Stdin
		err = sshCom.Run()
		if err != nil {
			log.Fatal(err)
		}
	}
}

func doCurrentProject(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}

	config := loadConfig()
	project := config.Core.Project
	fmt.Println("project: " + project)
}

func loadConfig() configRoot {
	out, _ := exec.Command("gcloud", "config", "list", "--format=json").Output()
	buf := string(out)
	decoder := json.NewDecoder(strings.NewReader(buf))
	var d configRoot
	decoder.Decode(&d)
	return d
}

func renderProjectTable(projects []*cloudresourcemanager.Project) []byte {
	var buf bytes.Buffer
	table := tablewriter.NewWriter(&buf)
	table.SetHeader([]string{"PROJECT_ID", "NAME", "PROJECT_NUMBER"})
	for _, p := range projects {
		row := []string{p.ProjectId, p.Name, fmt.Sprintf("%v", p.ProjectNumber)}
		table.Append(row)
	}
	table.Render()

	return buf.Bytes()
}

func renderInstanceTable(projectID string, instances []*compute.Instance) []byte {
	var buf bytes.Buffer
	table := tablewriter.NewWriter(&buf)
	if projectID == "" {
		table.SetHeader([]string{"PROJECT", "NAME", "ZONE", "MACHINE_TYPE", "INTERNAL_IP", "EXTERNAL_IP", "STATUS"})
	} else {
		table.SetHeader([]string{"NAME", "ZONE", "MACHINE_TYPE", "INTERNAL_IP", "EXTERNAL_IP", "STATUS"})
	}
	for _, ins := range instances {
		p := (func(selflink string) string {
			return strings.Split(strings.Split(selflink, "https://www.googleapis.com/compute/v1/projects/")[1], "/")[0]
		})(ins.SelfLink)
		zone := (func(a []string) string { return a[len(a)-1] })(strings.Split(ins.Zone, "/"))
		machineType := (func(a []string) string { return a[len(a)-1] })(strings.Split(ins.MachineType, "/"))
		internalIP := ins.NetworkInterfaces[0].NetworkIP
		externalIP := ins.NetworkInterfaces[0].AccessConfigs[0].NatIP
		var row []string
		if projectID == "" {
			row = []string{p, ins.Name, zone, machineType, internalIP, externalIP, ins.Status}
			table.Append(row)
		} else {
			if projectID == p {
				row = []string{ins.Name, zone, machineType, internalIP, externalIP, ins.Status}
				table.Append(row)
			}
		}
	}
	table.Render()

	return buf.Bytes()
}

func pecoCommand(into []byte) string {
	var buff bytes.Buffer
	pecoCom := exec.Command("peco")
	pecoCom.Stdin = bytes.NewReader(into) // , _ = c.StdoutPipe()
	pecoCom.Stdout = &buff

	_ = pecoCom.Start()
	_ = pecoCom.Wait()

	out := buff.String()
	return out
}

func checkCommandsInstalled() error {
	notFoundCommands := []string{}
	requiredCommands := []string{
		"gcloud",
		"peco",
	}

	for _, cmd := range requiredCommands {
		_, err := exec.LookPath(cmd)
		if err != nil {
			notFoundCommands = append(notFoundCommands, cmd)
		}
	}

	if len(notFoundCommands) > 0 {
		return fmt.Errorf("Command(s) not found: %s", strings.Join(notFoundCommands, ", "))
	}

	return nil
}
<commit_msg>Support no external IP address instance<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/mitchellh/go-homedir"
	"github.com/olekukonko/tablewriter"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1beta1"
	compute "google.golang.org/api/compute/v1"
)

var commands = []cli.Command{
	commandCache,
	commandProject,
	commandSSH,
	commandCurrentProject,
}

var commandCache = cli.Command{
	Name:  "cache",
	Usage: "",
	Description: `
`,
	Action: doCache,
}

var commandProject = cli.Command{
	Name:  "project",
	Usage: "",
	Description: `
`,
	Action: doProject,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name: "zsh-widget, z",
		},
	},
}

var commandSSH = cli.Command{
	Name:  "ssh",
	Usage: "",
	Description: `
`,
	Action: doSSH,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name: "zsh-widget, z",
		},
	},
}

var commandCurrentProject = cli.Command{
	Name:  "current",
	Usage: "",
	Description: `
`,
	Action: doCurrentProject,
}

var maxParallelAPICalls = 10

// JSON struct
// config
type configCore struct {
	Project string `json:"project"`
	Account string `json:"account"`
}
type configRoot struct {
	Core configCore `json:"core"`
}

// cache
type cache struct {
	CacheDir  string
	Instances []*compute.Instance
	Projects  []*cloudresourcemanager.Project
}

func loadCache() (*cache, error) {
	cachedir, err := homedir.Expand("~/.cache/geco/")
	if err != nil {
		return nil, err
	}
	c := cache{CacheDir: cachedir}
	_, err = os.Stat(c.CacheDir)
	if os.IsNotExist(err) {
		os.MkdirAll(c.CacheDir, 0700)
	}
	fileinfos, _ := ioutil.ReadDir(c.CacheDir)
	for _, fileinfo := range fileinfos {
		if fileinfo.IsDir() {
			continue
		}
		if fileinfo.Name() == "projects.json" {
			projectsJSON, err := ioutil.ReadFile(filepath.Join(c.CacheDir, fileinfo.Name()))
			if err != nil {
				return nil, err
			}
			err = json.Unmarshal(projectsJSON, &c.Projects)
			if err != nil {
				return nil, err
			}
		}
		if fileinfo.Name() == "instances.json" {
			instancesJSON, err := ioutil.ReadFile(filepath.Join(c.CacheDir, fileinfo.Name()))
			if err != nil {
				return nil, err
			}
			err = json.Unmarshal(instancesJSON, &c.Instances)
			if err != nil {
				return nil, err
			}
		}
	}

	return &c, nil
}

func saveCache(c *cache) error {
	projectsJSON, _ := json.Marshal(c.Projects)
	err := ioutil.WriteFile(filepath.Join(c.CacheDir, "projects.json"), projectsJSON, 0600)
	if err != nil {
		return err
	}
	instancesJSON, _ := json.Marshal(c.Instances)
	err = ioutil.WriteFile(filepath.Join(c.CacheDir, "instances.json"), instancesJSON, 0600)
	if err != nil {
		return err
	}
	return nil
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func doCache(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}

	c, err := loadCache()
	if err != nil {
		panic(err)
	}
	c.Projects = []*cloudresourcemanager.Project{}
	c.Instances = []*compute.Instance{}

	ctx := oauth2.NoContext
	scopes := []string{compute.ComputeReadonlyScope}
	client, err := google.DefaultClient(ctx, scopes...)
	if err != nil {
		panic(err)
	}

	// gcloud beta projects list
	log.Println("loading projects...")
	service, err := cloudresourcemanager.New(client)
	if err != nil {
		panic(err)
	}

	projectsListCall := service.Projects.List()
	for {
		res, err := projectsListCall.Do()

		if err != nil {
			panic(err)
		}

		c.Projects = append(c.Projects, res.Projects...)

		if res.NextPageToken != "" {
			log.Printf("loading more projects with nextPageToken ...")
			projectsListCall.PageToken(res.NextPageToken)
		} else {
			break
		}
	}
	log.Printf("loaded projects, %d projects found.\n", len(c.Projects))

	semaphore := make(chan int, maxParallelAPICalls)
	notify := make(chan []*compute.Instance)

	// gcloud compute instances list (in parallel)
	for _, project := range c.Projects {
		go func(project *cloudresourcemanager.Project, notify chan<- []*compute.Instance) {
			semaphore <- 0
			var instances []*compute.Instance

			log.Printf("loading instances in %s (%s)...\n", project.Name, project.ProjectId)
			service, err := compute.New(client)
			if err != nil {
				log.Printf("error on loading instances in %s (%s), ignored: %s\n", project.Name, project.ProjectId, err)
				notify <- nil
				<-semaphore
				return
			}

			aggregatedListCall := service.Instances.AggregatedList(project.ProjectId)
			// aggregated_list_call.MaxResults(10)
			for {
				res, err := aggregatedListCall.Do()

				if err != nil {
					log.Printf("error on loading instances in %s (%s), ignored: %s\n", project.Name, project.ProjectId, err)
					notify <- nil
					<-semaphore
					return
				}

				for _, instancesScopedList := range res.Items {
					instances = append(instances, instancesScopedList.Instances...)
				}

				if res.NextPageToken != "" {
					log.Printf("loading more instances with nextPageToken in %s (%s) ...", project.Name, project.ProjectId)
					aggregatedListCall.PageToken(res.NextPageToken)
				} else {
					break
				}
			}

			<-semaphore
			notify <- instances

			log.Printf("loaded instances in %s (%s), %d instances found.\n", project.Name, project.ProjectId, len(instances))
		}(project, notify)
	}
	for _ = range c.Projects {
		instances, _ := <-notify
		if instances != nil {
			c.Instances = append(c.Instances, instances...)
		}
	}

	// sort projects, instances
	sort.Slice(c.Projects, func(i, j int) bool {
		return c.Projects[i].ProjectId < c.Projects[j].ProjectId
	})
	sort.Slice(c.Instances, func(i, j int) bool {
		return c.Instances[i].SelfLink < c.Instances[j].SelfLink
	})

	saveCache(c)
	log.Println("saved cache.")
}

func doProject(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}

	cache, err := loadCache()
	if err != nil {
		panic(err)
	}

	buf := renderProjectTable(cache.Projects)
	out := pecoCommand(buf)
	projectLine := strings.Fields(out)
	if len(projectLine) == 0 {
		os.Exit(1)
	}

	projectID := projectLine[1]

	cmd := []string{"gcloud", "config", "set", "project", projectID}

	if cliCtx.Bool("zsh-widget") {
		fmt.Println(strings.Join(cmd, " "))
	} else {
		log.Println(strings.Join(cmd, " "))
		exec.Command(cmd[0], cmd[1:]...).Output()
	}
}

func doSSH(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}
	cache, err := loadCache()
	if err != nil {
		panic(err)
	}

	config := loadConfig()
	project := config.Core.Project

	buf := renderInstanceTable(project, cache.Instances)
	out := pecoCommand(buf)
	instanceLine := strings.Fields(out)

	if len(instanceLine) == 0 {
		os.Exit(1)
	}

	var instance, zone string
	if project == "" {
		project = instanceLine[1]
		instance = instanceLine[3]
		zone = instanceLine[5]
	} else {
		instance = instanceLine[1]
		zone = instanceLine[3]
	}

	cmd := []string{"gcloud", "compute", "ssh", "--project=" + project, "--zone=" + zone, instance}

	if cliCtx.Bool("zsh-widget") {
		fmt.Println(strings.Join(cmd, " "))
	} else {
		log.Println(strings.Join(cmd, " "))
		sshCom := exec.Command(cmd[0], cmd[1:]...)
		sshCom.Stdout = os.Stdout
		sshCom.Stderr = os.Stderr
		sshCom.Stdin = os.Stdin
		err = sshCom.Run()
		if err != nil {
			log.Fatal(err)
		}
	}
}

func doCurrentProject(cliCtx *cli.Context) {
	if err := checkCommandsInstalled(); err != nil {
		log.Fatal(err)
	}

	config := loadConfig()
	project := config.Core.Project
	fmt.Println("project: " + project)
}

func loadConfig() configRoot {
	out, _ := exec.Command("gcloud", "config", "list", "--format=json").Output()
	buf := string(out)
	decoder := json.NewDecoder(strings.NewReader(buf))
	var d configRoot
	decoder.Decode(&d)
	return d
}

func renderProjectTable(projects []*cloudresourcemanager.Project) []byte {
	var buf bytes.Buffer
	table := tablewriter.NewWriter(&buf)
	table.SetHeader([]string{"PROJECT_ID", "NAME", "PROJECT_NUMBER"})
	for _, p := range projects {
		row := []string{p.ProjectId, p.Name, fmt.Sprintf("%v", p.ProjectNumber)}
		table.Append(row)
	}
	table.Render()

	return buf.Bytes()
}

func renderInstanceTable(projectID string, instances []*compute.Instance) []byte {
	var buf bytes.Buffer
	table := tablewriter.NewWriter(&buf)
	if projectID == "" {
		table.SetHeader([]string{"PROJECT", "NAME", "ZONE", "MACHINE_TYPE", "INTERNAL_IP", "EXTERNAL_IP", "STATUS"})
	} else {
		table.SetHeader([]string{"NAME", "ZONE", "MACHINE_TYPE", "INTERNAL_IP", "EXTERNAL_IP", "STATUS"})
	}
	for _, ins := range instances {
		p := (func(selflink string) string {
			return strings.Split(strings.Split(selflink, "https://www.googleapis.com/compute/v1/projects/")[1], "/")[0]
		})(ins.SelfLink)
		zone := (func(a []string) string { return a[len(a)-1] })(strings.Split(ins.Zone, "/"))
		machineType := (func(a []string) string { return a[len(a)-1] })(strings.Split(ins.MachineType, "/"))
		internalIP := ins.NetworkInterfaces[0].NetworkIP
		externalIP := ""
		if accessConfigs := ins.NetworkInterfaces[0].AccessConfigs; len(accessConfigs) > 0 {
			externalIP = accessConfigs[0].NatIP
		}
		var row []string
		if projectID == "" {
			row = []string{p, ins.Name, zone, machineType, internalIP, externalIP, ins.Status}
			table.Append(row)
		} else {
			if projectID == p {
				row = []string{ins.Name, zone, machineType, internalIP, externalIP, ins.Status}
				table.Append(row)
			}
		}
	}
	table.Render()

	return buf.Bytes()
}

func pecoCommand(into []byte) string {
	var buff bytes.Buffer
	pecoCom := exec.Command("peco")
	pecoCom.Stdin = bytes.NewReader(into) // , _ = c.StdoutPipe()
	pecoCom.Stdout = &buff

	_ = pecoCom.Start()
	_ = pecoCom.Wait()

	out := buff.String()
	return out
}

func checkCommandsInstalled() error {
	notFoundCommands := []string{}
	requiredCommands := []string{
		"gcloud",
		"peco",
	}

	for _, cmd := range requiredCommands {
		_, err := exec.LookPath(cmd)
		if err != nil {
			notFoundCommands = append(notFoundCommands, cmd)
		}
	}

	if len(notFoundCommands) > 0 {
		return fmt.Errorf("Command(s) not found: %s", strings.Join(notFoundCommands, ", "))
	}

	return nil
}
<|endoftext|>


<commit_before>// 4crossbuild builds the circuit on a remote host (usually with a different OS
// and/or architecture) and ships the result locally.
package main

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

var (
	flagBinary           = flag.String("binary", "4r", "Preferred name for the resulting runtime binary")
	flagJail             = flag.String("jail", path.Join(os.Getenv("HOME"), "_circuit/build"), "Build jail directory")

	flagAppRepo          = flag.String("app", "", "App repository")
	flagAppPath          = flag.String("appsrc", "", "GOPATH relative to app repository")

	flagPkg              = flag.String("pkg", "", "Package to import for side-effects in circuit runtime binary")

	flagShow             = flag.Bool("show", false, "Show output of underlying build commands")
	flagRebuildGo        = flag.Bool("rebuildgo", false, "Force fetch and rebuild of the Go compiler")

	flagZookeeperInclude = flag.String("zinclude", path.Join(os.Getenv("HOME"), "local/include/c-client-src") , "Zookeeper C headers directory")
	flagZookeeperLib     = flag.String("zlib", path.Join(os.Getenv("HOME"), "local/lib") , "Zookeeper libraries directory")

	flagCircuitRepo      = flag.String("cir", "git@github.com:tumblr/gocircuit.git", "Circuit repository")
	flagCircuitPath      = flag.String("cirsrc", ".", "GOPATH relative to circuit repository")
)

/*
	Build jail layout:
		_build/go
		_build/app/src
		_build/circuit/src
*/

var x struct {
	env       Env
	jail      string
	appPkgs   []string
	binary    string
	zinclude  string
	zlib      string
	goRoot    string
	goBin     string
	goCmd     string
	goPath    map[string]string
}

func main() {
	flag.Parse()

	// Initialize build environment
	x.binary = *flagBinary
	if strings.TrimSpace(x.binary) == "" {
		println("Missing name of target binary")
		os.Exit(1)
	}
	x.env = OSEnv()
	x.jail = *flagJail
	x.appPkgs = []string{*flagPkg}
	x.zinclude = *flagZookeeperInclude
	x.zlib = *flagZookeeperLib
	x.goPath = make(map[string]string)

	// Make jail if not present
	var err error
	if err = os.MkdirAll(x.jail, 0700); err != nil {
		Fatalf("Problem creating build jail (%s)\n", err)
	}

	Errorf("Building Go compiler\n")
	buildGoCompiler(*flagRebuildGo)

	Errorf("Updating circuit repository\n")
	fetchRepo("circuit", *flagCircuitRepo, *flagCircuitPath)

	Errorf("Updating app repository\n")
	fetchRepo("app", *flagAppRepo, *flagAppPath)

	Errorf("Building circuit binaries\n")
	buildCircuit()

	Errorf("Shipping install package\n")
	bundleDir := shipCircuit()
	Errorf("Build successful!\n")

	// Print temporary directory containing bundle
	Printf("%s\n", bundleDir)
}

func shipCircuit() string {
	tmpdir, err := MakeTempDir()
	if err != nil {
		Fatalf("Problem making packaging directory (%s)\n", err)
	}

	// Copy binaries over to shipping directory
	println("+Packaging", x.binary)
	binpkg := binPkg()
	shipFile := path.Join(tmpdir, x.binary)
	if _, err = CopyFile(path.Join(binpkg, x.binary), shipFile); err != nil {
		Fatalf("Problem copying circuit binary (%s)\n", err)
	}
	if err = os.Chmod(shipFile, 0755); err != nil {
		Fatalf("Problem chmod'ing circuit binary (%s)\n", err)
	}

	// zookeeper lib
	println("+Packaging Zookeeper libraries")
	if err = ShellCopyFile(path.Join(x.zlib, "libzookeeper*"), tmpdir + "/"); err != nil {
		Fatalf("Problem copying Zookeeper library files (%s)\n", err)
	}

	return tmpdir
}

// Source code of a circuit runtime executable
const mainSrc = `
package main
import (
	_ "circuit/load"
	{{range .}}
	_ "{{.}}"
	{{end}}
)
func main() {}
`

func binPkg() string {
	return path.Join(x.goPath["circuit"], "src", "autopkg", x.binary)
}

func buildCircuit() {

	// Prepare cgo environment for Zookeeper
	// TODO: Add Zookeeper build step. Don't rely on a prebuilt one.
	x.env.Set("CGO_CFLAGS", "-I" + x.zinclude)
	x.env.Set("CGO_LDFLAGS", "-L" + x.zlib + " -lm -lpthread -lzookeeper_mt")
	defer x.env.Unset("CGO_CFLAGS")
	defer x.env.Unset("CGO_LDFLAGS")

	// Remove any installed packages
	if err := os.RemoveAll(path.Join(x.goPath["circuit"], "pkg")); err != nil {
		Fatalf("Problem removing circuit pkg directory (%s)\n", err)
	}
	if err := os.RemoveAll(path.Join(x.goPath["app"], "pkg")); err != nil {
		Fatalf("Problem removing app pkg directory (%s)\n", err)
	}

	// Re-build application package
	/*
	for _, apkg := range x.appPkgs {
		if err := Exec(x.env, path.Join(x.goPath["app"], "src", apkg) , x.goCmd, "install"); err != nil {
			Fatalf("Problem compiling main.go (%s)\n", err)
		}
	}
	*/

	// Create a package for the runtime executable
	binpkg := binPkg()
	if err := os.MkdirAll(binpkg, 0700); err != nil {
		Fatalf("Problem creating runtime package %s (%s)\n", binpkg, err)
	}

	// Write main.go
	t := template.New("main")
	template.Must(t.Parse(mainSrc))
	var w bytes.Buffer
	if err := t.Execute(&w, x.appPkgs); err != nil {
		Fatalf("Problem preparing main.go (%s)\n", err)
	}
	if err := ioutil.WriteFile(path.Join(binpkg, "main.go"), w.Bytes(), 0664); err != nil {
		Fatalf("Problem writing main.go (%s)\n", err)
	}

	// Build circuit runtime binary
	println("+Building", x.binary)
	if err := Exec(x.env, binpkg, x.goCmd, "build"); err != nil {
		Fatalf("Problem compiling main.go (%s)\n", err)
	}
}

// repoName returns the top-level name of a GIT repository from its URL
// E.g. git@github.com:tumblr/cirapp.git -> cirapp
func repoName(repo string) string {
	if strings.HasSuffix(repo, ".git") {
		repo = repo[:len(repo) - len(".git")]
	}
__For:
	for i := len(repo) - 1; i >= 0; i-- {
		switch repo[i] {
		case ':', '/', '@':
			repo = repo[i + 1:]
			break __For
		}
	}
	return repo
}

func repoSchema(s string) (schema, url string) {
	switch {
	case strings.HasPrefix(s, "{git}"):
		return "git", s[len("{git}"):]
	case strings.HasPrefix(s, "{rsync}"):
		return "rsync", s[len("{rsync}"):]
	}
	Fatalf("Repo '%s' has unrecognizable schema\n", s)
	panic("unr")
}

func cloneGitRepo(repo, parent string) {
	// If not, clone the source tree
	if err := Exec(nil, parent, "git", "clone", repo); err != nil {
		Fatalf("Problem cloning repo '%s' (%s)", repo, err)
	}
}

func pullGitRepo(dir string) {
	if err := Exec(nil, dir, "git", "pull", "origin", "master"); err != nil {
		Fatalf("Problem pulling repo in %s (%s)", dir, err)
	}
}

func rsyncRepo(src, dstparent string) {
	if err := Exec(nil, "", "rsync", "-acrv", "--exclude", ".git", "--exclude", ".hg", src, dstparent); err != nil {
		Fatalf("Problem rsyncing dir '%s' to within '%s' (%s)", src, dstparent, err)
	}
}

func fetchRepo(namespace, repo, gopath string) {

	schema, repo := repoSchema(repo)

	// Make _build/namespace/src
	repoSrc := path.Join(x.jail, path.Join(namespace, "src"))
	if err := os.MkdirAll(repoSrc, 0700); err != nil {
		Fatalf("Problem creating app source path %s (%s)\n", repoSrc, err)
	}
	repoPath := path.Join(repoSrc, repoName(repo))

	// Check whether repo directory exists
	ok, err := Exists(repoPath)
	if err != nil {
		Fatalf("Problem stat'ing %s (%s)", repoPath, err)
	}
	switch schema {
	case "git":
		if !ok {
			cloneGitRepo(repo, repoSrc)
		} else {
			pullGitRepo(repoPath)
		}
	case "rsync":
		rsyncRepo(repo, repoSrc)
	default:
		Fatalf("Unrecognized repo schema: %s\n", schema)
	}

	// Create build environment for building in this repo
	oldGoPath := x.env.Get("GOPATH")
	var p string
	if gopath == "" {
		p = path.Join(x.jail, namespace)
	} else {
		p = path.Join(repoPath, gopath)
	}
	x.env.Set("GOPATH", p + ":" + oldGoPath)
	x.goPath[namespace] = p
}

func buildGoCompiler(rebuild bool) {
	// Check whether compiler subdirectory directory exists,
	// $jail/go
	ok, err := Exists(path.Join(x.jail, "/go"))
	if err != nil {
		Fatalf("Problem stat'ing %s (%s)", path.Join(x.jail, "/go"), err)
	}
	//Errorf("EEE %#v\n", x.env.Environ())
	if !ok {
		// If not, fetch the source tree
		if err = Exec(x.env, x.jail, "hg", "clone", "-u", "tip", "https://code.google.com/p/go"); err != nil {
			Fatalf("Problem cloning Go repository (%s)", err)
		}
		// Force rebuild
		rebuild = true
	} else {
		if rebuild {
			// Pull changes
			if err = Exec(x.env, path.Join(x.jail, "/go"), "hg", "pull"); err != nil {
				Fatalf("Problem pulling Go repository changes (%s)", err)
			}
			// Update working copy
			if err = Exec(x.env, path.Join(x.jail, "/go"), "hg", "update"); err != nil {
				Fatalf("Problem updating Go repository changes (%s)", err)
			}
		}
	}
	if rebuild {
		// Build Go compiler
		if err = Exec(x.env, path.Join(x.jail, "/go/src"), path.Join(x.jail, "/go/src/all.bash")); err != nil {
			if !IsExitError(err) {
				Fatalf("Problem building Go (%s)", err)
			}
		}
	}

	// Create build environment for building with this compiler
	x.goRoot = path.Join(x.jail, "/go")
	x.goBin = path.Join(x.goRoot, "/bin")
	x.goCmd = path.Join(x.goBin, "go")
	x.env.Set("PATH", x.goBin + ":" + x.env.Get("PATH"))
	x.env.Set("GOROOT", x.goRoot)
}
<commit_msg>add some portability to build sys<commit_after>// 4crossbuild builds the circuit on a remote host (usually with a different OS
// and/or architecture) and ships the result locally.
package main

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

var (
	flagBinary           = flag.String("binary", "4r", "Preferred name for the resulting runtime binary")
	flagJail             = flag.String("jail", path.Join(os.Getenv("HOME"), "_circuit/build"), "Build jail directory")

	flagAppRepo          = flag.String("app", "", "App repository")
	flagAppPath          = flag.String("appsrc", "", "GOPATH relative to app repository")

	flagPkg              = flag.String("pkg", "", "Package to import for side-effects in circuit runtime binary")

	flagShow             = flag.Bool("show", false, "Show output of underlying build commands")
	flagRebuildGo        = flag.Bool("rebuildgo", false, "Force fetch and rebuild of the Go compiler")

	flagZookeeperInclude = flag.String("zinclude", path.Join(os.Getenv("HOME"), "local/include/c-client-src") , "Zookeeper C headers directory")
	flagZookeeperLib     = flag.String("zlib", path.Join(os.Getenv("HOME"), "local/lib") , "Zookeeper libraries directory")

	flagCircuitRepo      = flag.String("cir", "git@github.com:tumblr/gocircuit.git", "Circuit repository")
	flagCircuitPath      = flag.String("cirsrc", ".", "GOPATH relative to circuit repository")
)

/*
	Build jail layout:
		_build/go
		_build/app/src
		_build/circuit/src
*/

var x struct {
	env       Env
	jail      string
	appPkgs   []string
	binary    string
	zinclude  string
	zlib      string
	goRoot    string
	goBin     string
	goCmd     string
	goPath    map[string]string
}

func main() {
	flag.Parse()

	// Initialize build environment
	x.binary = *flagBinary
	if strings.TrimSpace(x.binary) == "" {
		println("Missing name of target binary")
		os.Exit(1)
	}
	x.env = OSEnv()
	x.jail = *flagJail
	x.appPkgs = []string{*flagPkg}
	x.zinclude = *flagZookeeperInclude
	x.zlib = *flagZookeeperLib
	x.goPath = make(map[string]string)

	// Make jail if not present
	var err error
	if err = os.MkdirAll(x.jail, 0700); err != nil {
		Fatalf("Problem creating build jail (%s)\n", err)
	}

	Errorf("Building Go compiler\n")
	buildGoCompiler(*flagRebuildGo)

	Errorf("Updating circuit repository\n")
	fetchRepo("circuit", *flagCircuitRepo, *flagCircuitPath)

	Errorf("Updating app repository\n")
	fetchRepo("app", *flagAppRepo, *flagAppPath)

	Errorf("Building circuit binaries\n")
	buildCircuit()

	Errorf("Shipping install package\n")
	bundleDir := shipCircuit()
	Errorf("Build successful!\n")

	// Print temporary directory containing bundle
	Printf("%s\n", bundleDir)
}

func shipCircuit() string {
	tmpdir, err := MakeTempDir()
	if err != nil {
		Fatalf("Problem making packaging directory (%s)\n", err)
	}

	// Copy binaries over to shipping directory
	println("+Packaging", x.binary)
	binpkg := binPkg()
	shipFile := path.Join(tmpdir, x.binary)
	if _, err = CopyFile(path.Join(binpkg, x.binary), shipFile); err != nil {
		Fatalf("Problem copying circuit binary (%s)\n", err)
	}
	if err = os.Chmod(shipFile, 0755); err != nil {
		Fatalf("Problem chmod'ing circuit binary (%s)\n", err)
	}

	// zookeeper lib
	println("+Packaging Zookeeper libraries")
	if err = ShellCopyFile(path.Join(x.zlib, "libzookeeper*"), tmpdir + "/"); err != nil {
		Fatalf("Problem copying Zookeeper library files (%s)\n", err)
	}

	return tmpdir
}

// Source code of a circuit runtime executable
const mainSrc = `
package main
import (
	_ "circuit/load"
	{{range .}}
	_ "{{.}}"
	{{end}}
)
func main() {}
`

func binPkg() string {
	return path.Join(x.goPath["circuit"], "src", "autopkg", x.binary)
}

func buildCircuit() {

	// Prepare cgo environment for Zookeeper
	// TODO: Add Zookeeper build step. Don't rely on a prebuilt one.
	x.env.Set("CGO_CFLAGS", "-I" + x.zinclude)
	x.env.Set("CGO_LDFLAGS", "-L" + x.zlib + " -lm -lpthread -lzookeeper_mt")
	defer x.env.Unset("CGO_CFLAGS")
	defer x.env.Unset("CGO_LDFLAGS")

	// Remove any installed packages
	if err := os.RemoveAll(path.Join(x.goPath["circuit"], "pkg")); err != nil {
		Fatalf("Problem removing circuit pkg directory (%s)\n", err)
	}
	if err := os.RemoveAll(path.Join(x.goPath["app"], "pkg")); err != nil {
		Fatalf("Problem removing app pkg directory (%s)\n", err)
	}

	// Re-build application package
	/*
	for _, apkg := range x.appPkgs {
		if err := Exec(x.env, path.Join(x.goPath["app"], "src", apkg) , x.goCmd, "install"); err != nil {
			Fatalf("Problem compiling main.go (%s)\n", err)
		}
	}
	*/

	// Create a package for the runtime executable
	binpkg := binPkg()
	if err := os.MkdirAll(binpkg, 0700); err != nil {
		Fatalf("Problem creating runtime package %s (%s)\n", binpkg, err)
	}

	// Write main.go
	t := template.New("main")
	template.Must(t.Parse(mainSrc))
	var w bytes.Buffer
	if err := t.Execute(&w, x.appPkgs); err != nil {
		Fatalf("Problem preparing main.go (%s)\n", err)
	}
	if err := ioutil.WriteFile(path.Join(binpkg, "main.go"), w.Bytes(), 0664); err != nil {
		Fatalf("Problem writing main.go (%s)\n", err)
	}

	// Build circuit runtime binary
	println("+Building", x.binary)
	if err := Exec(x.env, binpkg, x.goCmd, "build"); err != nil {
		Fatalf("Problem compiling main.go (%s)\n", err)
	}
}

// repoName returns the top-level name of a GIT repository from its URL
// E.g. git@github.com:tumblr/cirapp.git -> cirapp
func repoName(repo string) string {
	if strings.HasSuffix(repo, ".git") {
		repo = repo[:len(repo) - len(".git")]
	}
__For:
	for i := len(repo) - 1; i >= 0; i-- {
		switch repo[i] {
		case ':', '/', '@':
			repo = repo[i + 1:]
			break __For
		}
	}
	return repo
}

func repoSchema(s string) (schema, url string) {
	switch {
	case strings.HasPrefix(s, "{git}"):
		return "git", s[len("{git}"):]
	case strings.HasPrefix(s, "{rsync}"):
		return "rsync", s[len("{rsync}"):]
	}
	Fatalf("Repo '%s' has unrecognizable schema\n", s)
	panic("unr")
}

func cloneGitRepo(repo, parent string) {
	// If not, clone the source tree
	if err := Exec(nil, parent, "git", "clone", repo); err != nil {
		Fatalf("Problem cloning repo '%s' (%s)", repo, err)
	}
}

func pullGitRepo(dir string) {
	if err := Exec(nil, dir, "git", "pull", "origin", "master"); err != nil {
		Fatalf("Problem pulling repo in %s (%s)", dir, err)
	}
}

func rsyncRepo(src, dstparent string) {
	if err := Exec(nil, "", "rsync", "-acrv", "--exclude", ".git", "--exclude", ".hg", src, dstparent); err != nil {
		Fatalf("Problem rsyncing dir '%s' to within '%s' (%s)", src, dstparent, err)
	}
}

func fetchRepo(namespace, repo, gopath string) {

	schema, repo := repoSchema(repo)

	// Make _build/namespace/src
	repoSrc := path.Join(x.jail, path.Join(namespace, "src"))
	if err := os.MkdirAll(repoSrc, 0700); err != nil {
		Fatalf("Problem creating app source path %s (%s)\n", repoSrc, err)
	}
	repoPath := path.Join(repoSrc, repoName(repo))

	// Check whether repo directory exists
	ok, err := Exists(repoPath)
	if err != nil {
		Fatalf("Problem stat'ing %s (%s)", repoPath, err)
	}
	switch schema {
	case "git":
		if !ok {
			cloneGitRepo(repo, repoSrc)
		} else {
			pullGitRepo(repoPath)
		}
	case "rsync":
		rsyncRepo(repo, repoSrc)
	default:
		Fatalf("Unrecognized repo schema: %s\n", schema)
	}

	// Create build environment for building in this repo
	oldGoPath := x.env.Get("GOPATH")
	var p string
	if gopath == "" {
		p = path.Join(x.jail, namespace)
	} else {
		p = path.Join(repoPath, gopath)
	}
	x.env.Set("GOPATH", p + ":" + oldGoPath)
	x.goPath[namespace] = p
}

func buildGoCompiler(rebuild bool) {
	// Oddly, On Linux the dynamic linker seems to need Zookeeper libs even to compile Go,
	// likely in effect of an existing CGO_LDFLAGS.
	x.env.Set("LD_LIBRARY_PATH", x.zlib)
	// So, to be safe, we provision this for OSX as well.
	x.env.Set("DYLD_LIBRARY_PATH", x.zlib)

	// Check whether compiler subdirectory directory exists,
	// $jail/go
	ok, err := Exists(path.Join(x.jail, "/go"))
	if err != nil {
		Fatalf("Problem stat'ing %s (%s)", path.Join(x.jail, "/go"), err)
	}
	//Errorf("EEE %#v\n", x.env.Environ())
	if !ok {
		// If not, fetch the source tree
		if err = Exec(x.env, x.jail, "hg", "clone", "-u", "tip", "https://code.google.com/p/go"); err != nil {
			Fatalf("Problem cloning Go repository (%s)", err)
		}
		// Force rebuild
		rebuild = true
	} else {
		if rebuild {
			// Pull changes
			if err = Exec(x.env, path.Join(x.jail, "/go"), "hg", "pull"); err != nil {
				Fatalf("Problem pulling Go repository changes (%s)", err)
			}
			// Update working copy
			if err = Exec(x.env, path.Join(x.jail, "/go"), "hg", "update"); err != nil {
				Fatalf("Problem updating Go repository changes (%s)", err)
			}
		}
	}
	if rebuild {
		// Build Go compiler
		if err = Exec(x.env, path.Join(x.jail, "/go/src"), path.Join(x.jail, "/go/src/all.bash")); err != nil {
			if !IsExitError(err) {
				Fatalf("Problem building Go (%s)", err)
			}
		}
	}

	// Create build environment for building with this compiler
	x.goRoot = path.Join(x.jail, "/go")
	x.goBin = path.Join(x.goRoot, "/bin")
	x.goCmd = path.Join(x.goBin, "go")
	x.env.Set("PATH", x.goBin + ":" + x.env.Get("PATH"))
	x.env.Set("GOROOT", x.goRoot)
}
<|endoftext|>