<commit_before>// Copyright 2017 Microsoft. All rights reserved.
// MIT License

package ipam

import (
	"encoding/json"
	"net"
	"strconv"

	"github.com/Azure/azure-container-networking/cni"
	"github.com/Azure/azure-container-networking/common"
	"github.com/Azure/azure-container-networking/ipam"
	"github.com/Azure/azure-container-networking/log"
	"github.com/Azure/azure-container-networking/platform"

	cniSkel "github.com/containernetworking/cni/pkg/skel"
	cniTypes "github.com/containernetworking/cni/pkg/types"
	cniTypesImpl "github.com/containernetworking/cni/pkg/types/020"
)

const (
	// Plugin name.
	name = "azure-vnet-ipam"
)

var (
	// Azure VNET pre-allocated host IDs.
	ipv4DefaultGatewayHostId = net.ParseIP("::1")
	ipv4DnsPrimaryHostId     = net.ParseIP("::2")
	ipv4DnsSecondaryHostId   = net.ParseIP("::3")

	ipv4DefaultRouteDstPrefix = net.IPNet{net.IPv4zero, net.IPv4Mask(0, 0, 0, 0)}
)

// IpamPlugin represents the CNI IPAM plugin.
type ipamPlugin struct {
	*cni.Plugin
	am ipam.AddressManager
}

// NewPlugin creates a new ipamPlugin object.
func NewPlugin(config *common.PluginConfig) (*ipamPlugin, error) {
	// Setup base plugin.
	plugin, err := cni.NewPlugin(name, config.Version)
	if err != nil {
		return nil, err
	}

	// Setup address manager.
	am, err := ipam.NewAddressManager()
	if err != nil {
		return nil, err
	}

	// Create IPAM plugin.
	ipamPlg := &ipamPlugin{
		Plugin: plugin,
		am:     am,
	}

	config.IpamApi = ipamPlg

	return ipamPlg, nil
}

// Starts the plugin.
func (plugin *ipamPlugin) Start(config *common.PluginConfig) error {
	// Initialize base plugin.
	err := plugin.Initialize(config)
	if err != nil {
		log.Printf("[cni-ipam] Failed to initialize base plugin, err:%v.", err)
		return err
	}

	// Log platform information.
	log.Printf("[cni-ipam] Plugin %v version %v.", plugin.Name, plugin.Version)
	log.Printf("[cni-ipam] Running on %v", platform.GetOSInfo())

	// Initialize address manager.
	err = plugin.am.Initialize(config, plugin.Options)
	if err != nil {
		log.Printf("[cni-ipam] Failed to initialize address manager, err:%v.", err)
		return err
	}

	log.Printf("[cni-ipam] Plugin started.")

	return nil
}

// Stops the plugin.
func (plugin *ipamPlugin) Stop() {
	plugin.am.Uninitialize()
	plugin.Uninitialize()
	log.Printf("[cni-ipam] Plugin stopped.")
}

// Configure parses and applies the given network configuration.
func (plugin *ipamPlugin) Configure(stdinData []byte) (*cni.NetworkConfig, error) {
	// Parse network configuration from stdin.
	nwCfg, err := cni.ParseNetworkConfig(stdinData)
	if err != nil {
		return nil, err
	}

	log.Printf("[cni-ipam] Read network configuration %+v.", nwCfg)

	// Apply IPAM configuration.

	// Set deployment environment.
	if nwCfg.Ipam.Environment == "" {
		nwCfg.Ipam.Environment = common.OptEnvironmentAzure
	}
	plugin.SetOption(common.OptEnvironment, nwCfg.Ipam.Environment)

	// Set query interval.
	if nwCfg.Ipam.QueryInterval != "" {
		i, _ := strconv.Atoi(nwCfg.Ipam.QueryInterval)
		plugin.SetOption(common.OptIpamQueryInterval, i)
	}

	err = plugin.am.StartSource(plugin.Options)
	if err != nil {
		return nil, err
	}

	// Set default address space if not specified.
	if nwCfg.Ipam.AddrSpace == "" {
		nwCfg.Ipam.AddrSpace = ipam.LocalDefaultAddressSpaceId
	}

	return nwCfg, nil
}

//
// CNI implementation
// https://github.com/containernetworking/cni/blob/master/SPEC.md
//

// Add handles CNI add commands.
func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
	log.Printf("[cni-ipam] Processing ADD command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.",
		args.ContainerID, args.Netns, args.IfName, args.Args, args.Path)

	// Parse network configuration from stdin.
	nwCfg, err := plugin.Configure(args.StdinData)
	if err != nil {
		return plugin.Errorf("Failed to parse network configuration: %v", err)
	}

	var poolId string
	var subnet string
	var ipv4Address *net.IPNet
	var result cniTypes.Result
	var resultImpl *cniTypesImpl.Result
	var apInfo *ipam.AddressPoolInfo

	// Check if an address pool is specified.
	if nwCfg.Ipam.Subnet == "" {
		// Select the requested interface.
		options := make(map[string]string)
		options[ipam.OptInterface] = nwCfg.Master

		// Allocate an address pool.
		poolId, subnet, err = plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", options, false)
		if err != nil {
			return plugin.Errorf("Failed to allocate pool: %v.", err)
		}

		nwCfg.Ipam.Subnet = subnet
		log.Printf("[cni-ipam] Allocated address poolId %v with subnet %v.", poolId, subnet)
	}

	// Allocate an address for the endpoint.
	address, err := plugin.am.RequestAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address, nil)
	if err != nil {
		err = plugin.Errorf("Failed to allocate address: %v", err)
		goto Rollback
	}

	log.Printf("[cni-ipam] Allocated address %v.", address)

	// Parse IP address.
	ipv4Address, err = platform.ConvertStringToIPNet(address)
	if err != nil {
		err = plugin.Errorf("Failed to parse address: %v", err)
		goto Rollback
	}

	// Query pool information for gateways and DNS servers.
	apInfo, err = plugin.am.GetPoolInfo(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet)
	if err != nil {
		err = plugin.Errorf("Failed to get pool information: %v", err)
		goto Rollback
	}

	// Populate IP configuration.
	resultImpl = &cniTypesImpl.Result{
		IP4: &cniTypesImpl.IPConfig{
			IP:      *ipv4Address,
			Gateway: apInfo.Gateway,
			Routes: []cniTypes.Route{
				cniTypes.Route{
					Dst: ipv4DefaultRouteDstPrefix,
					GW:  apInfo.Gateway,
				},
			},
		},
	}

	// Populate DNS servers.
	for _, ip := range apInfo.DnsServers {
		resultImpl.DNS.Nameservers = append(resultImpl.DNS.Nameservers, ip.String())
	}

	// Convert result to the requested CNI version.
	result, err = resultImpl.GetAsVersion(nwCfg.CniVersion)
	if err != nil {
		err = plugin.Errorf("Failed to convert result: %v", err)
		goto Rollback
	}

	// Output the result.
	if nwCfg.Ipam.Type == cni.Internal {
		// Called via the internal interface. Pass output back in args.
		args.StdinData, _ = json.Marshal(result)
	} else {
		// Called via the executable interface. Print output to stdout.
		result.Print()
	}

	log.Printf("[cni-ipam] ADD succeeded with output %+v.", result)

	return nil

Rollback:
	// Roll back allocations made during this call.
	log.Printf("[cni-ipam] ADD failed, err:%v.", err)

	if address != "" {
		log.Printf("[cni-ipam] Releasing address %v.", address)
		plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, address)
	}

	if poolId != "" {
		log.Printf("[cni-ipam] Releasing pool %v.", poolId)
		plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, poolId)
	}

	return err
}

// Delete handles CNI delete commands.
func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error {
	log.Printf("[cni-ipam] Processing DEL command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.",
		args.ContainerID, args.Netns, args.IfName, args.Args, args.Path)

	// Parse network configuration from stdin.
	nwCfg, err := plugin.Configure(args.StdinData)
	if err != nil {
		plugin.Errorf("Failed to parse network configuration: %v", err)
	}

	// If an address is specified, release that address. Otherwise, release the pool.
	if nwCfg.Ipam.Address != "" {
		// Release the address.
		err := plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address)
		if err != nil {
			plugin.Errorf("Failed to release address: %v", err)
		}
	} else {
		// Release the pool.
		err := plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet)
		if err != nil {
			plugin.Errorf("Failed to release pool: %v", err)
		}
	}

	log.Printf("[cni-ipam] DEL succeeded.")

	return nil
}
<commit_msg>Updated IPAM to return the VNET gateway address for libnetwork gateway requests<commit_after>// Copyright 2017 Microsoft. All rights reserved.
// MIT License

package ipam

import (
	"encoding/json"
	"net"
	"strconv"

	"github.com/Azure/azure-container-networking/cni"
	"github.com/Azure/azure-container-networking/common"
	"github.com/Azure/azure-container-networking/ipam"
	"github.com/Azure/azure-container-networking/log"
	"github.com/Azure/azure-container-networking/platform"

	cniSkel "github.com/containernetworking/cni/pkg/skel"
	cniTypes "github.com/containernetworking/cni/pkg/types"
	cniTypesImpl "github.com/containernetworking/cni/pkg/types/020"
)

const (
	// Plugin name.
	name = "azure-vnet-ipam"
)

var (
	// Azure VNET pre-allocated host IDs.
	ipv4DefaultGatewayHostId = net.ParseIP("::1")
	ipv4DnsPrimaryHostId     = net.ParseIP("::2")
	ipv4DnsSecondaryHostId   = net.ParseIP("::3")

	ipv4DefaultRouteDstPrefix = net.IPNet{net.IPv4zero, net.IPv4Mask(0, 0, 0, 0)}
)

// IpamPlugin represents the CNI IPAM plugin.
type ipamPlugin struct {
	*cni.Plugin
	am ipam.AddressManager
}

// NewPlugin creates a new ipamPlugin object.
func NewPlugin(config *common.PluginConfig) (*ipamPlugin, error) {
	// Setup base plugin.
	plugin, err := cni.NewPlugin(name, config.Version)
	if err != nil {
		return nil, err
	}

	// Setup address manager.
	am, err := ipam.NewAddressManager()
	if err != nil {
		return nil, err
	}

	// Create IPAM plugin.
	ipamPlg := &ipamPlugin{
		Plugin: plugin,
		am:     am,
	}

	config.IpamApi = ipamPlg

	return ipamPlg, nil
}

// Starts the plugin.
func (plugin *ipamPlugin) Start(config *common.PluginConfig) error {
	// Initialize base plugin.
	err := plugin.Initialize(config)
	if err != nil {
		log.Printf("[cni-ipam] Failed to initialize base plugin, err:%v.", err)
		return err
	}

	// Log platform information.
	log.Printf("[cni-ipam] Plugin %v version %v.", plugin.Name, plugin.Version)
	log.Printf("[cni-ipam] Running on %v", platform.GetOSInfo())

	// Initialize address manager.
	err = plugin.am.Initialize(config, plugin.Options)
	if err != nil {
		log.Printf("[cni-ipam] Failed to initialize address manager, err:%v.", err)
		return err
	}

	log.Printf("[cni-ipam] Plugin started.")

	return nil
}

// Stops the plugin.
func (plugin *ipamPlugin) Stop() {
	plugin.am.Uninitialize()
	plugin.Uninitialize()
	log.Printf("[cni-ipam] Plugin stopped.")
}

