<commit_before>package sshd

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/shazow/ssh-chat/sshd/terminal"
	"golang.org/x/crypto/ssh"
)

var keepaliveInterval = time.Second * 30
var keepaliveRequest = "keepalive@ssh-chat"

// ErrNoSessionChannel is returned when there is no session channel.
var ErrNoSessionChannel = errors.New("no session channel")

// ErrNotSessionChannel is returned when a channel is not a session channel.
var ErrNotSessionChannel = errors.New("terminal requires session channel")

// Connection is an interface with fields necessary to operate an sshd host.
type Connection interface {
	PublicKey() ssh.PublicKey
	RemoteAddr() net.Addr
	Name() string
	ClientVersion() []byte
	Close() error
}

type sshConn struct {
	*ssh.ServerConn
}

func (c sshConn) PublicKey() ssh.PublicKey {
	if c.Permissions == nil {
		return nil
	}

	s, ok := c.Permissions.Extensions["pubkey"]
	if !ok {
		return nil
	}

	key, err := ssh.ParsePublicKey([]byte(s))
	if err != nil {
		return nil
	}

	return key
}

func (c sshConn) Name() string {
	return c.User()
}

// Terminal extends ssh/terminal to include a close method
type Terminal struct {
	terminal.Terminal
	Conn    Connection
	Channel ssh.Channel

	done      chan struct{}
	closeOnce sync.Once
}

// Make new terminal from a session channel
func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) {
	if ch.ChannelType() != "session" {
		return nil, ErrNotSessionChannel
	}
	channel, requests, err := ch.Accept()
	if err != nil {
		return nil, err
	}
	term := Terminal{
		Terminal: *terminal.NewTerminal(channel, "Connecting..."),
		Conn:     sshConn{conn},
		Channel:  channel,

		done: make(chan struct{}),
	}

	go term.listen(requests)

	go func() {
		// Keep-Alive Ticker
		ticker := time.Tick(keepaliveInterval)
		for {
			select {
			case <-ticker:
				_, err := channel.SendRequest(keepaliveRequest, true, nil)
				if err != nil {
					// Connection is gone
					logger.Printf("[%s] Keepalive failed, closing terminal: %s", term.Conn.RemoteAddr(), err)
					term.Close()
					return
				}
			case <-term.done:
				return
			}
		}
	}()

	return &term, nil
}

// NewSession Finds a session channel and make a Terminal from it
func NewSession(conn *ssh.ServerConn, channels <-chan ssh.NewChannel) (*Terminal, error) {
	// Make a terminal from the first session found
	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			logger.Printf("[%s] Ignored channel type: %s", conn.RemoteAddr(), t)
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		return NewTerminal(conn, ch)
	}

	return nil, ErrNoSessionChannel
}

// Close terminal and ssh connection
func (t *Terminal) Close() error {
	var err error
	t.closeOnce.Do(func() {
		close(t.done)
		t.Channel.Close()
		err = t.Conn.Close()
	})
	return err
}

// Negotiate terminal type and settings
func (t *Terminal) listen(requests <-chan *ssh.Request) {
	hasShell := false

	for req := range requests {
		var width, height int
		var ok bool

		switch req.Type {
		case "shell":
			if !hasShell {
				ok = true
				hasShell = true
			}
		case "pty-req":
			width, height, ok = parsePtyRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		case "window-change":
			width, height, ok = parseWinchRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		}

		if req.WantReply {
			req.Reply(ok, nil)
		}
	}
}
<commit_msg>sshd: Add Terminal.Env()<commit_after>package sshd

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/shazow/ssh-chat/sshd/terminal"
	"golang.org/x/crypto/ssh"
)

var keepaliveInterval = time.Second * 30
var keepaliveRequest = "keepalive@ssh-chat"

// ErrNoSessionChannel is returned when there is no session channel.
var ErrNoSessionChannel = errors.New("no session channel")

// ErrNotSessionChannel is returned when a channel is not a session channel.
var ErrNotSessionChannel = errors.New("terminal requires session channel")

// Connection is an interface with fields necessary to operate an sshd host.
type Connection interface {
	PublicKey() ssh.PublicKey
	RemoteAddr() net.Addr
	Name() string
	ClientVersion() []byte
	Close() error
}

type sshConn struct {
	*ssh.ServerConn
}

func (c sshConn) PublicKey() ssh.PublicKey {
	if c.Permissions == nil {
		return nil
	}

	s, ok := c.Permissions.Extensions["pubkey"]
	if !ok {
		return nil
	}

	key, err := ssh.ParsePublicKey([]byte(s))
	if err != nil {
		return nil
	}

	return key
}

func (c sshConn) Name() string {
	return c.User()
}

// EnvVar is an environment variable key-value pair
type EnvVar struct {
	Key   string
	Value string
}

func (v EnvVar) String() string {
	return v.Key + "=" + v.Value
}

// Env is a wrapper type around []EnvVar with some helper methods
type Env []EnvVar

// Get returns the latest value for a given key, or empty string if not found
func (e Env) Get(key string) string {
	for i := len(e) - 1; i >= 0; i-- {
		if e[i].Key == key {
			return e[i].Value
		}
	}
	return ""
}

// Terminal extends ssh/terminal to include a close method
type Terminal struct {
	terminal.Terminal
	Conn    Connection
	Channel ssh.Channel

	done      chan struct{}
	closeOnce sync.Once

	mu  sync.Mutex
	env []EnvVar
}

// Make new terminal from a session channel
func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) {
	if ch.ChannelType() != "session" {
		return nil, ErrNotSessionChannel
	}
	channel, requests, err := ch.Accept()
	if err != nil {
		return nil, err
	}
	term := Terminal{
		Terminal: *terminal.NewTerminal(channel, "Connecting..."),
		Conn:     sshConn{conn},
		Channel:  channel,

		done: make(chan struct{}),
	}

	go term.listen(requests)

	go func() {
		// Keep-Alive Ticker
		ticker := time.Tick(keepaliveInterval)
		for {
			select {
			case <-ticker:
				_, err := channel.SendRequest(keepaliveRequest, true, nil)
				if err != nil {
					// Connection is gone
					logger.Printf("[%s] Keepalive failed, closing terminal: %s", term.Conn.RemoteAddr(), err)
					term.Close()
					return
				}
			case <-term.done:
				return
			}
		}
	}()

	return &term, nil
}

// NewSession Finds a session channel and make a Terminal from it
func NewSession(conn *ssh.ServerConn, channels <-chan ssh.NewChannel) (*Terminal, error) {
	// Make a terminal from the first session found
	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			logger.Printf("[%s] Ignored channel type: %s", conn.RemoteAddr(), t)
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		return NewTerminal(conn, ch)
	}

	return nil, ErrNoSessionChannel
}

// Close terminal and ssh connection
func (t *Terminal) Close() error {
	var err error
	t.closeOnce.Do(func() {
		close(t.done)
		t.Channel.Close()
		err = t.Conn.Close()
	})
	return err
}

// Negotiate terminal type and settings
func (t *Terminal) listen(requests <-chan *ssh.Request) {
	hasShell := false

	for req := range requests {
		var width, height int
		var ok bool

		switch req.Type {
		case "shell":
			if !hasShell {
				ok = true
				hasShell = true
			}
		case "pty-req":
			width, height, ok = parsePtyRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		case "window-change":
			width, height, ok = parseWinchRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		case "env":
			var v EnvVar
			if err := ssh.Unmarshal(req.Payload, &v); err != nil {
				t.mu.Lock()
				t.env = append(t.env, v)
				t.mu.Unlock()
				ok = true
			}
		}

		if req.WantReply {
			req.Reply(ok, nil)
		}
	}
}

// Env returns a list of environment key-values that have been set. They are
// returned in the order that they have been set, there is no deduplication or
// other pre-processing applied.
func (t *Terminal) Env() Env {
	t.mu.Lock()
	defer t.mu.Unlock()
	return Env(t.env)
}
<|endoftext|>


<commit_before>package html

import (
	"fmt"
	"io"
	"time"

	"github.com/Symantec/Dominator/lib/format"
)

func writeFooter(writer io.Writer) {
	fmt.Fprintf(writer, "Page generated at: %s
\n",
		time.Now().Format(format.TimeFormatSeconds))
}
<commit_msg>Show Go runtime version in HTML footer.<commit_after>package html

import (
	"fmt"
	"io"
	"runtime"
	"time"

	"github.com/Symantec/Dominator/lib/format"
)

func writeFooter(writer io.Writer) {
	fmt.Fprintf(writer, "Page generated at: %s with %s
\n",
		time.Now().Format(format.TimeFormatSeconds),
		runtime.Version())
}
<|endoftext|>


<commit_before>package drouter

import (
	"fmt"
	"net"
	"testing"

	log "github.com/Sirupsen/logrus"
	logtest "github.com/Sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	dockerTypes "github.com/docker/engine-api/types"
	dockerNTypes "github.com/docker/engine-api/types/network"
	"github.com/vishvananda/netlink"
)

const (
	NET_NAME  = "drntest_n%v"
	NET_GW    = "192.168.242.%v"
	NET_IPNET = "192.168.242.%v/29"
	DR_INST   = "dr_test"
)

func createNetwork(n int, dr bool, t *testing.T) string {
	name := fmt.Sprintf(NET_NAME, n)
	opts := make(map[string]string)
	if dr {
		opts["drouter"] = DR_INST
	}
	r, err := dc.NetworkCreate(bg, name, dockerTypes.NetworkCreate{
		Options: opts,
		IPAM: dockerNTypes.IPAM{
			Config: []dockerNTypes.IPAMConfig{
				dockerNTypes.IPAMConfig{
					Subnet:  fmt.Sprintf(NET_IPNET, n*8),
					Gateway: fmt.Sprintf(NET_GW, n*8+1),
				},
			},
		},
	})
	require.Equal(t, err, nil, "Error creating network")
	return r.ID
}

func createMultiSubnetNetwork(n int, dr bool, t *testing.T) string {
	name := fmt.Sprintf(NET_NAME, n)
	opts := make(map[string]string)
	if dr {
		opts["drouter"] = DR_INST
	}
	r, err := dc.NetworkCreate(bg, name, dockerTypes.NetworkCreate{
		Options: opts,
		IPAM: dockerNTypes.IPAM{
			Config: []dockerNTypes.IPAMConfig{
				dockerNTypes.IPAMConfig{
					Subnet:  fmt.Sprintf(NET_IPNET, n*8),
					Gateway: fmt.Sprintf(NET_GW, n*8+1),
				},
				dockerNTypes.IPAMConfig{
					Subnet:  fmt.Sprintf("192.168.243.%v/29", n*8),
					Gateway: fmt.Sprintf("192.168.243.%v", n*8+1),
				},
			},
		},
	})
	require.Equal(t, err, nil, "Error creating network")
	return r.ID
}

func removeNetwork(id string, t *testing.T) {
	err := dc.NetworkRemove(bg, id)
	if err != nil {
		t.Fatalf("Error removing network: %v", err)
	}
}

func TestNetworkConnect(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()
	for _, e := range hook.Entries {
		assert.Equal(log.DebugLevel, e.Level, "All messages should be debug")
	}
	n0.disconnect()
	checkLogs(hook.Entries, t)
}

func TestIPOffset(t *testing.T) {
	assert := assert.New(t)
	ipOffset = 2
	defer func() { ipOffset = 0 }()

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()

	routes, err := netlink.RouteGet(net.ParseIP("192.168.242.1"))
	assert.Equal(err, nil, "Error getting routes")
	assert.True(routes[0].Src.Equal(net.ParseIP("192.168.242.2")), "IP not what was expected")

	n0.disconnect()
	checkLogs(hook.Entries, t)
}

func TestNegativeIPOffset(t *testing.T) {
	assert := assert.New(t)
	ipOffset = -1
	defer func() { ipOffset = 0 }()

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()

	routes, err := netlink.RouteGet(net.ParseIP("192.168.242.1"))
	assert.Equal(err, nil, "Error getting routes")
	assert.True(routes[0].Src.Equal(net.ParseIP("192.168.242.6")), "IP not what was expected")

	n0.disconnect()
	checkLogs(hook.Entries, t)
}

// This test is disabled. Apparently docker doesn't accept multiple subnets anyway, despite being stored and presented as an array. Perhaps that is only for dual stack.
func testMultiSubnetIPOffset(t *testing.T) {
	assert := assert.New(t)
	ipOffset = 2
	defer func() { ipOffset = 0 }()

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()

	routes, err := netlink.RouteGet(net.ParseIP("192.168.242.1"))
	assert.Equal(err, nil, "Error getting routes")

	ip1 := false
	ip2 := false
	for _, r := range routes {
		if r.Src.Equal(net.ParseIP("192.168.242.2")) {
			ip1 = true
		}
		if r.Src.Equal(net.ParseIP("192.168.243.2")) {
			ip2 = true
		}
	}

	assert.True(ip1, "First ip not set")
	assert.True(ip2, "Second ip not set")

	n0.disconnect()
	checkLogs(hook.Entries, t)
}

