<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package networker_test

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils"
	"github.com/juju/utils/set"
	gc "launchpad.net/gocheck"

	"github.com/juju/juju/agent"
	"github.com/juju/juju/juju/testing"
	"github.com/juju/juju/state"
	"github.com/juju/juju/state/api"
	apinetworker "github.com/juju/juju/state/api/networker"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker"
	"github.com/juju/juju/worker/networker"
)

type networkerSuite struct {
	testing.JujuConnSuite

	networks []state.NetworkInfo
	machine  *state.Machine
	ifaces   []state.NetworkInterfaceInfo

	st             *api.State
	networkerState *apinetworker.State
	configStates   []*configState
	executed       chan bool
}

var _ = gc.Suite(&networkerSuite{})

// Create several networks.
func (s *networkerSuite) setUpNetworks(c *gc.C) {
	s.networks = []state.NetworkInfo{{
		Name:       "net1",
		ProviderId: "net1",
		CIDR:       "0.1.2.0/24",
		VLANTag:    0,
	}, {
		Name:       "vlan42",
		ProviderId: "vlan42",
		CIDR:       "0.2.2.0/24",
		VLANTag:    42,
	}, {
		Name:       "vlan69",
		ProviderId: "vlan69",
		CIDR:       "0.3.2.0/24",
		VLANTag:    69,
	}, {
		Name:       "vlan123",
		ProviderId: "vlan123",
		CIDR:       "0.4.2.0/24",
		VLANTag:    123,
	}, {
		Name:       "net2",
		ProviderId: "net2",
		CIDR:       "0.5.2.0/24",
		VLANTag:    0,
	}}
}

// Create a machine and login to it.
func (s *networkerSuite) setUpMachine(c *gc.C) {
	var err error
	s.machine, err = s.State.AddMachine("quantal", state.JobHostUnits)
	c.Assert(err, gc.IsNil)
	password, err := utils.RandomPassword()
	c.Assert(err, gc.IsNil)
	err = s.machine.SetPassword(password)
	c.Assert(err, gc.IsNil)
	s.ifaces = []state.NetworkInterfaceInfo{{
		MACAddress:    "aa:bb:cc:dd:ee:f0",
		InterfaceName: "eth0",
		NetworkName:   "net1",
		IsVirtual:     false,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f1",
		InterfaceName: "eth1",
		NetworkName:   "net1",
		IsVirtual:     false,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f1",
		InterfaceName: "eth1.42",
		NetworkName:   "vlan42",
		IsVirtual:     true,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f0",
		InterfaceName: "eth0.69",
		NetworkName:   "vlan69",
		IsVirtual:     true,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f2",
		InterfaceName: "eth2",
		NetworkName:   "net2",
		IsVirtual:     false,
	}}
	err = s.machine.SetInstanceInfo("i-am", "fake_nonce", nil, s.networks, s.ifaces)
	c.Assert(err, gc.IsNil)
	s.st = s.OpenAPIAsMachine(c, s.machine.Tag(), password, "fake_nonce")
	c.Assert(s.st, gc.NotNil)
}

func (s *networkerSuite) SetUpTest(c *gc.C) {
	s.JujuConnSuite.SetUpTest(c)

	// Create test juju state.
	s.setUpNetworks(c)
	s.setUpMachine(c)

	// Create the networker API facade.
	s.networkerState = s.st.Networker()
	c.Assert(s.networkerState, gc.NotNil)

	// Create temporary directory to store interfaces file.
	networker.ChangeConfigDirName(c.MkDir())
}

type mockConfig struct {
	agent.Config
	tag names.Tag
}

func (mock *mockConfig) Tag() names.Tag {
	return mock.tag
}

func agentConfig(tag names.Tag) agent.Config {
	return &mockConfig{tag: tag}
}

const sampleInterfacesFile = `# This file describes the network interfaces available on your system
# and how to activate them. For more information see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
source %s/eth0.config

auto wlan0
iface wlan0 inet dhcp
`
const sampleEth0DotConfigFile = `iface eth0 inet manual

auto br0
iface br0 inet dhcp
  bridge_ports eth0
`

var readyInterfaces = set.NewStrings("eth0", "br0", "wlan0")
var interfacesWithAddress = set.NewStrings("br0", "wlan0")

var expectedInterfacesFile = `# This file describes the network interfaces available on your system
# and how to activate them. For more information see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

` + networker.SourceCommentAndCommand

type configState struct {
	files                 networker.ConfigFiles
	commands              []string
	readyInterfaces       []string
	interfacesWithAddress []string
}

func executeCommandsHook(c *gc.C, s *networkerSuite, commands []string) error {
	cs := &configState{}
	err := networker.ReadAll(&cs.files)
	c.Assert(err, gc.IsNil)
	cs.commands = append(cs.commands, commands...)
	// modify state of interfaces
	for _, cmd := range commands {
		args := strings.Split(cmd, " ")
		if len(args) == 2 && args[0] == "ifup" {
			readyInterfaces.Add(args[1])
			interfacesWithAddress.Add(args[1])
		} else if len(args) == 2 && args[0] == "ifdown" {
			readyInterfaces.Remove(args[1])
			interfacesWithAddress.Remove(args[1])
		}
	}
	cs.readyInterfaces = readyInterfaces.SortedValues()
	cs.interfacesWithAddress = interfacesWithAddress.SortedValues()
	s.configStates = append(s.configStates, cs)
	s.executed <- true
	return nil
}

func (s *networkerSuite) TestNewNetworkerReturnsErrorWithoutConfig(c *gc.C) {
	_, err := os.Stat(networker.ConfigFileName)
	c.Assert(err, jc.Satisfies, os.IsNotExist)
	nw, err := networker.NewNetworker(s.networkerState, agentConfig(s.machine.Tag()))
	c.Assert(err, gc.ErrorMatches, `missing ".*" config file`)
	c.Assert(nw, gc.IsNil)
}

func (s *networkerSuite) TestNetworker(c *gc.C) {
	// Create a sample interfaces file (MAAS configuration)
	interfacesFileContents := fmt.Sprintf(sampleInterfacesFile, networker.ConfigDirName)
	err := utils.AtomicWriteFile(networker.ConfigFileName, []byte(interfacesFileContents), 0644)
	c.Assert(err, gc.IsNil)
	err = utils.AtomicWriteFile(filepath.Join(networker.ConfigDirName, "eth0.config"), []byte(sampleEth0DotConfigFile), 0644)
	c.Assert(err, gc.IsNil)

	// Patch the network interface functions
	s.PatchValue(&networker.InterfaceIsUp,
		func(name string) bool {
			return readyInterfaces.Contains(name)
		})
	s.PatchValue(&networker.InterfaceHasAddress,
		func(name string) bool {
			return interfacesWithAddress.Contains(name)
		})

	// Patch the command executor function
	s.configStates = []*configState{}
	s.PatchValue(&networker.ExecuteCommands,
		func(commands []string) error {
			return executeCommandsHook(c, s, commands)
		},
	)

	// Create and setup networker.
	s.executed = make(chan bool)
	nw, err := networker.NewNetworker(s.networkerState, agentConfig(s.machine.Tag()))
	c.Assert(err, gc.IsNil)
	defer func() { c.Assert(worker.Stop(nw), gc.IsNil) }()

	executeCount := 0
loop:
	for {
		select {
		case <-s.executed:
			executeCount++
			if executeCount == 3 {
				break loop
			}
		case <-time.After(coretesting.ShortWait):
			fmt.Printf("%#v\n", s.configStates)
			c.Fatalf("command not executed")
		}
	}

	// Verify the executed commands from SetUp()
	expectedConfigFiles := networker.ConfigFiles{
		networker.ConfigFileName: {
			Data: fmt.Sprintf(expectedInterfacesFile, networker.ConfigSubDirName,
				networker.ConfigSubDirName, networker.ConfigSubDirName, networker.ConfigSubDirName),
		},
		networker.IfaceConfigFileName("br0"): {
			Data: "auto br0\niface br0 inet dhcp\n  bridge_ports eth0\n",
		},
		networker.IfaceConfigFileName("eth0"): {
			Data: "auto eth0\niface eth0 inet manual\n",
		},
		networker.IfaceConfigFileName("wlan0"): {
			Data: "auto wlan0\niface wlan0 inet dhcp\n",
		},
	}
	c.Assert(s.configStates[0].files, gc.DeepEquals, expectedConfigFiles)
	expectedCommands := []string(nil)
	c.Assert(s.configStates[0].commands, gc.DeepEquals, expectedCommands)
	c.Assert(s.configStates[0].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "wlan0"})
	c.Assert(s.configStates[0].interfacesWithAddress, gc.DeepEquals, []string{"br0", "wlan0"})

	// Verify the executed commands from Handle()
	c.Assert(s.configStates[1].files, gc.DeepEquals, expectedConfigFiles)
	expectedCommands = []string(nil)
	c.Assert(s.configStates[1].commands, gc.DeepEquals, expectedCommands)
	c.Assert(s.configStates[1].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "wlan0"})
	c.Assert(s.configStates[1].interfacesWithAddress, gc.DeepEquals, []string{"br0", "wlan0"})

	// Verify the executed commands from Handle()
	expectedConfigFiles[networker.IfaceConfigFileName("eth0.69")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth0.69\niface eth0.69 inet dhcp\n\tvlan-raw-device eth0\n",
	}
	expectedConfigFiles[networker.IfaceConfigFileName("eth1")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth1\niface eth1 inet dhcp\n",
	}
	expectedConfigFiles[networker.IfaceConfigFileName("eth1.42")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth1.42\niface eth1.42 inet dhcp\n\tvlan-raw-device eth1\n",
	}
	expectedConfigFiles[networker.IfaceConfigFileName("eth2")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth2\niface eth2 inet dhcp\n",
	}
	for k, _ := range s.configStates[2].files {
		c.Check(s.configStates[2].files[k], gc.DeepEquals, expectedConfigFiles[k])
	}
	c.Assert(s.configStates[2].files, gc.DeepEquals, expectedConfigFiles)
	expectedCommands = []string{
		"dpkg-query -s vlan || apt-get --option Dpkg::Options::=--force-confold --assume-yes install vlan",
		"lsmod | grep -q 8021q || modprobe 8021q",
		"grep -q 8021q /etc/modules || echo 8021q >> /etc/modules",
		"vconfig set_name_type DEV_PLUS_VID_NO_PAD",
		"ifup eth0.69",
		"ifup eth1",
		"ifup eth1.42",
		"ifup eth2",
	}
	c.Assert(s.configStates[2].commands, gc.DeepEquals, expectedCommands)
	c.Assert(s.configStates[2].readyInterfaces, gc.DeepEquals,
		[]string{"br0", "eth0", "eth0.69", "eth1", "eth1.42", "eth2", "wlan0"})
	c.Assert(s.configStates[2].interfacesWithAddress, gc.DeepEquals,
		[]string{"br0", "eth0.69", "eth1", "eth1.42", "eth2", "wlan0"})
}

func (s *networkerSuite) TestSafeNetworkerDoesNotWriteConfigFiles(c *gc.C) {
	// Create a sample interfaces file (MAAS configuration)
	interfacesFileContents := fmt.Sprintf(sampleInterfacesFile, networker.ConfigDirName)
	err := utils.AtomicWriteFile(networker.ConfigFileName, []byte(interfacesFileContents), 0644)
	c.Assert(err, gc.IsNil)
	err = utils.AtomicWriteFile(filepath.Join(networker.ConfigDirName, "eth0.config"), []byte(sampleEth0DotConfigFile), 0644)
	c.Assert(err, gc.IsNil)
	// Patch the command executor function
	s.configStates = []*configState{}
	s.PatchValue(&networker.ExecuteCommands,
		func(commands []string) error {
			return executeCommandsHook(c, s, commands)
		},
	)

	// Create and setup networker.
	s.executed = make(chan bool)
	nw, err := networker.NewSafeNetworker(s.networkerState, agentConfig(s.machine.Tag()))
	c.Assert(err, gc.IsNil)
	defer func() { c.Assert(worker.Stop(nw), gc.IsNil) }()

	for {
		select {
		case <-s.executed:
			c.Fatalf("command executed unexpectedly")
		case <-time.After(coretesting.ShortWait):
			return
		}
	}
}
<commit_msg>Adding periods to comments<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package networker_test

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils"
	"github.com/juju/utils/set"
	gc "launchpad.net/gocheck"

	"github.com/juju/juju/agent"
	"github.com/juju/juju/juju/testing"
	"github.com/juju/juju/state"
	"github.com/juju/juju/state/api"
	apinetworker "github.com/juju/juju/state/api/networker"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker"
	"github.com/juju/juju/worker/networker"
)

type networkerSuite struct {
	testing.JujuConnSuite

	networks []state.NetworkInfo
	machine  *state.Machine
	ifaces   []state.NetworkInterfaceInfo

	st             *api.State
	networkerState *apinetworker.State
	configStates   []*configState
	executed       chan bool
}

var _ = gc.Suite(&networkerSuite{})

// Create several networks.
func (s *networkerSuite) setUpNetworks(c *gc.C) {
	s.networks = []state.NetworkInfo{{
		Name:       "net1",
		ProviderId: "net1",
		CIDR:       "0.1.2.0/24",
		VLANTag:    0,
	}, {
		Name:       "vlan42",
		ProviderId: "vlan42",
		CIDR:       "0.2.2.0/24",
		VLANTag:    42,
	}, {
		Name:       "vlan69",
		ProviderId: "vlan69",
		CIDR:       "0.3.2.0/24",
		VLANTag:    69,
	}, {
		Name:       "vlan123",
		ProviderId: "vlan123",
		CIDR:       "0.4.2.0/24",
		VLANTag:    123,
	}, {
		Name:       "net2",
		ProviderId: "net2",
		CIDR:       "0.5.2.0/24",
		VLANTag:    0,
	}}
}