// Configure parses and applies the given network configuration.
func (plugin *ipamPlugin) Configure(stdinData []byte) (*cni.NetworkConfig, error) {
	// Parse network configuration from stdin.
	nwCfg, err := cni.ParseNetworkConfig(stdinData)
	if err != nil {
		return nil, err
	}

	log.Printf("[cni-ipam] Read network configuration %+v.", nwCfg)

	// Apply IPAM configuration.

	// Set deployment environment.
	if nwCfg.Ipam.Environment == "" {
		nwCfg.Ipam.Environment = common.OptEnvironmentAzure
	}
	plugin.SetOption(common.OptEnvironment, nwCfg.Ipam.Environment)

	// Set query interval.
	if nwCfg.Ipam.QueryInterval != "" {
		i, _ := strconv.Atoi(nwCfg.Ipam.QueryInterval)
		plugin.SetOption(common.OptIpamQueryInterval, i)
	}

	err = plugin.am.StartSource(plugin.Options)
	if err != nil {
		return nil, err
	}

	// Set default address space if not specified.
	if nwCfg.Ipam.AddrSpace == "" {
		nwCfg.Ipam.AddrSpace = ipam.LocalDefaultAddressSpaceId
	}

	return nwCfg, nil
}

//
// CNI implementation
// https://github.com/containernetworking/cni/blob/master/SPEC.md
//

// Add handles CNI add commands.
func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
	log.Printf("[cni-ipam] Processing ADD command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.",
		args.ContainerID, args.Netns, args.IfName, args.Args, args.Path)

	// Parse network configuration from stdin.
	nwCfg, err := plugin.Configure(args.StdinData)
	if err != nil {
		return plugin.Errorf("Failed to parse network configuration: %v", err)
	}

	var poolId string
	var subnet string
	var ipv4Address *net.IPNet
	var result cniTypes.Result
	var resultImpl *cniTypesImpl.Result
	var apInfo *ipam.AddressPoolInfo

	// Check if an address pool is specified.
	if nwCfg.Ipam.Subnet == "" {
		// Select the requested interface.
		options := make(map[string]string)
		options[ipam.OptInterfaceName] = nwCfg.Master

		// Allocate an address pool.
		poolId, subnet, err = plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", options, false)
		if err != nil {
			return plugin.Errorf("Failed to allocate pool: %v.", err)
		}

		nwCfg.Ipam.Subnet = subnet
		log.Printf("[cni-ipam] Allocated address poolId %v with subnet %v.", poolId, subnet)
	}

	// Allocate an address for the endpoint.
	address, err := plugin.am.RequestAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address, nil)
	if err != nil {
		err = plugin.Errorf("Failed to allocate address: %v", err)
		goto Rollback
	}

	log.Printf("[cni-ipam] Allocated address %v.", address)

	// Parse IP address.
	ipv4Address, err = platform.ConvertStringToIPNet(address)
	if err != nil {
		err = plugin.Errorf("Failed to parse address: %v", err)
		goto Rollback
	}

	// Query pool information for gateways and DNS servers.
	apInfo, err = plugin.am.GetPoolInfo(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet)
	if err != nil {
		err = plugin.Errorf("Failed to get pool information: %v", err)
		goto Rollback
	}

	// Populate IP configuration.
	resultImpl = &cniTypesImpl.Result{
		IP4: &cniTypesImpl.IPConfig{
			IP:      *ipv4Address,
			Gateway: apInfo.Gateway,
			Routes: []cniTypes.Route{
				cniTypes.Route{
					Dst: ipv4DefaultRouteDstPrefix,
					GW:  apInfo.Gateway,
				},
			},
		},
	}

	// Populate DNS servers.
	for _, ip := range apInfo.DnsServers {
		resultImpl.DNS.Nameservers = append(resultImpl.DNS.Nameservers, ip.String())
	}

	// Convert result to the requested CNI version.
	result, err = resultImpl.GetAsVersion(nwCfg.CniVersion)
	if err != nil {
		err = plugin.Errorf("Failed to convert result: %v", err)
		goto Rollback
	}

	// Output the result.
	if nwCfg.Ipam.Type == cni.Internal {
		// Called via the internal interface. Pass output back in args.
		args.StdinData, _ = json.Marshal(result)
	} else {
		// Called via the executable interface. Print output to stdout.
		result.Print()
	}

	log.Printf("[cni-ipam] ADD succeeded with output %+v.", result)

	return nil

Rollback:
	// Roll back allocations made during this call.
	log.Printf("[cni-ipam] ADD failed, err:%v.", err)

	if address != "" {
		log.Printf("[cni-ipam] Releasing address %v.", address)
		plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, address)
	}

	if poolId != "" {
		log.Printf("[cni-ipam] Releasing pool %v.", poolId)
		plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, poolId)
	}

	return err
}

// Delete handles CNI delete commands.
func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error {
	log.Printf("[cni-ipam] Processing DEL command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.",
		args.ContainerID, args.Netns, args.IfName, args.Args, args.Path)

	// Parse network configuration from stdin.
	nwCfg, err := plugin.Configure(args.StdinData)
	if err != nil {
		plugin.Errorf("Failed to parse network configuration: %v", err)
	}

	// If an address is specified, release that address. Otherwise, release the pool.
	if nwCfg.Ipam.Address != "" {
		// Release the address.
		err := plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address)
		if err != nil {
			plugin.Errorf("Failed to release address: %v", err)
		}
	} else {
		// Release the pool.
		err := plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet)
		if err != nil {
			plugin.Errorf("Failed to release pool: %v", err)
		}
	}

	log.Printf("[cni-ipam] DEL succeeded.")

	return nil
}
<|endoftext|>


<commit_before>package commands

import (
	"github.com/mislav/everyenv/cli"
	"github.com/mislav/everyenv/config"
	"os"
	"strings"
	"syscall"
)

var execHelp = `
Usage: $program_name exec <command> [arg1 arg2...]

Runs an executable by first preparing PATH so that the selected version's
'bin' directory is directly in the front.
`

func execCmd(args cli.Args) {
	currentVersion := detectVersion()
	exeName := args.Required(0)
	exePath, err := findExecutable(exeName, currentVersion)
	if err != nil {
		cli.Errorf("%s: %s\n", args.ProgramName(), err)
		cli.Exit(1)
	}

	env := os.Environ()
	if !currentVersion.IsSystem() {
		for i, value := range env {
			if strings.HasPrefix(value, "PATH=") {
				pair := strings.SplitN(value, "=", 2)
				versionBindir := config.VersionDir(currentVersion.Name).Join("bin")
				env[i] = "PATH=" + versionBindir.String() + ":" + pair[1]
			}
		}
	}

	argv := []string{exeName}
	argv = append(argv, args.ARGV[3:]...)

	err = syscall.Exec(exePath.String(), argv, env)
	if err != nil {
		cli.Errorf("%s: %s\n", args.ProgramName(), err)
		cli.Exit(1)
	}
}

func init() {
	cli.Register("exec", execCmd, execHelp)
}
<commit_msg>Use `strings.TrimPrefix()` for simplicity<commit_after>package commands

import (
	"github.com/mislav/everyenv/cli"
	"github.com/mislav/everyenv/config"
	"os"
	"strings"
	"syscall"
)

var execHelp = `
Usage: $program_name exec <command> [arg1 arg2...]

Runs an executable by first preparing PATH so that the selected version's
'bin' directory is directly in the front.
`

func execCmd(args cli.Args) {
	currentVersion := detectVersion()
	exeName := args.Required(0)
	exePath, err := findExecutable(exeName, currentVersion)
	if err != nil {
		cli.Errorf("%s: %s\n", args.ProgramName(), err)
		cli.Exit(1)
	}

	env := os.Environ()
	if !currentVersion.IsSystem() {
		for i, value := range env {
			if strings.HasPrefix(value, "PATH=") {
				oldPath := strings.TrimPrefix(value, "PATH=")
				versionBindir := config.VersionDir(currentVersion.Name).Join("bin")
				env[i] = "PATH=" + versionBindir.String() + ":" + oldPath
			}
		}
	}

	argv := []string{exeName}
	argv = append(argv, args.ARGV[3:]...)

	err = syscall.Exec(exePath.String(), argv, env)
	if err != nil {
		cli.Errorf("%s: %s\n", args.ProgramName(), err)
		cli.Exit(1)
	}
}

func init() {
	cli.Register("exec", execCmd, execHelp)
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"strings"
)

type ReadCommand struct {
	Meta
}

func (c *ReadCommand) Help() string {
	helpText := `
Usage: aero read <key>
  Returns the value of the metdata key provided.
  `

	return strings.TrimSpace(helpText)
}

func (c *ReadCommand) Synopsis() string {
	return "Read a metadata value"
}

func (c *ReadCommand) Run(args []string) int {
	if len(args) < 1 {
		fmt.Printf("Expected a metdata key to read.\n")
		return 1
	}

	metadataKey := args[0]

	p := *c.Meta.CurrentProvider
	metadataValue, err := p.Read(metadataKey)

	if err != nil {
		fmt.Printf("error reading value [%s]: %s \n", metadataKey, err)
		return 1
	}

	if metadataValue == "" {
		fmt.Printf("No known metadata [%s].\n", metadataKey)
		return 1
	}

	fmt.Printf("%s", metadataValue)
	return 0
}
<commit_msg>Add newline to read output.<commit_after>package commands

import (
	"fmt"
	"strings"
)

type ReadCommand struct {
	Meta
}

func (c *ReadCommand) Help() string {
	helpText := `
Usage: aero read <key>
  Returns the value of the metdata key provided.
  `

	return strings.TrimSpace(helpText)
}

func (c *ReadCommand) Synopsis() string {
	return "Read a metadata value"
}

func (c *ReadCommand) Run(args []string) int {
	if len(args) < 1 {
		fmt.Printf("Expected a metdata key to read.\n")
		return 1
	}

	metadataKey := args[0]

	p := *c.Meta.CurrentProvider
	metadataValue, err := p.Read(metadataKey)

	if err != nil {
		fmt.Printf("error reading value [%s]: %s \n", metadataKey, err)
		return 1
	}

	if metadataValue == "" {
		fmt.Printf("No known metadata [%s].\n", metadataKey)
		return 1
	}

	fmt.Printf("%s\n", metadataValue)
	return 0
}
<|endoftext|>


<commit_before>package commands

import (
	"strings"
	"testing"

	"github.com/ambientsound/pms/api"
	"github.com/ambientsound/pms/input/lexer"
	"github.com/stretchr/testify/assert"
)

// TestData contains data needed for a single Command table test.
type TestData struct {
	T    *testing.T
	Cmd  Command
	Api  api.API
	Test Test
}

// Test is a structure for test data, and can be used to conveniently
// test Command instances.
type Test struct {

	// The input data for the command, as seen on the command line.
	Input string

	// True if the command should parse and execute properly, false otherwise.
	Success bool

	// An initialization function for tests.
	Init func(data *TestData)

	// A callback function to call for every test, allowing customization of tests.
	Callback func(data *TestData)

	// A slice of tab completion candidates to expect.
	TabComplete []string
}

// TestVerb runs table tests for Command implementations.
func TestVerb(t *testing.T, verb string, tests []Test) {
	for n, test := range tests {
		api := api.NewTestAPI()

		data := &TestData{
			T:    t,
			Api:  api,
			Cmd:  New(verb, api),
			Test: test,
		}

		assert.NotNil(t, data.Cmd, "Command '%s' is not implemented; it must be added to the commands.Verb variable.")

		if data.Test.Init != nil {
			t.Logf("### Initializing data for verb test '%s' number %d", test.Input, n+1)
			data.Test.Init(data)
		}

		t.Logf("### Test %d: '%s'", n+1, test.Input)
		TestCommand(data)
	}
}

// TestCommand runs a single test a for Command implementation.
func TestCommand(data *TestData) {
	reader := strings.NewReader(data.Test.Input)
	scanner := lexer.NewScanner(reader)

	// Parse command
	data.Cmd.SetScanner(scanner)
	err := data.Cmd.Parse()

	// Test success
	if data.Test.Success {
		assert.Nil(data.T, err, "Expected success when parsing '%s'", data.Test.Input)
	} else {
		assert.NotNil(data.T, err, "Expected error when parsing '%s'", data.Test.Input)
	}

	// Test tab completes
	completes := data.Cmd.TabComplete()
	assert.Equal(data.T, data.Test.TabComplete, completes)

	// Test callback function
	if data.Test.Callback != nil {
		data.Test.Callback(data)
	}
}
<commit_msg>Fix faulty error message in test framework.<commit_after>package commands