func TestMultipleConnectWarn(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()
	defer n0.disconnect()
	checkLogs(hook.Entries, t)

	n0.connect()

	warned := 0
	for _, e := range hook.Entries {
		if e.Level == log.WarnLevel {
			warned += 1
		}
	}
	assert.Equal(warned, 1, "Expected to be warned once")
}

func TestIsConnected(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	n0 := newNetwork(&n0r)
	hook := logtest.NewGlobal()
	assert.False(n0.isConnected(), "Network should not be connected")
	checkLogs(hook.Entries, t)

	n0.connect()
	hook.Reset()
	assert.True(n0.isConnected(), "Network should be connected")
	checkLogs(hook.Entries, t)

	n0.disconnect()
	assert.False(n0.isConnected(), "Network should not be connected")
	checkLogs(hook.Entries, t)
}

func TestIsDrouterTrue(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.True(n0.isDRouter())
}

func TestIsDrouterFalse(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, false, t)
	defer removeNetwork(n0ID, t)
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.False(n0.isDRouter())
}

func TestIsDrouterTransit(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, false, t)
	defer removeNetwork(n0ID, t)
	transitNetID = n0ID
	defer func() { transitNetID = "" }()
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.True(n0.isDRouter())
}
<commit_msg>freaking docker<commit_after>package drouter

import (
	"fmt"
	"net"
	"testing"

	log "github.com/Sirupsen/logrus"
	logtest "github.com/Sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	dockerTypes "github.com/docker/engine-api/types"
	dockerNTypes "github.com/docker/engine-api/types/network"
	"github.com/vishvananda/netlink"
)

const (
	NET_NAME  = "drntest_n%v"
	NET_GW    = "192.168.242.%v"
	NET_IPNET = "192.168.242.%v/29"
	DR_INST   = "dr_test"
)

func createNetwork(n int, dr bool, t *testing.T) string {
	name := fmt.Sprintf(NET_NAME, n)
	opts := make(map[string]string)
	if dr {
		opts["drouter"] = DR_INST
	}
	r, err := dc.NetworkCreate(bg, name, dockerTypes.NetworkCreate{
		Options: opts,
		IPAM: dockerNTypes.IPAM{
			Config: []dockerNTypes.IPAMConfig{
				dockerNTypes.IPAMConfig{
					Subnet:  fmt.Sprintf(NET_IPNET, n*8),
					Gateway: fmt.Sprintf(NET_GW, n*8+1),
				},
			},
		},
	})
	require.Equal(t, err, nil, "Error creating network")
	return r.ID
}

func removeNetwork(id string, t *testing.T) {
	err := dc.NetworkRemove(bg, id)
	if err != nil {
		t.Fatalf("Error removing network: %v", err)
	}
}

func TestNetworkConnect(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()
	for _, e := range hook.Entries {
		assert.Equal(log.DebugLevel, e.Level, "All messages should be debug")
	}
	n0.disconnect()
	checkLogs(hook.Entries, t)
}

func TestIPOffset(t *testing.T) {
	assert := assert.New(t)
	ipOffset = 2
	defer func() { ipOffset = 0 }()

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()

	routes, err := netlink.RouteGet(net.ParseIP("192.168.242.1"))
	assert.Equal(err, nil, "Error getting routes")
	assert.True(routes[0].Src.Equal(net.ParseIP("192.168.242.2")), "IP not what was expected")

	n0.disconnect()
	checkLogs(hook.Entries, t)
}

func TestNegativeIPOffset(t *testing.T) {
	assert := assert.New(t)
	ipOffset = -1
	defer func() { ipOffset = 0 }()

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()

	routes, err := netlink.RouteGet(net.ParseIP("192.168.242.1"))
	assert.Equal(err, nil, "Error getting routes")
	assert.True(routes[0].Src.Equal(net.ParseIP("192.168.242.6")), "IP not what was expected")

	n0.disconnect()
	checkLogs(hook.Entries, t)
}

func TestMultipleConnectWarn(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()
	defer n0.disconnect()
	checkLogs(hook.Entries, t)

	n0.connect()

	warned := 0
	for _, e := range hook.Entries {
		if e.Level == log.WarnLevel {
			warned += 1
		}
	}
	assert.Equal(warned, 1, "Expected to be warned once")
}

func TestIsConnected(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	n0 := newNetwork(&n0r)
	hook := logtest.NewGlobal()
	assert.False(n0.isConnected(), "Network should not be connected")
	checkLogs(hook.Entries, t)

	n0.connect()
	hook.Reset()
	assert.True(n0.isConnected(), "Network should be connected")
	checkLogs(hook.Entries, t)

	n0.disconnect()
	assert.False(n0.isConnected(), "Network should not be connected")
	checkLogs(hook.Entries, t)
}

func TestIsDrouterTrue(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, true, t)
	defer removeNetwork(n0ID, t)
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.True(n0.isDRouter())
}

func TestIsDrouterFalse(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, false, t)
	defer removeNetwork(n0ID, t)
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.False(n0.isDRouter())
}

func TestIsDrouterTransit(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, false, t)
	defer removeNetwork(n0ID, t)
	transitNetID = n0ID
	defer func() { transitNetID = "" }()
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.True(n0.isDRouter())
}

func TestAdminDownNonAggressive(t *testing.T) {
	assert := assert.New(t)

	n0ID := createNetwork(0, false, t)
	defer removeNetwork(n0ID, t)
	transitNetID = n0ID
	defer func() { transitNetID = "" }()
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.False(n0.adminDown)
	err = n0.disconnectEvent()
	assert.Equal(err, nil, "Error with disconnectEvent")
	assert.False(n0.adminDown, "adminDown should be False")
}

func TestAdminDownAggressive(t *testing.T) {
	assert := assert.New(t)
	aggressive = true
	defer func() { aggressive = false }()

	n0ID := createNetwork(0, false, t)
	defer removeNetwork(n0ID, t)
	transitNetID = n0ID
	defer func() { transitNetID = "" }()
	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")
	n0 := newNetwork(&n0r)

	assert.False(n0.adminDown)
	err = n0.disconnectEvent()
	assert.Equal(err, nil, "Error with disconnectEvent")
	assert.True(n0.adminDown, "adminDown should be True")

	err = n0.connectEvent()
	assert.Equal(err, nil, "Error with connectEvent")
	assert.False(n0.adminDown, "adminDown should be False")
}

func createMultiSubnetNetwork(n int, dr bool, t *testing.T) string {
	name := fmt.Sprintf(NET_NAME, n)
	opts := make(map[string]string)
	if dr {
		opts["drouter"] = DR_INST
	}
	r, err := dc.NetworkCreate(bg, name, dockerTypes.NetworkCreate{
		Options: opts,
		IPAM: dockerNTypes.IPAM{
			Config: []dockerNTypes.IPAMConfig{
				dockerNTypes.IPAMConfig{
					Subnet:  fmt.Sprintf(NET_IPNET, n*8),
					Gateway: fmt.Sprintf(NET_GW, n*8+1),
				},
				dockerNTypes.IPAMConfig{
					Subnet:  fmt.Sprintf("192.168.243.%v/29", n*8),
					Gateway: fmt.Sprintf("192.168.243.%v", n*8+1),
				},
			},
		},
	})
	require.Equal(t, err, nil, "Error creating network")
	return r.ID
}

// disabled becasue bridge driver doesn't support multiple subnets
func testMultiSubnetIPOffset(t *testing.T) {
	assert := assert.New(t)
	ipOffset = 2
	defer func() { ipOffset = 0 }()

	n0ID := createMultiSubnetNetwork(0, true, t)
	defer removeNetwork(n0ID, t)

	n0r, err := dc.NetworkInspect(bg, n0ID)
	assert.Equal(err, nil, "Error inspecting network")

	hook := logtest.NewGlobal()
	n0 := newNetwork(&n0r)
	n0.connect()

	routes, err := netlink.RouteGet(net.ParseIP("192.168.242.1"))
	assert.Equal(err, nil, "Error getting routes")

	ip1 := false
	ip2 := false
	for _, r := range routes {
		if r.Src.Equal(net.ParseIP("192.168.242.2")) {
			ip1 = true
		}
		if r.Src.Equal(net.ParseIP("192.168.243.2")) {
			ip2 = true
		}
	}

	assert.True(ip1, "First ip not set")
	assert.True(ip2, "Second ip not set")

	n0.disconnect()
	checkLogs(hook.Entries, t)
}
<|endoftext|>


<commit_before>package stager

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"

	"github.com/cloudfoundry-incubator/linux-circus/buildpackrunner"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
)

type BuildpackRunner interface {
	Run() error
}

func RunBuildpack(writer io.Writer, runner BuildpackRunner) error {
	fmt.Fprintln(writer, "Running Buildpacks...")
	return runner.Run()
}

func NewBuildpackRunner(buildpackDir string) *buildpackrunner.Runner {
	prepareMd5BuildpacksDir(buildpackDir, "/tmp/buildpacks")
	config := models.NewCircusTailorConfig(subDirs(buildpackDir))
	return buildpackrunner.New(&config)
}

func prepareMd5BuildpacksDir(src string, dst string) {
	os.MkdirAll(src, 0755)
	os.MkdirAll(dst, 0755)
	dirs := subDirs(src)
	for _, dir := range dirs {
		if err := os.Symlink(src+"/"+dir, dst+"/"+md5sum(dir)); err != nil {
			log.Fatalf(" %s", err)
		}
	}
}

func subDirs(dir string) (dirs []string) {
	var contents []os.FileInfo
	var err error
	if contents, err = ioutil.ReadDir(dir); err != nil {
		log.Fatalf(" %s", err)
	}
	for _, file := range contents {
		if file.IsDir() {
			dirs = append(dirs, file.Name())
		}
	}
	return
}

func md5sum(src string) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(src)))
}
<commit_msg>Converted stager to using SubDirs from utils<commit_after>package stager

import (
	"crypto/md5"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/hatofmonkeys/cloudfocker/utils"

	"github.com/cloudfoundry-incubator/linux-circus/buildpackrunner"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
)

type BuildpackRunner interface {
	Run() error
}

func RunBuildpack(writer io.Writer, runner BuildpackRunner) error {
	fmt.Fprintln(writer, "Running Buildpacks...")
	return runner.Run()
}

func NewBuildpackRunner(buildpackDir string) *buildpackrunner.Runner {
	prepareMd5BuildpacksDir(buildpackDir, "/tmp/buildpacks")
	var err error
	dirs := []string{}
	if dirs, err = utils.SubDirs(buildpackDir); err != nil {
		log.Fatalf(" %s", err)
	}
	config := models.NewCircusTailorConfig(dirs)
	return buildpackrunner.New(&config)
}

func prepareMd5BuildpacksDir(src string, dst string) {
	os.MkdirAll(src, 0755)
	os.MkdirAll(dst, 0755)
	var err error
	dirs := []string{}
	if dirs, err = utils.SubDirs(src); err != nil {
		log.Fatalf(" %s", err)
	}
	for _, dir := range dirs {
		if err := os.Symlink(src+"/"+dir, dst+"/"+md5sum(dir)); err != nil {
			log.Fatalf(" %s", err)
		}
	}
}

func md5sum(src string) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(src)))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"

	"github.com/fatih/color"
)

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

var (
	red  = color.RedString
	blue = color.BlueString
	cwd  string
)

type AliasFile struct {
	buf    bytes.Buffer
	writer *bufio.Writer
}

func NewAliasFile() *AliasFile {
	a := &AliasFile{}
	a.writer = bufio.NewWriter(&a.buf)
	return a
}

func (a *AliasFile) WriteAlias(index int, filename, linenum string) {
	fmt.Fprintf(a.writer, "alias f%d='vim %s/%s +%s'\n", index, cwd, filename, linenum)
}

func (a *AliasFile) WriteFile(filename string) {
	err := a.writer.Flush()
	check(err)

	err = ioutil.WriteFile(filename, a.buf.Bytes(), 0644)
	check(err)
}

func getOutputColors(args []string) (pathColor string, lineNumberColor string) {
	pathColor, lineNumberColor = "1;32", "1;33"
	for i, flag := range args {
		if flag == "--color-path" && i+1 < len(args) {
			pathColor = args[i+1]
		} else if flag == "--color-line-number" && i+1 < len(args) {
			lineNumberColor = args[i+1]
		}
	}
	return
}