// Create a machine and login to it.
func (s *networkerSuite) setUpMachine(c *gc.C) {
	var err error
	s.machine, err = s.State.AddMachine("quantal", state.JobHostUnits)
	c.Assert(err, gc.IsNil)
	password, err := utils.RandomPassword()
	c.Assert(err, gc.IsNil)
	err = s.machine.SetPassword(password)
	c.Assert(err, gc.IsNil)
	s.ifaces = []state.NetworkInterfaceInfo{{
		MACAddress:    "aa:bb:cc:dd:ee:f0",
		InterfaceName: "eth0",
		NetworkName:   "net1",
		IsVirtual:     false,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f1",
		InterfaceName: "eth1",
		NetworkName:   "net1",
		IsVirtual:     false,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f1",
		InterfaceName: "eth1.42",
		NetworkName:   "vlan42",
		IsVirtual:     true,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f0",
		InterfaceName: "eth0.69",
		NetworkName:   "vlan69",
		IsVirtual:     true,
	}, {
		MACAddress:    "aa:bb:cc:dd:ee:f2",
		InterfaceName: "eth2",
		NetworkName:   "net2",
		IsVirtual:     false,
	}}
	err = s.machine.SetInstanceInfo("i-am", "fake_nonce", nil, s.networks, s.ifaces)
	c.Assert(err, gc.IsNil)
	s.st = s.OpenAPIAsMachine(c, s.machine.Tag(), password, "fake_nonce")
	c.Assert(s.st, gc.NotNil)
}

func (s *networkerSuite) SetUpTest(c *gc.C) {
	s.JujuConnSuite.SetUpTest(c)

	// Create test juju state.
	s.setUpNetworks(c)
	s.setUpMachine(c)

	// Create the networker API facade.
	s.networkerState = s.st.Networker()
	c.Assert(s.networkerState, gc.NotNil)

	// Create temporary directory to store interfaces file.
	networker.ChangeConfigDirName(c.MkDir())
}

type mockConfig struct {
	agent.Config
	tag names.Tag
}

func (mock *mockConfig) Tag() names.Tag {
	return mock.tag
}

func agentConfig(tag names.Tag) agent.Config {
	return &mockConfig{tag: tag}
}

const sampleInterfacesFile = `# This file describes the network interfaces available on your system
# and how to activate them. For more information see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
source %s/eth0.config

auto wlan0
iface wlan0 inet dhcp
`
const sampleEth0DotConfigFile = `iface eth0 inet manual

auto br0
iface br0 inet dhcp
  bridge_ports eth0
`

var readyInterfaces = set.NewStrings("eth0", "br0", "wlan0")
var interfacesWithAddress = set.NewStrings("br0", "wlan0")

var expectedInterfacesFile = `# This file describes the network interfaces available on your system
# and how to activate them. For more information see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

` + networker.SourceCommentAndCommand

type configState struct {
	files                 networker.ConfigFiles
	commands              []string
	readyInterfaces       []string
	interfacesWithAddress []string
}

func executeCommandsHook(c *gc.C, s *networkerSuite, commands []string) error {
	cs := &configState{}
	err := networker.ReadAll(&cs.files)
	c.Assert(err, gc.IsNil)
	cs.commands = append(cs.commands, commands...)
	// modify state of interfaces
	for _, cmd := range commands {
		args := strings.Split(cmd, " ")
		if len(args) == 2 && args[0] == "ifup" {
			readyInterfaces.Add(args[1])
			interfacesWithAddress.Add(args[1])
		} else if len(args) == 2 && args[0] == "ifdown" {
			readyInterfaces.Remove(args[1])
			interfacesWithAddress.Remove(args[1])
		}
	}
	cs.readyInterfaces = readyInterfaces.SortedValues()
	cs.interfacesWithAddress = interfacesWithAddress.SortedValues()
	s.configStates = append(s.configStates, cs)
	s.executed <- true
	return nil
}

func (s *networkerSuite) TestNewNetworkerReturnsErrorWithoutConfig(c *gc.C) {
	_, err := os.Stat(networker.ConfigFileName)
	c.Assert(err, jc.Satisfies, os.IsNotExist)
	nw, err := networker.NewNetworker(s.networkerState, agentConfig(s.machine.Tag()))
	c.Assert(err, gc.ErrorMatches, `missing ".*" config file`)
	c.Assert(nw, gc.IsNil)
}

func (s *networkerSuite) TestNetworker(c *gc.C) {
	// Create a sample interfaces file (MAAS configuration)
	interfacesFileContents := fmt.Sprintf(sampleInterfacesFile, networker.ConfigDirName)
	err := utils.AtomicWriteFile(networker.ConfigFileName, []byte(interfacesFileContents), 0644)
	c.Assert(err, gc.IsNil)
	err = utils.AtomicWriteFile(filepath.Join(networker.ConfigDirName, "eth0.config"), []byte(sampleEth0DotConfigFile), 0644)
	c.Assert(err, gc.IsNil)

	// Patch the network interface functions
	s.PatchValue(&networker.InterfaceIsUp,
		func(name string) bool {
			return readyInterfaces.Contains(name)
		})
	s.PatchValue(&networker.InterfaceHasAddress,
		func(name string) bool {
			return interfacesWithAddress.Contains(name)
		})

	// Patch the command executor function
	s.configStates = []*configState{}
	s.PatchValue(&networker.ExecuteCommands,
		func(commands []string) error {
			return executeCommandsHook(c, s, commands)
		},
	)

	// Create and setup networker.
	s.executed = make(chan bool)
	nw, err := networker.NewNetworker(s.networkerState, agentConfig(s.machine.Tag()))
	c.Assert(err, gc.IsNil)
	defer func() { c.Assert(worker.Stop(nw), gc.IsNil) }()

	executeCount := 0
loop:
	for {
		select {
		case <-s.executed:
			executeCount++
			if executeCount == 3 {
				break loop
			}
		case <-time.After(coretesting.ShortWait):
			fmt.Printf("%#v\n", s.configStates)
			c.Fatalf("command not executed")
		}
	}

	// Verify the executed commands from SetUp()
	expectedConfigFiles := networker.ConfigFiles{
		networker.ConfigFileName: {
			Data: fmt.Sprintf(expectedInterfacesFile, networker.ConfigSubDirName,
				networker.ConfigSubDirName, networker.ConfigSubDirName, networker.ConfigSubDirName),
		},
		networker.IfaceConfigFileName("br0"): {
			Data: "auto br0\niface br0 inet dhcp\n  bridge_ports eth0\n",
		},
		networker.IfaceConfigFileName("eth0"): {
			Data: "auto eth0\niface eth0 inet manual\n",
		},
		networker.IfaceConfigFileName("wlan0"): {
			Data: "auto wlan0\niface wlan0 inet dhcp\n",
		},
	}
	c.Assert(s.configStates[0].files, gc.DeepEquals, expectedConfigFiles)
	expectedCommands := []string(nil)
	c.Assert(s.configStates[0].commands, gc.DeepEquals, expectedCommands)
	c.Assert(s.configStates[0].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "wlan0"})
	c.Assert(s.configStates[0].interfacesWithAddress, gc.DeepEquals, []string{"br0", "wlan0"})

	// Verify the executed commands from Handle()
	c.Assert(s.configStates[1].files, gc.DeepEquals, expectedConfigFiles)
	expectedCommands = []string(nil)
	c.Assert(s.configStates[1].commands, gc.DeepEquals, expectedCommands)
	c.Assert(s.configStates[1].readyInterfaces, gc.DeepEquals, []string{"br0", "eth0", "wlan0"})
	c.Assert(s.configStates[1].interfacesWithAddress, gc.DeepEquals, []string{"br0", "wlan0"})

	// Verify the executed commands from Handle()
	expectedConfigFiles[networker.IfaceConfigFileName("eth0.69")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth0.69\niface eth0.69 inet dhcp\n\tvlan-raw-device eth0\n",
	}
	expectedConfigFiles[networker.IfaceConfigFileName("eth1")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth1\niface eth1 inet dhcp\n",
	}
	expectedConfigFiles[networker.IfaceConfigFileName("eth1.42")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth1.42\niface eth1.42 inet dhcp\n\tvlan-raw-device eth1\n",
	}
	expectedConfigFiles[networker.IfaceConfigFileName("eth2")] = &networker.ConfigFile{
		Data: "# Managed by Juju, don't change.\nauto eth2\niface eth2 inet dhcp\n",
	}
	for k, _ := range s.configStates[2].files {
		c.Check(s.configStates[2].files[k], gc.DeepEquals, expectedConfigFiles[k])
	}
	c.Assert(s.configStates[2].files, gc.DeepEquals, expectedConfigFiles)
	expectedCommands = []string{
		"dpkg-query -s vlan || apt-get --option Dpkg::Options::=--force-confold --assume-yes install vlan",
		"lsmod | grep -q 8021q || modprobe 8021q",
		"grep -q 8021q /etc/modules || echo 8021q >> /etc/modules",
		"vconfig set_name_type DEV_PLUS_VID_NO_PAD",
		"ifup eth0.69",
		"ifup eth1",
		"ifup eth1.42",
		"ifup eth2",
	}
	c.Assert(s.configStates[2].commands, gc.DeepEquals, expectedCommands)
	c.Assert(s.configStates[2].readyInterfaces, gc.DeepEquals,
		[]string{"br0", "eth0", "eth0.69", "eth1", "eth1.42", "eth2", "wlan0"})
	c.Assert(s.configStates[2].interfacesWithAddress, gc.DeepEquals,
		[]string{"br0", "eth0.69", "eth1", "eth1.42", "eth2", "wlan0"})
}

func (s *networkerSuite) TestSafeNetworkerDoesNotWriteConfigFiles(c *gc.C) {
	// Create a sample interfaces file (MAAS configuration).
	interfacesFileContents := fmt.Sprintf(sampleInterfacesFile, networker.ConfigDirName)
	err := utils.AtomicWriteFile(networker.ConfigFileName, []byte(interfacesFileContents), 0644)
	c.Assert(err, gc.IsNil)
	err = utils.AtomicWriteFile(filepath.Join(networker.ConfigDirName, "eth0.config"), []byte(sampleEth0DotConfigFile), 0644)
	c.Assert(err, gc.IsNil)
	// Patch the command executor function.
	s.configStates = []*configState{}
	s.PatchValue(&networker.ExecuteCommands,
		func(commands []string) error {
			return executeCommandsHook(c, s, commands)
		},
	)

	// Create and setup networker.
	s.executed = make(chan bool)
	nw, err := networker.NewSafeNetworker(s.networkerState, agentConfig(s.machine.Tag()))
	c.Assert(err, gc.IsNil)
	defer func() { c.Assert(worker.Stop(nw), gc.IsNil) }()

	for {
		select {
		case <-s.executed:
			c.Fatalf("command executed unexpectedly")
		case <-time.After(coretesting.ShortWait):
			return
		}
	}
}
<|endoftext|>


<commit_before>package spi

import (
	"reflect"
	"rtos"
	"sync/atomic"
	"sync/fence"
	"unsafe"

	"stm32/hal/dma"
)

type DriverError byte

const ErrTimeout DriverError = 1

func (e DriverError) Error() string {
	switch e {
	case ErrTimeout:
		return "timeout"
	default:
		return ""
	}
}

type Driver struct {
	deadline int64

	P     *Periph
	RxDMA *dma.Channel
	TxDMA *dma.Channel

	dmacnt int
	done   rtos.EventFlag
	err    uint
}

// NewDriver provides convenient way to create heap allocated Driver struct.
func NewDriver(p *Periph, rxdma, txdma *dma.Channel) *Driver {
	d := new(Driver)
	d.P = p
	d.RxDMA = rxdma
	d.TxDMA = txdma
	return d
}

func (d *Driver) DMAISR(ch *dma.Channel) {
	ch.DisableIRQ(dma.EvAll, dma.ErrAll)
	_, e := ch.Status()
	if e&^dma.ErrFIFO != 0 || atomic.AddInt(&d.dmacnt, -1) == 0 {
		d.done.Signal(1)
	}
}

func (d *Driver) ISR() {
	d.P.DisableIRQ(RxNotEmpty | Err)
	d.done.Signal(1)
}

func (d *Driver) SetDeadline(deadline int64) {
	d.deadline = deadline
}

// WriteReadByte writes and reads byte.
func (d *Driver) WriteReadByte(b byte) byte {
	if d.err != 0 {
		return 0
	}
	d.P.SetDuplex(Full)
	d.done.Reset(0)
	d.P.EnableIRQ(RxNotEmpty | Err)
	fence.W() // This orders writes to normal and I/O memory.
	d.P.StoreByte(b)
	if !d.done.Wait(1, d.deadline) {
		d.err = uint(ErrTimeout) << 16
		return 0
	}
	b = d.P.LoadByte()
	if _, e := d.P.Status(); e != 0 {
		d.err = uint(e) << 8
		return 0
	}
	return b
}

// WriteReadWord16 writes and reads 16-bit word.
func (d *Driver) WriteReadWord16(w uint16) uint16 {
	if d.err != 0 {
		return 0
	}
	d.P.SetDuplex(Full)
	d.done.Reset(0)
	d.P.EnableIRQ(RxNotEmpty | Err)
	fence.W() // This orders writes to normal and I/O memory.
	d.P.StoreWord16(w)
	if !d.done.Wait(1, d.deadline) {
		d.err = uint(ErrTimeout) << 16
		return 0
	}
	w = d.P.LoadWord16()
	if _, e := d.P.Status(); e != 0 {
		d.err = uint(e) << 8
		return 0
	}
	return w
}

func (d *Driver) setupDMA(ch *dma.Channel, mode dma.Mode, wordSize uintptr) {
	ch.Setup(mode)
	ch.SetWordSize(wordSize, wordSize)
	ch.SetAddrP(unsafe.Pointer(d.P.raw.DR.U16.Addr()))
}

func startDMA(ch *dma.Channel, addr uintptr, n int) {
	ch.SetAddrM(unsafe.Pointer(addr))
	ch.SetLen(n)
	ch.Clear(dma.EvAll, dma.ErrAll)
	ch.EnableIRQ(dma.Complete, dma.ErrAll&^dma.ErrFIFO)
	fence.W() // This orders writes to normal and I/O memory.
	ch.Enable()
}

