<commit_before>// Copyright 2012 The Go 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 runtime_test

import (
	"io"
	. "runtime"
	"runtime/debug"
	"strings"
	"testing"
	"unsafe"
)

func init() {
	// We're testing the runtime, so make tracebacks show things
	// in the runtime. This only raises the level, so it won't
	// override GOTRACEBACK=crash from the user.
	SetTracebackEnv("system")
}

var errf error

func errfn() error {
	return errf
}

func errfn1() error {
	return io.EOF
}

func BenchmarkIfaceCmp100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn() == io.EOF {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkIfaceCmpNil100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn1() == nil {
				b.Fatal("bad comparison")
			}
		}
	}
}

var efaceCmp1 interface{}
var efaceCmp2 interface{}

func BenchmarkEfaceCmpDiff(b *testing.B) {
	x := 5
	efaceCmp1 = &x
	y := 6
	efaceCmp2 = &y
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if efaceCmp1 == efaceCmp2 {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer1()
	}
}

func defer1() {
	defer func(x, y, z int) {
		if recover() != nil || x != 1 || y != 2 || z != 3 {
			panic("bad recover")
		}
	}(1, 2, 3)
}

func BenchmarkDefer10(b *testing.B) {
	for i := 0; i < b.N/10; i++ {
		defer2()
	}
}

func defer2() {
	for i := 0; i < 10; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

func BenchmarkDeferMany(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

// golang.org/issue/7063
func TestStopCPUProfilingWithProfilerOff(t *testing.T) {
	SetCPUProfileRate(0)
}

// Addresses to test for faulting behavior.
// This is less a test of SetPanicOnFault and more a check that
// the operating system and the runtime can process these faults
// correctly. That is, we're indirectly testing that without SetPanicOnFault
// these would manage to turn into ordinary crashes.
// Note that these are truncated on 32-bit systems, so the bottom 32 bits
// of the larger addresses must themselves be invalid addresses.
// We might get unlucky and the OS might have mapped one of these
// addresses, but probably not: they're all in the first page, very high
// addresses that normally an OS would reserve for itself, or malformed
// addresses. Even so, we might have to remove one or two on different
// systems. We will see.

var faultAddrs = []uint64{
	// low addresses
	0,
	1,
	0xfff,
	// high (kernel) addresses
	// or else malformed.
	0xffffffffffffffff,
	0xfffffffffffff001,
	0xffffffffffff0001,
	0xfffffffffff00001,
	0xffffffffff000001,
	0xfffffffff0000001,
	0xffffffff00000001,
	0xfffffff000000001,
	0xffffff0000000001,
	0xfffff00000000001,
	0xffff000000000001,
	0xfff0000000000001,
	0xff00000000000001,
	0xf000000000000001,
	0x8000000000000001,
}

func TestSetPanicOnFault(t *testing.T) {
	old := debug.SetPanicOnFault(true)
	defer debug.SetPanicOnFault(old)

	nfault := 0
	for _, addr := range faultAddrs {
		testSetPanicOnFault(t, uintptr(addr), &nfault)
	}
	if nfault == 0 {
		t.Fatalf("none of the addresses faulted")
	}
}

func testSetPanicOnFault(t *testing.T, addr uintptr, nfault *int) {
	if GOOS == "nacl" {
		t.Skip("nacl doesn't seem to fault on high addresses")
	}

	defer func() {
		if err := recover(); err != nil {
			*nfault++
		}
	}()

	// The read should fault, except that sometimes we hit
	// addresses that have had C or kernel pages mapped there
	// readable by user code. So just log the content.
	// If no addresses fault, we'll fail the test.
	v := *(*byte)(unsafe.Pointer(addr))
	t.Logf("addr %#x: %#x\n", addr, v)
}

func eqstring_generic(s1, s2 string) bool {
	if len(s1) != len(s2) {
		return false
	}
	// optimization in assembly versions:
	// if s1.str == s2.str { return true }
	for i := 0; i < len(s1); i++ {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

func TestEqString(t *testing.T) {
	// This isn't really an exhaustive test of == on strings, it's
	// just a convenient way of documenting (via eqstring_generic)
	// what == does.
	s := []string{
		"",
		"a",
		"c",
		"aaa",
		"ccc",
		"cccc"[:3], // same contents, different string
		"1234567890",
	}
	for _, s1 := range s {
		for _, s2 := range s {
			x := s1 == s2
			y := eqstring_generic(s1, s2)
			if x != y {
				t.Errorf(`("%s" == "%s") = %t, want %t`, s1, s2, x, y)
			}
		}
	}
}

func TestTrailingZero(t *testing.T) {
	// make sure we add padding for structs with trailing zero-sized fields
	type T1 struct {
		n int32
		z [0]byte
	}
	if unsafe.Sizeof(T1{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T1{}, unsafe.Sizeof(T1{}))
	}
	type T2 struct {
		n int64
		z struct{}
	}
	if unsafe.Sizeof(T2{}) != 8+unsafe.Sizeof(Uintreg(0)) {
		t.Errorf("sizeof(%#v)==%d, want %d", T2{}, unsafe.Sizeof(T2{}), 8+unsafe.Sizeof(Uintreg(0)))
	}
	type T3 struct {
		n byte
		z [4]struct{}
	}
	if unsafe.Sizeof(T3{}) != 2 {
		t.Errorf("sizeof(%#v)==%d, want 2", T3{}, unsafe.Sizeof(T3{}))
	}
	// make sure padding can double for both zerosize and alignment
	type T4 struct {
		a int32
		b int16
		c int8
		z struct{}
	}
	if unsafe.Sizeof(T4{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T4{}, unsafe.Sizeof(T4{}))
	}
	// make sure we don't pad a zero-sized thing
	type T5 struct {
	}
	if unsafe.Sizeof(T5{}) != 0 {
		t.Errorf("sizeof(%#v)==%d, want 0", T5{}, unsafe.Sizeof(T5{}))
	}
}

func TestBadOpen(t *testing.T) {
	if GOOS == "windows" || GOOS == "nacl" {
		t.Skip("skipping OS that doesn't have open/read/write/close")
	}
	// make sure we get the correct error code if open fails. Same for
	// read/write/close on the resulting -1 fd. See issue 10052.
	nonfile := []byte("/notreallyafile")
	fd := Open(&nonfile[0], 0, 0)
	if fd != -1 {
		t.Errorf("open(\"%s\")=%d, want -1", string(nonfile), fd)
	}
	var buf [32]byte
	r := Read(-1, unsafe.Pointer(&buf[0]), int32(len(buf)))
	if r != -1 {
		t.Errorf("read()=%d, want -1", r)
	}
	w := Write(^uintptr(0), unsafe.Pointer(&buf[0]), int32(len(buf)))
	if w != -1 {
		t.Errorf("write()=%d, want -1", w)
	}
	c := Close(-1)
	if c != -1 {
		t.Errorf("close()=%d, want -1", c)
	}
}

func TestAppendGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, 1)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

var One = []int64{1}

func TestAppendSliceGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, One...)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

func TestGoroutineProfileTrivial(t *testing.T) {
	// Calling GoroutineProfile twice in a row should find the same number of goroutines,
	// but it's possible there are goroutines just about to exit, so we might end up
	// with fewer in the second call. Try a few times; it should converge once those
	// zombies are gone.
	for i := 0; ; i++ {
		n1, ok := GoroutineProfile(nil) // should fail, there's at least 1 goroutine
		if n1 < 1 || ok {
			t.Fatalf("GoroutineProfile(nil) = %d, %v, want >0, false", n1, ok)
		}
		n2, ok := GoroutineProfile(make([]StackRecord, n1))
		if n2 == n1 && ok {
			break
		}
		t.Logf("GoroutineProfile(%d) = %d, %v, want %d, true", n1, n2, ok, n1)
		if i >= 10 {
			t.Fatalf("GoroutineProfile not converging")
		}
	}
}

func TestVersion(t *testing.T) {
	// Test that version does not contain \r or \n.
	vers := Version()
	if strings.Contains(vers, "\r") || strings.Contains(vers, "\n") {
		t.Fatalf("cr/nl in version: %q", vers)
	}
}
<commit_msg>runtime: add TestIntendedInlining<commit_after>// Copyright 2012 The Go 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 runtime_test

import (
	"bytes"
	"internal/testenv"
	"io"
	"os/exec"
	. "runtime"
	"runtime/debug"
	"strings"
	"testing"
	"unsafe"
)

func init() {
	// We're testing the runtime, so make tracebacks show things
	// in the runtime. This only raises the level, so it won't
	// override GOTRACEBACK=crash from the user.
	SetTracebackEnv("system")
}

var errf error

func errfn() error {
	return errf
}

func errfn1() error {
	return io.EOF
}

func BenchmarkIfaceCmp100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn() == io.EOF {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkIfaceCmpNil100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn1() == nil {
				b.Fatal("bad comparison")
			}
		}
	}
}

var efaceCmp1 interface{}
var efaceCmp2 interface{}

func BenchmarkEfaceCmpDiff(b *testing.B) {
	x := 5
	efaceCmp1 = &x
	y := 6
	efaceCmp2 = &y
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if efaceCmp1 == efaceCmp2 {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer1()
	}
}

func defer1() {
	defer func(x, y, z int) {
		if recover() != nil || x != 1 || y != 2 || z != 3 {
			panic("bad recover")
		}
	}(1, 2, 3)
}

func BenchmarkDefer10(b *testing.B) {
	for i := 0; i < b.N/10; i++ {
		defer2()
	}
}