func generateShortcuts(cmd *exec.Cmd) {
	pathColor, lineNumberColor := getOutputColors(os.Args[1:])
	pathRe := regexp.MustCompile(
		fmt.Sprintf(`^\x1b\[%sm([^\x1b]+)`, pathColor))
	lineNumberRe := regexp.MustCompile(
		fmt.Sprintf(`^\x1b\[%sm(\d+)\x1b\[0m\x1b\[K:`, lineNumberColor))

	stdout, err := cmd.StdoutPipe()
	check(err)
	scanner := bufio.NewScanner(stdout)

	cwd, err = os.Getwd()
	check(err)

	var (
		line      []byte
		curPath   string
		groupIdxs []int
	)
	aliasFile := NewAliasFile()
	aliasIndex := 1

	err = cmd.Start()
	check(err)

	for scanner.Scan() {
		line = scanner.Bytes()
		if len(line) > 0 {
			if groupIdxs = lineNumberRe.FindSubmatchIndex(line); len(groupIdxs) > 0 {
				aliasFile.WriteAlias(aliasIndex, curPath, string(line[groupIdxs[2]:groupIdxs[3]]))
				fmt.Printf("%s%s%s %s\n", blue("["), red("%d", aliasIndex), blue("]"), string(line))
				aliasIndex++
				continue
			} else if groupIdxs = pathRe.FindSubmatchIndex(line); len(groupIdxs) > 0 {
				curPath = string(line[groupIdxs[2]:groupIdxs[3]])
			}
		}
		fmt.Println(string(line))
	}

	aliasFile.WriteFile("/tmp/tag_aliases")
}

func passThrough(cmd *exec.Cmd) {
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	if err := cmd.Run(); err != nil {
		os.Exit(1)
	}
}

func main() {
	args := []string{"--group"}
	args = append(args, os.Args[1:]...)
	args = append(args, "--color")

	cmd := exec.Command("ag", args...)

	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 { // Data being piped from stdin
		passThrough(cmd)
		return
	}

	generateShortcuts(cmd)
}
<commit_msg>better error passthrough<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"regexp"

	"github.com/fatih/color"
)

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

var (
	red  = color.RedString
	blue = color.BlueString
	cwd  string
)

type AliasFile struct {
	buf    bytes.Buffer
	writer *bufio.Writer
}

func NewAliasFile() *AliasFile {
	a := &AliasFile{}
	a.writer = bufio.NewWriter(&a.buf)
	return a
}

func (a *AliasFile) WriteAlias(index int, filename, linenum string) {
	fmt.Fprintf(a.writer, "alias f%d='vim %s/%s +%s'\n", index, cwd, filename, linenum)
}

func (a *AliasFile) WriteFile(filename string) {
	err := a.writer.Flush()
	check(err)

	err = ioutil.WriteFile(filename, a.buf.Bytes(), 0644)
	check(err)
}

func getOutputColors(args []string) (pathColor string, lineNumberColor string) {
	pathColor, lineNumberColor = "1;32", "1;33"
	for i, flag := range args {
		if flag == "--color-path" && i+1 < len(args) {
			pathColor = args[i+1]
		} else if flag == "--color-line-number" && i+1 < len(args) {
			lineNumberColor = args[i+1]
		}
	}
	return
}

func generateShortcuts(cmd *exec.Cmd) int {
	pathColor, lineNumberColor := getOutputColors(os.Args[1:])
	pathRe := regexp.MustCompile(
		fmt.Sprintf(`^\x1b\[%sm([^\x1b]+)`, pathColor))
	lineNumberRe := regexp.MustCompile(
		fmt.Sprintf(`^\x1b\[%sm(\d+)\x1b\[0m\x1b\[K:`, lineNumberColor))

	stdout, err := cmd.StdoutPipe()
	check(err)
	scanner := bufio.NewScanner(stdout)

	cwd, err = os.Getwd()
	check(err)

	var (
		line      []byte
		curPath   string
		groupIdxs []int
	)
	aliasFile := NewAliasFile()
	defer aliasFile.WriteFile("/tmp/tag_aliases")
	aliasIndex := 1

	err = cmd.Start()
	check(err)

	for scanner.Scan() {
		line = scanner.Bytes()
		if len(line) > 0 {
			if groupIdxs = lineNumberRe.FindSubmatchIndex(line); len(groupIdxs) > 0 {
				aliasFile.WriteAlias(aliasIndex, curPath, string(line[groupIdxs[2]:groupIdxs[3]]))
				fmt.Printf("%s%s%s %s\n", blue("["), red("%d", aliasIndex), blue("]"), string(line))
				aliasIndex++
				continue
			} else if groupIdxs = pathRe.FindSubmatchIndex(line); len(groupIdxs) > 0 {
				curPath = string(line[groupIdxs[2]:groupIdxs[3]])
			}
		}
		fmt.Println(string(line))
	}

	err = cmd.Wait()
	if err != nil {
		return 1
	}
	return 0
}

func passThrough(cmd *exec.Cmd) int {
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout

	err := cmd.Run()
	if err != nil {
		return 1
	}
	return 0
}