func (d *Driver) writeReadDMA(out, in uintptr, olen, ilen int, wsize uintptr) (n int) {
	txdmacfg := dma.MTP | dma.FIFO_4_4
	if olen > 1 {
		txdmacfg |= dma.IncM
	}
	d.setupDMA(d.TxDMA, txdmacfg, 1)
	d.setupDMA(d.RxDMA, dma.PTM|dma.IncM|dma.FIFO_1_4, wsize)
	d.P.SetDuplex(Full)
	d.P.EnableDMA(RxNotEmpty | TxEmpty)
	d.P.EnableIRQ(Err)
	for {
		m := ilen - n
		if m == 0 {
			break
		}
		if m > 0xffff {
			m = 0xffff
		}
		d.dmacnt = 2
		d.done.Reset(0)
		startDMA(d.RxDMA, in, m)
		startDMA(d.TxDMA, out, m)
		if olen > 1 {
			out += uintptr(m)
		}
		in += uintptr(m)
		n += m
		done := d.done.Wait(1, d.deadline)
		d.TxDMA.Disable() // Required by STM32F1 to allow setup next transfer.
		d.RxDMA.Disable() // Required by STM32F1 to allow setup next transfer.
		if !done {
			d.TxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.RxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.err = uint(ErrTimeout) << 16
			n -= d.RxDMA.Len()
			break
		}
		if _, e := d.P.Status(); e != 0 {
			d.err = uint(e) << 8
			n -= d.RxDMA.Len()
			break
		}
		_, rxe := d.RxDMA.Status()
		_, txe := d.TxDMA.Status()
		if e := (rxe | txe) &^ dma.ErrFIFO; e != 0 {
			d.err = uint(e)
			n -= d.RxDMA.Len()
			break
		}
	}
	d.P.DisableDMA(RxNotEmpty | TxEmpty)
	d.P.DisableIRQ(Err)
	return
}

func (d *Driver) writeDMA(out uintptr, n int, wsize uintptr, incm dma.Mode) {
	d.setupDMA(d.TxDMA, dma.MTP|incm|dma.FIFO_4_4, wsize)
	d.P.SetDuplex(HalfOut) // Avoid ErrOverflow.
	d.P.EnableDMA(TxEmpty)
	d.P.EnableIRQ(Err)
	for n > 0 {
		m := n
		if m > 0xffff {
			m = 0xffff
		}
		d.dmacnt = 1
		d.done.Reset(0)
		startDMA(d.TxDMA, out, m)
		n -= m
		if incm != 0 {
			out += uintptr(m)
		}
		done := d.done.Wait(1, d.deadline)
		d.TxDMA.Disable() // Required by STM32F1 to allow setup next transfer
		if !done {
			d.TxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.err = uint(ErrTimeout) << 16
			break
		}
		if _, e := d.P.Status(); e != 0 {
			d.err = uint(e) << 8
			break
		}
		_, txe := d.TxDMA.Status()
		if e := txe &^ dma.ErrFIFO; e != 0 {
			d.err = uint(e)
			break
		}
	}
	d.P.DisableDMA(TxEmpty)
	d.P.DisableIRQ(Err)
}

// Err returns the first error that was encountered by the Driver. It also
// clears internal error flags so subsequent Err call returns nil or next error.
func (d *Driver) Err() error {
	e := d.err
	if e == 0 {
		return nil
	}
	d.err = 0
	if err := DriverError(e >> 16); err != 0 {
		return err
	}
	if err := Error(e >> 8); err != 0 {
		if err&ErrOverrun != 0 {
			d.P.LoadByte()
			d.P.Status()
		}
		return err
	}
	return dma.Error(e)
}

func (d *Driver) writeRead(oaddr, iaddr uintptr, olen, ilen int, wsize uintptr) int {
	if olen > ilen {
		var n int
		if ilen > 0 {
			n = d.writeReadDMA(oaddr, iaddr, ilen, ilen, wsize)
			if d.err != 0 {
				return n
			}
			olen -= ilen
			oaddr += uintptr(ilen)
		}
		d.writeDMA(oaddr, olen, wsize, dma.IncM)
		return n
	}
	if ilen > olen {
		var n int
		ffff := uint16(0xffff)
		if olen > 0 {
			n = d.writeReadDMA(oaddr, iaddr, olen, olen, wsize)
			if d.err != 0 {
				return n
			}
			ilen -= olen
			iaddr += uintptr(olen)
			oaddr += uintptr(olen - 1)
		} else {
			oaddr = uintptr(unsafe.Pointer(&ffff))
		}
		return n + d.writeReadDMA(oaddr, iaddr, 1, ilen, wsize)
	}
	return d.writeReadDMA(oaddr, iaddr, ilen, ilen, wsize)
}

func (d *Driver) WriteStringRead(out string, in []byte) int {
	olen := len(out)
	ilen := len(in)
	if d.err != 0 || olen == 0 && ilen == 0 {
		return 0
	}
	if olen <= 1 && ilen <= 1 {
		// Avoid DMA for one byte transfers.
		b := byte(0xff)
		if olen != 0 {
			b = out[0]
		}
		b = d.WriteReadByte(b)
		if ilen != 0 {
			in[0] = b
			return 1
		}
		return 0
	}
	oaddr := (*reflect.StringHeader)(unsafe.Pointer(&out)).Data
	iaddr := (*reflect.SliceHeader)(unsafe.Pointer(&in)).Data
	return d.writeRead(oaddr, iaddr, olen, ilen, 1)
}

func (d *Driver) WriteRead(out, in []byte) int {
	return d.WriteStringRead(*(*string)(unsafe.Pointer(&out)), in)
}

func (d *Driver) WriteReadMany(oi ...[]byte) int {
	var n int
	for k := 0; k < len(oi); k += 2 {
		var in []byte
		if k+1 < len(oi) {
			in = oi[k+1]
		}
		out := oi[k]
		n += d.WriteRead(out, in)
	}
	return n
}

func (d *Driver) RepeatByte(b byte, n int) {
	if d.err != 0 {
		return
	}
	switch {
	case n > 1:
		d.writeDMA(uintptr(unsafe.Pointer(&b)), n, 1, 0)
	case n == 1:
		// Avoid DMA for one byte transfers.
		d.WriteReadByte(b)
	}
}

func (d *Driver) WriteRead16(out, in []uint16) int {
	olen := len(out)
	ilen := len(in)
	if d.err != 0 || olen == 0 && ilen == 0 {
		return 0
	}
	if olen <= 1 && ilen <= 1 {
		// Avoid DMA for one word transfers.
		w := uint16(0xffff)
		if olen != 0 {
			w = out[0]
		}
		w = d.WriteReadWord16(w)
		if ilen != 0 {
			in[0] = w
			return 1
		}
		return 0
	}
	oaddr := (*reflect.SliceHeader)(unsafe.Pointer(&out)).Data
	iaddr := (*reflect.SliceHeader)(unsafe.Pointer(&in)).Data
	return d.writeRead(oaddr, iaddr, olen, ilen, 2)
}

func (d *Driver) WriteReadMany16(oi ...[]uint16) int {
	var n int
	for k := 0; k < len(oi); k += 2 {
		var in []uint16
		if k+1 < len(oi) {
			in = oi[k+1]
		}
		out := oi[k]
		n += d.WriteRead16(out, in)
	}
	return n
}

func (d *Driver) RepeatWord16(w uint16, n int) {
	if d.err != 0 {
		return
	}
	switch {
	case n > 1:
		d.writeDMA(uintptr(unsafe.Pointer(&w)), n, 2, 0)
	case n == 1:
		// Avoid DMA for one word transfers.
		d.WriteReadWord16(w)
	}
}
<commit_msg>stm32/hal/spi: Small improvements.<commit_after>package spi

import (
	"reflect"
	"rtos"
	"sync/atomic"
	"sync/fence"
	"unsafe"

	"stm32/hal/dma"
)

type DriverError byte

const ErrTimeout DriverError = 1

func (e DriverError) Error() string {
	switch e {
	case ErrTimeout:
		return "timeout"
	default:
		return ""
	}
}

type Driver struct {
	deadline int64

	P     *Periph
	RxDMA *dma.Channel
	TxDMA *dma.Channel

	dmacnt int
	done   rtos.EventFlag
	err    uint
}

// NewDriver provides convenient way to create heap allocated Driver struct.
func NewDriver(p *Periph, rxdma, txdma *dma.Channel) *Driver {
	d := new(Driver)
	d.P = p
	d.RxDMA = rxdma
	d.TxDMA = txdma
	return d
}

func (d *Driver) DMAISR(ch *dma.Channel) {
	ch.DisableIRQ(dma.EvAll, dma.ErrAll)
	_, e := ch.Status()
	if e&^dma.ErrFIFO != 0 || atomic.AddInt(&d.dmacnt, -1) == 0 {
		d.done.Signal(1)
	}
}

func (d *Driver) ISR() {
	d.P.DisableIRQ(RxNotEmpty | Err)
	d.done.Signal(1)
}

func (d *Driver) SetDeadline(deadline int64) {
	d.deadline = deadline
}

// WriteReadByte writes and reads byte.
func (d *Driver) WriteReadByte(b byte) byte {
	if d.err != 0 {
		return 0
	}
	p := d.P
	p.SetDuplex(Full)
	d.done.Reset(0)
	p.EnableIRQ(RxNotEmpty | Err)
	fence.W() // This orders writes to normal and I/O memory.
	p.StoreByte(b)
	if !d.done.Wait(1, d.deadline) {
		d.err = uint(ErrTimeout) << 16
		return 0
	}
	b = p.LoadByte()
	if _, e := p.Status(); e != 0 {
		d.err = uint(e) << 8
		return 0
	}
	return b
}

// WriteReadWord16 writes and reads 16-bit word.
func (d *Driver) WriteReadWord16(w uint16) uint16 {
	if d.err != 0 {
		return 0
	}
	p := d.P
	p.SetDuplex(Full)
	d.done.Reset(0)
	p.EnableIRQ(RxNotEmpty | Err)
	fence.W() // This orders writes to normal and I/O memory.
	p.StoreWord16(w)
	if !d.done.Wait(1, d.deadline) {
		d.err = uint(ErrTimeout) << 16
		return 0
	}
	w = p.LoadWord16()
	if _, e := p.Status(); e != 0 {
		d.err = uint(e) << 8
		return 0
	}
	return w
}

func (d *Driver) setupDMA(ch *dma.Channel, mode dma.Mode, wordSize uintptr) {
	ch.Setup(mode)
	ch.SetWordSize(wordSize, wordSize)
	ch.SetAddrP(unsafe.Pointer(d.P.raw.DR.U16.Addr()))
}

func startDMA(ch *dma.Channel, addr uintptr, n int) {
	ch.SetAddrM(unsafe.Pointer(addr))
	ch.SetLen(n)
	ch.Clear(dma.EvAll, dma.ErrAll)
	ch.EnableIRQ(dma.Complete, dma.ErrAll&^dma.ErrFIFO)
	fence.W() // This orders writes to normal and I/O memory.
	ch.Enable()
}

func (d *Driver) writeReadDMA(out, in uintptr, olen, ilen int, wsize uintptr) (n int) {
	txdmacfg := dma.MTP | dma.FIFO_4_4
	if olen > 1 {
		txdmacfg |= dma.IncM
	}
	d.setupDMA(d.TxDMA, txdmacfg, 1)
	d.setupDMA(d.RxDMA, dma.PTM|dma.IncM|dma.FIFO_1_4, wsize)
	p := d.P
	p.SetDuplex(Full)
	p.EnableDMA(RxNotEmpty | TxEmpty)
	p.EnableIRQ(Err)
	for {
		m := ilen - n
		if m == 0 {
			break
		}
		if m > 0xffff {
			m = 0xffff
		}
		d.dmacnt = 2
		d.done.Reset(0)
		startDMA(d.RxDMA, in, m)
		startDMA(d.TxDMA, out, m)
		if olen > 1 {
			out += uintptr(m)
		}
		in += uintptr(m)
		n += m
		done := d.done.Wait(1, d.deadline)
		d.TxDMA.Disable() // Required by STM32F1 to allow setup next transfer.
		d.RxDMA.Disable() // Required by STM32F1 to allow setup next transfer.
		if !done {
			d.TxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.RxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.err = uint(ErrTimeout) << 16
			n -= d.RxDMA.Len()
			break
		}
		if _, e := p.Status(); e != 0 {
			d.TxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.RxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.err = uint(e) << 8
			n -= d.RxDMA.Len()
			break
		}
		_, rxe := d.RxDMA.Status()
		_, txe := d.TxDMA.Status()
		if e := (rxe | txe) &^ dma.ErrFIFO; e != 0 {
			d.err = uint(e)
			n -= d.RxDMA.Len()
			break
		}
	}
	p.DisableDMA(RxNotEmpty | TxEmpty)
	p.DisableIRQ(Err)
	return
}

func (d *Driver) writeDMA(out uintptr, n int, wsize uintptr, incm dma.Mode) {
	d.setupDMA(d.TxDMA, dma.MTP|incm|dma.FIFO_4_4, wsize)
	p := d.P
	p.SetDuplex(HalfOut) // Avoid ErrOverflow.
	p.EnableDMA(TxEmpty)
	p.EnableIRQ(Err)
	for n > 0 {
		m := n
		if m > 0xffff {
			m = 0xffff
		}
		d.dmacnt = 1
		d.done.Reset(0)
		startDMA(d.TxDMA, out, m)
		n -= m
		if incm != 0 {
			out += uintptr(m)
		}
		done := d.done.Wait(1, d.deadline)
		d.TxDMA.Disable() // Required by STM32F1 to allow setup next transfer
		if !done {
			d.TxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.err = uint(ErrTimeout) << 16
			break
		}
		if _, e := p.Status(); e != 0 {
			d.TxDMA.DisableIRQ(dma.EvAll, dma.ErrAll)
			d.err = uint(e) << 8
			break
		}
		_, txe := d.TxDMA.Status()
		if e := txe &^ dma.ErrFIFO; e != 0 {
			d.err = uint(e)
			break
		}
	}
	p.DisableDMA(TxEmpty)
	p.DisableIRQ(Err)
}

// Err returns the first error that was encountered by the Driver. It also
// clears internal error flags so subsequent Err call returns nil or next error.
func (d *Driver) Err() error {
	e := d.err
	if e == 0 {
		return nil
	}
	d.err = 0
	if err := DriverError(e >> 16); err != 0 {
		return err
	}
	if err := Error(e >> 8); err != 0 {
		if err&ErrOverrun != 0 {
			d.P.LoadByte()
			d.P.Status()
		}
		return err
	}
	return dma.Error(e)
}