func defer2() {
	for i := 0; i < 10; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

func BenchmarkDeferMany(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

// golang.org/issue/7063
func TestStopCPUProfilingWithProfilerOff(t *testing.T) {
	SetCPUProfileRate(0)
}

// Addresses to test for faulting behavior.
// This is less a test of SetPanicOnFault and more a check that
// the operating system and the runtime can process these faults
// correctly. That is, we're indirectly testing that without SetPanicOnFault
// these would manage to turn into ordinary crashes.
// Note that these are truncated on 32-bit systems, so the bottom 32 bits
// of the larger addresses must themselves be invalid addresses.
// We might get unlucky and the OS might have mapped one of these
// addresses, but probably not: they're all in the first page, very high
// addresses that normally an OS would reserve for itself, or malformed
// addresses. Even so, we might have to remove one or two on different
// systems. We will see.

var faultAddrs = []uint64{
	// low addresses
	0,
	1,
	0xfff,
	// high (kernel) addresses
	// or else malformed.
	0xffffffffffffffff,
	0xfffffffffffff001,
	0xffffffffffff0001,
	0xfffffffffff00001,
	0xffffffffff000001,
	0xfffffffff0000001,
	0xffffffff00000001,
	0xfffffff000000001,
	0xffffff0000000001,
	0xfffff00000000001,
	0xffff000000000001,
	0xfff0000000000001,
	0xff00000000000001,
	0xf000000000000001,
	0x8000000000000001,
}

func TestSetPanicOnFault(t *testing.T) {
	old := debug.SetPanicOnFault(true)
	defer debug.SetPanicOnFault(old)

	nfault := 0
	for _, addr := range faultAddrs {
		testSetPanicOnFault(t, uintptr(addr), &nfault)
	}
	if nfault == 0 {
		t.Fatalf("none of the addresses faulted")
	}
}

func testSetPanicOnFault(t *testing.T, addr uintptr, nfault *int) {
	if GOOS == "nacl" {
		t.Skip("nacl doesn't seem to fault on high addresses")
	}

	defer func() {
		if err := recover(); err != nil {
			*nfault++
		}
	}()

	// The read should fault, except that sometimes we hit
	// addresses that have had C or kernel pages mapped there
	// readable by user code. So just log the content.
	// If no addresses fault, we'll fail the test.
	v := *(*byte)(unsafe.Pointer(addr))
	t.Logf("addr %#x: %#x\n", addr, v)
}

func eqstring_generic(s1, s2 string) bool {
	if len(s1) != len(s2) {
		return false
	}
	// optimization in assembly versions:
	// if s1.str == s2.str { return true }
	for i := 0; i < len(s1); i++ {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

func TestEqString(t *testing.T) {
	// This isn't really an exhaustive test of == on strings, it's
	// just a convenient way of documenting (via eqstring_generic)
	// what == does.
	s := []string{
		"",
		"a",
		"c",
		"aaa",
		"ccc",
		"cccc"[:3], // same contents, different string
		"1234567890",
	}
	for _, s1 := range s {
		for _, s2 := range s {
			x := s1 == s2
			y := eqstring_generic(s1, s2)
			if x != y {
				t.Errorf(`("%s" == "%s") = %t, want %t`, s1, s2, x, y)
			}
		}
	}
}

func TestTrailingZero(t *testing.T) {
	// make sure we add padding for structs with trailing zero-sized fields
	type T1 struct {
		n int32
		z [0]byte
	}
	if unsafe.Sizeof(T1{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T1{}, unsafe.Sizeof(T1{}))
	}
	type T2 struct {
		n int64
		z struct{}
	}
	if unsafe.Sizeof(T2{}) != 8+unsafe.Sizeof(Uintreg(0)) {
		t.Errorf("sizeof(%#v)==%d, want %d", T2{}, unsafe.Sizeof(T2{}), 8+unsafe.Sizeof(Uintreg(0)))
	}
	type T3 struct {
		n byte
		z [4]struct{}
	}
	if unsafe.Sizeof(T3{}) != 2 {
		t.Errorf("sizeof(%#v)==%d, want 2", T3{}, unsafe.Sizeof(T3{}))
	}
	// make sure padding can double for both zerosize and alignment
	type T4 struct {
		a int32
		b int16
		c int8
		z struct{}
	}
	if unsafe.Sizeof(T4{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T4{}, unsafe.Sizeof(T4{}))
	}
	// make sure we don't pad a zero-sized thing
	type T5 struct {
	}
	if unsafe.Sizeof(T5{}) != 0 {
		t.Errorf("sizeof(%#v)==%d, want 0", T5{}, unsafe.Sizeof(T5{}))
	}
}

func TestBadOpen(t *testing.T) {
	if GOOS == "windows" || GOOS == "nacl" {
		t.Skip("skipping OS that doesn't have open/read/write/close")
	}
	// make sure we get the correct error code if open fails. Same for
	// read/write/close on the resulting -1 fd. See issue 10052.
	nonfile := []byte("/notreallyafile")
	fd := Open(&nonfile[0], 0, 0)
	if fd != -1 {
		t.Errorf("open(\"%s\")=%d, want -1", string(nonfile), fd)
	}
	var buf [32]byte
	r := Read(-1, unsafe.Pointer(&buf[0]), int32(len(buf)))
	if r != -1 {
		t.Errorf("read()=%d, want -1", r)
	}
	w := Write(^uintptr(0), unsafe.Pointer(&buf[0]), int32(len(buf)))
	if w != -1 {
		t.Errorf("write()=%d, want -1", w)
	}
	c := Close(-1)
	if c != -1 {
		t.Errorf("close()=%d, want -1", c)
	}
}

func TestAppendGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, 1)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

var One = []int64{1}

func TestAppendSliceGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, One...)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

func TestGoroutineProfileTrivial(t *testing.T) {
	// Calling GoroutineProfile twice in a row should find the same number of goroutines,
	// but it's possible there are goroutines just about to exit, so we might end up
	// with fewer in the second call. Try a few times; it should converge once those
	// zombies are gone.
	for i := 0; ; i++ {
		n1, ok := GoroutineProfile(nil) // should fail, there's at least 1 goroutine
		if n1 < 1 || ok {
			t.Fatalf("GoroutineProfile(nil) = %d, %v, want >0, false", n1, ok)
		}
		n2, ok := GoroutineProfile(make([]StackRecord, n1))
		if n2 == n1 && ok {
			break
		}
		t.Logf("GoroutineProfile(%d) = %d, %v, want %d, true", n1, n2, ok, n1)
		if i >= 10 {
			t.Fatalf("GoroutineProfile not converging")
		}
	}
}

func TestVersion(t *testing.T) {
	// Test that version does not contain \r or \n.
	vers := Version()
	if strings.Contains(vers, "\r") || strings.Contains(vers, "\n") {
		t.Fatalf("cr/nl in version: %q", vers)
	}
}

// TestIntendedInlining tests that specific runtime functions are inlined.
// This allows refactoring for code clarity and re-use without fear that
// changes to the compiler will cause silent performance regressions.
func TestIntendedInlining(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping in short mode")
	}
	testenv.MustHaveGoRun(t)
	t.Parallel()

	// want is the list of function names that should be inlined.
	want := []string{"tophash", "add"}

	m := make(map[string]bool, len(want))
	for _, s := range want {
		m[s] = true
	}

	cmd := testEnv(exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-m", "runtime"))
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Logf("%s", out)
		t.Fatal(err)
	}
	lines := bytes.Split(out, []byte{'\n'})
	for _, x := range lines {
		f := bytes.Split(x, []byte(": can inline "))
		if len(f) < 2 {
			continue
		}
		fn := bytes.TrimSpace(f[1])
		delete(m, string(fn))
	}

	for s := range m {
		t.Errorf("function %s not inlined", s)
	}
}
<|endoftext|>


<commit_before>package tunnel

import (
	"errors"
	"io"
	"time"

	"github.com/micro/go-micro/transport"
	"github.com/micro/go-micro/util/log"
)

// session is our pseudo session for transport.Socket
type session struct {
	// the tunnel id
	tunnel string
	// the channel name
	channel string
	// the session id based on Micro.Tunnel-Session
	session string
	// closed
	closed chan bool
	// remote addr
	remote string
	// local addr
	local string
	// send chan
	send chan *message
	// recv chan
	recv chan *message
	// wait until we have a connection
	wait chan bool
	// if the discovery worked
	discovered bool
	// if the session was accepted
	accepted bool
	// outbound marks the session as outbound dialled connection
	outbound bool
	// lookback marks the session as a loopback on the inbound
	loopback bool
	// mode of the connection
	mode Mode
	// the timeout
	timeout time.Duration
	// the link on which this message was received
	link string
	// the error response
	errChan chan error
}

// message is sent over the send channel
type message struct {
	// type of message
	typ string
	// tunnel id
	tunnel string
	// channel name
	channel string
	// the session id
	session string
	// outbound marks the message as outbound
	outbound bool
	// loopback marks the message intended for loopback
	loopback bool
	// mode of the connection
	mode Mode
	// the link to send the message on
	link string
	// transport data
	data *transport.Message
	// the error channel
	errChan chan error
}

func (s *session) Remote() string {
	return s.remote
}

func (s *session) Local() string {
	return s.local
}

func (s *session) Link() string {
	return s.link
}

func (s *session) Id() string {
	return s.session
}

func (s *session) Channel() string {
	return s.channel
}

// newMessage creates a new message based on the session
func (s *session) newMessage(typ string) *message {
	return &message{
		typ:      typ,
		tunnel:   s.tunnel,
		channel:  s.channel,
		session:  s.session,
		outbound: s.outbound,
		loopback: s.loopback,
		mode:     s.mode,
		link:     s.link,
		errChan:  s.errChan,
	}
}

// waitFor waits for the message type required until the timeout specified
func (s *session) waitFor(msgType string, timeout time.Duration) error {
	now := time.Now()

	after := func() time.Duration {
		d := time.Since(now)
		// dial timeout minus time since
		wait := timeout - d

		if wait < time.Duration(0) {
			return time.Duration(0)
		}

		return wait
	}

	// wait for the message type
loop:
	for {
		select {
		case msg := <-s.recv:
			// ignore what we don't want
			if msg.typ != msgType {
				log.Debugf("Tunnel received non %s message in waiting for %s", msg.typ, msgType)
				continue
			}

			// got the message
			break loop
		case <-time.After(after()):
			return ErrDialTimeout
		case <-s.closed:
			return io.EOF
		}
	}

	return nil
}

// Discover attempts to discover the link for a specific channel
func (s *session) Discover() error {
	// create a new discovery message for this channel
	msg := s.newMessage("discover")
	msg.mode = Broadcast
	msg.outbound = true
	msg.link = ""

	// send the discovery message
	s.send <- msg

	// set time now
	now := time.Now()

	after := func() time.Duration {
		d := time.Since(now)
		// dial timeout minus time since
		wait := s.timeout - d
		if wait < time.Duration(0) {
			return time.Duration(0)
		}
		return wait
	}

	// wait to hear back about the sent message
	select {
	case <-time.After(after()):
		return ErrDialTimeout
	case err := <-s.errChan:
		if err != nil {
			return err
		}
	}

	var err error

	// set a new dialTimeout
	dialTimeout := after()

	// set a shorter delay for multicast
	if s.mode != Unicast {
		// shorten this
		dialTimeout = time.Millisecond * 500
	}

	// wait for announce
	if err := s.waitFor("announce", dialTimeout); err != nil {
		return err
	}

	// if its multicast just go ahead because this is best effort
	if s.mode != Unicast {
		s.discovered = true
		s.accepted = true
		return nil
	}

	if err != nil {
		return err
	}

	// set discovered
	s.discovered = true

	return nil
}

// Open will fire the open message for the session. This is called by the dialler.
func (s *session) Open() error {
	// create a new message
	msg := s.newMessage("open")

	// send open message
	s.send <- msg

	// wait for an error response for send
	select {
	case err := <-msg.errChan:
		if err != nil {
			return err
		}
	case <-s.closed:
		return io.EOF
	}

	// don't wait on multicast/broadcast
	if s.mode == Multicast {
		s.accepted = true
		return nil
	}

	// now wait for the accept
	if err := s.waitFor("accept", s.timeout); err != nil {
		return err
	}

	// set to accepted
	s.accepted = true
	// set link
	s.link = msg.link

	return nil
}

// Accept sends the accept response to an open message from a dialled connection
func (s *session) Accept() error {
	msg := s.newMessage("accept")

	// send the accept message
	select {
	case <-s.closed:
		return io.EOF
	case s.send <- msg:
		// no op here
	}

	// don't wait on multicast/broadcast
	if s.mode == Multicast {
		return nil
	}

	// wait for send response
	select {
	case err := <-s.errChan:
		if err != nil {
			return err
		}
	case <-s.closed:
		return io.EOF
	}

	return nil
}

// Announce sends an announcement to notify that this session exists. This is primarily used by the listener.
func (s *session) Announce() error {
	msg := s.newMessage("announce")
	// we don't need an error back
	msg.errChan = nil
	// announce to all
	msg.mode = Broadcast
	// we don't need the link
	msg.link = ""

	select {
	case s.send <- msg:
		return nil
	case <-s.closed:
		return io.EOF
	}
}

// Send is used to send a message
func (s *session) Send(m *transport.Message) error {
	select {
	case <-s.closed:
		return io.EOF
	default:
		// no op
	}

	// make copy
	data := &transport.Message{
		Header: make(map[string]string),
		Body:   m.Body,
	}

	for k, v := range m.Header {
		data.Header[k] = v
	}

	// create a new message
	msg := s.newMessage("session")
	// set the data
	msg.data = data

	// if multicast don't set the link
	if s.mode == Multicast {
		msg.link = ""
	}

	log.Debugf("Appending %+v to send backlog", msg)
	// send the actual message
	s.send <- msg

	// wait for an error response
	select {
	case err := <-msg.errChan:
		return err
	case <-s.closed:
		return io.EOF
	}
}

// Recv is used to receive a message
func (s *session) Recv(m *transport.Message) error {
	select {
	case <-s.closed:
		return errors.New("session is closed")
	default:
		// no op
	}
	// recv from backlog
	msg := <-s.recv

	// check the error if one exists
	select {
	case err := <-msg.errChan:
		return err
	default:
	}

	log.Debugf("Received %+v from recv backlog", msg)
	// set message
	*m = *msg.data
	// return nil
	return nil
}

// Close closes the session by sending a close message
func (s *session) Close() error {
	select {
	case <-s.closed:
		// no op
	default:
		close(s.closed)

		// append to backlog
		msg := s.newMessage("close")
		// no error response on close
		msg.errChan = nil

		// send the close message
		select {
		case s.send <- msg:
		default:
		}
	}

	return nil
}
<commit_msg>We need the message back to set the link<commit_after>package tunnel

import (
	"errors"
	"io"
	"time"

	"github.com/micro/go-micro/transport"
	"github.com/micro/go-micro/util/log"
)

// session is our pseudo session for transport.Socket
type session struct {
	// the tunnel id
	tunnel string
	// the channel name
	channel string
	// the session id based on Micro.Tunnel-Session
	session string
	// closed
	closed chan bool
	// remote addr
	remote string
	// local addr
	local string
	// send chan
	send chan *message
	// recv chan
	recv chan *message
	// wait until we have a connection
	wait chan bool
	// if the discovery worked
	discovered bool
	// if the session was accepted
	accepted bool
	// outbound marks the session as outbound dialled connection
	outbound bool
	// lookback marks the session as a loopback on the inbound
	loopback bool
	// mode of the connection
	mode Mode
	// the timeout
	timeout time.Duration
	// the link on which this message was received
	link string
	// the error response
	errChan chan error
}

// message is sent over the send channel
type message struct {
	// type of message
	typ string
	// tunnel id
	tunnel string
	// channel name
	channel string
	// the session id
	session string
	// outbound marks the message as outbound
	outbound bool
	// loopback marks the message intended for loopback
	loopback bool
	// mode of the connection
	mode Mode
	// the link to send the message on
	link string
	// transport data
	data *transport.Message
	// the error channel
	errChan chan error
}

func (s *session) Remote() string {
	return s.remote
}

func (s *session) Local() string {
	return s.local
}

func (s *session) Link() string {
	return s.link
}

func (s *session) Id() string {
	return s.session
}

func (s *session) Channel() string {
	return s.channel
}

// newMessage creates a new message based on the session
func (s *session) newMessage(typ string) *message {
	return &message{
		typ:      typ,
		tunnel:   s.tunnel,
		channel:  s.channel,
		session:  s.session,
		outbound: s.outbound,
		loopback: s.loopback,
		mode:     s.mode,
		link:     s.link,
		errChan:  s.errChan,
	}
}

// waitFor waits for the message type required until the timeout specified
func (s *session) waitFor(msgType string, timeout time.Duration) (*message, error) {
	now := time.Now()

	after := func() time.Duration {
		d := time.Since(now)
		// dial timeout minus time since
		wait := timeout - d

		if wait < time.Duration(0) {
			return time.Duration(0)
		}

		return wait
	}

	// wait for the message type
	for {
		select {
		case msg := <-s.recv:
			// ignore what we don't want
			if msg.typ != msgType {
				log.Debugf("Tunnel received non %s message in waiting for %s", msg.typ, msgType)
				continue
			}
			// got the message
			return msg, nil
		case <-time.After(after()):
			return nil, ErrDialTimeout
		case <-s.closed:
			return nil, io.EOF
		}
	}
}

// Discover attempts to discover the link for a specific channel
func (s *session) Discover() error {
	// create a new discovery message for this channel
	msg := s.newMessage("discover")
	msg.mode = Broadcast
	msg.outbound = true
	msg.link = ""

	// send the discovery message
	s.send <- msg

	// set time now
	now := time.Now()

	after := func() time.Duration {
		d := time.Since(now)
		// dial timeout minus time since
		wait := s.timeout - d
		if wait < time.Duration(0) {
			return time.Duration(0)
		}
		return wait
	}

	// wait to hear back about the sent message
	select {
	case <-time.After(after()):
		return ErrDialTimeout
	case err := <-s.errChan:
		if err != nil {
			return err
		}
	}

	var err error

	// set a new dialTimeout
	dialTimeout := after()

	// set a shorter delay for multicast
	if s.mode != Unicast {
		// shorten this
		dialTimeout = time.Millisecond * 500
	}

	// wait for announce
	_, err = s.waitFor("announce", dialTimeout)
	if err != nil {
		return err
	}

	// if its multicast just go ahead because this is best effort
	if s.mode != Unicast {
		s.discovered = true
		s.accepted = true
		return nil
	}

	if err != nil {
		return err
	}

	// set discovered
	s.discovered = true

	return nil
}

// Open will fire the open message for the session. This is called by the dialler.
func (s *session) Open() error {
	// create a new message
	msg := s.newMessage("open")

	// send open message
	s.send <- msg

	// wait for an error response for send
	select {
	case err := <-msg.errChan:
		if err != nil {
			return err
		}
	case <-s.closed:
		return io.EOF
	}

	// don't wait on multicast/broadcast
	if s.mode == Multicast {
		s.accepted = true
		return nil
	}

	// now wait for the accept
	msg, err := s.waitFor("accept", s.timeout)
	if err != nil {
		return err
	}

	// set to accepted
	s.accepted = true
	// set link
	s.link = msg.link

	return nil
}

// Accept sends the accept response to an open message from a dialled connection
func (s *session) Accept() error {
	msg := s.newMessage("accept")

	// send the accept message
	select {
	case <-s.closed:
		return io.EOF
	case s.send <- msg:
		// no op here
	}

	// don't wait on multicast/broadcast
	if s.mode == Multicast {
		return nil
	}

	// wait for send response
	select {
	case err := <-s.errChan:
		if err != nil {
			return err
		}
	case <-s.closed:
		return io.EOF
	}

	return nil
}

// Announce sends an announcement to notify that this session exists. This is primarily used by the listener.
func (s *session) Announce() error {
	msg := s.newMessage("announce")
	// we don't need an error back
	msg.errChan = nil
	// announce to all
	msg.mode = Broadcast
	// we don't need the link
	msg.link = ""

	select {
	case s.send <- msg:
		return nil
	case <-s.closed:
		return io.EOF
	}
}

// Send is used to send a message
func (s *session) Send(m *transport.Message) error {
	select {
	case <-s.closed:
		return io.EOF
	default:
		// no op
	}

	// make copy
	data := &transport.Message{
		Header: make(map[string]string),
		Body:   m.Body,
	}

	for k, v := range m.Header {
		data.Header[k] = v
	}

	// create a new message
	msg := s.newMessage("session")
	// set the data
	msg.data = data

	// if multicast don't set the link
	if s.mode == Multicast {
		msg.link = ""
	}

	log.Debugf("Appending %+v to send backlog", msg)
	// send the actual message
	s.send <- msg

	// wait for an error response
	select {
	case err := <-msg.errChan:
		return err
	case <-s.closed:
		return io.EOF
	}
}

// Recv is used to receive a message
func (s *session) Recv(m *transport.Message) error {
	select {
	case <-s.closed:
		return errors.New("session is closed")
	default:
		// no op
	}
	// recv from backlog
	msg := <-s.recv

	// check the error if one exists
	select {
	case err := <-msg.errChan:
		return err
	default:
	}

	log.Debugf("Received %+v from recv backlog", msg)
	// set message
	*m = *msg.data
	// return nil
	return nil
}

// Close closes the session by sending a close message
func (s *session) Close() error {
	select {
	case <-s.closed:
		// no op
	default:
		close(s.closed)

		// append to backlog
		msg := s.newMessage("close")
		// no error response on close
		msg.errChan = nil

		// send the close message
		select {
		case s.send <- msg:
		default:
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package twitter

import (
	"net/url"
	"strconv"

	"github.com/ChimeraCoder/anaconda"
)

type (
	Client struct {
		api      *anaconda.TwitterApi
		stream   *anaconda.Stream
		callback func(t Tweet)
	}
)

func NewClient(accessConfig AccessConfig, callback func(t Tweet)) Client {
	return Client{
		api:      anaconda.NewTwitterApi(accessConfig.token, accessConfig.tokenSecret),
		callback: callback,
	}
}

func (c *Client) handleStream() {
	for t := range c.stream.C {
		if t, ok := t.(Tweet); ok {
			c.callback(t)
		}
	}
}

func (c *Client) Start() error {
	if ok, err := c.api.VerifyCredentials(); !ok {
		return err
	}

	v := url.Values{
		"replies": {"all"},
	}

	c.stream = c.api.UserStream(v)
	c.handleStream()

	return nil
}

func (c *Client) Stop() (err error) {
	close(c.stream.C)

	return
}

func (c *Client) post(message string, v url.Values) error {
	_, err := c.api.PostTweet(message, v)

	return err
}

func (c *Client) Post(message string, nsfw bool) error {
	v := url.Values{
		"possibly_sensitive": {strconv.FormatBool(nsfw)},
	}

	return c.post(message, v)
}

func (c *Client) Reply(tweet Tweet, message string, nsfw bool) error {
	v := url.Values{
		"possibly_sensitive":    {strconv.FormatBool(nsfw)},
		"in_reply_to_status_id": {tweet.IdStr},
	}

	return c.post(message, v)
}
<commit_msg>Call the stream's stop method to stop<commit_after>package twitter

import (
	"net/url"
	"strconv"

	"github.com/ChimeraCoder/anaconda"
)

type (
	Client struct {
		api      *anaconda.TwitterApi
		stream   *anaconda.Stream
		callback func(t Tweet)
	}
)

func NewClient(accessConfig AccessConfig, callback func(t Tweet)) Client {
	return Client{
		api:      anaconda.NewTwitterApi(accessConfig.token, accessConfig.tokenSecret),
		callback: callback,
	}
}

func (c *Client) handleStream() {
	for t := range c.stream.C {
		if t, ok := t.(Tweet); ok {
			c.callback(t)
		}
	}
}

func (c *Client) Start() error {
	if ok, err := c.api.VerifyCredentials(); !ok {
		return err
	}

	v := url.Values{
		"replies": {"all"},
	}

	c.stream = c.api.UserStream(v)
	c.handleStream()

	return nil
}

func (c *Client) Stop() (err error) {
	c.stream.Stop()

	return
}

func (c *Client) post(message string, v url.Values) error {
	_, err := c.api.PostTweet(message, v)

	return err
}

func (c *Client) Post(message string, nsfw bool) error {
	v := url.Values{
		"possibly_sensitive": {strconv.FormatBool(nsfw)},
	}

	return c.post(message, v)
}

func (c *Client) Reply(tweet Tweet, message string, nsfw bool) error {
	v := url.Values{
		"possibly_sensitive":    {strconv.FormatBool(nsfw)},
		"in_reply_to_status_id": {tweet.IdStr},
	}

	return c.post(message, v)
}
<|endoftext|>


<commit_before>package permissions

import (
	"testing"
)

func TestPerm(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")

	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}

	if userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should not be confirmed right now.")
	}

	userstate.MarkConfirmed("bob")

	if !userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should be marked as confirmed right now.")
	}

	if userstate.IsAdmin("bob") {
		t.Error("Error, user bob should not have admin rights")
	}

	userstate.SetAdminStatus("bob")

	if !userstate.IsAdmin("bob") {
		t.Error("Error, user bob should have admin rights")
	}

	userstate.RemoveUser("bob")

	if userstate.HasUser("bob") {
		t.Error("Error, user bob should not exist")
	}
}

func TestPasswordBasic(t *testing.T) {
	userstate := NewUserStateSimple()

	// Assert that the default password algorithm is "bcrypt+"
	if userstate.PasswordAlgo() != "bcrypt+" {
		t.Error("Error, bcrypt+ should be the default password algorithm")
	}

	// Set password algorithm
	userstate.SetPasswordAlgo("sha256")

	// Assert that the algorithm is now sha256
	if userstate.PasswordAlgo() != "sha256" {
		t.Error("Error, setting password algorithm failed")
	}

}

func TestPasswordBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("sha256")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	userstate.SetPasswordAlgo("sha256")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password really is correct")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password as stored as sha256, not bcrypt")
	}

	userstate.SetPasswordAlgo("bcrypt+")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password is not correct when checking with bcrypt+")
	}
}