import (
	"strings"
	"testing"

	"github.com/ambientsound/pms/api"
	"github.com/ambientsound/pms/input/lexer"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestData contains data needed for a single Command table test.
type TestData struct {
	T    *testing.T
	Cmd  Command
	Api  api.API
	Test Test
}

// Test is a structure for test data, and can be used to conveniently
// test Command instances.
type Test struct {

	// The input data for the command, as seen on the command line.
	Input string

	// True if the command should parse and execute properly, false otherwise.
	Success bool

	// An initialization function for tests.
	Init func(data *TestData)

	// A callback function to call for every test, allowing customization of tests.
	Callback func(data *TestData)

	// A slice of tab completion candidates to expect.
	TabComplete []string
}

// TestVerb runs table tests for Command implementations.
func TestVerb(t *testing.T, verb string, tests []Test) {
	for n, test := range tests {
		api := api.NewTestAPI()

		data := &TestData{
			T:    t,
			Api:  api,
			Cmd:  New(verb, api),
			Test: test,
		}

		require.NotNil(t, data.Cmd, "Command '%s' is not implemented; it must be added to the `commands.Verb` variable.", verb)

		if data.Test.Init != nil {
			t.Logf("### Initializing data for verb test '%s' number %d", test.Input, n+1)
			data.Test.Init(data)
		}

		t.Logf("### Test %d: '%s'", n+1, test.Input)
		TestCommand(data)
	}
}

// TestCommand runs a single test a for Command implementation.
func TestCommand(data *TestData) {
	reader := strings.NewReader(data.Test.Input)
	scanner := lexer.NewScanner(reader)

	// Parse command
	data.Cmd.SetScanner(scanner)
	err := data.Cmd.Parse()

	// Test success
	if data.Test.Success {
		assert.Nil(data.T, err, "Expected success when parsing '%s'", data.Test.Input)
	} else {
		assert.NotNil(data.T, err, "Expected error when parsing '%s'", data.Test.Input)
	}

	// Test tab completes
	completes := data.Cmd.TabComplete()
	assert.Equal(data.T, data.Test.TabComplete, completes)

	// Test callback function
	if data.Test.Callback != nil {
		data.Test.Callback(data)
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 Matthew Fonda. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// simhash package implements Charikar's simhash algorithm to generate a 64-bit
// fingerprint of a given document.
//
// simhash fingerprints have the property that similar documents will have a similar
// fingerprint. Therefore, the hamming distance between two fingerprints will be small
// if the documents are similar
package simhash

import (
	"bytes"
	"code.google.com/p/go.text/unicode/norm"
	"hash/fnv"
	"regexp"
)

type Vector [64]int

// Feature consists of a 64-bit hash and a weight
type Feature interface {
	// Sum returns the 64-bit sum of this feature
	Sum() uint64

	// Weight returns the weight of this feature
	Weight() int
}

// FeatureSet represents a set of features in a given document
type FeatureSet interface {
	GetFeatures() []Feature
}

// Vectorize generates 64 dimension vectors given a set of features.
// Vectors are initialized to zero. The i-th element of the vector is then
// incremented by weight of the i-th feature if the i-th bit of the feature
// is set, and decremented by the weight of the i-th feature otherwise.
func Vectorize(features []Feature) Vector {
	var v Vector
	for _, feature := range features {
		sum := feature.Sum()
		weight := feature.Weight()
		for i := uint8(0); i < 64; i++ {
			bit := ((sum >> i) & 1)
			if bit == 1 {
				v[i] += weight
			} else {
				v[i] -= weight
			}
		}
	}
	return v
}

// VectorizeBytes generates 64 dimension vectors given a set of [][]byte,
// where each []byte is a feature with even weight.
//
// Vectors are initialized to zero. The i-th element of the vector is then
// incremented by weight of the i-th feature if the i-th bit of the feature
// is set, and decremented by the weight of the i-th feature otherwise.
func VectorizeBytes(features [][]byte) Vector {
	var v Vector
	h := fnv.New64()
	for _, feature := range features {
		h.Reset()
		h.Write(feature)
		sum := h.Sum64()
		for i := uint8(0); i < 64; i++ {
			bit := ((sum >> i) & 1)
			if bit == 1 {
				v[i]++
			} else {
				v[i]--
			}
		}
	}
	return v
}

// Fingerprint returns a 64-bit fingerprint of the given vector.
// The fingerprint f of a given 64-dimension vector v is defined as follows:
//   f[i] = 1 if v[i] >= 0
//   f[i] = 0 if v[i] < 0
func Fingerprint(v Vector) uint64 {
	var f uint64
	for i := uint8(0); i < 64; i++ {
		if v[i] >= 0 {
			f |= (1 << i)
		}
	}
	return f
}

type feature struct {
	sum    uint64
	weight int
}

// Sum returns the 64-bit hash of this feature
func (f feature) Sum() uint64 {
	return f.sum
}

// Weight returns the weight of this feature
func (f feature) Weight() int {
	return f.weight
}

// Returns a new feature representing the given byte slice, using a weight of 1
func NewFeature(f []byte) feature {
	h := fnv.New64()
	h.Write(f)
	return feature{h.Sum64(), 1}
}

// Returns a new feature representing the given byte slice with the given weight
func NewFeatureWithWeight(f []byte, weight int) feature {
	fw := NewFeature(f)
	fw.weight = weight
	return fw
}

// Compare calculates the Hamming distance between two 64-bit integers
//
// Currently, this is calculated using the Kernighan method [1]. Other methods
// exist which may be more efficient and are worth exploring at some point
//
// [1] http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
func Compare(a uint64, b uint64) uint8 {
	v := a ^ b
	var c uint8
	for c = 0; v != 0; c++ {
		v &= v - 1
	}
	return c
}

// Returns a 64-bit simhash of the given feature set
func Simhash(fs FeatureSet) uint64 {
	return Fingerprint(Vectorize(fs.GetFeatures()))
}

// Returns a 64-bit simhash of the given bytes
func SimhashBytes(b [][]byte) uint64 {
	return Fingerprint(VectorizeBytes(b))
}

// WordFeatureSet is a feature set in which each word is a feature,
// all equal weight.
type WordFeatureSet struct {
	b []byte
}

func NewWordFeatureSet(b []byte) *WordFeatureSet {
	fs := &WordFeatureSet{b}
	fs.normalize()
	return fs
}

func (w *WordFeatureSet) normalize() {
	w.b = bytes.ToLower(w.b)
}

var boundaries = regexp.MustCompile(`[\w']+(?:\://[\w\./]+){0,1}`)
var unicodeBoundaries = regexp.MustCompile(`[\pL-_']+`)

// Returns a []Feature representing each word in the byte slice
func (w *WordFeatureSet) GetFeatures() []Feature {
	return getFeatures(w.b, boundaries)
}

// UnicodeWordFeatureSet is a feature set in which each word is a feature,
// all equal weight.
//
// See: http://blog.golang.org/normalization
// See: https://groups.google.com/forum/#!topic/golang-nuts/YyH1f_qCZVc
type UnicodeWordFeatureSet struct {
	b []byte
	f norm.Form
}

func NewUnicodeWordFeatureSet(b []byte, f norm.Form) *UnicodeWordFeatureSet {
	fs := &UnicodeWordFeatureSet{b, f}
	fs.normalize()
	return fs
}

func (w *UnicodeWordFeatureSet) normalize() {
	b := bytes.ToLower(w.f.Append(nil, w.b...))
	w.b = b
}

// Returns a []Feature representing each word in the byte slice
func (w *UnicodeWordFeatureSet) GetFeatures() []Feature {
	return getFeatures(w.b, unicodeBoundaries)
}

// Splits the given []byte using the given regexp, then returns a slice
// containing a Feature constructed from each piece matched by the regexp
func getFeatures(b []byte, r *regexp.Regexp) []Feature {
	words := r.FindAll(b, -1)
	features := make([]Feature, len(words))
	for i, w := range words {
		features[i] = NewFeature(w)
	}
	return features
}

// Shingle returns the w-shingling of the given set of bytes. For example, if the given
// input was {"this", "is", "a", "test"}, this returns {"this is", "is a", "a test"}
func Shingle(w int, b [][]byte) [][]byte {
	if w < 1 {
		// TODO: use error here instead of panic?
		panic("simhash.Shingle(): k must be a positive integer")
	}

	if w == 1 {
		return b
	}

	if w > len(b) {
		w = len(b)
	}

	count := len(b) - w + 1
	shingles := make([][]byte, count)
	for i := 0; i < count; i++ {
		shingles[i] = bytes.Join(b[i:i+w], []byte(" "))
	}
	return shingles
}
<commit_msg>moving away from code.google.com<commit_after>// Copyright 2013 Matthew Fonda. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// simhash package implements Charikar's simhash algorithm to generate a 64-bit
// fingerprint of a given document.
//
// simhash fingerprints have the property that similar documents will have a similar
// fingerprint. Therefore, the hamming distance between two fingerprints will be small
// if the documents are similar
package simhash

import (
	"bytes"
	"golang.org/x/text/unicode/norm"
	"hash/fnv"
	"regexp"
)

type Vector [64]int

// Feature consists of a 64-bit hash and a weight
type Feature interface {
	// Sum returns the 64-bit sum of this feature
	Sum() uint64

	// Weight returns the weight of this feature
	Weight() int
}

// FeatureSet represents a set of features in a given document
type FeatureSet interface {
	GetFeatures() []Feature
}

// Vectorize generates 64 dimension vectors given a set of features.
// Vectors are initialized to zero. The i-th element of the vector is then
// incremented by weight of the i-th feature if the i-th bit of the feature
// is set, and decremented by the weight of the i-th feature otherwise.
func Vectorize(features []Feature) Vector {
	var v Vector
	for _, feature := range features {
		sum := feature.Sum()
		weight := feature.Weight()
		for i := uint8(0); i < 64; i++ {
			bit := ((sum >> i) & 1)
			if bit == 1 {
				v[i] += weight
			} else {
				v[i] -= weight
			}
		}
	}
	return v
}

// VectorizeBytes generates 64 dimension vectors given a set of [][]byte,
// where each []byte is a feature with even weight.
//
// Vectors are initialized to zero. The i-th element of the vector is then
// incremented by weight of the i-th feature if the i-th bit of the feature
// is set, and decremented by the weight of the i-th feature otherwise.
func VectorizeBytes(features [][]byte) Vector {
	var v Vector
	h := fnv.New64()
	for _, feature := range features {
		h.Reset()
		h.Write(feature)
		sum := h.Sum64()
		for i := uint8(0); i < 64; i++ {
			bit := ((sum >> i) & 1)
			if bit == 1 {
				v[i]++
			} else {
				v[i]--
			}
		}
	}
	return v
}

// Fingerprint returns a 64-bit fingerprint of the given vector.
// The fingerprint f of a given 64-dimension vector v is defined as follows:
//   f[i] = 1 if v[i] >= 0
//   f[i] = 0 if v[i] < 0
func Fingerprint(v Vector) uint64 {
	var f uint64
	for i := uint8(0); i < 64; i++ {
		if v[i] >= 0 {
			f |= (1 << i)
		}
	}
	return f
}

type feature struct {
	sum    uint64
	weight int
}

// Sum returns the 64-bit hash of this feature
func (f feature) Sum() uint64 {
	return f.sum
}

// Weight returns the weight of this feature
func (f feature) Weight() int {
	return f.weight
}

// Returns a new feature representing the given byte slice, using a weight of 1
func NewFeature(f []byte) feature {
	h := fnv.New64()
	h.Write(f)
	return feature{h.Sum64(), 1}
}

// Returns a new feature representing the given byte slice with the given weight
func NewFeatureWithWeight(f []byte, weight int) feature {
	fw := NewFeature(f)
	fw.weight = weight
	return fw
}

// Compare calculates the Hamming distance between two 64-bit integers
//
// Currently, this is calculated using the Kernighan method [1]. Other methods
// exist which may be more efficient and are worth exploring at some point
//
// [1] http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
func Compare(a uint64, b uint64) uint8 {
	v := a ^ b
	var c uint8
	for c = 0; v != 0; c++ {
		v &= v - 1
	}
	return c
}

// Returns a 64-bit simhash of the given feature set
func Simhash(fs FeatureSet) uint64 {
	return Fingerprint(Vectorize(fs.GetFeatures()))
}

// Returns a 64-bit simhash of the given bytes
func SimhashBytes(b [][]byte) uint64 {
	return Fingerprint(VectorizeBytes(b))
}

// WordFeatureSet is a feature set in which each word is a feature,
// all equal weight.
type WordFeatureSet struct {
	b []byte
}

func NewWordFeatureSet(b []byte) *WordFeatureSet {
	fs := &WordFeatureSet{b}
	fs.normalize()
	return fs
}

func (w *WordFeatureSet) normalize() {
	w.b = bytes.ToLower(w.b)
}

var boundaries = regexp.MustCompile(`[\w']+(?:\://[\w\./]+){0,1}`)
var unicodeBoundaries = regexp.MustCompile(`[\pL-_']+`)

// Returns a []Feature representing each word in the byte slice
func (w *WordFeatureSet) GetFeatures() []Feature {
	return getFeatures(w.b, boundaries)
}

// UnicodeWordFeatureSet is a feature set in which each word is a feature,
// all equal weight.
//
// See: http://blog.golang.org/normalization
// See: https://groups.google.com/forum/#!topic/golang-nuts/YyH1f_qCZVc
type UnicodeWordFeatureSet struct {
	b []byte
	f norm.Form
}

func NewUnicodeWordFeatureSet(b []byte, f norm.Form) *UnicodeWordFeatureSet {
	fs := &UnicodeWordFeatureSet{b, f}
	fs.normalize()
	return fs
}

func (w *UnicodeWordFeatureSet) normalize() {
	b := bytes.ToLower(w.f.Append(nil, w.b...))
	w.b = b
}

// Returns a []Feature representing each word in the byte slice
func (w *UnicodeWordFeatureSet) GetFeatures() []Feature {
	return getFeatures(w.b, unicodeBoundaries)
}

// Splits the given []byte using the given regexp, then returns a slice
// containing a Feature constructed from each piece matched by the regexp
func getFeatures(b []byte, r *regexp.Regexp) []Feature {
	words := r.FindAll(b, -1)
	features := make([]Feature, len(words))
	for i, w := range words {
		features[i] = NewFeature(w)
	}
	return features
}

// Shingle returns the w-shingling of the given set of bytes. For example, if the given
// input was {"this", "is", "a", "test"}, this returns {"this is", "is a", "a test"}
func Shingle(w int, b [][]byte) [][]byte {
	if w < 1 {
		// TODO: use error here instead of panic?
		panic("simhash.Shingle(): k must be a positive integer")
	}

	if w == 1 {
		return b
	}

	if w > len(b) {
		w = len(b)
	}

	count := len(b) - w + 1
	shingles := make([][]byte, count)
	for i := 0; i < count; i++ {
		shingles[i] = bytes.Join(b[i:i+w], []byte(" "))
	}
	return shingles
}
<|endoftext|>


<commit_before>// Copyright 2015 Lukas Weber. All rights reserved.
// Use of this source code is governed by the MIT-styled
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"bytes"
	"errors"
	"net/textproto"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/nsf/termbox-go"
)

// ComposeBuffer is a MailBuffer that allows editing and sending the viewed message.
type ComposeBuffer struct {
	mb *MailBuffer
}

// NewComposeBuffer creates a new Composebuffer for displaying a mail.
func NewComposeBuffer(m *Mail) *ComposeBuffer {
	return &ComposeBuffer{NewMailBufferFromMail(m)}
}

// Draw draws the buffer content.
func (b *ComposeBuffer) Draw() {
	b.mb.Draw()
}

// Title returns the buffer's title string.
func (b *ComposeBuffer) Title() string {
	return b.mb.mail.Header.Get("Message-ID")
}

// Name returns the buffer's name.
func (b *ComposeBuffer) Name() string {
	return "compose"
}

// Close closes the buffer.
func (b *ComposeBuffer) Close() {
	b.mb.Close()
}

// writeEditString writes an editable version of a mail consisting of a
// header paragraph containing some of the mails headers and the message body.
//
// The format is as follows:
//
//	Header1: value
//	Header2: value
//	Header3: value
//
//	Hi,
//	This is the message body text.
func writeEditString(filename string, m *Mail) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	file.Write([]byte("From: " + m.Header.Get("From") + "\n"))
	file.Write([]byte("To: " + m.Header.Get("To") + "\n"))
	file.Write([]byte("Subject: " + m.Header.Get("Subject") + "\n"))
	file.Write([]byte{'\n'})

	// assume the first part exists, is text/plain, and contains the message body.
	if len(m.Parts) == 0 {
		h := make(textproto.MIMEHeader)
		h["Content-Type"] = []string{"text/plain; charset=\"utf-8\""}
		h["Content-Transfer-Encoding"] = []string{"quoted-printable"}
		m.Parts = append(m.Parts, Part{h, ""})
	}
	file.Write([]byte(m.Parts[0].Body))
	return nil
}

// parseEditString parses a string created by writeEditString and edited by the user.
// it updates the changed header flags and message body.
func parseEditString(filename string, m *Mail) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()
	var buf bytes.Buffer

	scanner := bufio.NewScanner(file)
	scanHeaders := true
	for scanner.Scan() {
		if len(scanner.Bytes()) == 0 {
			scanHeaders = false // don't scan headers after new line.
			continue
		}
		if scanHeaders {
			toks := strings.SplitN(scanner.Text(), ":", 2)
			if len(toks) != 2 {
				return errors.New("Error: invalid header section.")
			}
			m.Header[strings.TrimSpace(toks[0])] =
				[]string{strings.TrimSpace(toks[1])}
		} else {
			_, err := buf.WriteString(scanner.Text() + "\n")
			if err != nil {
				return err
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}

	if len(m.Parts) == 0 {
		return errors.New("Error: editing invalid mail.")
	}
	m.Parts[0].Body = buf.String()
	return nil
}

func (b *ComposeBuffer) openEditor(stack *BufferStack) {
	filename := b.mb.tmpDir + "/edit.eml"

	err := writeEditString(filename, b.mb.mail)
	if err != nil {
		StatusLine = err.Error()
		return
	}

	termbox.Close()
	cmd := exec.Command(config.Commands.Editor, filename)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	err = cmd.Run()
	if err != nil {
		StatusLine = err.Error()
	}
	termbox.Init()
	err = parseEditString(filename, b.mb.mail)
	if err != nil {
		StatusLine = err.Error()
	}
	b.mb.mail.Header["Date"] = []string{time.Now().Format(time.RFC1123Z)}
	b.mb.refreshBuf()
	stack.refresh()
}

// HandleCommand executes buffer local commands.
func (b *ComposeBuffer) HandleCommand(cmd string, args []string, stack *BufferStack) bool {
	switch cmd {
	case "reply", "raw": // disallow invalid commands in compose mode
	case "edit":
		b.openEditor(stack)
	case "send":
		StatusLine = "Sending..."
		stack.refresh()
		err := sendMail(b.mb.mail)
		if err != nil {
			StatusLine = err.Error()
		} else {
			StatusLine = "Mail sent."
		}
	case "attach":
		if len(args) == 0 {
			StatusLine = "Nothing to attach"
			break
		}

		err := b.mb.mail.attachFile(strings.Join(args, " "))
		if err != nil {
			StatusLine = err.Error()
		} else {
			StatusLine = "attached \"" + strings.Join(args, " ") + "\""
		}
		b.mb.refreshBuf()
		b.Draw()
	case "deattach":
		if len(b.mb.mail.Parts) > 1 {
			b.mb.mail.Parts = b.mb.mail.Parts[:len(b.mb.mail.Parts)-1]
		}
		b.mb.refreshBuf()
		b.Draw()
	default:
		return b.mb.HandleCommand(cmd, args, stack)
	}
	return true
}
<commit_msg>accidentially removed all empty line from mails<commit_after>// Copyright 2015 Lukas Weber. All rights reserved.
// Use of this source code is governed by the MIT-styled
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"bytes"
	"errors"
	"net/textproto"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/nsf/termbox-go"
)

// ComposeBuffer is a MailBuffer that allows editing and sending the viewed message.
type ComposeBuffer struct {
	mb *MailBuffer
}

// NewComposeBuffer creates a new Composebuffer for displaying a mail.
func NewComposeBuffer(m *Mail) *ComposeBuffer {
	return &ComposeBuffer{NewMailBufferFromMail(m)}
}

// Draw draws the buffer content.
func (b *ComposeBuffer) Draw() {
	b.mb.Draw()
}

// Title returns the buffer's title string.
func (b *ComposeBuffer) Title() string {
	return b.mb.mail.Header.Get("Message-ID")
}

// Name returns the buffer's name.
func (b *ComposeBuffer) Name() string {
	return "compose"
}

// Close closes the buffer.
func (b *ComposeBuffer) Close() {
	b.mb.Close()
}

// writeEditString writes an editable version of a mail consisting of a
// header paragraph containing some of the mails headers and the message body.
//
// The format is as follows:
//
//	Header1: value
//	Header2: value
//	Header3: value
//
//	Hi,
//	This is the message body text.
func writeEditString(filename string, m *Mail) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	file.Write([]byte("From: " + m.Header.Get("From") + "\n"))
	file.Write([]byte("To: " + m.Header.Get("To") + "\n"))
	file.Write([]byte("Subject: " + m.Header.Get("Subject") + "\n"))
	file.Write([]byte{'\n'})

	// assume the first part exists, is text/plain, and contains the message body.
	if len(m.Parts) == 0 {
		h := make(textproto.MIMEHeader)
		h["Content-Type"] = []string{"text/plain; charset=\"utf-8\""}
		h["Content-Transfer-Encoding"] = []string{"quoted-printable"}
		m.Parts = append(m.Parts, Part{h, ""})
	}
	file.Write([]byte(m.Parts[0].Body))
	return nil
}