func (d *Driver) writeRead(oaddr, iaddr uintptr, olen, ilen int, wsize uintptr) int {
	if olen > ilen {
		var n int
		if ilen > 0 {
			n = d.writeReadDMA(oaddr, iaddr, ilen, ilen, wsize)
			if d.err != 0 {
				return n
			}
			olen -= ilen
			oaddr += uintptr(ilen)
		}
		d.writeDMA(oaddr, olen, wsize, dma.IncM)
		return n
	}
	if ilen > olen {
		var n int
		ffff := uint16(0xffff)
		if olen > 0 {
			n = d.writeReadDMA(oaddr, iaddr, olen, olen, wsize)
			if d.err != 0 {
				return n
			}
			ilen -= olen
			iaddr += uintptr(olen)
			oaddr += uintptr(olen - 1)
		} else {
			oaddr = uintptr(unsafe.Pointer(&ffff))
		}
		return n + d.writeReadDMA(oaddr, iaddr, 1, ilen, wsize)
	}
	return d.writeReadDMA(oaddr, iaddr, ilen, ilen, wsize)
}

func (d *Driver) WriteStringRead(out string, in []byte) int {
	olen := len(out)
	ilen := len(in)
	if d.err != 0 || olen == 0 && ilen == 0 {
		return 0
	}
	if olen <= 1 && ilen <= 1 {
		// Avoid DMA for one byte transfers.
		b := byte(0xff)
		if olen != 0 {
			b = out[0]
		}
		b = d.WriteReadByte(b)
		if ilen != 0 {
			in[0] = b
			return 1
		}
		return 0
	}
	oaddr := (*reflect.StringHeader)(unsafe.Pointer(&out)).Data
	iaddr := (*reflect.SliceHeader)(unsafe.Pointer(&in)).Data
	return d.writeRead(oaddr, iaddr, olen, ilen, 1)
}

func (d *Driver) WriteRead(out, in []byte) int {
	return d.WriteStringRead(*(*string)(unsafe.Pointer(&out)), in)
}

func (d *Driver) WriteReadMany(oi ...[]byte) int {
	var n int
	for k := 0; k < len(oi); k += 2 {
		var in []byte
		if k+1 < len(oi) {
			in = oi[k+1]
		}
		out := oi[k]
		n += d.WriteRead(out, in)
	}
	return n
}

func (d *Driver) RepeatByte(b byte, n int) {
	if d.err != 0 {
		return
	}
	switch {
	case n > 1:
		d.writeDMA(uintptr(unsafe.Pointer(&b)), n, 1, 0)
	case n == 1:
		// Avoid DMA for one byte transfers.
		d.WriteReadByte(b)
	}
}

func (d *Driver) WriteRead16(out, in []uint16) int {
	olen := len(out)
	ilen := len(in)
	if d.err != 0 || olen == 0 && ilen == 0 {
		return 0
	}
	if olen <= 1 && ilen <= 1 {
		// Avoid DMA for one word transfers.
		w := uint16(0xffff)
		if olen != 0 {
			w = out[0]
		}
		w = d.WriteReadWord16(w)
		if ilen != 0 {
			in[0] = w
			return 1
		}
		return 0
	}
	oaddr := (*reflect.SliceHeader)(unsafe.Pointer(&out)).Data
	iaddr := (*reflect.SliceHeader)(unsafe.Pointer(&in)).Data
	return d.writeRead(oaddr, iaddr, olen, ilen, 2)
}

func (d *Driver) WriteReadMany16(oi ...[]uint16) int {
	var n int
	for k := 0; k < len(oi); k += 2 {
		var in []uint16
		if k+1 < len(oi) {
			in = oi[k+1]
		}
		out := oi[k]
		n += d.WriteRead16(out, in)
	}
	return n
}

func (d *Driver) RepeatWord16(w uint16, n int) {
	if d.err != 0 {
		return
	}
	switch {
	case n > 1:
		d.writeDMA(uintptr(unsafe.Pointer(&w)), n, 2, 0)
	case n == 1:
		// Avoid DMA for one word transfers.
		d.WriteReadWord16(w)
	}
}
<|endoftext|>


<commit_before>package ozcld

import (
	"strings"
)

// Dot 形式の文字列に変換できることを表すインターフェース
type Dot interface {
	// Dot 形式の文字列を返却する
	ToDot() string
}

// フィールド
type Field struct {
	field string
}

// フィールドを作成する
func CreateFieldFromString(def string) Field {
	return Field{def}
}

// Dot 形式の文字列を返却する
func (this Field) ToDot() string {
	return this.field + "\\l"
}

// フィールドリスト
type Fields struct {
	fields []Field
}

// フィールドリストを作成する
func CreateFieldsFromStrings(defs []string) Fields {
	// 必要な長さのスライスを作成
	fields := make([]Field, len(defs))

	// スライスにフィールド定義を格納
	for i, v := range defs {
		fields[i] = CreateFieldFromString(v)
	}

	// Fields 返却
	return Fields{fields}
}

// Dot 形式の文字列を返却する
func (this Fields) ToDot() string {
	// 必要な長さのスライスを作成
	defs := make([]string, len(this.fields))

	// スライスにフィールド定義を格納
	for i, v := range this.fields {
		defs[i] = v.ToDot()
	}

	// Fields 返却
	return strings.Join(defs, "")
}

// メソッド
type Method struct {
	method string
}

// メソッドを作成する
func CreateMethodFromString(def string) Method {
	return Method{def}
}

// Dot 形式の文字列を返却する
func (this Method) ToDot() string {
	return this.method + "\\l"
}

// メソッドリスト
type Methods struct {
	methods []Method
}

// メソッドリストを作成する
func CreateMethodsFromStrings(defs []string) Methods {
	// 必要な長さのスライスを作成
	methods := make([]Method, len(defs))

	// スライスにフィールド定義を格納
	for i, v := range defs {
		methods[i] = CreateMethodFromString(v)
	}

	// Methods 返却
	return Methods{methods}
}

// Dot 形式の文字列を返却する
func (this Methods) ToDot() string {
	// 必要な長さのスライスを作成
	defs := make([]string, len(this.methods))

	// スライスにフィールド定義を格納
	for i, v := range this.methods {
		defs[i] = v.ToDot()
	}

	// Fields 返却
	return strings.Join(defs, "")
}

// クラス
type Class struct {
	stereotype string
	name       string
	fields     Fields
	methods    Methods
}

// 文字列からクラスを作成する
func CreateClassFromDefs(stereotype string, name string, fieldDefs []string, methodDefs []string) Class {
	fields := CreateFieldsFromStrings(fieldDefs)
	methods := CreateMethodsFromStrings(methodDefs)
	return Class{stereotype, name, fields, methods}
}

// Dot 形式の文字列を返却する
func (this Class) ToDot() string {
	// 必要な長さのスライスを作成
	defs := []string{this.name, " [label = \"{"}

	if this.stereotype != "" {
		defs = append(defs, "\\<\\<", this.stereotype, "\\>\\>\\n")
	}

	defs = append(defs, this.name, "|", this.fields.ToDot(), "|", this.methods.ToDot(), "}\"];")

	// 文字列返却
	return strings.Join(defs, "")
}

// 名前空間(パッケージ)
type Namespace struct {
	name    string
	classes []Class
}

// Namespace を作成する
func CreateNamespace(name string, classes []Class) Namespace {
	return Namespace{name, classes}
}

// Dot 形式の文字列を返却する
func (this Namespace) ToDot() string {
	defs := []string{"subgraph cluster_" + this.name + " {"}

	defs = append(defs, "label = \""+this.name+"\";")

	for _, v := range this.classes {
		defs = append(defs, v.ToDot())
	}

	defs = append(defs, "}")

	return strings.Join(defs, "\n")
}

type RelationType int

// 関係
type Relation struct {
	name             string
	relationType     RelationType
	fromClassName    string
	toClassName      string
	fromMultiplicity string
	toMultiplicity   string
}

// Relation を作成する
func CreateRelation(name string, relationType RelationType, fromClassName string, toClassName string, fromMultiplicity string, toMultiplicity string) Relation {
	return Relation{name, relationType, fromClassName, toClassName, fromMultiplicity, toMultiplicity}
}

// 関係の種類
const (
	RELATION_NORMAL RelationType = iota
	RELATION_INHERIT
	RELATION_IMPLEMENT
	RELATION_AGGREGATION
	RELATION_COMPOSITION
)

func (this Relation) getEdgeStyles() (style string, arrowhead string) {

	if this.relationType == RELATION_INHERIT {
		style = "solid"
		arrowhead = "onormal"
	} else if this.relationType == RELATION_IMPLEMENT {
		style = "dashed"
		arrowhead = "onormal"
	} else if this.relationType == RELATION_AGGREGATION {
		style = "solid"
		arrowhead = ""
	} else if this.relationType == RELATION_COMPOSITION {
		style = "solid"
		arrowhead = ""
	}

	return style, arrowhead
}

// Dot 形式の文字列を返却する
func (this Relation) ToDot() string {

	style, arrowhead := this.getEdgeStyles()

	// 基本
	base := []string{"edge [style = \"" + style + "\", arrowhead = \"" + arrowhead + "\"];\n"}
	base = append(base, this.fromClassName+" -> "+this.toClassName)

	// 詳細
	detail := []string{}

	// 関係名名前
	if this.name != "" {
		detail = append(detail, "label = \""+this.name+"\"")
	}

	// FROM の処理
	if this.fromMultiplicity != "" {
		detail = append(detail, "taillabel = \""+this.fromMultiplicity+"\"")
	}

	// TO の処理
	if this.toMultiplicity != "" {
		detail = append(detail, "headlabel = \""+this.toMultiplicity+"\"")
	}

	var detailString string
	if len(detail) != 0 {
		detailString = "[" + strings.Join(detail, ",") + "];"
	} else {
		detailString = ""
	}

	return strings.Join(base, "") + detailString
}

// クラス図
type ClassDiagram struct {
	name       string
	namespaces []Namespace
	classes    []Class
	relations  []Relation
}

// クラス図作成
func CreateClassDiagram(name string, namespaces []Namespace, classes []Class, relations []Relation) ClassDiagram {
	return ClassDiagram{name, namespaces, classes, relations}
}

// Dot 形式の文字列を返却する
func (this ClassDiagram) ToDot() string {

	defs := []string{"digraph " + this.name + " {\nnode [shape = record];\n"}

	for _, v := range this.namespaces {
		defs = append(defs, v.ToDot())
	}

	for _, v := range this.classes {
		defs = append(defs, v.ToDot())
	}

	for _, v := range this.relations {
		defs = append(defs, v.ToDot())
	}

	defs = append(defs, "}")

	return strings.Join(defs, "\n")
}
<commit_msg>Modified method's receiver, object to pointer.<commit_after>package ozcld

import (
	"strings"
)

// Dot 形式の文字列に変換できることを表すインターフェース
type Dot interface {
	// Dot 形式の文字列を返却する
	ToDot() string
}

// フィールド
type Field struct {
	field string
}

// フィールドを作成する
func CreateFieldFromString(def string) Field {
	return Field{def}
}

// Dot 形式の文字列を返却する
func (this *Field) ToDot() string {
	return this.field + "\\l"
}

// フィールドリスト
type Fields struct {
	fields []Field
}

// フィールドリストを作成する
func CreateFieldsFromStrings(defs []string) Fields {
	// 必要な長さのスライスを作成
	fields := make([]Field, len(defs))

	// スライスにフィールド定義を格納
	for i, v := range defs {
		fields[i] = CreateFieldFromString(v)
	}

	// Fields 返却
	return Fields{fields}
}

// Dot 形式の文字列を返却する
func (this *Fields) ToDot() string {
	// 必要な長さのスライスを作成
	defs := make([]string, len(this.fields))

	// スライスにフィールド定義を格納
	for i, v := range this.fields {
		defs[i] = v.ToDot()
	}

	// Fields 返却
	return strings.Join(defs, "")
}

// メソッド
type Method struct {
	method string
}

// メソッドを作成する
func CreateMethodFromString(def string) Method {
	return Method{def}
}

// Dot 形式の文字列を返却する
func (this *Method) ToDot() string {
	return this.method + "\\l"
}

// メソッドリスト
type Methods struct {
	methods []Method
}

// メソッドリストを作成する
func CreateMethodsFromStrings(defs []string) Methods {
	// 必要な長さのスライスを作成
	methods := make([]Method, len(defs))

	// スライスにフィールド定義を格納
	for i, v := range defs {
		methods[i] = CreateMethodFromString(v)
	}

	// Methods 返却
	return Methods{methods}
}

// Dot 形式の文字列を返却する
func (this *Methods) ToDot() string {
	// 必要な長さのスライスを作成
	defs := make([]string, len(this.methods))

	// スライスにフィールド定義を格納
	for i, v := range this.methods {
		defs[i] = v.ToDot()
	}

	// Fields 返却
	return strings.Join(defs, "")
}

// クラス
type Class struct {
	stereotype string
	name       string
	fields     Fields
	methods    Methods
}

// 文字列からクラスを作成する
func CreateClassFromDefs(stereotype string, name string, fieldDefs []string, methodDefs []string) Class {
	fields := CreateFieldsFromStrings(fieldDefs)
	methods := CreateMethodsFromStrings(methodDefs)
	return Class{stereotype, name, fields, methods}
}

// Dot 形式の文字列を返却する
func (this *Class) ToDot() string {
	// 必要な長さのスライスを作成
	defs := []string{this.name, " [label = \"{"}

	if this.stereotype != "" {
		defs = append(defs, "\\<\\<", this.stereotype, "\\>\\>\\n")
	}

	defs = append(defs, this.name, "|", this.fields.ToDot(), "|", this.methods.ToDot(), "}\"];")

	// 文字列返却
	return strings.Join(defs, "")
}

// 名前空間(パッケージ)
type Namespace struct {
	name    string
	classes []Class
}

// Namespace を作成する
func CreateNamespace(name string, classes []Class) Namespace {
	return Namespace{name, classes}
}