func main() {
	args := []string{"--group"}
	args = append(args, os.Args[1:]...)
	args = append(args, "--color")

	cmd := exec.Command("ag", args...)
	cmd.Stderr = os.Stderr

	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 { // Data being piped from stdin
		os.Exit(passThrough(cmd))
	}

	os.Exit(generateShortcuts(cmd))
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced 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.

// +build unit

// Package agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

// This file tests the LoadBalancer interface aspect of the host agent.
package node

import (
	"github.com/control-center/serviced/domain/applicationendpoint"

	"testing"
)

// assert that the HostAgent implements the LoadBalancer interface
var _ LoadBalancer = &HostAgent{}

func TestAddControlPlaneEndpoints(t *testing.T) {
	agent := &HostAgent{}
	agent.master = "127.0.0.1:0"
	agent.uiport = ":443"
	endpoints := make(map[string][]applicationendpoint.ApplicationEndpoint)

	consumer_endpoint := applicationendpoint.ApplicationEndpoint{}
	consumer_endpoint.ServiceID = "controlplane_consumer"
	consumer_endpoint.Application = "controlplane_consumer"
	consumer_endpoint.ContainerIP = "127.0.0.1"
	consumer_endpoint.ContainerPort = 8443
	consumer_endpoint.ProxyPort = 8444
	consumer_endpoint.HostPort = 8443
	consumer_endpoint.HostIP = "127.0.0.1"
	consumer_endpoint.Protocol = "tcp"

	controlplane_endpoint := applicationendpoint.ApplicationEndpoint{}
	controlplane_endpoint.ServiceID = "controlplane"
	controlplane_endpoint.Application = "controlplane"
	controlplane_endpoint.ContainerIP = "127.0.0.1"
	controlplane_endpoint.ContainerPort = 443
	controlplane_endpoint.ProxyPort = 443
	controlplane_endpoint.HostPort = 443
	controlplane_endpoint.HostIP = "127.0.0.1"
	controlplane_endpoint.Protocol = "tcp"

	agent.addControlPlaneEndpoint(endpoints)
	agent.addControlPlaneConsumerEndpoint(endpoints)

	if _, ok := endpoints["tcp:8444"]; !ok {
		t.Fatalf(" mapping failed missing key[\"tcp:8444\"]")
	}

	if _, ok := endpoints["tcp:443"]; !ok {
		t.Fatalf(" mapping failed missing key[\"tcp:443\"]")
	}

	if len(endpoints["tcp:8444"]) != 1 {
		t.Fatalf(" mapping failed len(\"tcp:8444\"])=%d expected 1", len(endpoints["tcp:8444"]))
	}

	if len(endpoints["tcp:443"]) != 1 {
		t.Fatalf(" mapping failed len(\"tcp:443\"])=%d expected 1", len(endpoints["tcp:443"]))
	}

	if endpoints["tcp:8444"][0] != consumer_endpoint {
		t.Fatalf(" mapping failed %+v expected %+v", endpoints["tcp:8444"][0], consumer_endpoint)
	}

	if endpoints["tcp:443"][0] != controlplane_endpoint {
		t.Fatalf(" mapping failed %+v expected %+v", endpoints["tcp:443"][0], controlplane_endpoint)
	}
}
<commit_msg>fix broken unit test<commit_after>// Copyright 2014 The Serviced 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.

// +build unit

// Package agent implements a service that runs on a serviced node. It is
// responsible for ensuring that a particular node is running the correct services
// and reporting the state and health of those services back to the master
// serviced.

// This file tests the LoadBalancer interface aspect of the host agent.
package node

import (
	"github.com/control-center/serviced/domain/applicationendpoint"

	"fmt"
	"testing"
)

// assert that the HostAgent implements the LoadBalancer interface
var _ LoadBalancer = &HostAgent{}

func TestAddControlPlaneEndpoints(t *testing.T) {
	agent := &HostAgent{}
	agent.master = "127.0.0.1:0"
	agent.uiport = fmt.Sprintf(":%d", SERVICED_UI_ENDPOINT)
	endpoints := make(map[string][]applicationendpoint.ApplicationEndpoint)

	consumer_endpoint := applicationendpoint.ApplicationEndpoint{}
	consumer_endpoint.ServiceID = "controlplane_consumer"
	consumer_endpoint.Application = "controlplane_consumer"
	consumer_endpoint.ContainerIP = "127.0.0.1"
	consumer_endpoint.ContainerPort = 8443
	consumer_endpoint.ProxyPort = 8444
	consumer_endpoint.HostPort = 8443
	consumer_endpoint.HostIP = "127.0.0.1"
	consumer_endpoint.Protocol = "tcp"

	controlplane_endpoint := applicationendpoint.ApplicationEndpoint{}
	controlplane_endpoint.ServiceID = "controlplane"
	controlplane_endpoint.Application = "controlplane"
	controlplane_endpoint.ContainerIP = "127.0.0.1"
	controlplane_endpoint.ContainerPort = SERVICED_UI_ENDPOINT
	controlplane_endpoint.ProxyPort = SERVICED_UI_ENDPOINT
	controlplane_endpoint.HostPort = SERVICED_UI_ENDPOINT
	controlplane_endpoint.HostIP = "127.0.0.1"
	controlplane_endpoint.Protocol = "tcp"

	agent.addControlPlaneEndpoint(endpoints)
	agent.addControlPlaneConsumerEndpoint(endpoints)

	if _, ok := endpoints["tcp:8444"]; !ok {
		t.Fatalf(" mapping failed missing key[\"tcp:8444\"]")
	}

	ccuiport := fmt.Sprintf("tcp%s", agent.uiport)
	if _, ok := endpoints[ccuiport]; !ok {
		t.Fatalf(" mapping failed missing key[\"%s\"]", ccuiport)
	}

	if len(endpoints["tcp:8444"]) != 1 {
		t.Fatalf(" mapping failed len(\"tcp:8444\"])=%d expected 1", len(endpoints["tcp:8444"]))
	}

	if len(endpoints[ccuiport]) != 1 {
		t.Fatalf(" mapping failed len(\"%s\"])=%d expected 1", ccuiport, len(endpoints[ccuiport]))
	}

	if endpoints["tcp:8444"][0] != consumer_endpoint {
		t.Fatalf(" mapping failed %+v expected %+v", endpoints["tcp:8444"][0], consumer_endpoint)
	}

	if endpoints[ccuiport][0] != controlplane_endpoint {
		t.Fatalf(" mapping failed %+v expected %+v", endpoints[ccuiport][0], controlplane_endpoint)
	}
}
<|endoftext|>


<commit_before>package stageCmd

import (
	// Stdlib
	"errors"
	"fmt"
	"os"

	// Internal
	"github.com/salsaflow/salsaflow/action"
	"github.com/salsaflow/salsaflow/app"
	"github.com/salsaflow/salsaflow/changes"
	"github.com/salsaflow/salsaflow/errs"
	"github.com/salsaflow/salsaflow/git"
	"github.com/salsaflow/salsaflow/log"
	"github.com/salsaflow/salsaflow/modules"
	"github.com/salsaflow/salsaflow/modules/common"
	"github.com/salsaflow/salsaflow/prompt"
	"github.com/salsaflow/salsaflow/releases"
	"github.com/salsaflow/salsaflow/version"

	// Other
	"gopkg.in/tchap/gocli.v2"
)

var Command = &gocli.Command{
	UsageLine: `
  stage`,
	Short: "stage and close the current release",
	Long: `
  Stage and close the release that is currently running, i.e.

    1) Make sure the release can be staged (the stories are reviewed and tested)
    2) Tag the release branch and delete it.
    3) Move the staging branch to point to the tag.
    4) Push the changes.
	`,
	Action: run,
}

func run(cmd *gocli.Command, args []string) {
	if len(args) != 0 {
		cmd.Usage()
		os.Exit(2)
	}

	app.InitOrDie()

	defer prompt.RecoverCancel()

	if err := runMain(); err != nil {
		errs.Fatal(err)
	}
}

func runMain() (err error) {
	// Load git config.
	gitConfig, err := git.LoadConfig()
	if err != nil {
		return err
	}
	var (
		remoteName    = gitConfig.RemoteName()
		releaseBranch = gitConfig.ReleaseBranchName()
		stagingBranch = gitConfig.StagingBranchName()
	)

	// Instantiate the issue tracker.
	tracker, err := modules.GetIssueTracker()
	if err != nil {
		return err
	}

	// Get the current branch.
	task := "Get the current branch"
	currentBranch, err := git.CurrentBranch()
	if err != nil {
		return err
	}

	// Cannot be on the release branch, it will be deleted.
	task = "Make sure that the release branch is not checked out"
	if currentBranch == releaseBranch {
		return errs.NewError(
			task, errors.New("cannot stage the release while on the release branch"), nil)
	}

	// Fetch the remote repository.
	task = "Fetch the remote repository"
	log.Run(task)
	if err := git.UpdateRemotes(remoteName); err != nil {
		return errs.NewError(task, err, nil)
	}

	// Make sure that the local release branch exists.
	task = "Make sure that the local release branch exists"
	if err := git.CreateTrackingBranchUnlessExists(releaseBranch, remoteName); err != nil {
		return errs.NewError(task, err, nil)
	}

	// Make sure that the release branch is up to date.
	task = fmt.Sprintf("Make sure branch '%v' is up to date", releaseBranch)
	log.Run(task)
	if err := git.EnsureBranchSynchronized(releaseBranch, remoteName); err != nil {
		return errs.NewError(task, err, nil)
	}

	// Read the current release version.
	task = "Read the current release version"
	releaseVersion, err := version.GetByBranch(releaseBranch)
	if err != nil {
		return errs.NewError(task, err, nil)
	}

	// Make sure the release is stageable.
	release, err := tracker.RunningRelease(releaseVersion)
	if err != nil {
		return err
	}
	if err := release.EnsureStageable(); err != nil {
		return err
	}

	// Make sure there are no commits being left behind,
	// e.g. make sure no commits are forgotten on the trunk branch,
	// i.e. make sure that everything necessary was cherry-picked.
	if err := checkCommits(tracker, release, releaseBranch); err != nil {
		return err
	}

	// Tag the release branch with the associated version string.
	tag := releaseVersion.ReleaseTagString()
	task = fmt.Sprintf("Tag branch '%v' (tag = %v)", releaseBranch, tag)
	log.Run(task)
	tag = releaseVersion.ReleaseTagString()
	if err := git.Tag(tag, releaseBranch); err != nil {
		return errs.NewError(task, err, nil)
	}
	defer func(task string) {
		// On error, delete the release tag.
		if err != nil {
			log.Rollback(task)
			if ex := git.DeleteTag(tag); ex != nil {
				errs.LogError("Delete the release tag", ex, nil)
			}
		}
	}(task)

	// Reset the staging branch to point to the newly created tag.
	task = fmt.Sprintf("Reset branch '%v' to point to tag '%v'", stagingBranch, tag)
	log.Run(task)
	act, err := git.CreateOrResetBranch(stagingBranch, tag)
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	defer func(task string, act action.Action) {
		if err == nil {
			return
		}
		// Rollback on error.
		log.Rollback(task)
		if ex := act.Rollback(); ex != nil {
			errs.Log(ex)
		}
	}(task, act)

	// Delete the local release branch.
	task = fmt.Sprintf("Delete branch '%v'", releaseBranch)
	log.Run(task)
	if err := git.Branch("-d", releaseBranch); err != nil {
		return errs.NewError(task, err, nil)
	}
	defer func(task string) {
		if err == nil {
			return
		}
		// On error, re-create the local release branch.
		log.Rollback(task)
		if ex := git.Branch(releaseBranch, remoteName+"/"+releaseBranch); ex != nil {
			errs.Log(ex)
		}
	}(task)

	// Stage the release in the issue tracker.
	act, err = release.Stage()
	if err != nil {
		return err
	}
	defer func(act action.Action) {
		if err == nil {
			return
		}
		// On error, unstage the release.
		if ex := act.Rollback(); ex != nil {
			errs.Log(ex)
		}
	}(act)

	// Push to create the tag, reset client and delete release in the remote repository.
	task = "Push changes to the remote repository"
	log.Run(task)
	return git.Push(remoteName,
		"-f", "--tags", // Force push, push the release tag.
		":"+releaseBranch,               // Delete the release branch.
		stagingBranch+":"+stagingBranch) // Push the staging branch.
}

func checkCommits(
	tracker common.IssueTracker,
	release common.RunningRelease,
	releaseBranch string,
) error {

	var task = "Make sure no changes are being left behind"
	log.Run(task)

	stories, err := release.Stories()
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	if len(stories) == 0 {
		return nil
	}

	groups, err := changes.StoryChanges(stories)
	if err != nil {
		return errs.NewError(task, err, nil)
	}

	toCherryPick, err := releases.StoryChangesToCherryPick(groups)
	if err != nil {
		return errs.NewError(task, err, nil)
	}

	// In case there are some changes being left behind,
	// ask the user to confirm whether to proceed or not.
	if len(toCherryPick) == 0 {
		return nil
	}

	fmt.Println(`
Some changes are being left behind!

In other words, some changes that are assigned to the current release
have not been cherry-picked onto the release branch yet.
	`)
	if err := changes.DumpStoryChanges(os.Stdout, toCherryPick, tracker, false); err != nil {
		panic(err)
	}
	fmt.Println()

	confirmed, err := prompt.Confirm("Are you sure you really want to stage the release?")
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	if !confirmed {
		prompt.PanicCancel()
	}
	fmt.Println()

	return nil
}
<commit_msg>release stage: Make sure the remote branch exists<commit_after>package stageCmd

import (
	// Stdlib
	"errors"
	"fmt"
	"os"

	// Internal
	"github.com/salsaflow/salsaflow/action"
	"github.com/salsaflow/salsaflow/app"
	"github.com/salsaflow/salsaflow/changes"
	"github.com/salsaflow/salsaflow/errs"
	"github.com/salsaflow/salsaflow/git"
	"github.com/salsaflow/salsaflow/log"
	"github.com/salsaflow/salsaflow/modules"
	"github.com/salsaflow/salsaflow/modules/common"
	"github.com/salsaflow/salsaflow/prompt"
	"github.com/salsaflow/salsaflow/releases"
	"github.com/salsaflow/salsaflow/version"

	// Other
	"gopkg.in/tchap/gocli.v2"
)

var Command = &gocli.Command{
	UsageLine: `
  stage`,
	Short: "stage and close the current release",
	Long: `
  Stage and close the release that is currently running, i.e.

    1) Make sure the release can be staged (the stories are reviewed and tested)
    2) Tag the release branch and delete it.
    3) Move the staging branch to point to the tag.
    4) Push the changes.
	`,
	Action: run,
}

func run(cmd *gocli.Command, args []string) {
	if len(args) != 0 {
		cmd.Usage()
		os.Exit(2)
	}

	app.InitOrDie()

	defer prompt.RecoverCancel()

	if err := runMain(); err != nil {
		errs.Fatal(err)
	}
}

func runMain() (err error) {
	// Load git config.
	gitConfig, err := git.LoadConfig()
	if err != nil {
		return err
	}
	var (
		remoteName    = gitConfig.RemoteName()
		releaseBranch = gitConfig.ReleaseBranchName()
		stagingBranch = gitConfig.StagingBranchName()
	)

	// Instantiate the issue tracker.
	tracker, err := modules.GetIssueTracker()
	if err != nil {
		return err
	}

	// Get the current branch.
	task := "Get the current branch"
	currentBranch, err := git.CurrentBranch()
	if err != nil {
		return err
	}

	// Cannot be on the release branch, it will be deleted.
	task = "Make sure that the release branch is not checked out"
	if currentBranch == releaseBranch {
		return errs.NewError(
			task, errors.New("cannot stage the release while on the release branch"), nil)
	}

	// Fetch the remote repository.
	task = "Fetch the remote repository"
	log.Run(task)
	if err := git.UpdateRemotes(remoteName); err != nil {
		return errs.NewError(task, err, nil)
	}

	// Make sure the remote release branch exists.
	task = "Make sure that the remote release branch exists"
	exists, err := git.RemoteBranchExists(releaseBranch, remoteName)
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	if !exists {
		return errs.NewError(task,
			fmt.Errorf("branch '%v' not found in remote '%v'", releaseBranch, remoteName), nil)
	}

	// Make sure that the local release branch exists.
	task = "Make sure that the local release branch exists"
	if err := git.CreateTrackingBranchUnlessExists(releaseBranch, remoteName); err != nil {
		return errs.NewError(task, err, nil)
	}

	// Make sure that the release branch is up to date.
	task = fmt.Sprintf("Make sure branch '%v' is up to date", releaseBranch)
	log.Run(task)
	if err := git.EnsureBranchSynchronized(releaseBranch, remoteName); err != nil {
		return errs.NewError(task, err, nil)
	}

	// Read the current release version.
	task = "Read the current release version"
	releaseVersion, err := version.GetByBranch(releaseBranch)
	if err != nil {
		return errs.NewError(task, err, nil)
	}

	// Make sure the release is stageable.
	release, err := tracker.RunningRelease(releaseVersion)
	if err != nil {
		return err
	}
	if err := release.EnsureStageable(); err != nil {
		return err
	}

	// Make sure there are no commits being left behind,
	// e.g. make sure no commits are forgotten on the trunk branch,
	// i.e. make sure that everything necessary was cherry-picked.
	if err := checkCommits(tracker, release, releaseBranch); err != nil {
		return err
	}

	// Tag the release branch with the associated version string.
	tag := releaseVersion.ReleaseTagString()
	task = fmt.Sprintf("Tag branch '%v' (tag = %v)", releaseBranch, tag)
	log.Run(task)
	tag = releaseVersion.ReleaseTagString()
	if err := git.Tag(tag, releaseBranch); err != nil {
		return errs.NewError(task, err, nil)
	}
	defer func(task string) {
		// On error, delete the release tag.
		if err != nil {
			log.Rollback(task)
			if ex := git.DeleteTag(tag); ex != nil {
				errs.LogError("Delete the release tag", ex, nil)
			}
		}
	}(task)

	// Reset the staging branch to point to the newly created tag.
	task = fmt.Sprintf("Reset branch '%v' to point to tag '%v'", stagingBranch, tag)
	log.Run(task)
	act, err := git.CreateOrResetBranch(stagingBranch, tag)
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	defer func(task string, act action.Action) {
		if err == nil {
			return
		}
		// Rollback on error.
		log.Rollback(task)
		if ex := act.Rollback(); ex != nil {
			errs.Log(ex)
		}
	}(task, act)

	// Delete the local release branch.
	task = fmt.Sprintf("Delete branch '%v'", releaseBranch)
	log.Run(task)
	if err := git.Branch("-d", releaseBranch); err != nil {
		return errs.NewError(task, err, nil)
	}
	defer func(task string) {
		if err == nil {
			return
		}
		// On error, re-create the local release branch.
		log.Rollback(task)
		if ex := git.Branch(releaseBranch, remoteName+"/"+releaseBranch); ex != nil {
			errs.Log(ex)
		}
	}(task)

	// Stage the release in the issue tracker.
	act, err = release.Stage()
	if err != nil {
		return err
	}
	defer func(act action.Action) {
		if err == nil {
			return
		}
		// On error, unstage the release.
		if ex := act.Rollback(); ex != nil {
			errs.Log(ex)
		}
	}(act)

	// Push to create the tag, reset client and delete release in the remote repository.
	task = "Push changes to the remote repository"
	log.Run(task)
	return git.Push(remoteName,
		"-f", "--tags", // Force push, push the release tag.
		":"+releaseBranch,               // Delete the release branch.
		stagingBranch+":"+stagingBranch) // Push the staging branch.
}

func checkCommits(
	tracker common.IssueTracker,
	release common.RunningRelease,
	releaseBranch string,
) error {

	var task = "Make sure no changes are being left behind"
	log.Run(task)

	stories, err := release.Stories()
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	if len(stories) == 0 {
		return nil
	}

	groups, err := changes.StoryChanges(stories)
	if err != nil {
		return errs.NewError(task, err, nil)
	}

	toCherryPick, err := releases.StoryChangesToCherryPick(groups)
	if err != nil {
		return errs.NewError(task, err, nil)
	}

	// In case there are some changes being left behind,
	// ask the user to confirm whether to proceed or not.
	if len(toCherryPick) == 0 {
		return nil
	}

	fmt.Println(`
Some changes are being left behind!

In other words, some changes that are assigned to the current release
have not been cherry-picked onto the release branch yet.
	`)
	if err := changes.DumpStoryChanges(os.Stdout, toCherryPick, tracker, false); err != nil {
		panic(err)
	}
	fmt.Println()

	confirmed, err := prompt.Confirm("Are you sure you really want to stage the release?")
	if err != nil {
		return errs.NewError(task, err, nil)
	}
	if !confirmed {
		prompt.PanicCancel()
	}
	fmt.Println()

	return nil
}
<|endoftext|>


<commit_before>package prescription

import (
	"regexp"
	"time"

	"github.com/tidepool-org/platform/structure"
	structureValidator "github.com/tidepool-org/platform/structure/validator"
	"github.com/tidepool-org/platform/user"
)

const (
	TrainingInPerson = "inPerson"
	TrainingInModule = "inModule"

	TherapySettingInitial              = "initial"
	TherapySettingTransferPumpSettings = "transferPumpSettings"

	SexMale        = "male"
	SexFemale      = "female"
	SexUndisclosed = "undisclosed"

	UnitKg = "kg"

	AccountTypePatient   = "patient"
	AccountTypeCaregiver = "caregiver"

	usPhoneNumberRegexString = "^\\d{10}|\\(\\d{3}\\) ?\\d{3}\\-\\d{4}$" // Matches 1234567890, (123)456-7890 or (123) 456-7890
	usPhoneNumberCountryCode = 1
)

var (
	usPhoneNumberRegex = regexp.MustCompile(usPhoneNumberRegexString)
)

type RevisionCreate struct {
	DataAttributes `json:",inline"`
}

func NewRevisionCreate() *RevisionCreate {
	return &RevisionCreate{}
}

func (r *RevisionCreate) Validate(validator structure.Validator) {
	r.DataAttributes.Validate(validator)
}

type Signature struct {
	Value  string `json:"signature" bson:"signature"`
	UserID string `json:"signatureUserId" bson:"signatureUserId"`
	KeyID  string `json:"signatureKeyId" bson:"signatureKeyId"`
}

type Revision struct {
	RevisionID int         `json:"revisionId" bson:"revisionId"`
	Signature  *Signature  `json:"signature,omitempty" bson:"signature,omitempty"`
	Attributes *Attributes `json:"attributes" bson:"attributes"`
}

type Revisions []*Revision

func NewRevision(userID string, revisionID int, create *RevisionCreate) *Revision {
	now := time.Now()
	return &Revision{
		RevisionID: revisionID,
		Attributes: &Attributes{
			DataAttributes: DataAttributes{
				AccountType:             create.AccountType,
				CaregiverFirstName:      create.CaregiverFirstName,
				CaregiverLastName:       create.CaregiverLastName,
				FirstName:               create.FirstName,
				LastName:                create.LastName,
				Birthday:                create.Birthday,
				MRN:                     create.MRN,
				Email:                   create.Email,
				Sex:                     create.Sex,
				Weight:                  create.Weight,
				YearOfDiagnosis:         create.YearOfDiagnosis,
				PhoneNumber:             create.PhoneNumber,
				InitialSettings:         create.InitialSettings,
				Training:                create.Training,
				TherapySettings:         create.TherapySettings,
				PrescriberTermsAccepted: create.PrescriberTermsAccepted,
				State:                   create.State,
			},
			CreationAttributes: CreationAttributes{
				CreatedTime:   now,
				CreatedUserID: userID,
			},
		},
	}
}

func (r *Revision) Validate(validator structure.Validator) {
	validator.Int("revisionId", &r.RevisionID).GreaterThanOrEqualTo(0)
	attributesValidator := validator.WithReference("attributes")
	if r.Attributes != nil {
		r.Attributes.Validate(attributesValidator)
	} else {
		attributesValidator.ReportError(structureValidator.ErrorValueEmpty())
	}
}

func (r *Revision) CalculateExpirationTime() *time.Time {
	if r.Attributes.State != StateSubmitted {
		return nil
	}

	expiration := time.Now().Add(MaximumExpirationTime)
	return &expiration
}

func (r *Revision) GetPrescriberUserID() string {
	if r.Attributes.State != StateSubmitted {
		return ""
	}

	return r.Attributes.CreatedUserID
}

type Attributes struct {
	DataAttributes     `json:",inline" bson:",inline"`
	CreationAttributes `json:",inline" bson:",inline"`
}

func (a *Attributes) Validate(validator structure.Validator) {
	a.DataAttributes.Validate(validator)
	a.CreationAttributes.Validate(validator)
}

type DataAttributes struct {
	AccountType             string           `json:"accountType,omitempty" bson:"accountType"`
	CaregiverFirstName      string           `json:"caregiverFirstName,omitempty" bson:"caregiverFirstName"`
	CaregiverLastName       string           `json:"caregiverLastName,omitempty" bson:"caregiverLastName"`
	FirstName               string           `json:"firstName,omitempty" bson:"firstName"`
	LastName                string           `json:"lastName,omitempty" bson:"lastName"`
	Birthday                string           `json:"birthday,omitempty" bson:"birthday"`
	MRN                     string           `json:"mrn,omitempty" bson:"mrn"`
	Email                   string           `json:"email,omitempty" bson:"email"`
	Sex                     string           `json:"sex,omitempty" bson:"sex"`
	Weight                  *Weight          `json:"weight,omitempty" bson:"weight"`
	YearOfDiagnosis         int              `json:"yearOfDiagnosis,omitempty" bson:"yearOfDiagnosis"`
	PhoneNumber             *PhoneNumber     `json:"phoneNumber,omitempty" bson:"phoneNumber"`
	InitialSettings         *InitialSettings `json:"initialSettings,omitempty" bson:"initialSettings"`
	Training                string           `json:"training,omitempty" bson:"training"`
	TherapySettings         string           `json:"therapySettings,omitempty" bson:"therapySettings"`
	PrescriberTermsAccepted bool             `json:"prescriberTermsAccepted,omitempty" bson:"prescriberTermsAccepted"`
	State                   string           `json:"state" bson:"state"`
}

func (d *DataAttributes) Validate(validator structure.Validator) {
	if d.AccountType != "" {
		validator.String("accountType", &d.AccountType).OneOf(AccountTypes()...)
		if d.AccountType == AccountTypePatient {
			validator.String("caregiverFirstName", &d.CaregiverFirstName).Empty()
			validator.String("caregiverLastName", &d.CaregiverLastName).Empty()
		}
	}
	if d.Birthday != "" {
		validator.String("birthday", &d.Birthday).AsTime("2006-01-02").NotZero().BeforeNow(time.Second)
	}
	if d.Email != "" {
		validator.String("email", &d.Email).Email()
	}
	if d.Sex != "" {
		validator.String("sex", &d.Sex).OneOf(SexValues()...)
	}
	if d.YearOfDiagnosis != 0 {
		validator.Int("yearOfDiagnosis", &d.YearOfDiagnosis).GreaterThan(1900)
	}
	if d.PhoneNumber != nil {
		d.PhoneNumber.Validate(validator.WithReference("phoneNumber"))
	}
	if d.Training != "" {
		validator.String("training", &d.Training).OneOf(Trainings()...)
	}
	if d.TherapySettings != "" {
		validator.String("therapySettings", &d.TherapySettings).OneOf(TherapySettings()...)
	}
	if d.Weight != nil {
		d.Weight.Validate(validator.WithReference("weight"))
	}
	if d.InitialSettings != nil {
		d.InitialSettings.Validate(validator.WithReference("initialSettings"))
	}
	validator.String("state", &d.State).OneOf(RevisionStates()...)

	if d.State == StateSubmitted {
		d.ValidateSubmittedPrescription(validator)
	}
}

func (d *DataAttributes) ValidateSubmittedPrescription(validator structure.Validator) {
	validator.String("accountType", &d.AccountType).NotEmpty()
	if d.AccountType == AccountTypeCaregiver {
		validator.String("caregiverFirstName", &d.CaregiverFirstName).NotEmpty()
		validator.String("caregiverLastName", &d.CaregiverLastName).NotEmpty()
	}
	validator.String("firstName", &d.FirstName).NotEmpty()
	validator.String("lastName", &d.LastName).NotEmpty()
	validator.String("birthday", &d.Birthday).NotEmpty()
	validator.String("email", &d.Email).NotEmpty()
	validator.String("sex", &d.Sex).NotEmpty()
	validator.Int("yearOfDiagnosis", &d.YearOfDiagnosis).GreaterThan(1900)
	validator.String("training", &d.Training).NotEmpty()
	validator.String("therapySettings", &d.TherapySettings).NotEmpty()
	validator.Bool("prescriberTermsAccepted", &d.PrescriberTermsAccepted).True()

	// if phoneNumber is nil validate will fail
	phoneValidator := validator.WithReference("phoneNumber")
	if d.PhoneNumber != nil {
		d.PhoneNumber.Validate(phoneValidator)
	} else {
		phoneValidator.ReportError(structureValidator.ErrorValueEmpty())
	}

	weightValidator := validator.WithReference("weight")
	if d.Weight != nil {
		d.Weight.ValidateSubmittedPrescription(weightValidator)
	}

	initialSettingsValidator := validator.WithReference("initialSettings")
	if d.InitialSettings != nil {
		d.InitialSettings.ValidateSubmittedPrescription(initialSettingsValidator)
	} else {
		initialSettingsValidator.ReportError(structureValidator.ErrorValueEmpty())
	}
}

type CreationAttributes struct {
	CreatedTime   time.Time `json:"createdTime,omitempty" bson:"createdTime"`
	CreatedUserID string    `json:"createdUserId,omitempty" bson:"cratedUserId"`
}

func (c *CreationAttributes) Validate(validator structure.Validator) {
	validator.Time("createdTime", &c.CreatedTime).NotZero().BeforeNow(time.Second)
	validator.String("createdUserId", &c.CreatedUserID).Using(user.IDValidator)
}

type Weight struct {
	Value *float64 `json:"value,omitempty" bson:"value,omitempty"`
	Units string   `json:"units,omitempty" bson:"units,omitempty"`
}

func (w *Weight) Validate(validator structure.Validator) {
	if w.Value != nil {
		validator.Float64("value", w.Value).GreaterThan(0)
	}
	if w.Units != "" {
		validator.String("units", &w.Units).EqualTo(UnitKg)
	}
}

func (w *Weight) ValidateSubmittedPrescription(validator structure.Validator) {
	validator.Float64("value", w.Value).GreaterThan(0)
	validator.String("units", &w.Units).NotEmpty()
}

type PhoneNumber struct {
	CountryCode int    `json:"countryCode,omitempty" bson:"value,omitempty"`
	Number      string `json:"number,omitempty" bson:"number,omitempty"`
}

func (p *PhoneNumber) Validate(validator structure.Validator) {
	validator.Int("countryCode", &p.CountryCode).EqualTo(usPhoneNumberCountryCode)
	validator.String("number", &p.Number).Matches(usPhoneNumberRegex)
}

func RevisionStates() []string {
	return []string{
		StateDraft,
		StatePending,
		StateSubmitted,
	}
}

func Trainings() []string {
	return []string{
		TrainingInModule,
		TrainingInPerson,
	}
}

func TherapySettings() []string {
	return []string{
		TherapySettingInitial,
		TherapySettingTransferPumpSettings,
	}
}

func SexValues() []string {
	return []string{
		SexMale,
		SexFemale,
		SexUndisclosed,
	}
}

func AccountTypes() []string {
	return []string{
		AccountTypePatient,
		AccountTypeCaregiver,
	}
}
<commit_msg>Fix typo in prescription document attribute<commit_after>package prescription

import (
	"regexp"
	"time"

	"github.com/tidepool-org/platform/structure"
	structureValidator "github.com/tidepool-org/platform/structure/validator"
	"github.com/tidepool-org/platform/user"
)

const (
	TrainingInPerson = "inPerson"
	TrainingInModule = "inModule"

	TherapySettingInitial              = "initial"
	TherapySettingTransferPumpSettings = "transferPumpSettings"

	SexMale        = "male"
	SexFemale      = "female"
	SexUndisclosed = "undisclosed"

	UnitKg = "kg"

	AccountTypePatient   = "patient"
	AccountTypeCaregiver = "caregiver"

	usPhoneNumberRegexString = "^\\d{10}|\\(\\d{3}\\) ?\\d{3}\\-\\d{4}$" // Matches 1234567890, (123)456-7890 or (123) 456-7890
	usPhoneNumberCountryCode = 1
)

var (
	usPhoneNumberRegex = regexp.MustCompile(usPhoneNumberRegexString)
)

type RevisionCreate struct {
	DataAttributes `json:",inline"`
}

func NewRevisionCreate() *RevisionCreate {
	return &RevisionCreate{}
}

func (r *RevisionCreate) Validate(validator structure.Validator) {
	r.DataAttributes.Validate(validator)
}

type Signature struct {
	Value  string `json:"signature" bson:"signature"`
	UserID string `json:"signatureUserId" bson:"signatureUserId"`
	KeyID  string `json:"signatureKeyId" bson:"signatureKeyId"`
}

type Revision struct {
	RevisionID int         `json:"revisionId" bson:"revisionId"`
	Signature  *Signature  `json:"signature,omitempty" bson:"signature,omitempty"`
	Attributes *Attributes `json:"attributes" bson:"attributes"`
}

type Revisions []*Revision

func NewRevision(userID string, revisionID int, create *RevisionCreate) *Revision {
	now := time.Now()
	return &Revision{
		RevisionID: revisionID,
		Attributes: &Attributes{
			DataAttributes: DataAttributes{
				AccountType:             create.AccountType,
				CaregiverFirstName:      create.CaregiverFirstName,
				CaregiverLastName:       create.CaregiverLastName,
				FirstName:               create.FirstName,
				LastName:                create.LastName,
				Birthday:                create.Birthday,
				MRN:                     create.MRN,
				Email:                   create.Email,
				Sex:                     create.Sex,
				Weight:                  create.Weight,
				YearOfDiagnosis:         create.YearOfDiagnosis,
				PhoneNumber:             create.PhoneNumber,
				InitialSettings:         create.InitialSettings,
				Training:                create.Training,
				TherapySettings:         create.TherapySettings,
				PrescriberTermsAccepted: create.PrescriberTermsAccepted,
				State:                   create.State,
			},
			CreationAttributes: CreationAttributes{
				CreatedTime:   now,
				CreatedUserID: userID,
			},
		},
	}
}

func (r *Revision) Validate(validator structure.Validator) {
	validator.Int("revisionId", &r.RevisionID).GreaterThanOrEqualTo(0)
	attributesValidator := validator.WithReference("attributes")
	if r.Attributes != nil {
		r.Attributes.Validate(attributesValidator)
	} else {
		attributesValidator.ReportError(structureValidator.ErrorValueEmpty())
	}
}

func (r *Revision) CalculateExpirationTime() *time.Time {
	if r.Attributes.State != StateSubmitted {
		return nil
	}

	expiration := time.Now().Add(MaximumExpirationTime)
	return &expiration
}

func (r *Revision) GetPrescriberUserID() string {
	if r.Attributes.State != StateSubmitted {
		return ""
	}

	return r.Attributes.CreatedUserID
}

type Attributes struct {
	DataAttributes     `json:",inline" bson:",inline"`
	CreationAttributes `json:",inline" bson:",inline"`
}

func (a *Attributes) Validate(validator structure.Validator) {
	a.DataAttributes.Validate(validator)
	a.CreationAttributes.Validate(validator)
}

type DataAttributes struct {
	AccountType             string           `json:"accountType,omitempty" bson:"accountType"`
	CaregiverFirstName      string           `json:"caregiverFirstName,omitempty" bson:"caregiverFirstName"`
	CaregiverLastName       string           `json:"caregiverLastName,omitempty" bson:"caregiverLastName"`
	FirstName               string           `json:"firstName,omitempty" bson:"firstName"`
	LastName                string           `json:"lastName,omitempty" bson:"lastName"`
	Birthday                string           `json:"birthday,omitempty" bson:"birthday"`
	MRN                     string           `json:"mrn,omitempty" bson:"mrn"`
	Email                   string           `json:"email,omitempty" bson:"email"`
	Sex                     string           `json:"sex,omitempty" bson:"sex"`
	Weight                  *Weight          `json:"weight,omitempty" bson:"weight"`
	YearOfDiagnosis         int              `json:"yearOfDiagnosis,omitempty" bson:"yearOfDiagnosis"`
	PhoneNumber             *PhoneNumber     `json:"phoneNumber,omitempty" bson:"phoneNumber"`
	InitialSettings         *InitialSettings `json:"initialSettings,omitempty" bson:"initialSettings"`
	Training                string           `json:"training,omitempty" bson:"training"`
	TherapySettings         string           `json:"therapySettings,omitempty" bson:"therapySettings"`
	PrescriberTermsAccepted bool             `json:"prescriberTermsAccepted,omitempty" bson:"prescriberTermsAccepted"`
	State                   string           `json:"state" bson:"state"`
}

func (d *DataAttributes) Validate(validator structure.Validator) {
	if d.AccountType != "" {
		validator.String("accountType", &d.AccountType).OneOf(AccountTypes()...)
		if d.AccountType == AccountTypePatient {
			validator.String("caregiverFirstName", &d.CaregiverFirstName).Empty()
			validator.String("caregiverLastName", &d.CaregiverLastName).Empty()
		}
	}
	if d.Birthday != "" {
		validator.String("birthday", &d.Birthday).AsTime("2006-01-02").NotZero().BeforeNow(time.Second)
	}
	if d.Email != "" {
		validator.String("email", &d.Email).Email()
	}
	if d.Sex != "" {
		validator.String("sex", &d.Sex).OneOf(SexValues()...)
	}
	if d.YearOfDiagnosis != 0 {
		validator.Int("yearOfDiagnosis", &d.YearOfDiagnosis).GreaterThan(1900)
	}
	if d.PhoneNumber != nil {
		d.PhoneNumber.Validate(validator.WithReference("phoneNumber"))
	}
	if d.Training != "" {
		validator.String("training", &d.Training).OneOf(Trainings()...)
	}
	if d.TherapySettings != "" {
		validator.String("therapySettings", &d.TherapySettings).OneOf(TherapySettings()...)
	}
	if d.Weight != nil {
		d.Weight.Validate(validator.WithReference("weight"))
	}
	if d.InitialSettings != nil {
		d.InitialSettings.Validate(validator.WithReference("initialSettings"))
	}
	validator.String("state", &d.State).OneOf(RevisionStates()...)

	if d.State == StateSubmitted {
		d.ValidateSubmittedPrescription(validator)
	}
}

func (d *DataAttributes) ValidateSubmittedPrescription(validator structure.Validator) {
	validator.String("accountType", &d.AccountType).NotEmpty()
	if d.AccountType == AccountTypeCaregiver {
		validator.String("caregiverFirstName", &d.CaregiverFirstName).NotEmpty()
		validator.String("caregiverLastName", &d.CaregiverLastName).NotEmpty()
	}
	validator.String("firstName", &d.FirstName).NotEmpty()
	validator.String("lastName", &d.LastName).NotEmpty()
	validator.String("birthday", &d.Birthday).NotEmpty()
	validator.String("email", &d.Email).NotEmpty()
	validator.String("sex", &d.Sex).NotEmpty()
	validator.Int("yearOfDiagnosis", &d.YearOfDiagnosis).GreaterThan(1900)
	validator.String("training", &d.Training).NotEmpty()
	validator.String("therapySettings", &d.TherapySettings).NotEmpty()
	validator.Bool("prescriberTermsAccepted", &d.PrescriberTermsAccepted).True()

	// if phoneNumber is nil validate will fail
	phoneValidator := validator.WithReference("phoneNumber")
	if d.PhoneNumber != nil {
		d.PhoneNumber.Validate(phoneValidator)
	} else {
		phoneValidator.ReportError(structureValidator.ErrorValueEmpty())
	}

	weightValidator := validator.WithReference("weight")
	if d.Weight != nil {
		d.Weight.ValidateSubmittedPrescription(weightValidator)
	}

	initialSettingsValidator := validator.WithReference("initialSettings")
	if d.InitialSettings != nil {
		d.InitialSettings.ValidateSubmittedPrescription(initialSettingsValidator)
	} else {
		initialSettingsValidator.ReportError(structureValidator.ErrorValueEmpty())
	}
}

type CreationAttributes struct {
	CreatedTime   time.Time `json:"createdTime,omitempty" bson:"createdTime"`
	CreatedUserID string    `json:"createdUserId,omitempty" bson:"createdUserId"`
}

func (c *CreationAttributes) Validate(validator structure.Validator) {
	validator.Time("createdTime", &c.CreatedTime).NotZero().BeforeNow(time.Second)
	validator.String("createdUserId", &c.CreatedUserID).Using(user.IDValidator)
}

type Weight struct {
	Value *float64 `json:"value,omitempty" bson:"value,omitempty"`
	Units string   `json:"units,omitempty" bson:"units,omitempty"`
}

func (w *Weight) Validate(validator structure.Validator) {
	if w.Value != nil {
		validator.Float64("value", w.Value).GreaterThan(0)
	}
	if w.Units != "" {
		validator.String("units", &w.Units).EqualTo(UnitKg)
	}
}

func (w *Weight) ValidateSubmittedPrescription(validator structure.Validator) {
	validator.Float64("value", w.Value).GreaterThan(0)
	validator.String("units", &w.Units).NotEmpty()
}

type PhoneNumber struct {
	CountryCode int    `json:"countryCode,omitempty" bson:"value,omitempty"`
	Number      string `json:"number,omitempty" bson:"number,omitempty"`
}

func (p *PhoneNumber) Validate(validator structure.Validator) {
	validator.Int("countryCode", &p.CountryCode).EqualTo(usPhoneNumberCountryCode)
	validator.String("number", &p.Number).Matches(usPhoneNumberRegex)
}

func RevisionStates() []string {
	return []string{
		StateDraft,
		StatePending,
		StateSubmitted,
	}
}

func Trainings() []string {
	return []string{
		TrainingInModule,
		TrainingInPerson,
	}
}

func TherapySettings() []string {
	return []string{
		TherapySettingInitial,
		TherapySettingTransferPumpSettings,
	}
}

func SexValues() []string {
	return []string{
		SexMale,
		SexFemale,
		SexUndisclosed,
	}
}

func AccountTypes() []string {
	return []string{
		AccountTypePatient,
		AccountTypeCaregiver,
	}
}
<|endoftext|>


<commit_before><commit_msg>make data structs more readable.<commit_after><|endoftext|>


<commit_before>/*
 * Copyright 2018 the original author or 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 utils

import (
	"path/filepath"

	"github.com/projectriff/riff/riff-cli/pkg/options"
)

func ResolveOptions(functionArtifact string, language string, opts *options.InitOptions) {

	if opts.Input == "" {
		opts.Input = opts.FunctionName
	}

	if opts.Artifact == "" {
		opts.Artifact = filepath.Base(functionArtifact)
	}

	protocolForLanguage := map[string]string{
		"shell": "grpc",
		"java":  "grpc",
		"js":    "grpc",
		"node":  "grpc",
		"python": "stdio",
	}

	if opts.Protocol == "" {
		opts.Protocol = protocolForLanguage[language]
	}
}
<commit_msg>Switch python to grpc protocol<commit_after>/*
 * Copyright 2018 the original author or 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 utils

import (
	"path/filepath"

	"github.com/projectriff/riff/riff-cli/pkg/options"
)

func ResolveOptions(functionArtifact string, language string, opts *options.InitOptions) {

	if opts.Input == "" {
		opts.Input = opts.FunctionName
	}

	if opts.Artifact == "" {
		opts.Artifact = filepath.Base(functionArtifact)
	}

	protocolForLanguage := map[string]string{
		"shell":  "grpc",
		"java":   "grpc",
		"js":     "grpc",
		"node":   "grpc",
		"python": "grpc",
	}

	if opts.Protocol == "" {
		opts.Protocol = protocolForLanguage[language]
	}
}
<|endoftext|>


<commit_before>package bencode

import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"testing"
)

type any interface{}

func checkMarshal(expected string, data any) (err error) {
	var b bytes.Buffer
	if err = Marshal(&b, data); err != nil {
		return
	}
	s := b.String()
	if expected != s {
		err = errors.New(fmt.Sprintf("Expected %s got %s", expected, s))
		return
	}
	return
}

func check(expected string, data any) (err error) {
	if err = checkMarshal(expected, data); err != nil {
		return
	}
	b2 := bytes.NewBufferString(expected)
	val, err := Decode(b2)
	if err != nil {
		err = errors.New(fmt.Sprint("Failed decoding ", expected, " ", err))
		return
	}
	if err = checkFuzzyEqual(data, val); err != nil {
		return
	}
	return
}

func checkFuzzyEqual(a any, b any) (err error) {
	if !fuzzyEqual(a, b) {
		err = errors.New(fmt.Sprint(a, " != ", b,
			": ", reflect.ValueOf(a), "!=", reflect.ValueOf(b)))
	}
	return
}

func fuzzyEqual(a, b any) bool {
	return fuzzyEqualValue(reflect.ValueOf(a), reflect.ValueOf(b))
}

func checkFuzzyEqualValue(a, b reflect.Value) (err error) {
	if !fuzzyEqualValue(a, b) {
		err = fmt.Errorf("Wanted %v(%v) got %v(%v)", a, a.Interface(), b, b.Interface())
	}
	return
}

func fuzzyEqualInt64(a int64, b reflect.Value) bool {
	switch vb := b; vb.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return a == (vb.Int())
	default:
		return false
	}
	return false
}

func fuzzyEqualArrayOrSlice(va reflect.Value, b reflect.Value) bool {
	switch vb := b; vb.Kind() {
	case reflect.Array:
		return fuzzyEqualArrayOrSlice2(va, vb)
	case reflect.Slice:
		return fuzzyEqualArrayOrSlice2(va, vb)
	default:
		return false
	}
	return false
}

func deInterface(a reflect.Value) reflect.Value {
	switch va := a; va.Kind() {
	case reflect.Interface:
		return va.Elem()
	}
	return a
}

func fuzzyEqualArrayOrSlice2(a reflect.Value, b reflect.Value) bool {
	if a.Len() != b.Len() {
		return false
	}

	for i := 0; i < a.Len(); i++ {
		ea := deInterface(a.Index(i))
		eb := deInterface(b.Index(i))
		if !fuzzyEqualValue(ea, eb) {
			return false
		}
	}
	return true
}

func fuzzyEqualMap(a reflect.Value, b reflect.Value) bool {
	key := a.Type().Key()
	if key.Kind() != reflect.String {
		return false
	}
	key = b.Type().Key()
	if key.Kind() != reflect.String {
		return false
	}

	aKeys, bKeys := a.MapKeys(), b.MapKeys()

	if len(aKeys) != len(bKeys) {
		return false
	}

	for _, k := range aKeys {
		if !fuzzyEqualValue(a.MapIndex(k), b.MapIndex(k)) {
			return false
		}
	}
	return true
}

func fuzzyEqualStruct(a reflect.Value, b reflect.Value) bool {
	numA, numB := a.NumField(), b.NumField()
	if numA != numB {
		return false
	}

	for i := 0; i < numA; i++ {
		if !fuzzyEqualValue(a.Field(i), b.Field(i)) {
			return false
		}
	}
	return true
}

func fuzzyEqualValue(a, b reflect.Value) bool {
	switch va := a; va.Kind() {
	case reflect.String:
		switch vb := b; vb.Kind() {
		case reflect.String:
			return va.String() == vb.String()
		default:
			return false
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return fuzzyEqualInt64(va.Int(), b)
	case reflect.Array:
		return fuzzyEqualArrayOrSlice(va, b)
	case reflect.Slice:
		return fuzzyEqualArrayOrSlice(va, b)
	case reflect.Map:
		switch vb := b; vb.Kind() {
		case reflect.Map:
			return fuzzyEqualMap(va, vb)
		default:
			return false
		}
	case reflect.Struct:
		switch vb := b; vb.Kind() {
		case reflect.Struct:
			return fuzzyEqualStruct(va, vb)
		default:
			return false
		}
	case reflect.Interface:
		switch vb := b; vb.Kind() {
		case reflect.Interface:
			return fuzzyEqualValue(va.Elem(), vb.Elem())
		default:
			return false
		}
	default:
		return false
	}
	return false
}

func checkUnmarshal(expected string, data any) (err error) {
	if err = checkMarshal(expected, data); err != nil {
		return
	}
	dataValue := reflect.ValueOf(data)
	newOne := reflect.New(reflect.TypeOf(data))
	buf := bytes.NewBufferString(expected)
	if err = unmarshalValue(buf, newOne); err != nil {
		return
	}
	if err = checkFuzzyEqualValue(dataValue, newOne.Elem()); err != nil {
		return
	}
	return
}

type SVPair struct {
	s string
	v any
}

func TestDecode(t *testing.T) {
	tests := []SVPair{
		SVPair{"i0e", int64(0)},
		SVPair{"i0e", 0},
		SVPair{"i100e", 100},
		SVPair{"i-100e", -100},
		SVPair{"1:a", "a"},
		SVPair{"2:a\"", "a\""},
		SVPair{"11:0123456789a", "0123456789a"},
		SVPair{"le", []int64{}},
		SVPair{"li1ei2ee", []int{1, 2}},
		SVPair{"l3:abc3:defe", []string{"abc", "def"}},
		SVPair{"li42e3:abce", []any{42, "abc"}},
		SVPair{"de", map[string]any{}},
		SVPair{"d3:cati1e3:dogi2ee", map[string]any{"cat": 1, "dog": 2}},
	}
	for _, sv := range tests {
		if err := check(sv.s, sv.v); err != nil {
			t.Error(err.Error())
		}
	}
}

type structA struct {
	A int    "a"
	B string `example:"data" bencode:"b"`
}

func TestUnmarshal(t *testing.T) {
	type structNested struct {
		T string            "t"
		Y string            "y"
		Q string            "q"
		A map[string]string "a"
	}
	innerDict := map[string]string{"id": "abcdefghij0123456789"}
	nestedDictionary := structNested{"aa", "q", "ping", innerDict}

	tests := []SVPair{
		SVPair{"i100e", 100},
		SVPair{"i-100e", -100},
		SVPair{"1:a", "a"},
		SVPair{"2:a\"", "a\""},
		SVPair{"11:0123456789a", "0123456789a"},
		SVPair{"le", []int64{}},
		SVPair{"li1ei2ee", []int{1, 2}},
		SVPair{"l3:abc3:defe", []string{"abc", "def"}},
		SVPair{"li42e3:abce", []any{42, "abc"}},
		SVPair{"de", map[string]any{}},
		SVPair{"d3:cati1e3:dogi2ee", map[string]any{"cat": 1, "dog": 2}},
		SVPair{"d1:ai10e1:b3:fooe", structA{10, "foo"}},
		SVPair{"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe", nestedDictionary},
	}
	for _, sv := range tests {
		if err := checkUnmarshal(sv.s, sv.v); err != nil {
			t.Error(err.Error())
		}
	}
}
<commit_msg>Add test for whitespace in bencode name.<commit_after>package bencode

import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"testing"
)

type any interface{}

func checkMarshal(expected string, data any) (err error) {
	var b bytes.Buffer
	if err = Marshal(&b, data); err != nil {
		return
	}
	s := b.String()
	if expected != s {
		err = errors.New(fmt.Sprintf("Expected %s got %s", expected, s))
		return
	}
	return
}

func check(expected string, data any) (err error) {
	if err = checkMarshal(expected, data); err != nil {
		return
	}
	b2 := bytes.NewBufferString(expected)
	val, err := Decode(b2)
	if err != nil {
		err = errors.New(fmt.Sprint("Failed decoding ", expected, " ", err))
		return
	}
	if err = checkFuzzyEqual(data, val); err != nil {
		return
	}
	return
}

func checkFuzzyEqual(a any, b any) (err error) {
	if !fuzzyEqual(a, b) {
		err = errors.New(fmt.Sprint(a, " != ", b,
			": ", reflect.ValueOf(a), "!=", reflect.ValueOf(b)))
	}
	return
}

func fuzzyEqual(a, b any) bool {
	return fuzzyEqualValue(reflect.ValueOf(a), reflect.ValueOf(b))
}

func checkFuzzyEqualValue(a, b reflect.Value) (err error) {
	if !fuzzyEqualValue(a, b) {
		err = fmt.Errorf("Wanted %v(%v) got %v(%v)", a, a.Interface(), b, b.Interface())
	}
	return
}

func fuzzyEqualInt64(a int64, b reflect.Value) bool {
	switch vb := b; vb.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return a == (vb.Int())
	default:
		return false
	}
	return false
}

func fuzzyEqualArrayOrSlice(va reflect.Value, b reflect.Value) bool {
	switch vb := b; vb.Kind() {
	case reflect.Array:
		return fuzzyEqualArrayOrSlice2(va, vb)
	case reflect.Slice:
		return fuzzyEqualArrayOrSlice2(va, vb)
	default:
		return false
	}
	return false
}

func deInterface(a reflect.Value) reflect.Value {
	switch va := a; va.Kind() {
	case reflect.Interface:
		return va.Elem()
	}
	return a
}

func fuzzyEqualArrayOrSlice2(a reflect.Value, b reflect.Value) bool {
	if a.Len() != b.Len() {
		return false
	}

	for i := 0; i < a.Len(); i++ {
		ea := deInterface(a.Index(i))
		eb := deInterface(b.Index(i))
		if !fuzzyEqualValue(ea, eb) {
			return false
		}
	}
	return true
}

func fuzzyEqualMap(a reflect.Value, b reflect.Value) bool {
	key := a.Type().Key()
	if key.Kind() != reflect.String {
		return false
	}
	key = b.Type().Key()
	if key.Kind() != reflect.String {
		return false
	}

	aKeys, bKeys := a.MapKeys(), b.MapKeys()

	if len(aKeys) != len(bKeys) {
		return false
	}

	for _, k := range aKeys {
		if !fuzzyEqualValue(a.MapIndex(k), b.MapIndex(k)) {
			return false
		}
	}
	return true
}

func fuzzyEqualStruct(a reflect.Value, b reflect.Value) bool {
	numA, numB := a.NumField(), b.NumField()
	if numA != numB {
		return false
	}

	for i := 0; i < numA; i++ {
		if !fuzzyEqualValue(a.Field(i), b.Field(i)) {
			return false
		}
	}
	return true
}

func fuzzyEqualValue(a, b reflect.Value) bool {
	switch va := a; va.Kind() {
	case reflect.String:
		switch vb := b; vb.Kind() {
		case reflect.String:
			return va.String() == vb.String()
		default:
			return false
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return fuzzyEqualInt64(va.Int(), b)
	case reflect.Array:
		return fuzzyEqualArrayOrSlice(va, b)
	case reflect.Slice:
		return fuzzyEqualArrayOrSlice(va, b)
	case reflect.Map:
		switch vb := b; vb.Kind() {
		case reflect.Map:
			return fuzzyEqualMap(va, vb)
		default:
			return false
		}
	case reflect.Struct:
		switch vb := b; vb.Kind() {
		case reflect.Struct:
			return fuzzyEqualStruct(va, vb)
		default:
			return false
		}
	case reflect.Interface:
		switch vb := b; vb.Kind() {
		case reflect.Interface:
			return fuzzyEqualValue(va.Elem(), vb.Elem())
		default:
			return false
		}
	default:
		return false
	}
	return false
}

func checkUnmarshal(expected string, data any) (err error) {
	if err = checkMarshal(expected, data); err != nil {
		return
	}
	dataValue := reflect.ValueOf(data)
	newOne := reflect.New(reflect.TypeOf(data))
	buf := bytes.NewBufferString(expected)
	if err = unmarshalValue(buf, newOne); err != nil {
		return
	}
	if err = checkFuzzyEqualValue(dataValue, newOne.Elem()); err != nil {
		return
	}
	return
}

type SVPair struct {
	s string
	v any
}

func TestDecode(t *testing.T) {
	tests := []SVPair{
		SVPair{"i0e", int64(0)},
		SVPair{"i0e", 0},
		SVPair{"i100e", 100},
		SVPair{"i-100e", -100},
		SVPair{"1:a", "a"},
		SVPair{"2:a\"", "a\""},
		SVPair{"11:0123456789a", "0123456789a"},
		SVPair{"le", []int64{}},
		SVPair{"li1ei2ee", []int{1, 2}},
		SVPair{"l3:abc3:defe", []string{"abc", "def"}},
		SVPair{"li42e3:abce", []any{42, "abc"}},
		SVPair{"de", map[string]any{}},
		SVPair{"d3:cati1e3:dogi2ee", map[string]any{"cat": 1, "dog": 2}},
	}
	for _, sv := range tests {
		if err := check(sv.s, sv.v); err != nil {
			t.Error(err.Error())
		}
	}
}

type structA struct {
	A int    "a"
	B string `example:"data" bencode:"b"`
	C string `example:"data2" bencode:"sea monster"`
}

func TestUnmarshal(t *testing.T) {
	type structNested struct {
		T string            "t"
		Y string            "y"
		Q string            "q"
		A map[string]string "a"
	}
	innerDict := map[string]string{"id": "abcdefghij0123456789"}
	nestedDictionary := structNested{"aa", "q", "ping", innerDict}

	tests := []SVPair{
		SVPair{"i100e", 100},
		SVPair{"i-100e", -100},
		SVPair{"1:a", "a"},
		SVPair{"2:a\"", "a\""},
		SVPair{"11:0123456789a", "0123456789a"},
		SVPair{"le", []int64{}},
		SVPair{"li1ei2ee", []int{1, 2}},
		SVPair{"l3:abc3:defe", []string{"abc", "def"}},
		SVPair{"li42e3:abce", []any{42, "abc"}},
		SVPair{"de", map[string]any{}},
		SVPair{"d3:cati1e3:dogi2ee", map[string]any{"cat": 1, "dog": 2}},
		SVPair{"d1:ai10e1:b3:foo11:sea monster3:bare", structA{10, "foo", "bar"}},
		SVPair{"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe", nestedDictionary},
	}
	for _, sv := range tests {
		if err := checkUnmarshal(sv.s, sv.v); err != nil {
			t.Error(err.Error())
		}
	}
}
<|endoftext|>


<commit_before>package txtdirect

import (
	"bytes"
	"compress/gzip"
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/cretz/bine/tor"
	"github.com/mholt/caddy"
	cproxy "github.com/mholt/caddy/caddyhttp/proxy"
	"golang.org/x/net/proxy"
	lumberjack "gopkg.in/natefinch/lumberjack.v2"
)

// DefaultOnionServicePort is the port used to serve the onion service on
const DefaultOnionServicePort = 4242

// Tor type config struct
type Tor struct {
	Enable bool
	// Socks5 proxy port
	Port      int
	DataDir   string
	Torrc     string
	DebugMode bool
	LogFile   string

	instance        *tor.Tor
	contextCanceler context.CancelFunc
	onion           *tor.OnionService
}

type TorResponse struct {
	headers    http.Header
	body       []byte
	bodyReader bytes.Buffer
	bodyWriter bytes.Buffer
	status     int
}

// TODO: Discuss these values
const (
	torProxyKeepalive = 30000000
	torFallbackDelay  = 30000000 * time.Millisecond
	torProxyTimeout   = 30000000 * time.Second
)

var bufferPool = sync.Pool{New: createBuffer}

func createBuffer() interface{} {
	return make([]byte, 0, 32*1024)
}

func (t *Tor) Start(c *caddy.Controller) {
	var debugger io.Writer
	if t.DebugMode {
		if t.LogFile != "" {
			debugger = &lumberjack.Logger{
				Filename:   t.LogFile,
				MaxSize:    100,
				MaxAge:     14,
				MaxBackups: 10,
			}
		}
		debugger = os.Stdout
	}

	torInstance, err := tor.Start(nil, &tor.StartConf{
		NoAutoSocksPort: true,
		ExtraArgs:       []string{"--SocksPort", strconv.Itoa(t.Port)},
		TempDataDirBase: t.DataDir,
		TorrcFile:       t.Torrc,
		DebugWriter:     debugger,
	})
	if err != nil {
		log.Panicf("Unable to start Tor: %v", err)
	}

	listenCtx := context.Background()

	onion, err := torInstance.Listen(listenCtx, &tor.ListenConf{LocalPort: 8868, RemotePorts: []int{80}})
	if err != nil {
		log.Panicf("Unable to start onion service: %v", err)
	}

	t.onion = onion
	t.instance = torInstance
}

// Stop stops the tor instance, context listener and the onion service
func (t *Tor) Stop() error {
	if err := t.instance.Close(); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't close the tor instance. %s", err.Error())
	}
	t.onion.Close()
	return nil
}

// Proxy redirects the request to the local onion serivce and the actual proxying
// happens inside onion service's http handler
func (t *Tor) Proxy(w http.ResponseWriter, r *http.Request, rec record, c Config) error {
	u, err := url.Parse(rec.To)
	if err != nil {
		return err
	}
	u.Path = r.URL.Path

	// Create a socks5 dialer
	dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", t.Port), nil, proxy.Direct)
	if err != nil {
		log.Fatal(err)
	}

	reverseProxy := cproxy.NewSingleHostReverseProxy(u, "", torProxyKeepalive, torProxyTimeout, torFallbackDelay)
	reverseProxy.Transport = &http.Transport{
		Dial: dialer.Dial,
	}

	tmpResposne := TorResponse{headers: make(http.Header)}
	if err := reverseProxy.ServeHTTP(&tmpResposne, r, nil); err != nil {
		return fmt.Errorf("[txtdirect]: Coudln't proxy the request to the background onion service. %s", err.Error())
	}

	// Decompress the body based on "Content-Encoding" header and write to a writer buffer
	if err := tmpResposne.WriteBody(); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't write the response body: %s", err.Error())
	}

	// Replace the URL hosts with the request's host
	if err := tmpResposne.ReplaceBody(u.Scheme, u.Host, r.Host); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't replace urls inside the response body: %s", err.Error())
	}

	copyHeader(w.Header(), tmpResposne.Header())

	// Write the final response from the temporary ResponseWriter to the main ResponseWriter
	if _, err := w.Write(tmpResposne.Body()); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't write the temporary response to main response body: %s", err.Error())
	}

	return nil
}

// ParseTor parses the txtdirect config for Tor proxy
func (t *Tor) ParseTor(c *caddy.Controller) error {
	switch c.Val() {
	case "port":
		value, err := strconv.Atoi(c.RemainingArgs()[0])
		if err != nil {
			return fmt.Errorf("The given value for port field is not standard. It should be an integer")
		}
		t.Port = value

	case "datadir":
		t.DataDir = c.RemainingArgs()[0]

	case "torrc":
		t.Torrc = c.RemainingArgs()[0]

	case "debug_mode":
		value, err := strconv.ParseBool(c.RemainingArgs()[0])
		if err != nil {
			return fmt.Errorf("The given value for debug_mode field is not standard. It should be a boolean")
		}
		t.DebugMode = value

	case "logfile":
		t.LogFile = c.RemainingArgs()[0]

	default:
		return c.ArgErr() // unhandled option for tor
	}
	return nil
}

// SetDefaults sets the default values for prometheus config
// if the fields are empty
func (t *Tor) SetDefaults() {
	if t.Port == 0 {
		t.Port = DefaultOnionServicePort
	}
}

// Header returns response headers
func (t *TorResponse) Header() http.Header {
	return t.headers
}

func (t *TorResponse) Write(body []byte) (int, error) {
	reader := bytes.NewReader(body)
	pooledIoCopy(&t.bodyReader, reader)
	t.body = body
	return len(body), nil
}

// Body returns response's body. This method should only get called after WriteBody()
func (t *TorResponse) Body() []byte {
	return t.bodyWriter.Bytes()
}

// WriteHeader Writes the given status code to response
func (t *TorResponse) WriteHeader(status int) {
	t.status = status
}

func (t *TorResponse) ReplaceBody(scheme, to, host string) error {
	replacedBody := bytes.Replace(t.bodyWriter.Bytes(), []byte(scheme+"://"+to), []byte(scheme+"://"+host), -1)
	t.bodyWriter.Reset()
	if _, err := t.bodyWriter.Write(replacedBody); err != nil {
		return err
	}
	return nil
}

func (t *TorResponse) WriteBody() error {
	switch t.Header().Get("Content-Encoding") {
	case "gzip":
		reader, err := gzip.NewReader(&t.bodyReader)
		if err != nil {
			return err
		}
		defer reader.Close()
		_, err = io.Copy(&t.bodyWriter, reader)
		if err != nil {
			return err
		}
		t.Header().Del("Content-Encoding")
	default:
		_, err := io.Copy(&t.bodyWriter, &t.bodyReader)
		if err != nil {
			return err
		}
	}
	return nil
}

var skipHeaders = map[string]struct{}{
	"Content-Type":        {},
	"Content-Disposition": {},
	"Accept-Ranges":       {},
	"Set-Cookie":          {},
	"Cache-Control":       {},
	"Expires":             {},
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		if _, ok := dst[k]; ok {
			if _, shouldSkip := skipHeaders[k]; shouldSkip {
				continue
			}
			if k != "Server" {
				dst.Del(k)
			}
		}
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

func pooledIoCopy(dst io.Writer, src io.Reader) {
	buf := bufferPool.Get().([]byte)
	defer bufferPool.Put(buf)

	bufCap := cap(buf)
	io.CopyBuffer(dst, src, buf[0:bufCap:bufCap])
}
<commit_msg>Fix duplicating path information<commit_after>package txtdirect

import (
	"bytes"
	"compress/gzip"
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/cretz/bine/tor"
	"github.com/mholt/caddy"
	cproxy "github.com/mholt/caddy/caddyhttp/proxy"
	"golang.org/x/net/proxy"
	lumberjack "gopkg.in/natefinch/lumberjack.v2"
)

// DefaultOnionServicePort is the port used to serve the onion service on
const DefaultOnionServicePort = 4242

// Tor type config struct
type Tor struct {
	Enable bool
	// Socks5 proxy port
	Port      int
	DataDir   string
	Torrc     string
	DebugMode bool
	LogFile   string

	instance        *tor.Tor
	contextCanceler context.CancelFunc
	onion           *tor.OnionService
}

type TorResponse struct {
	headers    http.Header
	body       []byte
	bodyReader bytes.Buffer
	bodyWriter bytes.Buffer
	status     int
}

// TODO: Discuss these values
const (
	torProxyKeepalive = 30000000
	torFallbackDelay  = 30000000 * time.Millisecond
	torProxyTimeout   = 30000000 * time.Second
)

var bufferPool = sync.Pool{New: createBuffer}

func createBuffer() interface{} {
	return make([]byte, 0, 32*1024)
}

func (t *Tor) Start(c *caddy.Controller) {
	var debugger io.Writer
	if t.DebugMode {
		if t.LogFile != "" {
			debugger = &lumberjack.Logger{
				Filename:   t.LogFile,
				MaxSize:    100,
				MaxAge:     14,
				MaxBackups: 10,
			}
		}
		debugger = os.Stdout
	}

	torInstance, err := tor.Start(nil, &tor.StartConf{
		NoAutoSocksPort: true,
		ExtraArgs:       []string{"--SocksPort", strconv.Itoa(t.Port)},
		TempDataDirBase: t.DataDir,
		TorrcFile:       t.Torrc,
		DebugWriter:     debugger,
	})
	if err != nil {
		log.Panicf("Unable to start Tor: %v", err)
	}

	listenCtx := context.Background()

	onion, err := torInstance.Listen(listenCtx, &tor.ListenConf{LocalPort: 8868, RemotePorts: []int{80}})
	if err != nil {
		log.Panicf("Unable to start onion service: %v", err)
	}

	t.onion = onion
	t.instance = torInstance
}

// Stop stops the tor instance, context listener and the onion service
func (t *Tor) Stop() error {
	if err := t.instance.Close(); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't close the tor instance. %s", err.Error())
	}
	t.onion.Close()
	return nil
}

// Proxy redirects the request to the local onion serivce and the actual proxying
// happens inside onion service's http handler
func (t *Tor) Proxy(w http.ResponseWriter, r *http.Request, rec record, c Config) error {
	u, err := url.Parse(rec.To)
	if err != nil {
		return err
	}

	// Create a socks5 dialer
	dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", t.Port), nil, proxy.Direct)
	if err != nil {
		log.Fatal(err)
	}

	reverseProxy := cproxy.NewSingleHostReverseProxy(u, "", torProxyKeepalive, torProxyTimeout, torFallbackDelay)
	reverseProxy.Transport = &http.Transport{
		Dial: dialer.Dial,
	}

	tmpResposne := TorResponse{headers: make(http.Header)}
	if err := reverseProxy.ServeHTTP(&tmpResposne, r, nil); err != nil {
		return fmt.Errorf("[txtdirect]: Coudln't proxy the request to the background onion service. %s", err.Error())
	}

	// Decompress the body based on "Content-Encoding" header and write to a writer buffer
	if err := tmpResposne.WriteBody(); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't write the response body: %s", err.Error())
	}

	// Replace the URL hosts with the request's host
	if err := tmpResposne.ReplaceBody(u.Scheme, u.Host, r.Host); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't replace urls inside the response body: %s", err.Error())
	}

	copyHeader(w.Header(), tmpResposne.Header())

	// Write the final response from the temporary ResponseWriter to the main ResponseWriter
	if _, err := w.Write(tmpResposne.Body()); err != nil {
		return fmt.Errorf("[txtdirect]: Couldn't write the temporary response to main response body: %s", err.Error())
	}

	return nil
}

// ParseTor parses the txtdirect config for Tor proxy
func (t *Tor) ParseTor(c *caddy.Controller) error {
	switch c.Val() {
	case "port":
		value, err := strconv.Atoi(c.RemainingArgs()[0])
		if err != nil {
			return fmt.Errorf("The given value for port field is not standard. It should be an integer")
		}
		t.Port = value

	case "datadir":
		t.DataDir = c.RemainingArgs()[0]

	case "torrc":
		t.Torrc = c.RemainingArgs()[0]

	case "debug_mode":
		value, err := strconv.ParseBool(c.RemainingArgs()[0])
		if err != nil {
			return fmt.Errorf("The given value for debug_mode field is not standard. It should be a boolean")
		}
		t.DebugMode = value

	case "logfile":
		t.LogFile = c.RemainingArgs()[0]

	default:
		return c.ArgErr() // unhandled option for tor
	}
	return nil
}

// SetDefaults sets the default values for prometheus config
// if the fields are empty
func (t *Tor) SetDefaults() {
	if t.Port == 0 {
		t.Port = DefaultOnionServicePort
	}
}

// Header returns response headers
func (t *TorResponse) Header() http.Header {
	return t.headers
}

func (t *TorResponse) Write(body []byte) (int, error) {
	reader := bytes.NewReader(body)
	pooledIoCopy(&t.bodyReader, reader)
	t.body = body
	return len(body), nil
}

// Body returns response's body. This method should only get called after WriteBody()
func (t *TorResponse) Body() []byte {
	return t.bodyWriter.Bytes()
}

// WriteHeader Writes the given status code to response
func (t *TorResponse) WriteHeader(status int) {
	t.status = status
}

func (t *TorResponse) ReplaceBody(scheme, to, host string) error {
	replacedBody := bytes.Replace(t.bodyWriter.Bytes(), []byte(scheme+"://"+to), []byte(scheme+"://"+host), -1)
	t.bodyWriter.Reset()
	if _, err := t.bodyWriter.Write(replacedBody); err != nil {
		return err
	}
	return nil
}

func (t *TorResponse) WriteBody() error {
	switch t.Header().Get("Content-Encoding") {
	case "gzip":
		reader, err := gzip.NewReader(&t.bodyReader)
		if err != nil {
			return err
		}
		defer reader.Close()
		_, err = io.Copy(&t.bodyWriter, reader)
		if err != nil {
			return err
		}
		t.Header().Del("Content-Encoding")
	default:
		_, err := io.Copy(&t.bodyWriter, &t.bodyReader)
		if err != nil {
			return err
		}
	}
	return nil
}

var skipHeaders = map[string]struct{}{
	"Content-Type":        {},
	"Content-Disposition": {},
	"Accept-Ranges":       {},
	"Set-Cookie":          {},
	"Cache-Control":       {},
	"Expires":             {},
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		if _, ok := dst[k]; ok {
			if _, shouldSkip := skipHeaders[k]; shouldSkip {
				continue
			}
			if k != "Server" {
				dst.Del(k)
			}
		}
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

func pooledIoCopy(dst io.Writer, src io.Reader) {
	buf := bufferPool.Get().([]byte)
	defer bufferPool.Put(buf)

	bufCap := cap(buf)
	io.CopyBuffer(dst, src, buf[0:bufCap:bufCap])
}
<|endoftext|>