// parseEditString parses a string created by writeEditString and edited by the user.
// it updates the changed header flags and message body.
func parseEditString(filename string, m *Mail) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()
	var buf bytes.Buffer

	scanner := bufio.NewScanner(file)
	scanHeaders := true
	for scanner.Scan() {
		if len(scanner.Bytes()) == 0 && scanHeaders {
			scanHeaders = false // don't scan headers after new line.
			continue
		}
		if scanHeaders {
			toks := strings.SplitN(scanner.Text(), ":", 2)
			if len(toks) != 2 {
				return errors.New("Error: invalid header section.")
			}
			m.Header[strings.TrimSpace(toks[0])] =
				[]string{strings.TrimSpace(toks[1])}
		} else {
			_, err := buf.WriteString(scanner.Text() + "\n")
			if err != nil {
				return err
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}

	if len(m.Parts) == 0 {
		return errors.New("Error: editing invalid mail.")
	}
	m.Parts[0].Body = buf.String()
	return nil
}

func (b *ComposeBuffer) openEditor(stack *BufferStack) {
	filename := b.mb.tmpDir + "/edit.eml"

	err := writeEditString(filename, b.mb.mail)
	if err != nil {
		StatusLine = err.Error()
		return
	}

	termbox.Close()
	cmd := exec.Command(config.Commands.Editor, filename)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	err = cmd.Run()
	if err != nil {
		StatusLine = err.Error()
	}
	termbox.Init()
	err = parseEditString(filename, b.mb.mail)
	if err != nil {
		StatusLine = err.Error()
	}
	b.mb.mail.Header["Date"] = []string{time.Now().Format(time.RFC1123Z)}
	b.mb.refreshBuf()
	stack.refresh()
}

// HandleCommand executes buffer local commands.
func (b *ComposeBuffer) HandleCommand(cmd string, args []string, stack *BufferStack) bool {
	switch cmd {
	case "reply", "raw": // disallow invalid commands in compose mode
	case "edit":
		b.openEditor(stack)
	case "send":
		StatusLine = "Sending..."
		stack.refresh()
		err := sendMail(b.mb.mail)
		if err != nil {
			StatusLine = err.Error()
		} else {
			StatusLine = "Mail sent."
		}
	case "attach":
		if len(args) == 0 {
			StatusLine = "Nothing to attach"
			break
		}

		err := b.mb.mail.attachFile(strings.Join(args, " "))
		if err != nil {
			StatusLine = err.Error()
		} else {
			StatusLine = "attached \"" + strings.Join(args, " ") + "\""
		}
		b.mb.refreshBuf()
		b.Draw()
	case "deattach":
		if len(b.mb.mail.Parts) > 1 {
			b.mb.mail.Parts = b.mb.mail.Parts[:len(b.mb.mail.Parts)-1]
		}
		b.mb.refreshBuf()
		b.Draw()
	default:
		return b.mb.HandleCommand(cmd, args, stack)
	}
	return true
}
<|endoftext|>


<commit_before>package panos

import (
	"encoding/xml"
	"fmt"
	"github.com/scottdware/go-rested"
)

// ServiceObjects contains a slice of all service objects.
type ServiceObjects struct {
	XMLName  xml.Name  `xml:"response"`
	Services []Service `xml:"result>service>entry"`
}

// Service contains information about each individual service object.
type Service struct {
	Name        string `xml:"name,attr"`
	TCPPort     string `xml:"protocol>tcp>port"`
	UDPPort     string `xml:"protocol>udp>port"`
	Description string `xml:"description,omitempty"`
}

// ServiceGroups contains a slice of all service groups.
type ServiceGroups struct {
	XMLName xml.Name       `xml:"response"`
	Groups  []ServiceGroup `xml:"result>service-group>entry"`
}

// ServiceGroup contains information about each individual service group.
type ServiceGroup struct {
	Name        string   `xml:"name,attr"`
	Description string   `xml:"description"`
	Members     []string `xml:"members>member,omitempty"`
}

// Services returns information about all of the address objects.
func (p *PaloAlto) Services() *ServiceObjects {
	var svcs ServiceObjects
	r := rested.NewRequest()

	// xpath := "/config/devices/entry/vsys/entry/address"
	xpath := "/config/devices/entry//service"

	if p.DeviceType == "panorama" {
		// xpath = "/config/devices/entry/device-group/entry/address"
		xpath = "/config/devices/entry//service"
	}

	query := map[string]string{
		"type":   "config",
		"action": "get",
		"xpath":  xpath,
		"key":    p.Key,
	}
	svcData := r.Send("get", p.URI, nil, headers, query)

	if err := xml.Unmarshal(svcData.Body, &svcs); err != nil {
		fmt.Println(err)
	}

	return &svcs
}

// ServiceGroups returns information about all of the service groups.
func (p *PaloAlto) ServiceGroups() *ServiceGroups {
	var groups ServiceGroups
	r := rested.NewRequest()

	// xpath := "/config/devices/entry/vsys/entry/address-group"
	xpath := "/config/devices/entry//service-group"

	if p.DeviceType == "panorama" {
		// xpath = "/config/devices/entry/device-group/entry/address-group"
		xpath = "/config/devices/entry//service-group"
	}

	query := map[string]string{
		"type":   "config",
		"action": "get",
		"xpath":  xpath,
		"key":    p.Key,
	}
	groupData := r.Send("get", p.URI, nil, headers, query)

	if err := xml.Unmarshal(groupData.Body, &groups); err != nil {
		fmt.Println(err)
	}

	return &groups
}
<commit_msg>Syntax changes<commit_after>package panos

import (
	"encoding/xml"
	"fmt"
	"github.com/scottdware/go-rested"
)

// ServiceObjects contains a slice of all service objects.
type ServiceObjects struct {
	XMLName  xml.Name  `xml:"response"`
	Services []Service `xml:"result>service>entry"`
}

// Service contains information about each individual service object.
type Service struct {
	Name        string `xml:"name,attr"`
	TCPPort     string `xml:"protocol>tcp>port,omitempty"`
	UDPPort     string `xml:"protocol>udp>port,omitempty"`
	Description string `xml:"description,omitempty"`
}

// ServiceGroups contains a slice of all service groups.
type ServiceGroups struct {
	XMLName xml.Name       `xml:"response"`
	Groups  []ServiceGroup `xml:"result>service-group>entry"`
}

// ServiceGroup contains information about each individual service group.
type ServiceGroup struct {
	Name        string   `xml:"name,attr"`
	Members     []string `xml:"members>member,omitempty"`
	Description string   `xml:"description,omitempty"`
}

// Services returns information about all of the address objects.
func (p *PaloAlto) Services() *ServiceObjects {
	var svcs ServiceObjects
	r := rested.NewRequest()

	// xpath := "/config/devices/entry/vsys/entry/address"
	xpath := "/config/devices/entry//service"

	if p.DeviceType == "panorama" {
		// xpath = "/config/devices/entry/device-group/entry/address"
		xpath = "/config/devices/entry//service"
	}

	query := map[string]string{
		"type":   "config",
		"action": "get",
		"xpath":  xpath,
		"key":    p.Key,
	}
	svcData := r.Send("get", p.URI, nil, headers, query)

	if err := xml.Unmarshal(svcData.Body, &svcs); err != nil {
		fmt.Println(err)
	}

	return &svcs
}

// ServiceGroups returns information about all of the service groups.
func (p *PaloAlto) ServiceGroups() *ServiceGroups {
	var groups ServiceGroups
	r := rested.NewRequest()

	// xpath := "/config/devices/entry/vsys/entry/address-group"
	xpath := "/config/devices/entry//service-group"

	if p.DeviceType == "panorama" {
		// xpath = "/config/devices/entry/device-group/entry/address-group"
		xpath = "/config/devices/entry//service-group"
	}

	query := map[string]string{
		"type":   "config",
		"action": "get",
		"xpath":  xpath,
		"key":    p.Key,
	}
	groupData := r.Send("get", p.URI, nil, headers, query)

	if err := xml.Unmarshal(groupData.Body, &groups); err != nil {
		fmt.Println(err)
	}

	return &groups
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package main

import (
	"errors"
	"fmt"

	"github.com/sostheim/lbex/annotations"

	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
)

var (
	lbAPIPort = 8081
)

// Service models a backend service entry in the load balancer config.
// The Ep field can contain the ips of the pods that make up a service, or the
// clusterIP of the service itself (in which case the list has a single entry,
// and kubernetes handles loadbalancing across the service endpoints).
type Service struct {
	Name string
	Ep   []string

	// Kubernetes endpoint port.
	BackendPort int

	// FrontendPort is the port that the loadbalancer listens on for traffic
	// for this service. For each tcp service it is the service port of any
	// service matching a name in the tcpServices set.
	FrontendPort int

	// Host if not empty it will add a new
	Host string

	// Algorithm
	Algorithm string
}

type serviceByName []Service

func (s serviceByName) Len() int {
	return len(s)
}
func (s serviceByName) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s serviceByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// ValidateServiceObject returns true iff:
// - the object is of a valid v1 API Service object
// - is a service type we provide load balancing for
// - has a valid annotation indicating
// returns false otherwise
func ValidateServiceObject(obj interface{}) bool {
	err := ValidateServiceObjectType(obj)
	if err != nil {
		return false
	}
	if !IsValidServiceType(obj) {
		return false
	}
	if !annotations.IsValid(obj) {
		return false
	}
	return true
}

// ValidateServiceObjectType return wether or not the given object
// is of type *api.Service or *v1.Service -> valid true, valid false otherwise
func ValidateServiceObjectType(obj interface{}) error {
	switch obj.(type) {
	case *v1.Service:
		return nil
	case *api.Service:
		return errors.New("ValidateServiceObjectType: unsupported type api.* (must be v1.*)")
	}
	return errors.New("ValidateServiceObjectType: unexpected type")
}

// GetServiceName return validated service type's name, error otherwise.
func GetServiceName(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Name), nil

}

// GetServiceNamespace return validated service type's namespace, error otherwise.
func GetServiceNamespace(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Namespace), nil
}