// Dot 形式の文字列を返却する
func (this *Namespace) ToDot() string {
	defs := []string{"subgraph cluster_" + this.name + " {"}

	defs = append(defs, "label = \""+this.name+"\";")

	for _, v := range this.classes {
		defs = append(defs, v.ToDot())
	}

	defs = append(defs, "}")

	return strings.Join(defs, "\n")
}

type RelationType int

// 関係
type Relation struct {
	name             string
	relationType     RelationType
	fromClassName    string
	toClassName      string
	fromMultiplicity string
	toMultiplicity   string
}

// Relation を作成する
func CreateRelation(name string, relationType RelationType, fromClassName string, toClassName string, fromMultiplicity string, toMultiplicity string) Relation {
	return Relation{name, relationType, fromClassName, toClassName, fromMultiplicity, toMultiplicity}
}

// 関係の種類
const (
	RELATION_NORMAL RelationType = iota
	RELATION_INHERIT
	RELATION_IMPLEMENT
	RELATION_AGGREGATION
	RELATION_COMPOSITION
)

func (this *Relation) getEdgeStyles() (style string, arrowhead string) {

	if this.relationType == RELATION_INHERIT {
		style = "solid"
		arrowhead = "onormal"
	} else if this.relationType == RELATION_IMPLEMENT {
		style = "dashed"
		arrowhead = "onormal"
	} else if this.relationType == RELATION_AGGREGATION {
		style = "solid"
		arrowhead = ""
	} else if this.relationType == RELATION_COMPOSITION {
		style = "solid"
		arrowhead = ""
	}

	return style, arrowhead
}

// Dot 形式の文字列を返却する
func (this *Relation) ToDot() string {

	style, arrowhead := this.getEdgeStyles()

	// 基本
	base := []string{"edge [style = \"" + style + "\", arrowhead = \"" + arrowhead + "\"];\n"}
	base = append(base, this.fromClassName+" -> "+this.toClassName)

	// 詳細
	detail := []string{}

	// 関係名名前
	if this.name != "" {
		detail = append(detail, "label = \""+this.name+"\"")
	}

	// FROM の処理
	if this.fromMultiplicity != "" {
		detail = append(detail, "taillabel = \""+this.fromMultiplicity+"\"")
	}

	// TO の処理
	if this.toMultiplicity != "" {
		detail = append(detail, "headlabel = \""+this.toMultiplicity+"\"")
	}

	var detailString string
	if len(detail) != 0 {
		detailString = "[" + strings.Join(detail, ",") + "];"
	} else {
		detailString = ""
	}

	return strings.Join(base, "") + detailString
}

// クラス図
type ClassDiagram struct {
	name       string
	namespaces []Namespace
	classes    []Class
	relations  []Relation
}

// クラス図作成
func CreateClassDiagram(name string, namespaces []Namespace, classes []Class, relations []Relation) ClassDiagram {
	return ClassDiagram{name, namespaces, classes, relations}
}

// Dot 形式の文字列を返却する
func (this *ClassDiagram) ToDot() string {

	defs := []string{"digraph " + this.name + " {\nnode [shape = record];\n"}

	for _, v := range this.namespaces {
		defs = append(defs, v.ToDot())
	}

	for _, v := range this.classes {
		defs = append(defs, v.ToDot())
	}

	for _, v := range this.relations {
		defs = append(defs, v.ToDot())
	}

	defs = append(defs, "}")

	return strings.Join(defs, "\n")
}
<|endoftext|>


<commit_before>package smart

import (
	"bitbucket.org/sinbad/git-lob/util"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// factory for creating SSH connections
type SshTransportFactory struct {
}

// Standardise bare URLs of the form user@host.com:path/to/repo
func (*SshTransportFactory) cleanupBareUrl(u *url.URL) *url.URL {
	// Support ssh://user@host.com/path/to/repo and user@host.com:path/to/repo
	// The latter is entirely stored in the 'Path' field of url.URL though, so prefix
	if u.Scheme == "" && u.Path != "" {
		// Replace the path separator colon with /
		// Remember custom ports can include : too user@host.com:999:path/to/repo, must preserve
		parts := strings.Split(u.Path, ":")
		var newPath string
		if len(parts) > 2 { // port included; really should only ever be 3 parts
			newPath = fmt.Sprintf("%v:%v", parts[0], strings.Join(parts[1:], "/"))
		} else {
			newPath = strings.Join(parts, "/")
		}
		newUrlStr := fmt.Sprintf("ssh://%v", newPath)
		newu, err := url.Parse(newUrlStr)
		if err == nil {
			return newu
		}
	}
	return u
}

// Pull out the host & port for use on the command line from an already cleaned URL
func (*SshTransportFactory) getHostAndPort(cleanedUrl *url.URL) (host, port string) {
	// Host includes host & port when custom ports are used
	// Note, not trying to validate host here, simple approach (this simple regex supports non-FQ and IP addresses as a bonus)
	// this would allow non-RFC compliant domain names but we don't care
	regex := regexp.MustCompile(`^([^\:]+)(?:\:(\d+))?$`)
	host = ""
	port = ""
	if match := regex.FindStringSubmatch(cleanedUrl.Host); match != nil {
		host = match[1]
		if len(match) > 2 {
			port = match[2]
		}
	}
	return
}

func (self *SshTransportFactory) WillHandleUrl(u *url.URL) bool {
	if u.Scheme == "ssh" {
		return true
	}

	// try cleaning a bare URL user@host.com:something/something
	newu := self.cleanupBareUrl(u)
	return newu.Scheme == "ssh"
}
func (self *SshTransportFactory) Connect(u *url.URL) (Transport, error) {
	ssh := os.Getenv("GIT_SSH")
	isPlink := strings.EqualFold(filepath.Base(ssh), "plink")
	isTortoise := strings.EqualFold(filepath.Base(ssh), "tortoiseplink")
	if ssh == "" {
		ssh = "ssh"
	}
	// Clean up bare git@blah.com:port:path styles
	// we want to identify host & port, easiest to pull out of URL than parsing ourselves
	urlCleaned := self.cleanupBareUrl(u)
	// Cleaned URLs always have an ssh scheme
	if urlCleaned.Scheme != "ssh" {
		return nil, fmt.Errorf("%v is not a valid SSH URL", u.String())
	}
	host, port := self.getHostAndPort(urlCleaned)
	if host == "" {
		return nil, fmt.Errorf("No valid host found in url %v", u.String())
	}

	// Let's invoke ssh
	args := make([]string, 0, 2)
	if isTortoise {
		// TortoisePlink requires the -batch argument to behave like ssh/plink
		args = append(args, "-batch")
	}
	if port != "" {
		if isPlink {
			args = append(args, "-P")
		} else {
			args = append(args, "-p")
		}
		args = append(args, port)
	}
	args = append(args, host)

	// Now add remote program and path
	args = append(args, util.GlobalOptions.SSHServerCommand)
	// u.Path includes a preceding '/', strip off manually
	// rooted paths in the URL will be '//path/to/blah'
	// this is just how Go's URL parsing works
	path := u.Path
	if len(path) > 0 && strings.HasPrefix(path, "/") {
		path = path[1:]
	}
	args = append(args, path)
	cmd := exec.Command(ssh, args...)

	outp, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("Unable to connect to ssh stdout: %v", err.Error())
	}
	errp, err := cmd.StderrPipe()
	if err != nil {
		return nil, fmt.Errorf("Unable to connect to ssh stderr: %v", err.Error())
	}
	inp, err := cmd.StdinPipe()
	if err != nil {
		return nil, fmt.Errorf("Unable to connect to ssh stdin: %v", err.Error())
	}
	err = cmd.Start()
	if err != nil {
		return nil, fmt.Errorf("Unable to start ssh command: %v", err.Error())
	}

	conn := &SshConnection{
		cmd:    cmd,
		stdin:  inp,
		stdout: outp,
		stderr: errp,
	}

	return NewPersistentTransport(conn), nil

}

func RegisterSshTransportFactory() {
	RegisterTransportFactory(&SshTransportFactory{})
}

// Underlying SSH connection to smart server, for use with PersistentTransport
// Works by invoking ssh/plink/tortoise_plink and connecting stdout/stdin
type SshConnection struct {
	// The command which is running ssh
	cmd *exec.Cmd
	// Streams for communicating
	stdin  io.WriteCloser
	stdout io.ReadCloser
	stderr io.ReadCloser
}

// SSH Connection implementation
func (self *SshConnection) Read(p []byte) (n int, err error) {
	return self.stdout.Read(p)
}
func (self *SshConnection) Write(p []byte) (n int, err error) {
	return self.stdin.Write(p)
}
func (self *SshConnection) Close() error {
	// Docs say "It is incorrect to call Wait before all writes to the pipe have completed."
	// But that actually means in parallel https://github.com/golang/go/issues/9307 so we're ok here
	err := self.cmd.Wait()
	if err != nil {
		errbytes, readerr := ioutil.ReadAll(self.stderr)
		if readerr != nil {
			return fmt.Errorf("Error closing ssh connection: %v", err.Error())
		} else {
			return fmt.Errorf("Error closing ssh connection: %v\nstderr: %v", err.Error(), string(errbytes))
		}
	}

	return nil

}
<commit_msg>Add some debugging to SSH<commit_after>package smart

import (
	"bitbucket.org/sinbad/git-lob/util"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// factory for creating SSH connections
type SshTransportFactory struct {
}

// Standardise bare URLs of the form user@host.com:path/to/repo
func (*SshTransportFactory) cleanupBareUrl(u *url.URL) *url.URL {
	// Support ssh://user@host.com/path/to/repo and user@host.com:path/to/repo
	// The latter is entirely stored in the 'Path' field of url.URL though, so prefix
	if u.Scheme == "" && u.Path != "" {
		// Replace the path separator colon with /
		// Remember custom ports can include : too user@host.com:999:path/to/repo, must preserve
		parts := strings.Split(u.Path, ":")
		var newPath string
		if len(parts) > 2 { // port included; really should only ever be 3 parts
			newPath = fmt.Sprintf("%v:%v", parts[0], strings.Join(parts[1:], "/"))
		} else {
			newPath = strings.Join(parts, "/")
		}
		newUrlStr := fmt.Sprintf("ssh://%v", newPath)
		newu, err := url.Parse(newUrlStr)
		if err == nil {
			return newu
		}
	}
	return u
}

// Pull out the host & port for use on the command line from an already cleaned URL
func (*SshTransportFactory) getHostAndPort(cleanedUrl *url.URL) (host, port string) {
	// Host includes host & port when custom ports are used
	// Note, not trying to validate host here, simple approach (this simple regex supports non-FQ and IP addresses as a bonus)
	// this would allow non-RFC compliant domain names but we don't care
	regex := regexp.MustCompile(`^([^\:]+)(?:\:(\d+))?$`)
	host = ""
	port = ""
	if match := regex.FindStringSubmatch(cleanedUrl.Host); match != nil {
		host = match[1]
		if len(match) > 2 {
			port = match[2]
		}
	}
	return
}

func (self *SshTransportFactory) WillHandleUrl(u *url.URL) bool {
	if u.Scheme == "ssh" {
		return true
	}

	// try cleaning a bare URL user@host.com:something/something
	newu := self.cleanupBareUrl(u)
	return newu.Scheme == "ssh"
}
func (self *SshTransportFactory) Connect(u *url.URL) (Transport, error) {
	ssh := os.Getenv("GIT_SSH")
	isPlink := strings.EqualFold(filepath.Base(ssh), "plink")
	isTortoise := strings.EqualFold(filepath.Base(ssh), "tortoiseplink")
	if ssh == "" {
		ssh = "ssh"
	}
	// Clean up bare git@blah.com:port:path styles
	// we want to identify host & port, easiest to pull out of URL than parsing ourselves
	urlCleaned := self.cleanupBareUrl(u)
	// Cleaned URLs always have an ssh scheme
	if urlCleaned.Scheme != "ssh" {
		return nil, fmt.Errorf("%v is not a valid SSH URL", u.String())
	}
	host, port := self.getHostAndPort(urlCleaned)
	if host == "" {
		return nil, fmt.Errorf("No valid host found in url %v", u.String())
	}

	util.LogDebugf("Connecting to %v over SSH...", host)

	// Let's invoke ssh
	args := make([]string, 0, 2)
	if isTortoise {
		// TortoisePlink requires the -batch argument to behave like ssh/plink
		args = append(args, "-batch")
	}
	if port != "" {
		if isPlink {
			args = append(args, "-P")
		} else {
			args = append(args, "-p")
		}
		args = append(args, port)
	}
	args = append(args, host)

	// Now add remote program and path
	args = append(args, util.GlobalOptions.SSHServerCommand)
	// u.Path includes a preceding '/', strip off manually
	// rooted paths in the URL will be '//path/to/blah'
	// this is just how Go's URL parsing works
	path := u.Path
	if len(path) > 0 && strings.HasPrefix(path, "/") {
		path = path[1:]
	}
	args = append(args, path)

	util.LogDebugf("SSH command is: %v %v", ssh, strings.Join(args, " "))

	cmd := exec.Command(ssh, args...)

	outp, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("Unable to connect to ssh stdout: %v", err.Error())
	}
	errp, err := cmd.StderrPipe()
	if err != nil {
		return nil, fmt.Errorf("Unable to connect to ssh stderr: %v", err.Error())
	}
	inp, err := cmd.StdinPipe()
	if err != nil {
		return nil, fmt.Errorf("Unable to connect to ssh stdin: %v", err.Error())
	}
	err = cmd.Start()
	if err != nil {
		return nil, fmt.Errorf("Unable to start ssh command: %v", err.Error())
	}

	conn := &SshConnection{
		cmd:    cmd,
		stdin:  inp,
		stdout: outp,
		stderr: errp,
	}

	util.LogDebugf("SSH connection successful to %v", host)

	return NewPersistentTransport(conn), nil

}

func RegisterSshTransportFactory() {
	RegisterTransportFactory(&SshTransportFactory{})
}

// Underlying SSH connection to smart server, for use with PersistentTransport
// Works by invoking ssh/plink/tortoise_plink and connecting stdout/stdin
type SshConnection struct {
	// The command which is running ssh
	cmd *exec.Cmd
	// Streams for communicating
	stdin  io.WriteCloser
	stdout io.ReadCloser
	stderr io.ReadCloser
}