func TestPasswordAlgoMatching(t *testing.T) {
	userstate := NewUserStateSimple()
	// generate two different password using the same credentials but different algos
	userstate.SetPasswordAlgo("sha256")
	sha256_hash := userstate.HashPassword("testuser@example.com", "textpassword")
	userstate.SetPasswordAlgo("bcrypt")
	bcrypt_hash := userstate.HashPassword("testuser@example.com", "textpassword")

	// they shouldn't match
	if sha256_hash == bcrypt_hash {
		t.Error("Error, different algorithms should not have a password match")
	}
}

func TestUserStateKeeper(t *testing.T) {
	userstate := NewUserStateSimple()
	// Check that the userstate qualifies for the UserStateKeeper interface
	var _ UserStateKeeper = userstate
}
<commit_msg>Additional tests<commit_after>package permissions

import (
	"testing"
)

func TestPerm(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")

	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}

	if userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should not be confirmed right now.")
	}

	userstate.MarkConfirmed("bob")

	if !userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should be marked as confirmed right now.")
	}

	if userstate.IsAdmin("bob") {
		t.Error("Error, user bob should not have admin rights")
	}

	userstate.SetAdminStatus("bob")

	if !userstate.IsAdmin("bob") {
		t.Error("Error, user bob should have admin rights")
	}

	userstate.RemoveUser("bob")

	if userstate.HasUser("bob") {
		t.Error("Error, user bob should not exist")
	}
}