// GetServiceType return validated service type's Tupe, error otherwise.
func GetServiceType(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Spec.Type), nil
}

// ServiceTypeLoadBalancer returns true iff "Type: LoadBalancer"
func ServiceTypeLoadBalancer(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeLoadBalancer)
}

// ServiceTypeNodePort returns true iff "Type: NodePort"
func ServiceTypeNodePort(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeNodePort)
}

// ServiceTypeClusterIP returns true iff "Type: ClusterIP"
func ServiceTypeClusterIP(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeClusterIP)
}

// ServiceTypeExternalName returns true iff "Type: ExternalName"
func ServiceTypeExternalName(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeExternalName)
}

// ServiceTypeHeadless returns true iff "Type: NodNoneePort"
func ServiceTypeHeadless(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	// TODO: this should actually be spec.ClusterIP == None
	return serviceType == "None"
}

// IsValidServiceType returns true iff ServiceType is supported for external load balancing
func IsValidServiceType(obj interface{}) bool {
	if ServiceTypeNodePort(obj) || ServiceTypeLoadBalancer(obj) {
		return true
	}
	return false
}

// GetServicePortTargetPortInt returns the numeric value of TargetPort
func GetServicePortTargetPortInt(obj interface{}) (int, error) {
	servicePort, ok := obj.(*v1.ServicePort)
	if !ok {
		return 0, errors.New("type assertion failure")
	}
	return servicePort.TargetPort.IntValue(), nil
}

// GetServicePortTargetPortString returns the numeric value of TargetPort
func GetServicePortTargetPortString(obj interface{}) (string, error) {
	servicePort, ok := obj.(*v1.ServicePort)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return servicePort.TargetPort.StrVal, nil
}

// GetServiceNameForLBRule - convenience type name modifications for lb rules.
func GetServiceNameForLBRule(serviceName string, servicePort int) string {
	if servicePort == 80 {
		return serviceName
	}
	return fmt.Sprintf("%v:%v", serviceName, servicePort)
}
<commit_msg>add check for unsupported headless services<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package main

import (
	"errors"
	"fmt"

	"github.com/sostheim/lbex/annotations"

	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
)

var (
	lbAPIPort = 8081
)

// Service models a backend service entry in the load balancer config.
// The Ep field can contain the ips of the pods that make up a service, or the
// clusterIP of the service itself (in which case the list has a single entry,
// and kubernetes handles loadbalancing across the service endpoints).
type Service struct {
	Name string
	Ep   []string

	// Kubernetes endpoint port.
	BackendPort int

	// FrontendPort is the port that the loadbalancer listens on for traffic
	// for this service. For each tcp service it is the service port of any
	// service matching a name in the tcpServices set.
	FrontendPort int

	// Host if not empty it will add a new
	Host string

	// Algorithm
	Algorithm string
}

type serviceByName []Service

func (s serviceByName) Len() int {
	return len(s)
}
func (s serviceByName) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s serviceByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// ValidateServiceObject returns true iff:
// - the object is of a valid v1 API Service object
// - is a service type we provide load balancing for
// - has a valid annotation indicating
// returns false otherwise
func ValidateServiceObject(obj interface{}) bool {
	err := ValidateServiceObjectType(obj)
	if err != nil {
		return false
	}
	if !IsValidServiceType(obj) {
		return false
	}
	if !annotations.IsValid(obj) {
		return false
	}
	return true
}