// SSH Connection implementation
func (self *SshConnection) Read(p []byte) (n int, err error) {
	return self.stdout.Read(p)
}
func (self *SshConnection) Write(p []byte) (n int, err error) {
	return self.stdin.Write(p)
}
func (self *SshConnection) Close() error {
	// Docs say "It is incorrect to call Wait before all writes to the pipe have completed."
	// But that actually means in parallel https://github.com/golang/go/issues/9307 so we're ok here
	err := self.cmd.Wait()
	if err != nil {
		errbytes, readerr := ioutil.ReadAll(self.stderr)
		if readerr != nil {
			return fmt.Errorf("Error closing ssh connection: %v", err.Error())
		} else {
			return fmt.Errorf("Error closing ssh connection: %v\nstderr: %v", err.Error(), string(errbytes))
		}
	}

	return nil

}
<|endoftext|>


<commit_before>package auth

import (
	"encoding/json"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"sync"
	"time"

	"github.com/raintank/worldping-api/pkg/log"
)

var (
	validTTL   = time.Minute * 5
	invalidTTL = time.Second * 30
	cache      *AuthCache

	// global HTTP client.  By sharing the client we can take
	// advantage of keepalives and re-use connections instead
	// of establishing a new tcp connection for every request.
	client = &http.Client{
		Timeout: time.Second * 2,
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   5 * time.Second,
				KeepAlive: 30 * time.Second,
			}).DialContext,
			MaxIdleConns:          10,
			IdleConnTimeout:       300 * time.Second,
			TLSHandshakeTimeout:   5 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	}
)

type AuthCache struct {
	sync.RWMutex
	items map[string]CacheItem
}

type CacheItem struct {
	User       *SignedInUser
	ExpireTime time.Time
}

func (a *AuthCache) Get(key string) (*SignedInUser, bool) {
	a.RLock()
	defer a.RUnlock()
	if c, ok := a.items[key]; ok {
		return c.User, c.ExpireTime.After(time.Now())
	}
	return nil, false
}

func (a *AuthCache) Set(key string, u *SignedInUser, ttl time.Duration) {
	a.Lock()
	a.items[key] = CacheItem{
		User:       u,
		ExpireTime: time.Now().Add(ttl),
	}
	a.Unlock()
}

func init() {
	cache = &AuthCache{items: make(map[string]CacheItem)}
}

func Auth(adminKey, keyString string) (*SignedInUser, error) {
	if keyString == adminKey {
		return &SignedInUser{
			Role:    ROLE_ADMIN,
			OrgId:   1,
			OrgName: "Admin",
			OrgSlug: "admin",
			IsAdmin: true,
			key:     keyString,
		}, nil
	}

	// check the cache
	log.Debug("Checking cache for apiKey")
	user, cached := cache.Get(keyString)
	if cached {
		if user != nil {
			log.Debug("valid key cached")
			return user, nil
		}

		log.Debug("invalid key cached")
		return nil, ErrInvalidApiKey
	}

	payload := url.Values{}
	payload.Add("token", keyString)

	res, err := client.PostForm("https://grafana.net/api/api-keys/check", payload)
	if err != nil {
		log.Error(3, "failed to check apiKey. %s", err)

		// if we have an expired cached entry for the user, reset the cache expiration and return that
		// this allows the service to remain available if grafana.net is unreachable
		if user != nil {
			log.Debug("re-caching validKey response for %d seconds", validTTL/time.Second)
			cache.Set(keyString, user, validTTL)
			return user, nil
		}

		return nil, err
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	log.Debug("apiKey check response was: %s", body)

	if res.StatusCode >= 500 {
		log.Error(3, "failed to check apiKey. %s", res.Status)

		// if we have an expired cached entry for the user, reset the cache expiration and return that
		// this allows the service to remain available if grafana.net is unreachable
		if user != nil {
			log.Debug("re-caching validKey response for %d seconds", validTTL/time.Second)
			cache.Set(keyString, user, validTTL)
			return user, nil
		}

		return nil, err
	}

	if res.StatusCode != 200 {
		// add the invalid key to the cache
		log.Debug("Caching invalidKey response for %d seconds", invalidTTL/time.Second)
		cache.Set(keyString, nil, invalidTTL)

		return nil, ErrInvalidApiKey
	}

	user = &SignedInUser{key: keyString}
	err = json.Unmarshal(body, user)
	if err != nil {
		log.Error(3, "failed to parse api-keys/check response. %s", err)
		return nil, err
	}

	// add the user to the cache.
	log.Debug("Caching validKey response for %d seconds", validTTL/time.Second)
	cache.Set(keyString, user, validTTL)
	return user, nil
}
<commit_msg>make auth-endpoint configurable<commit_after>package auth

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"sync"
	"time"

	"github.com/raintank/worldping-api/pkg/log"
)

var (
	validTTL     = time.Minute * 5
	invalidTTL   = time.Second * 30
	authEndpoint string
	cache        *AuthCache

	// global HTTP client.  By sharing the client we can take
	// advantage of keepalives and re-use connections instead
	// of establishing a new tcp connection for every request.
	client = &http.Client{
		Timeout: time.Second * 2,
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   5 * time.Second,
				KeepAlive: 30 * time.Second,
			}).DialContext,
			MaxIdleConns:          10,
			IdleConnTimeout:       300 * time.Second,
			TLSHandshakeTimeout:   5 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	}
)

type AuthCache struct {
	sync.RWMutex
	items map[string]CacheItem
}

type CacheItem struct {
	User       *SignedInUser
	ExpireTime time.Time
}

func (a *AuthCache) Get(key string) (*SignedInUser, bool) {
	a.RLock()
	defer a.RUnlock()
	if c, ok := a.items[key]; ok {
		return c.User, c.ExpireTime.After(time.Now())
	}
	return nil, false
}

func (a *AuthCache) Set(key string, u *SignedInUser, ttl time.Duration) {
	a.Lock()
	a.items[key] = CacheItem{
		User:       u,
		ExpireTime: time.Now().Add(ttl),
	}
	a.Unlock()
}

func init() {
	flag.StringVar(&authEndpoint, "auth-endpoint", "https://grafana.net", "Endpoint to authenticate users on")
	cache = &AuthCache{items: make(map[string]CacheItem)}
}

func Auth(adminKey, keyString string) (*SignedInUser, error) {
	if keyString == adminKey {
		return &SignedInUser{
			Role:    ROLE_ADMIN,
			OrgId:   1,
			OrgName: "Admin",
			OrgSlug: "admin",
			IsAdmin: true,
			key:     keyString,
		}, nil
	}

	// check the cache
	log.Debug("Checking cache for apiKey")
	user, cached := cache.Get(keyString)
	if cached {
		if user != nil {
			log.Debug("valid key cached")
			return user, nil
		}

		log.Debug("invalid key cached")
		return nil, ErrInvalidApiKey
	}

	payload := url.Values{}
	payload.Add("token", keyString)

	res, err := client.PostForm(fmt.Sprintf("%s/api/api-keys/check", authEndpoint), payload)
	if err != nil {
		log.Error(3, "failed to check apiKey. %s", err)

		// if we have an expired cached entry for the user, reset the cache expiration and return that
		// this allows the service to remain available if grafana.net is unreachable
		if user != nil {
			log.Debug("re-caching validKey response for %d seconds", validTTL/time.Second)
			cache.Set(keyString, user, validTTL)
			return user, nil
		}

		return nil, err
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	log.Debug("apiKey check response was: %s", body)

	if res.StatusCode >= 500 {
		log.Error(3, "failed to check apiKey. %s", res.Status)

		// if we have an expired cached entry for the user, reset the cache expiration and return that
		// this allows the service to remain available if grafana.net is unreachable
		if user != nil {
			log.Debug("re-caching validKey response for %d seconds", validTTL/time.Second)
			cache.Set(keyString, user, validTTL)
			return user, nil
		}

		return nil, err
	}

	if res.StatusCode != 200 {
		// add the invalid key to the cache
		log.Debug("Caching invalidKey response for %d seconds", invalidTTL/time.Second)
		cache.Set(keyString, nil, invalidTTL)

		return nil, ErrInvalidApiKey
	}

	user = &SignedInUser{key: keyString}
	err = json.Unmarshal(body, user)
	if err != nil {
		log.Error(3, "failed to parse api-keys/check response. %s", err)
		return nil, err
	}

	// add the user to the cache.
	log.Debug("Caching validKey response for %d seconds", validTTL/time.Second)
	cache.Set(keyString, user, validTTL)
	return user, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/metrics"
	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
)

type Cloud interface {
	// EC2 provides API to AWS EC2
	EC2() services.EC2

	// ELBV2 provides API to AWS ELBV2
	ELBV2() services.ELBV2

	// ACM provides API to AWS ACM
	ACM() services.ACM

	// WAFv2 provides API to AWS WAFv2
	WAFv2() services.WAFv2

	// WAFRegional provides API to AWS WAFRegional
	WAFRegional() services.WAFRegional

	// Shield provides API to AWS Shield
	Shield() services.Shield

	// RGT provides API to AWS RGT
	RGT() services.RGT

	// Region for the kubernetes cluster
	Region() string

	// VPC ID for the the kubernetes cluster
	VpcID() string
}

// NewCloud constructs new Cloud implementation.
func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer) (Cloud, error) {
	sess := session.Must(session.NewSession(aws.NewConfig()))
	injectUserAgent(&sess.Handlers)
	metadata := services.NewEC2Metadata(sess)
	if cfg.ThrottleConfig != nil {
		throttler := throttle.NewThrottler(cfg.ThrottleConfig)
		throttler.InjectHandlers(&sess.Handlers)
	}
	if metricsRegisterer != nil {
		metricsCollector, err := metrics.NewCollector(metricsRegisterer)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to initialize sdk metrics collector")
		}
		metricsCollector.InjectHandlers(&sess.Handlers)
	}

	if len(cfg.Region) == 0 {
		region, err := metadata.Region()
		if err != nil {
			return nil, errors.Wrap(err, "failed to introspect region from EC2Metadata, specify --aws-region instead if EC2Metadata is unavailable")
		}
		cfg.Region = region
	}

	if len(cfg.VpcID) == 0 {
		vpcId, err := metadata.VpcID()
		if err != nil {
			return nil, errors.Wrap(err, "failed to introspect vpcID from EC2Metadata, specify --aws-vpc-id instead if EC2Metadata is unavailable")
		}
		cfg.VpcID = vpcId
	}

	awsCfg := aws.NewConfig().WithRegion(cfg.Region).WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint).WithMaxRetries(cfg.MaxRetries)
	sess = sess.Copy(awsCfg)
	return &defaultCloud{
		cfg:         cfg,
		ec2:         services.NewEC2(sess),
		elbv2:       services.NewELBV2(sess),
		acm:         services.NewACM(sess),
		wafv2:       services.NewWAFv2(sess),
		wafRegional: services.NewWAFRegional(sess, cfg.Region),
		shield:      services.NewShield(sess),
		rgt:         services.NewRGT(sess),
	}, nil
}

var _ Cloud = &defaultCloud{}

type defaultCloud struct {
	cfg CloudConfig

	ec2   services.EC2
	elbv2 services.ELBV2

	acm         services.ACM
	wafv2       services.WAFv2
	wafRegional services.WAFRegional
	shield      services.Shield
	rgt         services.RGT
}

func (c *defaultCloud) EC2() services.EC2 {
	return c.ec2
}

func (c *defaultCloud) ELBV2() services.ELBV2 {
	return c.elbv2
}

func (c *defaultCloud) ACM() services.ACM {
	return c.acm
}

func (c *defaultCloud) WAFv2() services.WAFv2 {
	return c.wafv2
}

func (c *defaultCloud) WAFRegional() services.WAFRegional {
	return c.wafRegional
}

func (c *defaultCloud) Shield() services.Shield {
	return c.shield
}

func (c *defaultCloud) RGT() services.RGT {
	return c.rgt
}

func (c *defaultCloud) Region() string {
	return c.cfg.Region
}

func (c *defaultCloud) VpcID() string {
	return c.cfg.VpcID
}
<commit_msg>fix aws sdk session (#1508)<commit_after>package aws

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/metrics"
	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
)

type Cloud interface {
	// EC2 provides API to AWS EC2
	EC2() services.EC2

	// ELBV2 provides API to AWS ELBV2
	ELBV2() services.ELBV2

	// ACM provides API to AWS ACM
	ACM() services.ACM

	// WAFv2 provides API to AWS WAFv2
	WAFv2() services.WAFv2

	// WAFRegional provides API to AWS WAFRegional
	WAFRegional() services.WAFRegional

	// Shield provides API to AWS Shield
	Shield() services.Shield

	// RGT provides API to AWS RGT
	RGT() services.RGT

	// Region for the kubernetes cluster
	Region() string

	// VPC ID for the the kubernetes cluster
	VpcID() string
}

// NewCloud constructs new Cloud implementation.
func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer) (Cloud, error) {
	sess := session.Must(session.NewSession(aws.NewConfig()))
	injectUserAgent(&sess.Handlers)
	metadata := services.NewEC2Metadata(sess)
	if cfg.ThrottleConfig != nil {
		throttler := throttle.NewThrottler(cfg.ThrottleConfig)
		throttler.InjectHandlers(&sess.Handlers)
	}
	if metricsRegisterer != nil {
		metricsCollector, err := metrics.NewCollector(metricsRegisterer)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to initialize sdk metrics collector")
		}
		metricsCollector.InjectHandlers(&sess.Handlers)
	}

	if len(cfg.Region) == 0 {
		region, err := metadata.Region()
		if err != nil {
			return nil, errors.Wrap(err, "failed to introspect region from EC2Metadata, specify --aws-region instead if EC2Metadata is unavailable")
		}
		cfg.Region = region
	}

	if len(cfg.VpcID) == 0 {
		vpcId, err := metadata.VpcID()
		if err != nil {
			return nil, errors.Wrap(err, "failed to introspect vpcID from EC2Metadata, specify --aws-vpc-id instead if EC2Metadata is unavailable")
		}
		cfg.VpcID = vpcId
	}

	awsCFG := aws.NewConfig().WithRegion(cfg.Region).WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint).WithMaxRetries(cfg.MaxRetries)
	sess = session.Must(session.NewSession(awsCFG))
	return &defaultCloud{
		cfg:         cfg,
		ec2:         services.NewEC2(sess),
		elbv2:       services.NewELBV2(sess),
		acm:         services.NewACM(sess),
		wafv2:       services.NewWAFv2(sess),
		wafRegional: services.NewWAFRegional(sess, cfg.Region),
		shield:      services.NewShield(sess),
		rgt:         services.NewRGT(sess),
	}, nil
}