func TestPasswordBasic(t *testing.T) {
	userstate := NewUserStateSimple()

	// Assert that the default password algorithm is "bcrypt+"
	if userstate.PasswordAlgo() != "bcrypt+" {
		t.Error("Error, bcrypt+ should be the default password algorithm")
	}

	// Set password algorithm
	userstate.SetPasswordAlgo("sha256")

	// Assert that the algorithm is now sha256
	if userstate.PasswordAlgo() != "sha256" {
		t.Error("Error, setting password algorithm failed")
	}

}

// Check if the functionality for backwards compatible hashing works
func TestPasswordBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("sha256")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	userstate.SetPasswordAlgo("sha256")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password really is correct")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password as stored as sha256, not bcrypt")
	}

	userstate.SetPasswordAlgo("bcrypt+")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password is not correct when checking with bcrypt+")
	}

	userstate.RemoveUser("bob")
}

// Check if the functionality for backwards compatible hashing works
func TestPasswordNotBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("bcrypt")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	userstate.SetPasswordAlgo("sha256")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password is stored as bcrypt, should not be okay with sha256")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password should be correct when checking with bcrypt")
	}

	userstate.RemoveUser("bob")
}

func TestPasswordAlgoMatching(t *testing.T) {
	userstate := NewUserStateSimple()
	// generate two different password using the same credentials but different algos
	userstate.SetPasswordAlgo("sha256")
	sha256_hash := userstate.HashPassword("testuser@example.com", "textpassword")
	userstate.SetPasswordAlgo("bcrypt")
	bcrypt_hash := userstate.HashPassword("testuser@example.com", "textpassword")

	// they shouldn't match
	if sha256_hash == bcrypt_hash {
		t.Error("Error, different algorithms should not have a password match")
	}
}

func TestUserStateKeeper(t *testing.T) {
	userstate := NewUserStateSimple()
	// Check that the userstate qualifies for the UserStateKeeper interface
	var _ UserStateKeeper = userstate
}
<|endoftext|>


<commit_before>package file

import (
	"log"
	"os"
	"path/filepath"

	"github.com/arschles/gci/config/ci"
)

// WalkAndExclude walks the directory staring at root and returns all of the files (as relative paths) it finds, skipping all files and directories in exceptions
func WalkAndExclude(root string, excludes []ci.Exclude) ([]string, error) {
	var paths []string
	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Printf("Error found in WalkAndExclude (%s)", err)
			return nil
		}
		rel, err := filepath.Rel(root, path)
		if err != nil {
			return nil
		}
		if matchesExclude(rel, info, excludes) {
			if info.IsDir() {
				return filepath.SkipDir
			} else {
				return nil
			}
		}
		if info.IsDir() {
			return nil
		}
		paths = append(paths, path)
		return nil
	})

	if err != nil {
		return nil, err
	}
	return paths, nil
}

func matchesExclude(path string, info os.FileInfo, excludes []ci.Exclude) bool {
	// TODO: make this more efficient
	for _, exclude := range excludes {
		if exclude.Recursive && exclude.Name == info.Name() {
			return true
		} else if exclude.Name == path {
			if exclude.Name == path {
				return true
			}
		}
	}
	return false
}
<commit_msg>removing walk in file walker<commit_after>package file

import (
	"os"
	"path/filepath"

	"github.com/arschles/gci/config/ci"
)

// WalkAndExclude walks the directory staring at root and returns all of the files (as relative paths) it finds, skipping all files and directories in exceptions
func WalkAndExclude(root string, excludes []ci.Exclude) ([]string, error) {
	var paths []string
	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		rel, err := filepath.Rel(root, path)
		if err != nil {
			return nil
		}
		if matchesExclude(rel, info, excludes) {
			if info.IsDir() {
				return filepath.SkipDir
			} else {
				return nil
			}
		}
		if info.IsDir() {
			return nil
		}
		paths = append(paths, path)
		return nil
	})

	if err != nil {
		return nil, err
	}
	return paths, nil
}

func matchesExclude(path string, info os.FileInfo, excludes []ci.Exclude) bool {
	// TODO: make this more efficient
	for _, exclude := range excludes {
		if exclude.Recursive && exclude.Name == info.Name() {
			return true
		} else if exclude.Name == path {
			if exclude.Name == path {
				return true
			}
		}
	}
	return false
}
<|endoftext|>


<commit_before>package rbac

import (
	"context"
	"fmt"
	"time"

	"github.com/casbin/casbin"
	"github.com/casbin/casbin/model"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gobuffalo/packr"
	scas "github.com/qiangmzsx/string-adapter"
	log "github.com/sirupsen/logrus"
	apiv1 "k8s.io/api/core/v1"
	apierr "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	v1 "k8s.io/client-go/informers/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
)

const (
	ConfigMapPolicyCSVKey     = "policy.csv"
	ConfigMapPolicyDefaultKey = "policy.default"

	builtinModelFile      = "model.conf"
	defaultRBACSyncPeriod = 10 * time.Minute
)

// Enforcer is a wrapper around an Casbin enforcer that:
// * is backed by a kubernetes config map
// * has a predefined RBAC model
// * supports a built-in policy
// * supports a user-defined bolicy
// * supports a custom JWT claims enforce function
type Enforcer struct {
	*casbin.Enforcer
	adapter            *scas.Adapter
	clientset          kubernetes.Interface
	namespace          string
	configmap          string
	claimsEnforcerFunc ClaimsEnforcerFunc

	model             model.Model
	defaultRole       string
	builtinPolicy     string
	userDefinedPolicy string
}

// ClaimsEnforcerFunc is func template to enforce a JWT claims. The subject is replaced
type ClaimsEnforcerFunc func(claims jwt.Claims, rvals ...interface{}) bool

var (
	modelConf string
)

func init() {
	box := packr.NewBox(".")
	modelConf = box.String(builtinModelFile)
}

func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, claimsEnforcer ClaimsEnforcerFunc) *Enforcer {
	adapter := scas.NewAdapter("")
	builtInModel := newBuiltInModel()
	enf := casbin.NewEnforcer(builtInModel, adapter)
	enf.EnableLog(false)
	return &Enforcer{
		Enforcer:           enf,
		adapter:            adapter,
		clientset:          clientset,
		namespace:          namespace,
		configmap:          configmap,
		model:              builtInModel,
		claimsEnforcerFunc: claimsEnforcer,
	}
}

// SetDefaultRole sets a default role to use during enforcement. Will fall back to this role if
// normal enforcement fails
func (e *Enforcer) SetDefaultRole(roleName string) {
	e.defaultRole = roleName
}

// SetClaimsEnforcerFunc sets a claims enforce function during enforcement. The claims enforce function
// can extract claims from JWT token and do the proper enforcement based on user, group or any information
// available in the input parameter list
func (e *Enforcer) SetClaimsEnforcerFunc(claimsEnforcer ClaimsEnforcerFunc) {
	e.claimsEnforcerFunc = claimsEnforcer
}

// Enforce is a wrapper around casbin.Enforce to additionally enforce a default role and a custom
// claims function
func (e *Enforcer) Enforce(rvals ...interface{}) bool {
	return enforce(e.Enforcer, e.defaultRole, e.claimsEnforcerFunc, rvals...)
}

// EnforceRuntimePolicy enforces a policy defined at run-time which augments the built-in and
// user-defined policy. This allows any explicit denies of the built-in, and user-defined policies
// to override the run-time policy. Runs normal enforcement if run-time policy is empty.
func (e *Enforcer) EnforceRuntimePolicy(policy string, rvals ...interface{}) bool {
	var enf *casbin.Enforcer
	var err error
	if policy == "" {
		enf = e.Enforcer
	} else {
		policies := fmt.Sprintf("%s\n%s\n%s", e.builtinPolicy, e.userDefinedPolicy, policy)
		adapter := scas.NewAdapter(policies)
		enf, err = casbin.NewEnforcerSafe(newBuiltInModel(), adapter)
		if err != nil {
			log.Warnf("invalid runtime policy: %s", policy)
			enf = e.Enforcer
		}
	}
	return enforce(enf, e.defaultRole, e.claimsEnforcerFunc, rvals...)
}

// enforce is a helper to additionally check a default role and invoke a custom claims enforcement function
func enforce(enf *casbin.Enforcer, defaultRole string, claimsEnforcerFunc ClaimsEnforcerFunc, rvals ...interface{}) bool {
	// check the default role
	if defaultRole != "" && len(rvals) >= 2 {
		rvals = append([]interface{}{defaultRole}, rvals[1:]...)
		if enf.Enforce(rvals...) {
			return true
		}
	}
	// check if subject is jwt.Claims vs. a normal subject string and run custom claims
	// enforcement func (if set)
	sub := rvals[0]
	switch sub.(type) {
	case string:
		// noop
	case jwt.Claims:
		if claimsEnforcerFunc != nil && claimsEnforcerFunc(sub.(jwt.Claims), rvals...) {
			return true
		}
		rvals = append([]interface{}{""}, rvals[1:]...)
	default:
		rvals = append([]interface{}{""}, rvals[1:]...)
	}
	return enf.Enforce(rvals...)
}

// SetBuiltinPolicy sets a built-in policy, which augments any user defined policies
func (e *Enforcer) SetBuiltinPolicy(policy string) error {
	e.builtinPolicy = policy
	e.adapter.Line = fmt.Sprintf("%s\n%s", e.builtinPolicy, e.userDefinedPolicy)
	return e.LoadPolicy()
}

// SetUserPolicy sets a user policy, augmenting the built-in policy
func (e *Enforcer) SetUserPolicy(policy string) error {
	e.userDefinedPolicy = policy
	e.adapter.Line = fmt.Sprintf("%s\n%s", e.builtinPolicy, e.userDefinedPolicy)
	return e.LoadPolicy()
}

// newInformers returns an informer which watches updates on the rbac configmap
func (e *Enforcer) newInformer() cache.SharedIndexInformer {
	tweakConfigMap := func(options *metav1.ListOptions) {
		cmFieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", e.configmap))
		options.FieldSelector = cmFieldSelector.String()
	}
	return v1.NewFilteredConfigMapInformer(e.clientset, e.namespace, defaultRBACSyncPeriod, cache.Indexers{}, tweakConfigMap)
}

// RunPolicyLoader runs the policy loader which watches policy updates from the configmap and reloads them
func (e *Enforcer) RunPolicyLoader(ctx context.Context) error {
	cm, err := e.clientset.CoreV1().ConfigMaps(e.namespace).Get(e.configmap, metav1.GetOptions{})
	if err != nil {
		if !apierr.IsNotFound(err) {
			return err
		}
	} else {
		err = e.syncUpdate(cm)
		if err != nil {
			return err
		}
	}
	e.runInformer(ctx)
	return nil
}

func (e *Enforcer) runInformer(ctx context.Context) {
	cmInformer := e.newInformer()
	cmInformer.AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				if cm, ok := obj.(*apiv1.ConfigMap); ok {
					err := e.syncUpdate(cm)
					if err != nil {
						log.Error(err)
					} else {
						log.Infof("RBAC ConfigMap '%s' added", e.configmap)
					}
				}
			},
			UpdateFunc: func(old, new interface{}) {
				oldCM := old.(*apiv1.ConfigMap)
				newCM := new.(*apiv1.ConfigMap)
				if oldCM.ResourceVersion == newCM.ResourceVersion {
					return
				}
				err := e.syncUpdate(newCM)
				if err != nil {
					log.Error(err)
				} else {
					log.Infof("RBAC ConfigMap '%s' updated", e.configmap)
				}
			},
		},
	)
	log.Info("Starting rbac config informer")
	cmInformer.Run(ctx.Done())
	log.Info("rbac configmap informer cancelled")
}