// ValidateServiceObjectType return wether or not the given object
// is of type *api.Service or *v1.Service -> valid true, valid false otherwise
func ValidateServiceObjectType(obj interface{}) error {
	switch obj.(type) {
	case *v1.Service:
		return nil
	case *api.Service:
		return errors.New("ValidateServiceObjectType: unsupported type api.* (must be v1.*)")
	}
	return errors.New("ValidateServiceObjectType: unexpected type")
}

// GetServiceName return validated service type's name, error otherwise.
func GetServiceName(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Name), nil

}

// GetServiceNamespace return validated service type's namespace, error otherwise.
func GetServiceNamespace(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Namespace), nil
}

// GetServiceType return validated service type's Type string, error otherwise.
func GetServiceType(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Spec.Type), nil
}

// ServiceTypeLoadBalancer returns true iff "Type: LoadBalancer"
func ServiceTypeLoadBalancer(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeLoadBalancer)
}

// ServiceTypeNodePort returns true iff "Type: NodePort"
func ServiceTypeNodePort(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeNodePort)
}

// ServiceTypeClusterIP returns true iff "Type: ClusterIP"
func ServiceTypeClusterIP(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeClusterIP)
}

// ServiceTypeExternalName returns true iff "Type: ExternalName"
func ServiceTypeExternalName(obj interface{}) bool {
	serviceType, err := GetServiceType(obj)
	if err != nil {
		return false
	}
	return serviceType == string(api.ServiceTypeExternalName)
}

// GetClusterIP returns the services cluster ip value as a string, or an error
func GetClusterIP(obj interface{}) (string, error) {
	service, ok := obj.(*v1.Service)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return string(service.Spec.ClusterIP), nil
}

// ServiceTypeHeadless returns true iff ClusterIP is set to "None"
func ServiceTypeHeadless(obj interface{}) bool {
	clusterIP, err := GetClusterIP(obj)
	if err != nil {
		return false
	}
	return clusterIP == "None"
}

// IsValidServiceType returns true iff service properties are suche that
// external load balancing is appropriate
func IsValidServiceType(obj interface{}) bool {
	if !ServiceTypeHeadless(obj) && (ServiceTypeNodePort(obj) || ServiceTypeLoadBalancer(obj)) {
		return true
	}
	return false
}

// GetServicePortTargetPortInt returns the numeric value of TargetPort
func GetServicePortTargetPortInt(obj interface{}) (int, error) {
	servicePort, ok := obj.(*v1.ServicePort)
	if !ok {
		return 0, errors.New("type assertion failure")
	}
	return servicePort.TargetPort.IntValue(), nil
}

// GetServicePortTargetPortString returns the numeric value of TargetPort
func GetServicePortTargetPortString(obj interface{}) (string, error) {
	servicePort, ok := obj.(*v1.ServicePort)
	if !ok {
		return "", errors.New("type assertion failure")
	}
	return servicePort.TargetPort.StrVal, nil
}

// GetServiceNameForLBRule - convenience type name modifications for lb rules.
func GetServiceNameForLBRule(serviceName string, servicePort int) string {
	if servicePort == 80 {
		return serviceName
	}
	return fmt.Sprintf("%v:%v", serviceName, servicePort)
}
<|endoftext|>


<commit_before>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"github.com/monsti/service"
	"sync"
)

type InfoService struct {
	// Services maps service names to service paths
	Services map[string][]string
	// NodeTypes maps node types to service paths
	NodeTypes map[string][]string
	// Mutex to syncronize data access
	mutex sync.RWMutex
}

func (i *InfoService) PublishService(args service.PublishServiceArgs,
	reply *int) error {
	i.mutex.Lock()
	defer i.mutex.Unlock()
	if i.Services == nil {
		i.Services = make(map[string][]string)
	}
	switch args.Service {
	case "Node":
		if i.NodeTypes == nil {
			i.NodeTypes = make(map[string][]string)
		}
		nodeServ := service.NewNodeClient()
		if err := nodeServ.Connect(args.Path); err != nil {
			return fmt.Errorf("Could not connect to your node service: %v", err)
		}
		nodeTypes, err := nodeServ.GetNodeTypes()
		if err != nil {
			return fmt.Errorf("Could not retrieve your node types: %v", err)
		}
		for _, nodeType := range nodeTypes {
			if i.NodeTypes[nodeType] == nil {
				i.NodeTypes[nodeType] = make([]string, 0)
			}
			i.NodeTypes[nodeType] = append(i.NodeTypes[nodeType], args.Path)
		}
	case "Data":
	default:
		return fmt.Errorf("Unknown service type %v", args.Service)
	}

	if i.Services[args.Service] == nil {
		i.Services[args.Service] = make([]string, 0)
	}
	i.Services[args.Service] = append(i.Services[args.Service], args.Path)

	return nil
}

func (i *InfoService) FindNodeService(nodeType string, path *string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	if len(i.NodeTypes[nodeType]) == 0 {
		return fmt.Errorf("Unknown node type %v", nodeType)
	}
	*path = i.NodeTypes[nodeType][0]
	return nil
}

func (i *InfoService) FindDataService(arg int, path *string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	if len(i.Services["Data"]) == 0 {
		return fmt.Errorf("Could not find any data services")
	}
	*path = i.Services["Data"][0]
	return nil
}
<commit_msg>Add implementation for GetAddableNoteTypes<commit_after>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"github.com/monsti/service"
	"sync"
)

type InfoService struct {
	// Services maps service names to service paths
	Services map[string][]string
	// NodeTypes maps node types to service paths
	NodeTypes map[string][]string
	// Mutex to syncronize data access
	mutex sync.RWMutex
}

func (i *InfoService) PublishService(args service.PublishServiceArgs,
	reply *int) error {
	i.mutex.Lock()
	defer i.mutex.Unlock()
	if i.Services == nil {
		i.Services = make(map[string][]string)
	}
	switch args.Service {
	case "Node":
		if i.NodeTypes == nil {
			i.NodeTypes = make(map[string][]string)
		}
		nodeServ := service.NewNodeClient()
		if err := nodeServ.Connect(args.Path); err != nil {
			return fmt.Errorf("Could not connect to your node service: %v", err)
		}
		nodeTypes, err := nodeServ.GetNodeTypes()
		if err != nil {
			return fmt.Errorf("Could not retrieve your node types: %v", err)
		}
		for _, nodeType := range nodeTypes {
			if i.NodeTypes[nodeType] == nil {
				i.NodeTypes[nodeType] = make([]string, 0)
			}
			i.NodeTypes[nodeType] = append(i.NodeTypes[nodeType], args.Path)
		}
	case "Data":
	default:
		return fmt.Errorf("Unknown service type %v", args.Service)
	}

	if i.Services[args.Service] == nil {
		i.Services[args.Service] = make([]string, 0)
	}
	i.Services[args.Service] = append(i.Services[args.Service], args.Path)

	return nil
}

func (i *InfoService) FindNodeService(nodeType string, path *string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	if len(i.NodeTypes[nodeType]) == 0 {
		return fmt.Errorf("Unknown node type %v", nodeType)
	}
	*path = i.NodeTypes[nodeType][0]
	return nil
}

type GetAddableNodeTypesArgs struct{ Site, NodeType string }

func (i *InfoService) GetAddableNodeTypes(args GetAddableNodeTypesArgs,
	types *[]string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	*types = make([]string, 0)
	for nodeType, _ := range i.NodeTypes {
		*types = append(*types, nodeType)
	}
	return nil
}

func (i *InfoService) FindDataService(arg int, path *string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	if len(i.Services["Data"]) == 0 {
		return fmt.Errorf("Could not find any data services")
	}
	*path = i.Services["Data"][0]
	return nil
}
<|endoftext|>


<commit_before>package enroll

import (
	"golang.org/x/net/context"
	"io/ioutil"
)

type Service interface {
	Enroll(ctx context.Context) (Profile, error)
}