var _ Cloud = &defaultCloud{}

type defaultCloud struct {
	cfg CloudConfig

	ec2   services.EC2
	elbv2 services.ELBV2

	acm         services.ACM
	wafv2       services.WAFv2
	wafRegional services.WAFRegional
	shield      services.Shield
	rgt         services.RGT
}

func (c *defaultCloud) EC2() services.EC2 {
	return c.ec2
}

func (c *defaultCloud) ELBV2() services.ELBV2 {
	return c.elbv2
}

func (c *defaultCloud) ACM() services.ACM {
	return c.acm
}

func (c *defaultCloud) WAFv2() services.WAFv2 {
	return c.wafv2
}

func (c *defaultCloud) WAFRegional() services.WAFRegional {
	return c.wafRegional
}

func (c *defaultCloud) Shield() services.Shield {
	return c.shield
}

func (c *defaultCloud) RGT() services.RGT {
	return c.rgt
}

func (c *defaultCloud) Region() string {
	return c.cfg.Region
}

func (c *defaultCloud) VpcID() string {
	return c.cfg.VpcID
}
<|endoftext|>


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

package cpio

import (
	"fmt"
	"io"
	"path/filepath"
)

var (
	formatMap = make(map[string]RecordFormat)
	Debug     = func(string, ...interface{}) {}
)

// A RecordReader reads one record from an archive.
type RecordReader interface {
	ReadRecord() (Record, error)
}

// A RecordWriter writes on record to an archive.
type RecordWriter interface {
	WriteRecord(Record) error
}

// A RecordFormat gives readers and writers for dealing with archives.
type RecordFormat interface {
	Reader(r io.ReaderAt) RecordReader
	Writer(w io.Writer) RecordWriter
}

// Format returns the RecordFormat with that name, if it exists.
func Format(name string) (RecordFormat, error) {
	op, ok := formatMap[name]
	if !ok {
		return nil, fmt.Errorf("%q is not in cpio format map %v", name, formatMap)
	}
	return op, nil
}

// EOFReader is a RecordReader that converts the Trailer record to io.EOF.
type EOFReader struct {
	RecordReader
}

// ReadRecord implements RecordReader.
//
// ReadRecord returns io.EOF when the record name is TRAILER!!!.
func (r EOFReader) ReadRecord() (Record, error) {
	rec, err := r.RecordReader.ReadRecord()
	if err != nil {
		return Record{}, err
	}
	// The end of a CPIO archive is marked by a record whose name is
	// "TRAILER!!!".
	if rec.Name == Trailer {
		return Record{}, io.EOF
	}
	return rec, nil
}

// DedupWriter is a RecordWriter that does not write more than one record with
// the same path.
//
// There seems to be no harm done in stripping duplicate names when the record
// is written, and lots of harm done if we don't do it.
type DedupWriter struct {
	rw RecordWriter

	// alreadyWritten keeps track of paths already written to rw.
	alreadyWritten map[string]struct{}
}

// NewDedupWriter returns a new deduplicating rw.
func NewDedupWriter(rw RecordWriter) RecordWriter {
	return &DedupWriter{
		rw:             rw,
		alreadyWritten: make(map[string]struct{}),
	}
}

// Passthrough copies from a RecordReader to a RecordWriter
// It processes one record at a time to minimize the memory footprint.
func Passthrough(r RecordReader, w RecordWriter) error {
	for {
		rec, err := r.ReadRecord()
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("error reading records: %v", err)
		}
		if err := w.WriteRecord(rec); err != nil {
			return fmt.Errorf("Writing record %q failed: %v", rec, err)
		}
	}
	if err := WriteTrailer(w); err != nil {
		return fmt.Errorf("Writing Trailer failed: %v", err)
	}
	return nil
}

// WriteRecord implements RecordWriter.
//
// If rec.Name was already seen once before, it will not be written again and
// WriteRecord returns nil.
func (dw *DedupWriter) WriteRecord(rec Record) error {
	rec.Name = Normalize(rec.Name)

	if _, ok := dw.alreadyWritten[rec.Name]; ok {
		return nil
	}
	dw.alreadyWritten[rec.Name] = struct{}{}
	return dw.rw.WriteRecord(rec)
}

// WriteRecords writes multiple records.
func WriteRecords(w RecordWriter, files []Record) error {
	for _, f := range files {
		if err := w.WriteRecord(f); err != nil {
			return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err)
		}
	}
	return nil
}

// WriteTrailer writes the trailer record.
func WriteTrailer(w RecordWriter) error {
	return w.WriteRecord(TrailerRecord)
}

// Concat reads files from r one at a time, and writes them to w.
func Concat(w RecordWriter, r RecordReader, transform func(Record) Record) error {
	// Read and write one file at a time. We don't want all that in memory.
	for {
		f, err := r.ReadRecord()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		if transform != nil {
			f = transform(f)
		}
		if err := w.WriteRecord(f); err != nil {
			return err
		}
	}
}

// Archive implements RecordWriter and is an in-memory list of files.
//
// Archive.Reader returns a RecordReader for this archive.
type Archive struct {
	// Files is a map of relative archive path -> record.
	Files map[string]Record

	// Order is a list of relative archive paths and represents the order
	// in which Files were added.
	Order []string
}

// InMemArchive returns an in-memory file archive.
func InMemArchive() *Archive {
	return &Archive{
		Files: make(map[string]Record),
	}
}

// WriteRecord implements RecordWriter and adds a record to the archive.
func (a *Archive) WriteRecord(r Record) error {
	r.Name = Normalize(r.Name)
	a.Files[r.Name] = r
	a.Order = append(a.Order, r.Name)
	return nil
}

type archiveReader struct {
	a   *Archive
	pos int
}

// Reader returns a RecordReader for the archive.
func (a *Archive) Reader() RecordReader {
	return &EOFReader{&archiveReader{a: a}}
}

// ReadRecord implements RecordReader.
func (ar *archiveReader) ReadRecord() (Record, error) {
	if ar.pos >= len(ar.a.Order) {
		return Record{}, io.EOF
	}

	path := ar.a.Order[ar.pos]
	ar.pos++
	return ar.a.Files[path], nil
}

// Contains returns true if a record matching r is in the archive.
func (a *Archive) Contains(r Record) bool {
	r.Name = Normalize(r.Name)
	if s, ok := a.Files[r.Name]; ok {
		return Equal(r, s)
	}
	return false
}

// ReadArchive reads the entire archive in-memory and makes it accessible by
// paths.
func ReadArchive(rr RecordReader) (*Archive, error) {
	a := &Archive{
		Files: make(map[string]Record),
	}
	err := ForEachRecord(rr, func(r Record) error {
		return a.WriteRecord(r)
	})
	return a, err
}

// ReadAllRecords returns all records in `r` in the order in which they were
// read.
func ReadAllRecords(rr RecordReader) ([]Record, error) {
	var files []Record
	err := ForEachRecord(rr, func(r Record) error {
		files = append(files, r)
		return nil
	})
	return files, err
}

// ForEachRecord reads every record from r and applies f.
func ForEachRecord(rr RecordReader, fun func(Record) error) error {
	for {
		rec, err := rr.ReadRecord()
		switch err {
		case io.EOF:
			return nil

		case nil:
			if err := fun(rec); err != nil {
				return err
			}

		default:
			return err
		}
	}
}

// Normalize normalizes path to be relative to /.
func Normalize(path string) string {
	if filepath.IsAbs(path) {
		rel, err := filepath.Rel("/", path)
		if err != nil {
			panic("absolute filepath must be relative to /")
		}
		return rel
	}
	return path
}

// MakeReproducible changes any fields in a Record such that if we run cpio
// again, with the same files presented to it in the same order, and those
// files have unchanged contents, the cpio file it produces will be bit-for-bit
// identical. This is an essential property for firmware-embedded payloads.
func MakeReproducible(r Record) Record {
	r.Ino = 0
	r.Name = Normalize(r.Name)
	r.MTime = 0
	r.UID = 0
	r.GID = 0
	return r
}

// MakeAllReproducible makes all given records reproducible as in
// MakeReproducible.
func MakeAllReproducible(files []Record) {
	for i := range files {
		files[i] = MakeReproducible(files[i])
	}
}
<commit_msg>return more detailed error message<commit_after>// Copyright 2013-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cpio

import (
	"fmt"
	"io"
	"path/filepath"
)

var (
	formatMap = make(map[string]RecordFormat)
	Debug     = func(string, ...interface{}) {}
)

// A RecordReader reads one record from an archive.
type RecordReader interface {
	ReadRecord() (Record, error)
}

// A RecordWriter writes on record to an archive.
type RecordWriter interface {
	WriteRecord(Record) error
}

// A RecordFormat gives readers and writers for dealing with archives.
type RecordFormat interface {
	Reader(r io.ReaderAt) RecordReader
	Writer(w io.Writer) RecordWriter
}

// Format returns the RecordFormat with that name, if it exists.
func Format(name string) (RecordFormat, error) {
	op, ok := formatMap[name]
	if !ok {
		return nil, fmt.Errorf("%q is not in cpio format map %v", name, formatMap)
	}
	return op, nil
}

// EOFReader is a RecordReader that converts the Trailer record to io.EOF.
type EOFReader struct {
	RecordReader
}

// ReadRecord implements RecordReader.
//
// ReadRecord returns io.EOF when the record name is TRAILER!!!.
func (r EOFReader) ReadRecord() (Record, error) {
	rec, err := r.RecordReader.ReadRecord()
	if err != nil {
		return Record{}, err
	}
	// The end of a CPIO archive is marked by a record whose name is
	// "TRAILER!!!".
	if rec.Name == Trailer {
		return Record{}, io.EOF
	}
	return rec, nil
}

// DedupWriter is a RecordWriter that does not write more than one record with
// the same path.
//
// There seems to be no harm done in stripping duplicate names when the record
// is written, and lots of harm done if we don't do it.
type DedupWriter struct {
	rw RecordWriter

	// alreadyWritten keeps track of paths already written to rw.
	alreadyWritten map[string]struct{}
}

// NewDedupWriter returns a new deduplicating rw.
func NewDedupWriter(rw RecordWriter) RecordWriter {
	return &DedupWriter{
		rw:             rw,
		alreadyWritten: make(map[string]struct{}),
	}
}

// Passthrough copies from a RecordReader to a RecordWriter
// It processes one record at a time to minimize the memory footprint.
func Passthrough(r RecordReader, w RecordWriter) error {
	for {
		rec, err := r.ReadRecord()
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("Reading record %q failed: %v", rec, err)
		}
		if err := w.WriteRecord(rec); err != nil {
			return fmt.Errorf("Writing record %q failed: %v", rec, err)
		}
	}
	if err := WriteTrailer(w); err != nil {
		return fmt.Errorf("Writing Trailer failed: %v", err)
	}
	return nil
}

// WriteRecord implements RecordWriter.
//
// If rec.Name was already seen once before, it will not be written again and
// WriteRecord returns nil.
func (dw *DedupWriter) WriteRecord(rec Record) error {
	rec.Name = Normalize(rec.Name)

	if _, ok := dw.alreadyWritten[rec.Name]; ok {
		return nil
	}
	dw.alreadyWritten[rec.Name] = struct{}{}
	return dw.rw.WriteRecord(rec)
}

// WriteRecords writes multiple records.
func WriteRecords(w RecordWriter, files []Record) error {
	for _, f := range files {
		if err := w.WriteRecord(f); err != nil {
			return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err)
		}
	}
	return nil
}

// WriteTrailer writes the trailer record.
func WriteTrailer(w RecordWriter) error {
	return w.WriteRecord(TrailerRecord)
}

// Concat reads files from r one at a time, and writes them to w.
func Concat(w RecordWriter, r RecordReader, transform func(Record) Record) error {
	// Read and write one file at a time. We don't want all that in memory.
	for {
		f, err := r.ReadRecord()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		if transform != nil {
			f = transform(f)
		}
		if err := w.WriteRecord(f); err != nil {
			return err
		}
	}
}

// Archive implements RecordWriter and is an in-memory list of files.
//
// Archive.Reader returns a RecordReader for this archive.
type Archive struct {
	// Files is a map of relative archive path -> record.
	Files map[string]Record

	// Order is a list of relative archive paths and represents the order
	// in which Files were added.
	Order []string
}

// InMemArchive returns an in-memory file archive.
func InMemArchive() *Archive {
	return &Archive{
		Files: make(map[string]Record),
	}
}

// WriteRecord implements RecordWriter and adds a record to the archive.
func (a *Archive) WriteRecord(r Record) error {
	r.Name = Normalize(r.Name)
	a.Files[r.Name] = r
	a.Order = append(a.Order, r.Name)
	return nil
}

type archiveReader struct {
	a   *Archive
	pos int
}

// Reader returns a RecordReader for the archive.
func (a *Archive) Reader() RecordReader {
	return &EOFReader{&archiveReader{a: a}}
}

// ReadRecord implements RecordReader.
func (ar *archiveReader) ReadRecord() (Record, error) {
	if ar.pos >= len(ar.a.Order) {
		return Record{}, io.EOF
	}

	path := ar.a.Order[ar.pos]
	ar.pos++
	return ar.a.Files[path], nil
}

// Contains returns true if a record matching r is in the archive.
func (a *Archive) Contains(r Record) bool {
	r.Name = Normalize(r.Name)
	if s, ok := a.Files[r.Name]; ok {
		return Equal(r, s)
	}
	return false
}

// ReadArchive reads the entire archive in-memory and makes it accessible by
// paths.
func ReadArchive(rr RecordReader) (*Archive, error) {
	a := &Archive{
		Files: make(map[string]Record),
	}
	err := ForEachRecord(rr, func(r Record) error {
		return a.WriteRecord(r)
	})
	return a, err
}