// syncUpdate updates the enforcer
func (e *Enforcer) syncUpdate(cm *apiv1.ConfigMap) error {
	e.SetDefaultRole(cm.Data[ConfigMapPolicyDefaultKey])
	policyCSV, ok := cm.Data[ConfigMapPolicyCSVKey]
	if !ok {
		policyCSV = ""
	}
	return e.SetUserPolicy(policyCSV)
}

// ValidatePolicy verifies a policy string is acceptable to casbin
func ValidatePolicy(policy string) error {
	adapter := scas.NewAdapter(policy)
	_, err := casbin.NewEnforcerSafe(newBuiltInModel(), adapter)
	if err != nil {
		return fmt.Errorf("policy syntax error: %s", policy)
	}
	return nil
}

// newBuiltInModel is a helper to return a brand new casbin model from the built-in model string.
// This is needed because it is not safe to re-use the same casbin Model when instantiating new
// casbin enforcers.
func newBuiltInModel() model.Model {
	return casbin.NewModel(modelConf)
}
<commit_msg>Enforces looses user claims if default role is set (#907)<commit_after>package rbac

import (
	"context"
	"fmt"
	"time"

	"github.com/casbin/casbin"
	"github.com/casbin/casbin/model"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gobuffalo/packr"
	scas "github.com/qiangmzsx/string-adapter"
	log "github.com/sirupsen/logrus"
	apiv1 "k8s.io/api/core/v1"
	apierr "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	v1 "k8s.io/client-go/informers/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
)

const (
	ConfigMapPolicyCSVKey     = "policy.csv"
	ConfigMapPolicyDefaultKey = "policy.default"

	builtinModelFile      = "model.conf"
	defaultRBACSyncPeriod = 10 * time.Minute
)

// Enforcer is a wrapper around an Casbin enforcer that:
// * is backed by a kubernetes config map
// * has a predefined RBAC model
// * supports a built-in policy
// * supports a user-defined bolicy
// * supports a custom JWT claims enforce function
type Enforcer struct {
	*casbin.Enforcer
	adapter            *scas.Adapter
	clientset          kubernetes.Interface
	namespace          string
	configmap          string
	claimsEnforcerFunc ClaimsEnforcerFunc

	model             model.Model
	defaultRole       string
	builtinPolicy     string
	userDefinedPolicy string
}

// ClaimsEnforcerFunc is func template to enforce a JWT claims. The subject is replaced
type ClaimsEnforcerFunc func(claims jwt.Claims, rvals ...interface{}) bool

var (
	modelConf string
)

func init() {
	box := packr.NewBox(".")
	modelConf = box.String(builtinModelFile)
}

func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, claimsEnforcer ClaimsEnforcerFunc) *Enforcer {
	adapter := scas.NewAdapter("")
	builtInModel := newBuiltInModel()
	enf := casbin.NewEnforcer(builtInModel, adapter)
	enf.EnableLog(false)
	return &Enforcer{
		Enforcer:           enf,
		adapter:            adapter,
		clientset:          clientset,
		namespace:          namespace,
		configmap:          configmap,
		model:              builtInModel,
		claimsEnforcerFunc: claimsEnforcer,
	}
}

// SetDefaultRole sets a default role to use during enforcement. Will fall back to this role if
// normal enforcement fails
func (e *Enforcer) SetDefaultRole(roleName string) {
	e.defaultRole = roleName
}

// SetClaimsEnforcerFunc sets a claims enforce function during enforcement. The claims enforce function
// can extract claims from JWT token and do the proper enforcement based on user, group or any information
// available in the input parameter list
func (e *Enforcer) SetClaimsEnforcerFunc(claimsEnforcer ClaimsEnforcerFunc) {
	e.claimsEnforcerFunc = claimsEnforcer
}

// Enforce is a wrapper around casbin.Enforce to additionally enforce a default role and a custom
// claims function
func (e *Enforcer) Enforce(rvals ...interface{}) bool {
	return enforce(e.Enforcer, e.defaultRole, e.claimsEnforcerFunc, rvals...)
}

// EnforceRuntimePolicy enforces a policy defined at run-time which augments the built-in and
// user-defined policy. This allows any explicit denies of the built-in, and user-defined policies
// to override the run-time policy. Runs normal enforcement if run-time policy is empty.
func (e *Enforcer) EnforceRuntimePolicy(policy string, rvals ...interface{}) bool {
	var enf *casbin.Enforcer
	var err error
	if policy == "" {
		enf = e.Enforcer
	} else {
		policies := fmt.Sprintf("%s\n%s\n%s", e.builtinPolicy, e.userDefinedPolicy, policy)
		adapter := scas.NewAdapter(policies)
		enf, err = casbin.NewEnforcerSafe(newBuiltInModel(), adapter)
		if err != nil {
			log.Warnf("invalid runtime policy: %s", policy)
			enf = e.Enforcer
		}
	}
	return enforce(enf, e.defaultRole, e.claimsEnforcerFunc, rvals...)
}

// enforce is a helper to additionally check a default role and invoke a custom claims enforcement function
func enforce(enf *casbin.Enforcer, defaultRole string, claimsEnforcerFunc ClaimsEnforcerFunc, rvals ...interface{}) bool {
	// check the default role
	if defaultRole != "" && len(rvals) >= 2 {
		if enf.Enforce(append([]interface{}{defaultRole}, rvals[1:]...)...) {
			return true
		}
	}
	// check if subject is jwt.Claims vs. a normal subject string and run custom claims
	// enforcement func (if set)
	sub := rvals[0]
	switch sub.(type) {
	case string:
		// noop
	case jwt.Claims:
		if claimsEnforcerFunc != nil && claimsEnforcerFunc(sub.(jwt.Claims), rvals...) {
			return true
		}
		rvals = append([]interface{}{""}, rvals[1:]...)
	default:
		rvals = append([]interface{}{""}, rvals[1:]...)
	}
	return enf.Enforce(rvals...)
}

// SetBuiltinPolicy sets a built-in policy, which augments any user defined policies
func (e *Enforcer) SetBuiltinPolicy(policy string) error {
	e.builtinPolicy = policy
	e.adapter.Line = fmt.Sprintf("%s\n%s", e.builtinPolicy, e.userDefinedPolicy)
	return e.LoadPolicy()
}

// SetUserPolicy sets a user policy, augmenting the built-in policy
func (e *Enforcer) SetUserPolicy(policy string) error {
	e.userDefinedPolicy = policy
	e.adapter.Line = fmt.Sprintf("%s\n%s", e.builtinPolicy, e.userDefinedPolicy)
	return e.LoadPolicy()
}

// newInformers returns an informer which watches updates on the rbac configmap
func (e *Enforcer) newInformer() cache.SharedIndexInformer {
	tweakConfigMap := func(options *metav1.ListOptions) {
		cmFieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", e.configmap))
		options.FieldSelector = cmFieldSelector.String()
	}
	return v1.NewFilteredConfigMapInformer(e.clientset, e.namespace, defaultRBACSyncPeriod, cache.Indexers{}, tweakConfigMap)
}

// RunPolicyLoader runs the policy loader which watches policy updates from the configmap and reloads them
func (e *Enforcer) RunPolicyLoader(ctx context.Context) error {
	cm, err := e.clientset.CoreV1().ConfigMaps(e.namespace).Get(e.configmap, metav1.GetOptions{})
	if err != nil {
		if !apierr.IsNotFound(err) {
			return err
		}
	} else {
		err = e.syncUpdate(cm)
		if err != nil {
			return err
		}
	}
	e.runInformer(ctx)
	return nil
}

func (e *Enforcer) runInformer(ctx context.Context) {
	cmInformer := e.newInformer()
	cmInformer.AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				if cm, ok := obj.(*apiv1.ConfigMap); ok {
					err := e.syncUpdate(cm)
					if err != nil {
						log.Error(err)
					} else {
						log.Infof("RBAC ConfigMap '%s' added", e.configmap)
					}
				}
			},
			UpdateFunc: func(old, new interface{}) {
				oldCM := old.(*apiv1.ConfigMap)
				newCM := new.(*apiv1.ConfigMap)
				if oldCM.ResourceVersion == newCM.ResourceVersion {
					return
				}
				err := e.syncUpdate(newCM)
				if err != nil {
					log.Error(err)
				} else {
					log.Infof("RBAC ConfigMap '%s' updated", e.configmap)
				}
			},
		},
	)
	log.Info("Starting rbac config informer")
	cmInformer.Run(ctx.Done())
	log.Info("rbac configmap informer cancelled")
}

// syncUpdate updates the enforcer
func (e *Enforcer) syncUpdate(cm *apiv1.ConfigMap) error {
	e.SetDefaultRole(cm.Data[ConfigMapPolicyDefaultKey])
	policyCSV, ok := cm.Data[ConfigMapPolicyCSVKey]
	if !ok {
		policyCSV = ""
	}
	return e.SetUserPolicy(policyCSV)
}

// ValidatePolicy verifies a policy string is acceptable to casbin
func ValidatePolicy(policy string) error {
	adapter := scas.NewAdapter(policy)
	_, err := casbin.NewEnforcerSafe(newBuiltInModel(), adapter)
	if err != nil {
		return fmt.Errorf("policy syntax error: %s", policy)
	}
	return nil
}

// newBuiltInModel is a helper to return a brand new casbin model from the built-in model string.
// This is needed because it is not safe to re-use the same casbin Model when instantiating new
// casbin enforcers.
func newBuiltInModel() model.Model {
	return casbin.NewModel(modelConf)
}
<|endoftext|>


<commit_before>package utils

import (
	"crypto/sha1"
	"encoding/base64"
	"encoding/csv"
	"errors"
	"os"
	"time"
)

// SHA1Base64String hashes data with SHA1 and encodes the result as base64 string.
func SHA1Base64String(data string) string {
	hash := sha1.Sum([]byte(data))
	return base64.StdEncoding.EncodeToString(hash[:])
}

// ReadHtpasswdFile returns a map of usernames to base64 encoded SHA1 hashed passwords
func ReadHtpasswdFile(filename string) (userPass map[string]string, modified time.Time, err error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, time.Time{}, err
	}
	defer file.Close()

	info, err := file.Stat()
	if err != nil {
		return nil, time.Time{}, err
	}
	modified = info.ModTime()

	csvReader := csv.NewReader(file)
	csvReader.Comma = ':'
	csvReader.Comment = '#'
	csvReader.TrimLeadingSpace = true

	records, err := csvReader.ReadAll()
	if err != nil {
		return nil, time.Time{}, err
	}

	userPass = make(map[string]string)

	for _, record := range records {
		username := record[0]
		password := record[1]
		if len(password) < 5 {
			return nil, time.Time{}, errors.New("Invalid password")
		}
		if password[:5] != "{SHA}" {
			return nil, time.Time{}, errors.New("Unsupported password format, must be SHA1")
		}
		userPass[username] = password[5:]
	}

	return userPass, modified, nil
}
<commit_msg>added WriteHtpasswdFile<commit_after>package utils

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"encoding/csv"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"time"
)

// SHA1Base64String hashes data with SHA1 and encodes the result as base64 string.
func SHA1Base64String(data string) string {
	hash := sha1.Sum([]byte(data))
	return base64.StdEncoding.EncodeToString(hash[:])
}