func NewService(pushCertPath string, pushCertPass string, caCertPath string, scepURL string, scepChallenge string, url string) (Service, error) {
	pushTopic, err := GetPushTopicFromPKCS12(pushCertPath, pushCertPass)
	if err != nil {
		return nil, err
	}

	var caCert []byte

	if caCertPath != "" {
		caCert, err = ioutil.ReadFile(caCertPath)

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

	scepSubject := [][][]string{
		[][]string{
			[]string{"O", "MicroMDM"},
			[]string{"CN", "MDM Identity Certificate:UDID"},
		},
	}

	return &service{
		URL:           url,
		SCEPURL:       scepURL,
		SCEPSubject:   scepSubject,
		SCEPChallenge: scepChallenge,
		Topic:         pushTopic,
		CACert:        caCert,
	}, nil
}

type service struct {
	URL           string
	SCEPURL       string
	SCEPChallenge string
	SCEPSubject   [][][]string
	Topic         string // APNS Topic for MDM notifications
	CACert        []byte
}

func (svc service) Enroll(ctx context.Context) (Profile, error) {
	profile := NewProfile()
	profile.PayloadIdentifier = "com.github.micromdm.micromdm.mdm"
	profile.PayloadOrganization = "MicroMDM"
	profile.PayloadDisplayName = "Enrollment Profile"
	profile.PayloadDescription = "The server may alter your settings"
	profile.PayloadScope = "System"

	scepContent := SCEPPayloadContent{
		Challenge: svc.SCEPChallenge,
		URL:       svc.SCEPURL,
		Keysize:   1024,
		KeyType:   "RSA",
		KeyUsage:  0,
		Name:      "Device Management Identity Certificate",
		Subject:   svc.SCEPSubject,
	}

	scepPayload := NewPayload("com.apple.security.scep")
	scepPayload.PayloadDescription = "Configures SCEP"
	scepPayload.PayloadDisplayName = "SCEP"
	scepPayload.PayloadIdentifier = "com.github.micromdm.scep"
	scepPayload.PayloadContent = scepContent
	scepPayload.PayloadScope = "System"

	mdmPayload := NewPayload("com.apple.mdm")
	mdmPayload.PayloadDescription = "Enrolls with the MDM server"
	mdmPayload.PayloadOrganization = "MicroMDM"
	mdmPayload.PayloadIdentifier = "com.github.micromdm.mdm"
	mdmPayload.PayloadScope = "System"

	mdmPayloadContent := MDMPayloadContent{
		Payload:                 *mdmPayload,
		AccessRights:            8191,
		CheckInURL:              svc.URL + "/mdm/checkin",
		CheckOutWhenRemoved:     true,
		ServerURL:               svc.URL + "/mdm/connect",
		IdentityCertificateUUID: scepPayload.PayloadUUID,
		Topic: svc.Topic,
	}

	if len(svc.CACert) > 0 {
		caPayload := NewPayload("com.apple.ssl.certificate")
		caPayload.PayloadDisplayName = "Root certificate for MicroMDM"
		caPayload.PayloadDescription = "Installs the root CA certificate for MicroMDM"
		caPayload.PayloadIdentifier = "com.github.micromdm.ssl.ca"
		caPayload.PayloadContent = svc.CACert

		profile.PayloadContent = []interface{}{*scepPayload, mdmPayloadContent, *caPayload}
	} else {
		profile.PayloadContent = []interface{}{*scepPayload, mdmPayloadContent}
	}

	return *profile, nil
}
<commit_msg>installed_certificates #14 plus conflict resolution (#21)<commit_after>package enroll

import (
	"golang.org/x/net/context"
	"io/ioutil"
)

type Service interface {
	Enroll(ctx context.Context) (Profile, error)
}

func NewService(pushCertPath string, pushCertPass string, caCertPath string, scepURL string, scepChallenge string, url string) (Service, error) {
	pushTopic, err := GetPushTopicFromPKCS12(pushCertPath, pushCertPass)
	if err != nil {
		return nil, err
	}

	var caCert []byte

	if caCertPath != "" {
		caCert, err = ioutil.ReadFile(caCertPath)

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

	scepSubject := [][][]string{
		[][]string{
			[]string{"O", "MicroMDM"},
			[]string{"CN", "MDM Identity Certificate:UDID"},
		},
	}

	return &service{
		URL:           url,
		SCEPURL:       scepURL,
		SCEPSubject:   scepSubject,
		SCEPChallenge: scepChallenge,
		Topic:         pushTopic,
		CACert:        caCert,
	}, nil
}

type service struct {
	URL           string
	SCEPURL       string
	SCEPChallenge string
	SCEPSubject   [][][]string
	Topic         string // APNS Topic for MDM notifications
	CACert        []byte
}

func (svc service) Enroll(ctx context.Context) (Profile, error) {
	profile := NewProfile()
	profile.PayloadIdentifier = "com.github.micromdm.micromdm.mdm"
	profile.PayloadOrganization = "MicroMDM"
	profile.PayloadDisplayName = "Enrollment Profile"
	profile.PayloadDescription = "The server may alter your settings"
	profile.PayloadScope = "System"

	scepContent := SCEPPayloadContent{
		Challenge: svc.SCEPChallenge,
		URL:       svc.SCEPURL,
		Keysize:   1024,
		KeyType:   "RSA",
		KeyUsage:  0,
		Name:      "Device Management Identity Certificate",
		Subject:   svc.SCEPSubject,
	}

	scepPayload := NewPayload("com.apple.security.scep")
	scepPayload.PayloadDescription = "Configures SCEP"
	scepPayload.PayloadDisplayName = "SCEP"
	scepPayload.PayloadIdentifier = "com.github.micromdm.scep"
	scepPayload.PayloadOrganization = "MicroMDM"
	scepPayload.PayloadContent = scepContent
	scepPayload.PayloadScope = "System"

	mdmPayload := NewPayload("com.apple.mdm")
	mdmPayload.PayloadDescription = "Enrolls with the MDM server"
	mdmPayload.PayloadOrganization = "MicroMDM"
	mdmPayload.PayloadIdentifier = "com.github.micromdm.mdm"
	mdmPayload.PayloadScope = "System"

	mdmPayloadContent := MDMPayloadContent{
		Payload:                 *mdmPayload,
		AccessRights:            8191,
		CheckInURL:              svc.URL + "/mdm/checkin",
		CheckOutWhenRemoved:     true,
		ServerURL:               svc.URL + "/mdm/connect",
		IdentityCertificateUUID: scepPayload.PayloadUUID,
		Topic: svc.Topic,
	}

	if len(svc.CACert) > 0 {
		caPayload := NewPayload("com.apple.ssl.certificate")
		caPayload.PayloadDisplayName = "Root certificate for MicroMDM"
		caPayload.PayloadDescription = "Installs the root CA certificate for MicroMDM"
		caPayload.PayloadIdentifier = "com.github.micromdm.ssl.ca"
		caPayload.PayloadContent = svc.CACert

		profile.PayloadContent = []interface{}{*scepPayload, mdmPayloadContent, *caPayload}
	} else {
		profile.PayloadContent = []interface{}{*scepPayload, mdmPayloadContent}
	}

	return *profile, nil
}
<|endoftext|>


<commit_before>package rkv

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"strings"

	"github.com/relab/raft"
)

// Service exposes the Store api as a http service.
type Service struct {
	store *Store
}

// NewService creates a new Service backed by store.
func NewService(store *Store) *Service {
	return &Service{
		store: store,
	}
}

// ServeHTTP implements the http.Handler interface.
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := strings.Split(r.URL.Path, "/")

	if len(path) < 2 {
		http.Error(w, "400 Bad Request", http.StatusBadRequest)
		return
	}

	switch path[1] {
	case "register":
		id, err := s.store.Register()

		if err != nil {
			raftError(w, r, err)
			return
		}

		fmt.Fprintln(w, id)
		return
	case "store":
		if len(path) != 3 {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		key := path[2]

		if len(key) < 1 {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		switch r.Method {
		case http.MethodGet:
			value, err := s.store.Lookup(key, false)

			if err != nil {
				raftError(w, r, err)
				return
			}

			fmt.Fprintln(w, value)

		case http.MethodPut:
			query := r.URL.Query()
			idq := query["id"]
			seqq := query["seq"]

			if len(idq) != 1 || len(seqq) != 1 {
				http.Error(w, "400 Bad Request", http.StatusBadRequest)
				return
			}

			id := idq[0]
			seq, err := strconv.ParseUint(seqq[0], 10, 64)

			if err != nil {
				http.Error(w, "400 Bad Request", http.StatusBadRequest)
				return
			}

			value, err := ioutil.ReadAll(r.Body)

			if err != nil {
				http.Error(w, "400 Bad Request", http.StatusBadRequest)
				return
			}

			err = s.store.Insert(key, string(value), id, seq)

			if err != nil {
				raftError(w, r, err)
				return
			}

			// TODO Change to StatusOK when we actually verify commitment.
			w.WriteHeader(http.StatusAccepted)
		}
	default:
		http.NotFound(w, r)
		return
	}
}

func raftError(w http.ResponseWriter, r *http.Request, err error) {
	switch err := err.(type) {
	case raft.ErrNotLeader:
		if err.LeaderAddr == "" {
			// TODO Document that this means the client should
			// change to a random server.
			w.Header().Set("Retry-After", "-1")
			http.Error(w, "503 Service Unavailable", http.StatusServiceUnavailable)
		}

		host, port, erri := net.SplitHostPort(err.LeaderAddr)

		if erri != nil {
			http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		}

		if host == "" {
			host = "localhost"
		}

		// TODO Document that this service always uses Raft port - 100.
		p, erri := strconv.Atoi(port)

		if erri != nil {
			http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		}

		port = strconv.Itoa(p - 100)
		addr := net.JoinHostPort(host, port)

		http.Redirect(w, r, "http://"+addr+r.URL.RequestURI(), http.StatusTemporaryRedirect)
	default:
		if err == ErrSessionExpired {
			// TODO Document that this means the session didn't
			// exist or expired.
			http.Error(w, "410 Gone", http.StatusGone)
			return
		}

		// TODO Document that this means the client should retry with
		// the same server in 1s. We could probably do exponential
		// back-off here.
		w.Header().Set("Retry-After", "1")
		http.Error(w, "503 Service Unavailable", http.StatusServiceUnavailable)
	}
}
<commit_msg>service.go: Extract insert/lookup handling into method handleStore<commit_after>package rkv

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"strings"

	"github.com/relab/raft"
)

// Service exposes the Store api as a http service.
type Service struct {
	store *Store
}

// NewService creates a new Service backed by store.
func NewService(store *Store) *Service {
	return &Service{
		store: store,
	}
}

// ServeHTTP implements the http.Handler interface.
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := strings.Split(r.URL.Path, "/")

	if len(path) < 2 {
		http.Error(w, "400 Bad Request", http.StatusBadRequest)
		return
	}

	switch path[1] {
	case "register":
		id, err := s.store.Register()

		if err != nil {
			raftError(w, r, err)
			return
		}

		fmt.Fprintln(w, id)
		return
	case "store":
		if len(path) != 3 {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		key := path[2]

		if len(key) < 1 {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		s.handleStore(w, r, key)

	default:
		http.NotFound(w, r)
		return
	}
}

func (s *Service) handleStore(w http.ResponseWriter, r *http.Request, key string) {
	switch r.Method {
	case http.MethodGet:
		value, err := s.store.Lookup(key, false)

		if err != nil {
			raftError(w, r, err)
			return
		}

		fmt.Fprintln(w, value)

	case http.MethodPut:
		query := r.URL.Query()
		idq := query["id"]
		seqq := query["seq"]

		if len(idq) != 1 || len(seqq) != 1 {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		id := idq[0]
		seq, err := strconv.ParseUint(seqq[0], 10, 64)

		if err != nil {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		value, err := ioutil.ReadAll(r.Body)

		if err != nil {
			http.Error(w, "400 Bad Request", http.StatusBadRequest)
			return
		}

		err = s.store.Insert(key, string(value), id, seq)

		if err != nil {
			raftError(w, r, err)
			return
		}

		// TODO Change to StatusOK when we actually verify commitment.
		w.WriteHeader(http.StatusAccepted)
	}
}

func raftError(w http.ResponseWriter, r *http.Request, err error) {
	switch err := err.(type) {
	case raft.ErrNotLeader:
		if err.LeaderAddr == "" {
			// TODO Document that this means the client should
			// change to a random server.
			w.Header().Set("Retry-After", "-1")
			http.Error(w, "503 Service Unavailable", http.StatusServiceUnavailable)
		}

		host, port, erri := net.SplitHostPort(err.LeaderAddr)

		if erri != nil {
			http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		}

		if host == "" {
			host = "localhost"
		}

		// TODO Document that this service always uses Raft port - 100.
		p, erri := strconv.Atoi(port)

		if erri != nil {
			http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
		}

		port = strconv.Itoa(p - 100)
		addr := net.JoinHostPort(host, port)

		http.Redirect(w, r, "http://"+addr+r.URL.RequestURI(), http.StatusTemporaryRedirect)
	default:
		if err == ErrSessionExpired {
			// TODO Document that this means the session didn't
			// exist or expired.
			http.Error(w, "410 Gone", http.StatusGone)
			return
		}

		// TODO Document that this means the client should retry with
		// the same server in 1s. We could probably do exponential
		// back-off here.
		w.Header().Set("Retry-After", "1")
		http.Error(w, "503 Service Unavailable", http.StatusServiceUnavailable)
	}
}
<|endoftext|>


<commit_before>package aiven

import (
	"encoding/json"
	"errors"
	"fmt"
)

type (
	Service struct {
		CloudName  string   `json:"cloud_name"`
		CreateTime string   `json:"create_time"`
		UpdateTime string   `json:"update_time"`
		GroupList  []string `json:"group_list"`
		NodeCount  int      `json:"node_count"`
		Plan       string   `json:"plan"`
		Name       string   `json:"service_name"`
		Type       string   `json:"service_type"`
		Uri        string   `json:"service_uri"`
		State      string   `json:"state"`
	}

	ServicesHandler struct {
		client *Client
	}

	CreateServiceRequest struct {
		Cloud       string `json:"cloud,omitempty"`
		GroupName   string `json:"group_name,omitempty"`
		Plan        string `json:"plan,omitempty"`
		ServiceName string `json:"service_name"`
		ServiceType string `json:"service_type"`
	}

	UpdateServiceRequest struct {
		Cloud     string `json:"cloud,omitempty"`
		GroupName string `json:"group_name,omitempty"`
		Plan      string `json:"plan,omitempty"`
		Powered   bool   `json:"powered"` // TODO: figure out if we can overwrite the default?
	}

	ServiceResponse struct {
		APIResponse
		Service *Service `json:"service"`
	}

	ServiceListResponse struct {
		APIResponse
		Services []*Service `json:"services"`
	}
)

func (h *ServicesHandler) Create(project string, req CreateServiceRequest) (*Service, error) {
	rsp, err := h.client.doPostRequest(fmt.Sprintf("/project/%s/service", project), req)
	if err != nil {
		return nil, err
	}

	return parseServiceResponse(rsp)
}

func (h *ServicesHandler) Get(project, service string) (*Service, error) {
	rsp, err := h.client.doGetRequest(fmt.Sprintf("/project/%s/service/%s", project, service), nil)
	if err != nil {
		return nil, err
	}

	return parseServiceResponse(rsp)
}

func (h *ServicesHandler) Update(project, service string, req UpdateServiceRequest) (*Service, error) {
	rsp, err := h.client.doPutRequest(fmt.Sprintf("/project/%s/service/%s", project, service), req)
	if err != nil {
		return nil, err
	}

	return parseServiceResponse(rsp)
}

func (h *ServicesHandler) Delete(project, service string) error {
	bts, err := h.client.doDeleteRequest(fmt.Sprintf("/project/%s/service/%s", project, service), nil)
	if err != nil {
		return err
	}

	return handleDeleteResponse(bts)
}

func (h *ServicesHandler) List(project string) ([]*Service, error) {
	rsp, err := h.client.doGetRequest(fmt.Sprintf("/project/%s/service", project), nil)
	if err != nil {
		return nil, err
	}

	var response *ServiceListResponse
	if err := json.Unmarshal(rsp, &response); err != nil {
		return nil, err
	}

	if len(response.Errors) != 0 {
		return nil, errors.New(response.Message)
	}

	return response.Services, nil
}

func parseServiceResponse(rsp []byte) (*Service, error) {
	var response *ServiceResponse
	if err := json.Unmarshal(rsp, &response); err != nil {
		return nil, err
	}

	if len(response.Errors) != 0 {
		return nil, errors.New(response.Message)
	}

	return response.Service, nil
}
<commit_msg>Service: add users.<commit_after>package aiven

import (
	"encoding/json"
	"errors"
	"fmt"
)

type (
	Service struct {
		CloudName  string         `json:"cloud_name"`
		CreateTime string         `json:"create_time"`
		UpdateTime string         `json:"update_time"`
		GroupList  []string       `json:"group_list"`
		NodeCount  int            `json:"node_count"`
		Plan       string         `json:"plan"`
		Name       string         `json:"service_name"`
		Type       string         `json:"service_type"`
		Uri        string         `json:"service_uri"`
		State      string         `json:"state"`
		Metadata   interface{}    `json:"metadata"`
		Users      []*ServiceUser `json:"users"`
	}

	ServicesHandler struct {
		client *Client
	}

	CreateServiceRequest struct {
		Cloud       string `json:"cloud,omitempty"`
		GroupName   string `json:"group_name,omitempty"`
		Plan        string `json:"plan,omitempty"`
		ServiceName string `json:"service_name"`
		ServiceType string `json:"service_type"`
	}

	UpdateServiceRequest struct {
		Cloud     string `json:"cloud,omitempty"`
		GroupName string `json:"group_name,omitempty"`
		Plan      string `json:"plan,omitempty"`
		Powered   bool   `json:"powered"` // TODO: figure out if we can overwrite the default?
	}

	ServiceResponse struct {
		APIResponse
		Service *Service `json:"service"`
	}

	ServiceListResponse struct {
		APIResponse
		Services []*Service `json:"services"`
	}
)

func (h *ServicesHandler) Create(project string, req CreateServiceRequest) (*Service, error) {
	rsp, err := h.client.doPostRequest(fmt.Sprintf("/project/%s/service", project), req)
	if err != nil {
		return nil, err
	}

	return parseServiceResponse(rsp)
}

func (h *ServicesHandler) Get(project, service string) (*Service, error) {
	rsp, err := h.client.doGetRequest(fmt.Sprintf("/project/%s/service/%s", project, service), nil)
	if err != nil {
		return nil, err
	}

	return parseServiceResponse(rsp)
}

func (h *ServicesHandler) Update(project, service string, req UpdateServiceRequest) (*Service, error) {
	rsp, err := h.client.doPutRequest(fmt.Sprintf("/project/%s/service/%s", project, service), req)
	if err != nil {
		return nil, err
	}

	return parseServiceResponse(rsp)
}

func (h *ServicesHandler) Delete(project, service string) error {
	bts, err := h.client.doDeleteRequest(fmt.Sprintf("/project/%s/service/%s", project, service), nil)
	if err != nil {
		return err
	}

	return handleDeleteResponse(bts)
}

func (h *ServicesHandler) List(project string) ([]*Service, error) {
	rsp, err := h.client.doGetRequest(fmt.Sprintf("/project/%s/service", project), nil)
	if err != nil {
		return nil, err
	}

	var response *ServiceListResponse
	if err := json.Unmarshal(rsp, &response); err != nil {
		return nil, err
	}

	if len(response.Errors) != 0 {
		return nil, errors.New(response.Message)
	}

	return response.Services, nil
}

func parseServiceResponse(rsp []byte) (*Service, error) {
	var response *ServiceResponse
	if err := json.Unmarshal(rsp, &response); err != nil {
		return nil, err
	}

	if len(response.Errors) != 0 {
		return nil, errors.New(response.Message)
	}

	return response.Service, nil
}
<|endoftext|>


<commit_before>package kuiperbelt

import (
	"errors"
	"io"
	"sync"
)

var (
	sessionMap           = map[string]Session{}
	sessionMapLocker     = new(sync.Mutex)
	sessionNotFoundError = errors.New("session is not found.")
)

type Session interface {
	io.ReadWriteCloser
	Key() string
	NotifiedClose(bool)
}

func AddSession(s Session) {
	sessionMapLocker.Lock()
	defer sessionMapLocker.Unlock()
	sessionMap[s.Key()] = s
}

func GetSession(key string) (Session, error) {
	sessionMapLocker.Lock()
	defer sessionMapLocker.Unlock()
	s, ok := sessionMap[key]
	if !ok {
		return nil, sessionNotFoundError
	}
	return s, nil
}

func DelSession(key string) error {
	sessionMapLocker.Lock()
	defer sessionMapLocker.Unlock()
	if _, ok := sessionMap[key]; !ok {
		return sessionNotFoundError
	}
	delete(sessionMap, key)
	return nil
}
<commit_msg>fix golint warning<commit_after>package kuiperbelt

import (
	"errors"
	"io"
	"sync"
)

var (
	sessionMap         = map[string]Session{}
	sessionMapLocker   = new(sync.Mutex)
	errSessionNotFound = errors.New("kuiperbelt: session is not found")
)

type Session interface {
	io.ReadWriteCloser
	Key() string
	NotifiedClose(bool)
}

func AddSession(s Session) {
	sessionMapLocker.Lock()
	defer sessionMapLocker.Unlock()
	sessionMap[s.Key()] = s
}

func GetSession(key string) (Session, error) {
	sessionMapLocker.Lock()
	defer sessionMapLocker.Unlock()
	s, ok := sessionMap[key]
	if !ok {
		return nil, errSessionNotFound
	}
	return s, nil
}

func DelSession(key string) error {
	sessionMapLocker.Lock()
	defer sessionMapLocker.Unlock()
	if _, ok := sessionMap[key]; !ok {
		return errSessionNotFound
	}
	delete(sessionMap, key)
	return nil
}
<|endoftext|>


<commit_before>package interactive

import (
	"io"
	"os"
	"strings"

	"golang.org/x/crypto/ssh/terminal"
)

// A Session is an interactive shell. The New() function should be used to
// obtain a new Session instance.
type Session struct {
	// Action is the actual application logic that is looped until the
	// application gets terminated.
	Action ActionFunc
	// After is run AFTER the action function, BEFORE the session is closed.
	// It is invoked by context.Close().
	After AfterFunc
	// Before is run BEFORE the action function.
	Before BeforeFunc

	context *Context
	fd      int
	state   *terminal.State
	term    *terminal.Terminal
}

// New spawns an interactive session in the current terminal. A prompt character
// needs to be provided which will be printed when user input is awaited.
func New(prompt string) *Session {
	// Save old state and set terminal into raw mode.
	fd := int(os.Stdin.Fd())
	oldState, err := terminal.MakeRaw(fd)
	if err != nil {
		panic(err)
	}

	// Satisfies the ReadWriter interface and serves as I/O for the new terminal.
	shell := &Shell{
		r: os.Stdin,
		w: os.Stdout,
	}

	// Create new terminal with desired prompt sign.
	term := terminal.NewTerminal(shell, strings.Trim(prompt, " ")+" ")

	// Finally create the session.
	s := &Session{
		Action: dummyAction,
		fd:     fd,
		state:  oldState,
		term:   term,
	}
	s.context = &Context{session: s}

	// Set up Ctrl^C listener.
	term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
		if key == '\x03' {
			s.close(0)
		}
		return "", 0, false
	}

	return s
}

// Run is a blocking method that executes the actual logic.
func (s *Session) Run() {
	// Run Before function if present.
	if s.Before != nil {
		s.Before(s.context)
	}

	// Loop root action. Close if an error is present.
	for {
		if err := s.Action(s.context); err != nil {
			s.writeLine(err.Error())
			s.close(1)
		}
	}
}

func (s *Session) close(exitCode int) {
	// Run After function if present.
	if s.After != nil {
		s.After(s.context)
	}

	// Restore terminal.
	terminal.Restore(s.fd, s.state)
	os.Exit(exitCode)
}

func (s *Session) readLine() string {
	text, err := s.term.ReadLine()
	if err != nil {
		// Close session on Ctrl^D.
		if err == io.EOF {
			s.close(0)
		}
		panic(err)
	}
	return text
}

func (s *Session) writeLine(text string) {
	s.term.Write([]byte(text + "\n"))
}

func dummyAction(c *Context) error {
	c.WriteLine("No Action defined!")
	c.Close()
	return nil
}
<commit_msg>Returning an error in the Before() function will close the session.<commit_after>package interactive

import (
	"io"
	"os"
	"strings"

	"golang.org/x/crypto/ssh/terminal"
)

// A Session is an interactive shell. The New() function should be used to
// obtain a new Session instance.
type Session struct {
	// Action is the actual application logic that is looped until the
	// application gets terminated.
	Action ActionFunc
	// After is run AFTER the action function, BEFORE the session is closed.
	// It is invoked by context.Close().
	After AfterFunc
	// Before is run BEFORE the action function.
	Before BeforeFunc

	context *Context
	fd      int
	state   *terminal.State
	term    *terminal.Terminal
}

// New spawns an interactive session in the current terminal. A prompt character
// needs to be provided which will be printed when user input is awaited.
func New(prompt string) *Session {
	// Save old state and set terminal into raw mode.
	fd := int(os.Stdin.Fd())
	oldState, err := terminal.MakeRaw(fd)
	if err != nil {
		panic(err)
	}

	// Satisfies the ReadWriter interface and serves as I/O for the new terminal.
	shell := &Shell{
		r: os.Stdin,
		w: os.Stdout,
	}

	// Create new terminal with desired prompt sign.
	term := terminal.NewTerminal(shell, strings.Trim(prompt, " ")+" ")

	// Finally create the session.
	s := &Session{
		Action: dummyAction,
		fd:     fd,
		state:  oldState,
		term:   term,
	}
	s.context = &Context{session: s}

	// Set up Ctrl^C listener.
	term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
		if key == '\x03' {
			s.close(0)
		}
		return "", 0, false
	}

	return s
}

// Run is a blocking method that executes the actual logic.
func (s *Session) Run() {
	// Run Before function if present. Close session if an error occurs.
	if s.Before != nil {
		if err := s.Before(s.context); err != nil {
			s.writeLine(err.Error())
			s.close(1)
		}
	}

	// Loop root action. Close session if an error occurs.
	for {
		if err := s.Action(s.context); err != nil {
			s.writeLine(err.Error())
			s.close(1)
		}
	}
}

func (s *Session) close(exitCode int) {
	// Run After function if present.
	if s.After != nil {
		if err := s.After(s.context); err != nil {
			s.writeLine(err.Error())
		}
	}

	// Restore terminal.
	terminal.Restore(s.fd, s.state)
	os.Exit(exitCode)
}

func (s *Session) readLine() string {
	text, err := s.term.ReadLine()
	if err != nil {
		// Close session on Ctrl^D.
		if err == io.EOF {
			s.close(0)
		}
		panic(err)
	}
	return text
}

func (s *Session) writeLine(text string) {
	s.term.Write([]byte(text + "\n"))
}

func dummyAction(c *Context) error {
	c.WriteLine("No Action defined!")
	c.Close()
	return nil
}
<|endoftext|>