// ReadAllRecords returns all records in `r` in the order in which they were
// read.
func ReadAllRecords(rr RecordReader) ([]Record, error) {
	var files []Record
	err := ForEachRecord(rr, func(r Record) error {
		files = append(files, r)
		return nil
	})
	return files, err
}

// ForEachRecord reads every record from r and applies f.
func ForEachRecord(rr RecordReader, fun func(Record) error) error {
	for {
		rec, err := rr.ReadRecord()
		switch err {
		case io.EOF:
			return nil

		case nil:
			if err := fun(rec); err != nil {
				return err
			}

		default:
			return err
		}
	}
}

// Normalize normalizes path to be relative to /.
func Normalize(path string) string {
	if filepath.IsAbs(path) {
		rel, err := filepath.Rel("/", path)
		if err != nil {
			panic("absolute filepath must be relative to /")
		}
		return rel
	}
	return path
}

// MakeReproducible changes any fields in a Record such that if we run cpio
// again, with the same files presented to it in the same order, and those
// files have unchanged contents, the cpio file it produces will be bit-for-bit
// identical. This is an essential property for firmware-embedded payloads.
func MakeReproducible(r Record) Record {
	r.Ino = 0
	r.Name = Normalize(r.Name)
	r.MTime = 0
	r.UID = 0
	r.GID = 0
	return r
}

// MakeAllReproducible makes all given records reproducible as in
// MakeReproducible.
func MakeAllReproducible(files []Record) {
	for i := range files {
		files[i] = MakeReproducible(files[i])
	}
}
<|endoftext|>


<commit_before>package fltr

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/emicklei/go-restful"
	"github.com/fatih/structs"
	"gopkg.in/mgo.v2/bson"
)

var (
	eq  string = "eq"
	gt         = "gt"
	gte        = "gte"
	lt         = "lt"
	lte        = "lte"
	in         = "in"
	neq        = "neq"
	nin        = "nin"

	all = []string{eq, gt, gte, lt, lte, in, neq, nin}
)

var (
	FilterTag       = "fltr"
	ModifierDivider = "_"
)

type Enumer interface {
	Enum() []interface{}
}

type Converter interface {
	Convert(string) (interface{}, error)
}

type EnumConverter interface {
	Enumer
	Converter
}

func GetQuery(raw interface{}) bson.M {
	result := bson.M{}
	if raw == nil {
		return result
	}
	s := structs.New(raw)
	for _, field := range s.Fields() {
		if field.IsZero() {
			continue
		}
		name := field.Name()
		if tagValue := field.Tag(FilterTag); tagValue != "" {
			tags := strings.Split(tagValue, ",")
			if len(tags) > 0 {
				if tags[0] != "" {
					name = tags[0] // take field name from tag
				}
			}
		}
		result[name] = field.Value()
	}
	return result
}

func getBsonName(field *structs.Field) string {
	tag := field.Tag("bson")
	if tag == "" || tag == "-" {
		return ""
	}
	tags := strings.Split(tag, ",")
	return tags[0]
}

//
func GetParams(ws *restful.WebService, raw interface{}) []*restful.Parameter {
	params := []*restful.Parameter{}
	s := structs.New(raw)
	for _, field := range s.Fields() {
		name := field.Name()
		tags := strings.Split(field.Tag(FilterTag), ",")
		if len(tags) > 0 {
			if tags[0] != "" {
				name = tags[0] // take field name from tag
			}
			tags = tags[1:]
		}
		modifiers := getModifiers(tags)

		desc := field.Tag("description")
		// generate description automatically
		if desc == "" {
			desc = fmt.Sprintf("filter by %s", name)
			if enum, casted := field.Value().(Enumer); casted {
				enumValues := enum.Enum()
				desc = fmt.Sprintf("%s, one of %v", desc, enumValues)
			}
		}
		if desc == "-" {
			desc = ""
		}

		param := ws.QueryParameter(name, desc)
		if hasTag(tags, "required") {
			param.Required(true)
		}

		params = append(params, param)

		for _, m := range modifiers {
			mName := fmt.Sprintf("%s%s%s", name, ModifierDivider, m)
			param := ws.QueryParameter(mName, desc)
			if m == in || m == nin {
				param.AllowMultiple(true)
			}
			params = append(params, param)
		}
	}

	return params
}

func getModifiers(tags []string) []string {
	modifiers := []string{}
	if hasTag(tags, "all") {
		return append(modifiers, all...)
	} else {
		for _, m := range all {
			if hasTag(tags, m) {
				modifiers = append(modifiers, m)
			}
		}
	}
	return modifiers
}

func hasTag(tags []string, tag string) bool {
	for _, t := range tags {
		if t == tag {
			return true
		}
	}
	return false
}

func FromRequest(req *restful.Request, raw interface{}) (bson.M, error) {
	result := bson.M{}
	s := structs.New(raw)
fields:
	for _, field := range s.Fields() {
		name := field.Name()
		tags := strings.Split(field.Tag(FilterTag), ",")
		if len(tags) > 0 {
			name = tags[0] // take field name from tag
			tags = tags[1:]
		}
		bsonName := getBsonName(field)
		if bsonName == "" {
			bsonName = name
		}
		val := req.QueryParameter(name)
		if val != "" {
			if v, err := parseValue(field, val); err != nil {
				return nil, fmt.Errorf("param %s: %v", name, err)
			} else {
				result[bsonName] = v
				// if there is an eq value then we just skip modifiers
				continue fields
			}
		}
		modifiers := getModifiers(tags)
	modifiers:
		for _, m := range modifiers {
			mName := fmt.Sprintf("%s%s%s", name, ModifierDivider, m)
			val := req.QueryParameter(mName)
			if val == "" {
				continue modifiers
			}
			if m == in || m == nin {
				ins := []interface{}{}
				for _, val := range strings.Split(val, ",") {
					if v, err := parseValue(field, val); err != nil {
						return nil, fmt.Errorf("param %s: %v", mName, err)
					} else {
						ins = append(ins, v)
					}
				}
				result[bsonName] = bson.M{fmt.Sprintf("$%s", m): ins}
				continue modifiers
			}
			// gt, gte, lt, lte, ne
			if v, err := parseValue(field, val); err != nil {
				return nil, fmt.Errorf("param %s: %v", name, err)
			} else {
				result[bsonName] = bson.M{fmt.Sprintf("$%s", m): v}
				// if there is an eq value then we just skip modifiers
				continue fields
			}
		}
	}
	return result, nil
}

func parseValue(field *structs.Field, val string) (interface{}, error) {
	switch field.Kind() {
	case reflect.Int:
		v, err := strconv.Atoi(val)
		if err != nil {
			return nil, err
		}
		return v, nil
	case reflect.Float64:
		v, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return nil, err
		}
		return v, nil
	case reflect.Bool:
		v, err := strconv.ParseBool(val)
		if err != nil {
			return nil, err
		}
		return v, nil
	default:
		fieldVal := field.Value()

		if _, casted := fieldVal.(bson.ObjectId); casted {
			if bson.IsObjectIdHex(val) {
				return bson.ObjectIdHex(val), nil
			} else {
				return nil, fmt.Errorf("should be bson.ObjectId hex")
			}
		}
		if _, casted := (fieldVal).(time.Time); casted {
			v := &time.Time{}
			return v, v.UnmarshalText([]byte(val))
		}
		if convertable, casted := fieldVal.(Converter); casted {
			converted, err := convertable.Convert(val)
			if err != nil {
				return nil, err
			}
			if enum, casted := fieldVal.(Enumer); casted {
				enumValues := enum.Enum()
				for _, eV := range enumValues {
					if eV == converted {
						return converted, nil
					}
				}
				return nil, fmt.Errorf("should be one of %v", enumValues)
			}
			return converted, nil
		}
		return val, nil
	}

}
<commit_msg>Add types for filters<commit_after>package fltr

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/emicklei/go-restful"
	"github.com/fatih/structs"
	"gopkg.in/mgo.v2/bson"
)

var (
	eq  string = "eq"
	gt         = "gt"
	gte        = "gte"
	lt         = "lt"
	lte        = "lte"
	in         = "in"
	neq        = "neq"
	nin        = "nin"

	all = []string{eq, gt, gte, lt, lte, in, neq, nin}
)

var (
	FilterTag       = "fltr"
	ModifierDivider = "_"
)

type Enumer interface {
	Enum() []interface{}
}

type Converter interface {
	Convert(string) (interface{}, error)
}

type EnumConverter interface {
	Enumer
	Converter
}

func GetQuery(raw interface{}) bson.M {
	result := bson.M{}
	if raw == nil {
		return result
	}
	s := structs.New(raw)
	for _, field := range s.Fields() {
		if field.IsZero() {
			continue
		}
		name := field.Name()
		if tagValue := field.Tag(FilterTag); tagValue != "" {
			tags := strings.Split(tagValue, ",")
			if len(tags) > 0 {
				if tags[0] != "" {
					name = tags[0] // take field name from tag
				}
			}
		}
		result[name] = field.Value()
	}
	return result
}

func getBsonName(field *structs.Field) string {
	tag := field.Tag("bson")
	if tag == "" || tag == "-" {
		return ""
	}
	tags := strings.Split(tag, ",")
	return tags[0]
}

//
func GetParams(ws *restful.WebService, raw interface{}) []*restful.Parameter {
	params := []*restful.Parameter{}
	s := structs.New(raw)
	for _, field := range s.Fields() {
		name := field.Name()
		tags := strings.Split(field.Tag(FilterTag), ",")
		if len(tags) > 0 {
			if tags[0] != "" {
				name = tags[0] // take field name from tag
			}
			tags = tags[1:]
		}
		modifiers := getModifiers(tags)

		desc := field.Tag("description")
		// generate description automatically
		if desc == "" {
			desc = fmt.Sprintf("filter by %s", name)
			if enum, casted := field.Value().(Enumer); casted {
				enumValues := enum.Enum()
				desc = fmt.Sprintf("%s, one of %v", desc, enumValues)
			}
		}
		if desc == "-" {
			desc = ""
		}
		dataType := "string"
		switch field.Kind() {
		case reflect.Int:
			dataType = "integer"
		case reflect.Float64:
			dataType = "number"
		case reflect.Float32:
			dataType = "number"
		case reflect.Bool:
			dataType = "boolean"
		}

		param := ws.QueryParameter(name, desc)
		if hasTag(tags, "required") {
			param.Required(true)
		}

		params = append(params, param)

		for _, m := range modifiers {
			mName := fmt.Sprintf("%s%s%s", name, ModifierDivider, m)
			param := ws.QueryParameter(mName, desc).DataType(dataType)
			if m == in || m == nin {
				param.AllowMultiple(true)
			}
			params = append(params, param)
		}
	}

	return params
}

func getModifiers(tags []string) []string {
	modifiers := []string{}
	if hasTag(tags, "all") {
		return append(modifiers, all...)
	} else {
		for _, m := range all {
			if hasTag(tags, m) {
				modifiers = append(modifiers, m)
			}
		}
	}
	return modifiers
}

func hasTag(tags []string, tag string) bool {
	for _, t := range tags {
		if t == tag {
			return true
		}
	}
	return false
}

func FromRequest(req *restful.Request, raw interface{}) (bson.M, error) {
	result := bson.M{}
	s := structs.New(raw)
fields:
	for _, field := range s.Fields() {
		name := field.Name()
		tags := strings.Split(field.Tag(FilterTag), ",")
		if len(tags) > 0 {
			name = tags[0] // take field name from tag
			tags = tags[1:]
		}
		bsonName := getBsonName(field)
		if bsonName == "" {
			bsonName = name
		}
		val := req.QueryParameter(name)
		if val != "" {
			if v, err := parseValue(field, val); err != nil {
				return nil, fmt.Errorf("param %s: %v", name, err)
			} else {
				result[bsonName] = v
				// if there is an eq value then we just skip modifiers
				continue fields
			}
		}
		modifiers := getModifiers(tags)
	modifiers:
		for _, m := range modifiers {
			mName := fmt.Sprintf("%s%s%s", name, ModifierDivider, m)
			val := req.QueryParameter(mName)
			if val == "" {
				continue modifiers
			}
			if m == in || m == nin {
				ins := []interface{}{}
				for _, val := range strings.Split(val, ",") {
					if v, err := parseValue(field, val); err != nil {
						return nil, fmt.Errorf("param %s: %v", mName, err)
					} else {
						ins = append(ins, v)
					}
				}
				result[bsonName] = bson.M{fmt.Sprintf("$%s", m): ins}
				continue modifiers
			}
			// gt, gte, lt, lte, ne
			if v, err := parseValue(field, val); err != nil {
				return nil, fmt.Errorf("param %s: %v", name, err)
			} else {
				result[bsonName] = bson.M{fmt.Sprintf("$%s", m): v}
				// if there is an eq value then we just skip modifiers
				continue fields
			}
		}
	}
	return result, nil
}

func parseValue(field *structs.Field, val string) (interface{}, error) {
	switch field.Kind() {
	case reflect.Int:
		v, err := strconv.Atoi(val)
		if err != nil {
			return nil, err
		}
		return v, nil
	case reflect.Float64:
		v, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return nil, err
		}
		return v, nil
	case reflect.Bool:
		v, err := strconv.ParseBool(val)
		if err != nil {
			return nil, err
		}
		return v, nil
	default:
		fieldVal := field.Value()

		if _, casted := fieldVal.(bson.ObjectId); casted {
			if bson.IsObjectIdHex(val) {
				return bson.ObjectIdHex(val), nil
			} else {
				return nil, fmt.Errorf("should be bson.ObjectId hex")
			}
		}
		if _, casted := (fieldVal).(time.Time); casted {
			v := &time.Time{}
			return v, v.UnmarshalText([]byte(val))
		}
		if convertable, casted := fieldVal.(Converter); casted {
			converted, err := convertable.Convert(val)
			if err != nil {
				return nil, err
			}
			if enum, casted := fieldVal.(Enumer); casted {
				enumValues := enum.Enum()
				for _, eV := range enumValues {
					if eV == converted {
						return converted, nil
					}
				}
				return nil, fmt.Errorf("should be one of %v", enumValues)
			}
			return converted, nil
		}
		return val, nil
	}

}
<|endoftext|>