// ReadHtpasswdFile returns a map of usernames to base64 encoded SHA1 hashed passwords
func ReadHtpasswdFile(filename string) (userPass map[string]string, modified time.Time, err error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, time.Time{}, err
	}
	defer file.Close()

	info, err := file.Stat()
	if err != nil {
		return nil, time.Time{}, err
	}
	modified = info.ModTime()

	csvReader := csv.NewReader(file)
	csvReader.Comma = ':'
	csvReader.Comment = '#'
	csvReader.TrimLeadingSpace = true

	records, err := csvReader.ReadAll()
	if err != nil {
		return nil, time.Time{}, err
	}

	userPass = make(map[string]string)

	for _, record := range records {
		username := record[0]
		password := record[1]
		if len(password) < 5 {
			return nil, time.Time{}, errors.New("Invalid password")
		}
		if password[:5] != "{SHA}" {
			return nil, time.Time{}, errors.New("Unsupported password format, must be SHA1")
		}
		userPass[username] = password[5:]
	}

	return userPass, modified, nil
}

func WriteHtpasswdFile(filename string, userPass map[string]string) error {
	var buf bytes.Buffer
	for user, pass := range userPass {
		_, err := fmt.Fprintf(&buf, "%s:{SHA}%s\n", user, pass)
		if err != nil {
			return err
		}
	}
	return ioutil.WriteFile(filename, buf.Bytes(), 0660)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"time"

	"github.com/codegangsta/cli"
	"github.com/kardianos/osext"
)

var InitialPath string
var InitialGoPath string

var Verbose bool

var SpinnerCharSet = 14
var SpinnerInterval = 50 * time.Millisecond

func main() {
	currentExecutable, _ := osext.Executable()
	vendoredBunchPath := path.Join(".vendor", "bin", "bunch")

	fi1, errStat1 := os.Stat(currentExecutable)
	fi2, errStat2 := os.Stat(vendoredBunchPath)

	if exists, _ := pathExists(vendoredBunchPath); errStat1 == nil && errStat2 == nil && exists && !os.SameFile(fi1, fi2) {
		cmd := exec.Command(vendoredBunchPath, os.Args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		err := cmd.Run()
		if err == nil {
			if cmd.ProcessState.Success() {
				os.Exit(0)
			}
		}

		// if "subbunch" succeeded, exit, otherwise, continue with regular bunch business
		fmt.Println("vendored bunch exited with a non-zero exit status, trying again with global bunch")
	}

	InitialPath = os.Getenv("PATH")
	InitialGoPath = os.Getenv("GOPATH")

	app := cli.NewApp()
	app.Name = "bunch"
	app.Usage = "npm-like tool for managing Go dependencies"
	app.Version = "0.0.1"
	app.Authors = []cli.Author{cli.Author{Name: "Daniil Kulchenko", Email: "daniil@kulchenko.com"}}

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose",
			Usage: "output more information",
		},
	}

	app.Before = func(context *cli.Context) error {
		Verbose = context.GlobalBool("verbose")

		return nil
	}

	app.Commands = []cli.Command{
		{
			Name:    "install",
			Aliases: []string{"i"},
			Usage:   "install package(s)",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "save",
					Usage: "save installed package to Bunchfile",
				},
				cli.BoolFlag{
					Name:  "g",
					Usage: "install package to global $GOPATH instead of vendored directory",
				},
			},
			Action: func(c *cli.Context) {
				installCommand(c, false, true, true)
			},
		},
		{
			Name:    "update",
			Aliases: []string{"u"},
			Usage:   "update package(s)",
			Action: func(c *cli.Context) {
				installCommand(c, true, true, false)
			},
		},
		{
			Name:    "uninstall",
			Aliases: []string{"r"},
			Usage:   "uninstall package(s)",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "save",
					Usage: "save uninstalled package to Bunchfile",
				},
				cli.BoolFlag{
					Name:  "g",
					Usage: "uninstall package from global $GOPATH instead of vendored directory",
				},
			},
			Action: func(c *cli.Context) {
				uninstallCommand(c)
			},
		},
		{
			Name:  "prune",
			Usage: "remove packages not referenced in Bunchfile",
			Action: func(c *cli.Context) {
				pruneCommand(c)
			},
		},
		{
			Name:  "outdated",
			Usage: "list outdated packages",
			Action: func(c *cli.Context) {
				outdatedCommand(c)
			},
		},
		{
			Name:  "lock",
			Usage: "generate a file locking down current versions of dependencies",
			Action: func(c *cli.Context) {
				lockCommand(c)
			},
		},
		{
			Name:  "rebuild",
			Usage: "rebuild all dependencies",
			Action: func(c *cli.Context) {
				installCommand(c, true, false, true)
			},
		},
		{
			Name:  "generate",
			Usage: "generate a Bunchfile based on package imports in current directory",
			Action: func(c *cli.Context) {
				generateCommand(c)
			},
		},
		{
			Name:            "go",
			Usage:           "run a Go command within the vendor environment (e.g. bunch go fmt)",
			SkipFlagParsing: true,
			Action: func(c *cli.Context) {
				goCommand(c)
			},
		},
		{
			Name:            "exec",
			Usage:           "run any command within the vendor environment (e.g. bunch exec make)",
			SkipFlagParsing: true,
			Action: func(c *cli.Context) {
				execCommand(c)
			},
		},
		{
			Name:            "shell",
			Usage:           "start a shell within the vendor environment",
			SkipFlagParsing: true,
			Action: func(c *cli.Context) {
				shellCommand(c)
			},
		},
		{
			Name:  "shim",
			Usage: "sourced in .bash_profile to alias the 'go' tool",
			Action: func(c *cli.Context) {
				shimCommand(c)
			},
		},
	}

	app.Run(os.Args)
}
<commit_msg>bump version (closes #29)<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"time"

	"github.com/codegangsta/cli"
	"github.com/kardianos/osext"
)

var InitialPath string
var InitialGoPath string

var Verbose bool

var SpinnerCharSet = 14
var SpinnerInterval = 50 * time.Millisecond

func main() {
	currentExecutable, _ := osext.Executable()
	vendoredBunchPath := path.Join(".vendor", "bin", "bunch")

	fi1, errStat1 := os.Stat(currentExecutable)
	fi2, errStat2 := os.Stat(vendoredBunchPath)

	if exists, _ := pathExists(vendoredBunchPath); errStat1 == nil && errStat2 == nil && exists && !os.SameFile(fi1, fi2) {
		cmd := exec.Command(vendoredBunchPath, os.Args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		err := cmd.Run()
		if err == nil {
			if cmd.ProcessState.Success() {
				os.Exit(0)
			}
		}

		// if "subbunch" succeeded, exit, otherwise, continue with regular bunch business
		fmt.Println("vendored bunch exited with a non-zero exit status, trying again with global bunch")
	}

	InitialPath = os.Getenv("PATH")
	InitialGoPath = os.Getenv("GOPATH")

	app := cli.NewApp()
	app.Name = "bunch"
	app.Usage = "npm-like tool for managing Go dependencies"
	app.Version = "0.6"
	app.Authors = []cli.Author{cli.Author{Name: "Daniil Kulchenko", Email: "daniil@kulchenko.com"}}

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose",
			Usage: "output more information",
		},
	}

	app.Before = func(context *cli.Context) error {
		Verbose = context.GlobalBool("verbose")

		return nil
	}

	app.Commands = []cli.Command{
		{
			Name:    "install",
			Aliases: []string{"i"},
			Usage:   "install package(s)",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "save",
					Usage: "save installed package to Bunchfile",
				},
				cli.BoolFlag{
					Name:  "g",
					Usage: "install package to global $GOPATH instead of vendored directory",
				},
			},
			Action: func(c *cli.Context) {
				installCommand(c, false, true, true)
			},
		},
		{
			Name:    "update",
			Aliases: []string{"u"},
			Usage:   "update package(s)",
			Action: func(c *cli.Context) {
				installCommand(c, true, true, false)
			},
		},
		{
			Name:    "uninstall",
			Aliases: []string{"r"},
			Usage:   "uninstall package(s)",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "save",
					Usage: "save uninstalled package to Bunchfile",
				},
				cli.BoolFlag{
					Name:  "g",
					Usage: "uninstall package from global $GOPATH instead of vendored directory",
				},
			},
			Action: func(c *cli.Context) {
				uninstallCommand(c)
			},
		},
		{
			Name:  "prune",
			Usage: "remove packages not referenced in Bunchfile",
			Action: func(c *cli.Context) {
				pruneCommand(c)
			},
		},
		{
			Name:  "outdated",
			Usage: "list outdated packages",
			Action: func(c *cli.Context) {
				outdatedCommand(c)
			},
		},
		{
			Name:  "lock",
			Usage: "generate a file locking down current versions of dependencies",
			Action: func(c *cli.Context) {
				lockCommand(c)
			},
		},
		{
			Name:  "rebuild",
			Usage: "rebuild all dependencies",
			Action: func(c *cli.Context) {
				installCommand(c, true, false, true)
			},
		},
		{
			Name:  "generate",
			Usage: "generate a Bunchfile based on package imports in current directory",
			Action: func(c *cli.Context) {
				generateCommand(c)
			},
		},
		{
			Name:            "go",
			Usage:           "run a Go command within the vendor environment (e.g. bunch go fmt)",
			SkipFlagParsing: true,
			Action: func(c *cli.Context) {
				goCommand(c)
			},
		},
		{
			Name:            "exec",
			Usage:           "run any command within the vendor environment (e.g. bunch exec make)",
			SkipFlagParsing: true,
			Action: func(c *cli.Context) {
				execCommand(c)
			},
		},
		{
			Name:            "shell",
			Usage:           "start a shell within the vendor environment",
			SkipFlagParsing: true,
			Action: func(c *cli.Context) {
				shellCommand(c)
			},
		},
		{
			Name:  "shim",
			Usage: "sourced in .bash_profile to alias the 'go' tool",
			Action: func(c *cli.Context) {
				shimCommand(c)
			},
		},
	}

	app.Run(os.Args)
}
<|endoftext|>


<commit_before>/*
Package configdir monitors a config directory for a supplied file suffix. Channel updates
return bytes.Buffer containing all matching files concatenated together.

Updates return
only if the update contains a unique byte array from previous runs by calculating md5 checksums.
*/
package configdir

import (
	"gopkg.in/fsnotify.v1"

	"bytes"
	"crypto/md5"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

//Printer interface just to make a passable Logger
//
//
type Printer interface {
	Print(...interface{})
	Printf(string, ...interface{})
	Println(...interface{})
}

var logger Printer

//DirectoryUpdates starts a goroutine and returns bytes.Buffer receive channel
//
//
func DirectoryUpdates(dir, suffix string, p Printer) (chan []byte, error) {
	if p != nil {
		logger = p
	} else {
		logger = log.New(os.Stderr, "[dircfg]",
			log.Ldate|log.Ltime|log.Lshortfile,
		)

	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	err = watcher.Add(dir)
	if err != nil {
		return nil, err
	}

	dirBytesCh := make(chan []byte)
	userCb := func(cfgB []byte, cfgChecksum []byte, e error) {
		if err != nil {
			logger.Print(e)
		} else {
			dirBytesCh <- cfgB

		}
	}

	if err := DirectoryUpdatesF(dir, suffix, userCb); err != nil {
		return nil, err
	}

	return dirBytesCh, nil
}

//DirectoryUpdatesF similar to DirectoryUpdates caller's closure on updates
//
//more info packed into the callback: directory bytes, checksum bytes, andy errors encountered
func DirectoryUpdatesF(dir, suffix string, userCb func([]byte, []byte, error)) error {

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	err = watcher.Add(dir)
	if err != nil {
		return err
	}

	go func() {
		var (
			dirBytes           []byte
			currentMD5, tmpMD5 []byte
		)
		dirBytes, tmpMD5 = bytesFromDir(dir, suffix)
		currentMD5 = make([]byte, len(tmpMD5))
		copy(currentMD5, tmpMD5)
		userCb(dirBytes, currentMD5, nil)

		evI := func(o fsnotify.Op) bool {
			switch {
			case o&fsnotify.Write == fsnotify.Write:
				return true
			case o&fsnotify.Rename == fsnotify.Rename:
				return true

			}

			return false
		}
		updates := 0

		for {
			select {
			case ev := <-watcher.Events:

				if strings.HasSuffix(ev.Name, suffix) && evI(ev.Op) {
					dirBytes, tmpMD5 = bytesFromDir(dir, suffix)
				}
			case err := <-watcher.Errors:
				userCb(nil, nil, err)
				logger.Println("error:", err)
			}
			if !bytes.Equal(tmpMD5, currentMD5) {
				copy(currentMD5, tmpMD5)
				userCb(dirBytes, currentMD5, nil)
				updates++
			}
		}
	}()

	return nil
}

func bytesFromDir(dir, suffix string) ([]byte, []byte) {

	var buf *bytes.Buffer = &bytes.Buffer{}
	md5 := md5.New()

	files, _ := ioutil.ReadDir(dir)
	for _, f := range files {
		if strings.HasSuffix(f.Name(), suffix) {

			if b, err := ioutil.ReadFile(filepath.Join(dir, f.Name())); err != nil {
				logger.Println(err)
				logger.Printf("unable tor read file: %s, '%s', skipping", f.Name(), err)
			} else {
				buf.Write(b)
				md5.Write(b)
			}

		}
	}
	return buf.Bytes(), md5.Sum(nil)

}
<commit_msg>try to improve clarity of func signature<commit_after>/*
Package configdir monitors a config directory for a supplied file suffix. Channel updates
return bytes.Buffer containing all matching files concatenated together.

Updates return
only if the update contains a unique byte array from previous runs by calculating md5 checksums.
*/
package configdir

import (
	"gopkg.in/fsnotify.v1"

	"bytes"
	"crypto/md5"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

//Printer interface just to make a passable Logger
//
//
type Printer interface {
	Print(...interface{})
	Printf(string, ...interface{})
	Println(...interface{})
}

var logger Printer

//DirectoryUpdates starts a goroutine and returns bytes.Buffer receive channel
//
//
func DirectoryUpdates(dir, suffix string, p Printer) (chan []byte, error) {
	if p != nil {
		logger = p
	} else {
		logger = log.New(os.Stderr, "[dircfg]",
			log.Ldate|log.Ltime|log.Lshortfile,
		)

	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	err = watcher.Add(dir)
	if err != nil {
		return nil, err
	}

	dirBytesCh := make(chan []byte)
	userCb := func(cfgB []byte, cfgChecksum []byte, e error) {
		if err != nil {
			logger.Print(e)
		} else {
			dirBytesCh <- cfgB

		}
	}

	if err := DirectoryUpdatesF(dir, suffix, userCb); err != nil {
		return nil, err
	}

	return dirBytesCh, nil
}

//DirectoryUpdatesF similar to DirectoryUpdates but instead feeds daata to caller's closure on updates
//
//closure is called with directory bytes, checksum bytes, and any error
func DirectoryUpdatesF(dir, suffix string, userCb func([]byte, []byte, error)) error {

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	err = watcher.Add(dir)
	if err != nil {
		return err
	}

	go func() {
		var (
			dirBytes           []byte
			currentMD5, tmpMD5 []byte
		)
		dirBytes, tmpMD5 = bytesFromDir(dir, suffix)
		currentMD5 = make([]byte, len(tmpMD5))
		copy(currentMD5, tmpMD5)
		userCb(dirBytes, currentMD5, nil)

		evI := func(o fsnotify.Op) bool {
			switch {
			case o&fsnotify.Write == fsnotify.Write:
				return true
			case o&fsnotify.Rename == fsnotify.Rename:
				return true

			}

			return false
		}
		updates := 0

		for {
			select {
			case ev := <-watcher.Events:

				if strings.HasSuffix(ev.Name, suffix) && evI(ev.Op) {
					dirBytes, tmpMD5 = bytesFromDir(dir, suffix)
				}
			case err := <-watcher.Errors:
				userCb(nil, nil, err)
				logger.Println("error:", err)
			}
			if !bytes.Equal(tmpMD5, currentMD5) {
				copy(currentMD5, tmpMD5)
				userCb(dirBytes, currentMD5, nil)
				updates++
			}
		}
	}()

	return nil
}

func bytesFromDir(dir, suffix string) ([]byte, []byte) {

	var buf *bytes.Buffer = &bytes.Buffer{}
	md5 := md5.New()

	files, _ := ioutil.ReadDir(dir)
	for _, f := range files {
		if strings.HasSuffix(f.Name(), suffix) {

			if b, err := ioutil.ReadFile(filepath.Join(dir, f.Name())); err != nil {
				logger.Println(err)
				logger.Printf("unable tor read file: %s, '%s', skipping", f.Name(), err)
			} else {
				buf.Write(b)
				md5.Write(b)
			}

		}
	}
	return buf.Bytes(), md5.Sum(nil)

}
<|endoftext|>


<commit_before>// Copyright 2011 The Go 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 syscall

// Constants
const (
	// Invented values to support what package os expects.
	O_CREAT    = 0x02000
	O_APPEND   = 0x00400
	O_NOCTTY   = 0x00000
	O_NONBLOCK = 0x00000
	O_SYNC     = 0x00000
	O_ASYNC    = 0x00000

	S_IFMT   = 0x1f000
	S_IFIFO  = 0x1000
	S_IFCHR  = 0x2000
	S_IFDIR  = 0x4000
	S_IFBLK  = 0x6000
	S_IFREG  = 0x8000
	S_IFLNK  = 0xa000
	S_IFSOCK = 0xc000
)

// Errors
var (
	EINVAL       = NewError("bad arg in system call")
	ENOTDIR      = NewError("not a directory")
	EISDIR       = NewError("file is a directory")
	ENOENT       = NewError("file does not exist")
	EEXIST       = NewError("file already exists")
	EMFILE       = NewError("no free file descriptors")
	EIO          = NewError("i/o error")
	ENAMETOOLONG = NewError("file name too long")
	EINTR        = NewError("interrupted")
	EPERM        = NewError("permission denied")
	EBUSY        = NewError("no free devices")
	ETIMEDOUT    = NewError("connection timed out")
	EPLAN9       = NewError("not supported by plan 9")

	// The following errors do not correspond to any
	// Plan 9 system messages. Invented to support
	// what package os and others expect.
	EACCES       = NewError("access permission denied")
	EAFNOSUPPORT = NewError("address family not supported by protocol")
)

// Notes
const (
	SIGABRT = Note("abort")
	SIGALRM = Note("alarm")
	SIGHUP  = Note("hangup")
	SIGINT  = Note("interrupt")
	SIGKILL = Note("kill")
	SIGTERM = Note("interrupt")
)
<commit_msg>syscall: define ESPIPE on Plan 9<commit_after>// Copyright 2011 The Go 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 syscall

// Constants
const (
	// Invented values to support what package os expects.
	O_CREAT    = 0x02000
	O_APPEND   = 0x00400
	O_NOCTTY   = 0x00000
	O_NONBLOCK = 0x00000
	O_SYNC     = 0x00000
	O_ASYNC    = 0x00000

	S_IFMT   = 0x1f000
	S_IFIFO  = 0x1000
	S_IFCHR  = 0x2000
	S_IFDIR  = 0x4000
	S_IFBLK  = 0x6000
	S_IFREG  = 0x8000
	S_IFLNK  = 0xa000
	S_IFSOCK = 0xc000
)

// Errors
var (
	EINVAL       = NewError("bad arg in system call")
	ENOTDIR      = NewError("not a directory")
	EISDIR       = NewError("file is a directory")
	ENOENT       = NewError("file does not exist")
	EEXIST       = NewError("file already exists")
	EMFILE       = NewError("no free file descriptors")
	EIO          = NewError("i/o error")
	ENAMETOOLONG = NewError("file name too long")
	EINTR        = NewError("interrupted")
	EPERM        = NewError("permission denied")
	EBUSY        = NewError("no free devices")
	ETIMEDOUT    = NewError("connection timed out")
	EPLAN9       = NewError("not supported by plan 9")

	// The following errors do not correspond to any
	// Plan 9 system messages. Invented to support
	// what package os and others expect.
	EACCES       = NewError("access permission denied")
	EAFNOSUPPORT = NewError("address family not supported by protocol")
	ESPIPE       = NewError("illegal seek")
)

// Notes
const (
	SIGABRT = Note("abort")
	SIGALRM = Note("alarm")
	SIGHUP  = Note("hangup")
	SIGINT  = Note("interrupt")
	SIGKILL = Note("kill")
	SIGTERM = Note("interrupt")
)
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package awstasks

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/elbv2"
	"k8s.io/klog/v2"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter"
)

// +kops:fitask
type TargetGroup struct {
	Name      *string
	Lifecycle fi.Lifecycle
	VPC       *VPC
	Tags      map[string]string
	Port      *int64
	Protocol  *string

	// ARN is the Amazon Resource Name for the Target Group
	ARN *string

	// Shared is set if this is an external LB (one we don't create or own)
	Shared *bool

	Interval           *int64
	HealthyThreshold   *int64
	UnhealthyThreshold *int64
}

var _ fi.CompareWithID = &TargetGroup{}

func (e *TargetGroup) CompareWithID() *string {
	return e.ARN
}

func (e *TargetGroup) Find(c *fi.Context) (*TargetGroup, error) {
	cloud := c.Cloud.(awsup.AWSCloud)

	request := &elbv2.DescribeTargetGroupsInput{}
	if e.ARN != nil {
		request.TargetGroupArns = []*string{e.ARN}
	} else if e.Name != nil {
		request.Names = []*string{e.Name}
	}

	response, err := cloud.ELBV2().DescribeTargetGroups(request)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == elbv2.ErrCodeTargetGroupNotFoundException {
			if !fi.ValueOf(e.Shared) {
				return nil, nil
			}
		}
		return nil, fmt.Errorf("error describing targetgroup %s: %v", *e.Name, err)
	}

	if len(response.TargetGroups) > 1 {
		return nil, fmt.Errorf("found %d TargetGroups with ID %q, expected 1", len(response.TargetGroups), fi.ValueOf(e.Name))
	} else if len(response.TargetGroups) == 0 {
		return nil, nil
	}

	tg := response.TargetGroups[0]

	actual := &TargetGroup{
		Name:               tg.TargetGroupName,
		Port:               tg.Port,
		Protocol:           tg.Protocol,
		ARN:                tg.TargetGroupArn,
		Interval:           tg.HealthCheckIntervalSeconds,
		HealthyThreshold:   tg.HealthyThresholdCount,
		UnhealthyThreshold: tg.UnhealthyThresholdCount,
		VPC:                &VPC{ID: tg.VpcId},
	}
	// Interval cannot be changed after TargetGroup creation
	e.Interval = actual.Interval

	e.ARN = tg.TargetGroupArn

	tagsResp, err := cloud.ELBV2().DescribeTags(&elbv2.DescribeTagsInput{
		ResourceArns: []*string{tg.TargetGroupArn},
	})
	if err != nil {
		return nil, err
	}
	tags := make(map[string]string)
	for _, tagDesc := range tagsResp.TagDescriptions {
		for _, tag := range tagDesc.Tags {
			tags[fi.ValueOf(tag.Key)] = fi.ValueOf(tag.Value)
		}
	}
	actual.Tags = tags

	// Prevent spurious changes
	actual.Lifecycle = e.Lifecycle
	actual.Shared = e.Shared

	return actual, nil
}

func FindTargetGroupByName(cloud awsup.AWSCloud, findName string) (*elbv2.TargetGroup, error) {
	klog.V(2).Infof("Listing all TargetGroups for FindTargetGroupByName")

	request := &elbv2.DescribeTargetGroupsInput{
		Names: []*string{aws.String(findName)},
	}
	// ELB DescribeTags has a limit of 20 names, so we set the page size here to 20 also
	request.PageSize = aws.Int64(20)

	resp, err := cloud.ELBV2().DescribeTargetGroups(request)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == elbv2.ErrCodeTargetGroupNotFoundException {
			return nil, nil
		}
		return nil, fmt.Errorf("error describing TargetGroups: %v", err)
	}
	if len(resp.TargetGroups) == 0 {
		return nil, nil
	}

	if len(resp.TargetGroups) != 1 {
		return nil, fmt.Errorf("Found multiple TargetGroups with Name %q", findName)
	}

	return resp.TargetGroups[0], nil
}

func (e *TargetGroup) Run(c *fi.Context) error {
	return fi.DefaultDeltaRunMethod(e, c)
}

func (_ *TargetGroup) ShouldCreate(a, e, changes *TargetGroup) (bool, error) {
	if fi.ValueOf(e.Shared) {
		return false, nil
	}
	return true, nil
}

func (s *TargetGroup) CheckChanges(a, e, changes *TargetGroup) error {
	return nil
}

func (_ *TargetGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *TargetGroup) error {
	shared := fi.ValueOf(e.Shared)
	if shared {
		return nil
	}

	// You register targets for your Network Load Balancer with a target group. By default, the load balancer sends requests
	// to registered targets using the port and protocol that you specified for the target group. You can override this port
	// when you register each target with the target group.

	if a == nil {
		request := &elbv2.CreateTargetGroupInput{
			Name:                       e.Name,
			Port:                       e.Port,
			Protocol:                   e.Protocol,
			VpcId:                      e.VPC.ID,
			HealthCheckIntervalSeconds: e.Interval,
			HealthyThresholdCount:      e.HealthyThreshold,
			UnhealthyThresholdCount:    e.UnhealthyThreshold,
			Tags:                       awsup.ELBv2Tags(e.Tags),
		}

		klog.V(2).Infof("Creating Target Group for NLB")
		response, err := t.Cloud.ELBV2().CreateTargetGroup(request)
		if err != nil {
			return fmt.Errorf("Error creating target group for NLB : %v", err)
		}

		targetGroupArn := *response.TargetGroups[0].TargetGroupArn
		e.ARN = fi.PtrTo(targetGroupArn)
	} else {
		if a.ARN != nil {
			if err := t.AddELBV2Tags(fi.ValueOf(a.ARN), e.Tags); err != nil {
				return err
			}
		}
	}
	return nil
}

// OrderTargetGroupsByName implements sort.Interface for []OrderTargetGroupsByName, based on port number
type OrderTargetGroupsByName []*TargetGroup

func (a OrderTargetGroupsByName) Len() int      { return len(a) }
func (a OrderTargetGroupsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a OrderTargetGroupsByName) Less(i, j int) bool {
	return fi.ValueOf(a[i].Name) < fi.ValueOf(a[j].Name)
}

type terraformTargetGroup struct {
	Name        string                          `cty:"name"`
	Port        int64                           `cty:"port"`
	Protocol    string                          `cty:"protocol"`
	VPCID       terraformWriter.Literal         `cty:"vpc_id"`
	Tags        map[string]string               `cty:"tags"`
	HealthCheck terraformTargetGroupHealthCheck `cty:"health_check"`
}

type terraformTargetGroupHealthCheck struct {
	Interval           int64  `cty:"interval"`
	HealthyThreshold   int64  `cty:"healthy_threshold"`
	UnhealthyThreshold int64  `cty:"unhealthy_threshold"`
	Protocol           string `cty:"protocol"`
}

func (_ *TargetGroup) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *TargetGroup) error {
	shared := fi.ValueOf(e.Shared)
	if shared {
		return nil
	}

	if e.VPC == nil {
		return fmt.Errorf("Missing VPC task from target group:\n%v\n%v", e, e.VPC)
	}

	tf := &terraformTargetGroup{
		Name:     *e.Name,
		Port:     *e.Port,
		Protocol: *e.Protocol,
		VPCID:    *e.VPC.TerraformLink(),
		Tags:     e.Tags,
		HealthCheck: terraformTargetGroupHealthCheck{
			Interval:           *e.Interval,
			HealthyThreshold:   *e.HealthyThreshold,
			UnhealthyThreshold: *e.UnhealthyThreshold,
			Protocol:           elbv2.ProtocolEnumTcp,
		},
	}

	return t.RenderResource("aws_lb_target_group", *e.Name, tf)
}

func (e *TargetGroup) TerraformLink(params ...string) *terraformWriter.Literal {
	shared := fi.ValueOf(e.Shared)
	if shared {
		if e.ARN != nil {
			return terraformWriter.LiteralFromStringValue(*e.ARN)
		} else {
			klog.Warningf("ID not set on shared Target Group %v", e)
		}
	}
	return terraformWriter.LiteralProperty("aws_lb_target_group", *e.Name, "id")
}
<commit_msg>Store pointer to terraformWriter.Literal<commit_after>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package awstasks

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/elbv2"
	"k8s.io/klog/v2"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
	"k8s.io/kops/upup/pkg/fi/cloudup/terraformWriter"
)

// +kops:fitask
type TargetGroup struct {
	Name      *string
	Lifecycle fi.Lifecycle
	VPC       *VPC
	Tags      map[string]string
	Port      *int64
	Protocol  *string

	// ARN is the Amazon Resource Name for the Target Group
	ARN *string

	// Shared is set if this is an external LB (one we don't create or own)
	Shared *bool

	Interval           *int64
	HealthyThreshold   *int64
	UnhealthyThreshold *int64
}

var _ fi.CompareWithID = &TargetGroup{}

func (e *TargetGroup) CompareWithID() *string {
	return e.ARN
}

func (e *TargetGroup) Find(c *fi.Context) (*TargetGroup, error) {
	cloud := c.Cloud.(awsup.AWSCloud)

	request := &elbv2.DescribeTargetGroupsInput{}
	if e.ARN != nil {
		request.TargetGroupArns = []*string{e.ARN}
	} else if e.Name != nil {
		request.Names = []*string{e.Name}
	}

	response, err := cloud.ELBV2().DescribeTargetGroups(request)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == elbv2.ErrCodeTargetGroupNotFoundException {
			if !fi.ValueOf(e.Shared) {
				return nil, nil
			}
		}
		return nil, fmt.Errorf("error describing targetgroup %s: %v", *e.Name, err)
	}

	if len(response.TargetGroups) > 1 {
		return nil, fmt.Errorf("found %d TargetGroups with ID %q, expected 1", len(response.TargetGroups), fi.ValueOf(e.Name))
	} else if len(response.TargetGroups) == 0 {
		return nil, nil
	}

	tg := response.TargetGroups[0]

	actual := &TargetGroup{
		Name:               tg.TargetGroupName,
		Port:               tg.Port,
		Protocol:           tg.Protocol,
		ARN:                tg.TargetGroupArn,
		Interval:           tg.HealthCheckIntervalSeconds,
		HealthyThreshold:   tg.HealthyThresholdCount,
		UnhealthyThreshold: tg.UnhealthyThresholdCount,
		VPC:                &VPC{ID: tg.VpcId},
	}
	// Interval cannot be changed after TargetGroup creation
	e.Interval = actual.Interval

	e.ARN = tg.TargetGroupArn

	tagsResp, err := cloud.ELBV2().DescribeTags(&elbv2.DescribeTagsInput{
		ResourceArns: []*string{tg.TargetGroupArn},
	})
	if err != nil {
		return nil, err
	}
	tags := make(map[string]string)
	for _, tagDesc := range tagsResp.TagDescriptions {
		for _, tag := range tagDesc.Tags {
			tags[fi.ValueOf(tag.Key)] = fi.ValueOf(tag.Value)
		}
	}
	actual.Tags = tags

	// Prevent spurious changes
	actual.Lifecycle = e.Lifecycle
	actual.Shared = e.Shared

	return actual, nil
}

func FindTargetGroupByName(cloud awsup.AWSCloud, findName string) (*elbv2.TargetGroup, error) {
	klog.V(2).Infof("Listing all TargetGroups for FindTargetGroupByName")

	request := &elbv2.DescribeTargetGroupsInput{
		Names: []*string{aws.String(findName)},
	}
	// ELB DescribeTags has a limit of 20 names, so we set the page size here to 20 also
	request.PageSize = aws.Int64(20)

	resp, err := cloud.ELBV2().DescribeTargetGroups(request)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok && aerr.Code() == elbv2.ErrCodeTargetGroupNotFoundException {
			return nil, nil
		}
		return nil, fmt.Errorf("error describing TargetGroups: %v", err)
	}
	if len(resp.TargetGroups) == 0 {
		return nil, nil
	}

	if len(resp.TargetGroups) != 1 {
		return nil, fmt.Errorf("Found multiple TargetGroups with Name %q", findName)
	}

	return resp.TargetGroups[0], nil
}

func (e *TargetGroup) Run(c *fi.Context) error {
	return fi.DefaultDeltaRunMethod(e, c)
}

func (_ *TargetGroup) ShouldCreate(a, e, changes *TargetGroup) (bool, error) {
	if fi.ValueOf(e.Shared) {
		return false, nil
	}
	return true, nil
}

func (s *TargetGroup) CheckChanges(a, e, changes *TargetGroup) error {
	return nil
}

func (_ *TargetGroup) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *TargetGroup) error {
	shared := fi.ValueOf(e.Shared)
	if shared {
		return nil
	}

	// You register targets for your Network Load Balancer with a target group. By default, the load balancer sends requests
	// to registered targets using the port and protocol that you specified for the target group. You can override this port
	// when you register each target with the target group.

	if a == nil {
		request := &elbv2.CreateTargetGroupInput{
			Name:                       e.Name,
			Port:                       e.Port,
			Protocol:                   e.Protocol,
			VpcId:                      e.VPC.ID,
			HealthCheckIntervalSeconds: e.Interval,
			HealthyThresholdCount:      e.HealthyThreshold,
			UnhealthyThresholdCount:    e.UnhealthyThreshold,
			Tags:                       awsup.ELBv2Tags(e.Tags),
		}

		klog.V(2).Infof("Creating Target Group for NLB")
		response, err := t.Cloud.ELBV2().CreateTargetGroup(request)
		if err != nil {
			return fmt.Errorf("Error creating target group for NLB : %v", err)
		}

		targetGroupArn := *response.TargetGroups[0].TargetGroupArn
		e.ARN = fi.PtrTo(targetGroupArn)
	} else {
		if a.ARN != nil {
			if err := t.AddELBV2Tags(fi.ValueOf(a.ARN), e.Tags); err != nil {
				return err
			}
		}
	}
	return nil
}

// OrderTargetGroupsByName implements sort.Interface for []OrderTargetGroupsByName, based on port number
type OrderTargetGroupsByName []*TargetGroup

func (a OrderTargetGroupsByName) Len() int      { return len(a) }
func (a OrderTargetGroupsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a OrderTargetGroupsByName) Less(i, j int) bool {
	return fi.ValueOf(a[i].Name) < fi.ValueOf(a[j].Name)
}

type terraformTargetGroup struct {
	Name        string                          `cty:"name"`
	Port        int64                           `cty:"port"`
	Protocol    string                          `cty:"protocol"`
	VPCID       *terraformWriter.Literal        `cty:"vpc_id"`
	Tags        map[string]string               `cty:"tags"`
	HealthCheck terraformTargetGroupHealthCheck `cty:"health_check"`
}

type terraformTargetGroupHealthCheck struct {
	Interval           int64  `cty:"interval"`
	HealthyThreshold   int64  `cty:"healthy_threshold"`
	UnhealthyThreshold int64  `cty:"unhealthy_threshold"`
	Protocol           string `cty:"protocol"`
}

func (_ *TargetGroup) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *TargetGroup) error {
	shared := fi.ValueOf(e.Shared)
	if shared {
		return nil
	}

	if e.VPC == nil {
		return fmt.Errorf("Missing VPC task from target group:\n%v\n%v", e, e.VPC)
	}

	tf := &terraformTargetGroup{
		Name:     *e.Name,
		Port:     *e.Port,
		Protocol: *e.Protocol,
		VPCID:    e.VPC.TerraformLink(),
		Tags:     e.Tags,
		HealthCheck: terraformTargetGroupHealthCheck{
			Interval:           *e.Interval,
			HealthyThreshold:   *e.HealthyThreshold,
			UnhealthyThreshold: *e.UnhealthyThreshold,
			Protocol:           elbv2.ProtocolEnumTcp,
		},
	}

	return t.RenderResource("aws_lb_target_group", *e.Name, tf)
}

func (e *TargetGroup) TerraformLink() *terraformWriter.Literal {
	shared := fi.ValueOf(e.Shared)
	if shared {
		if e.ARN != nil {
			return terraformWriter.LiteralFromStringValue(*e.ARN)
		} else {
			klog.Warningf("ID not set on shared Target Group %v", e)
		}
	}
	return terraformWriter.LiteralProperty("aws_lb_target_group", *e.Name, "id")
}
<|endoftext|>