<commit_before>package main

import (
	"encoding/xml"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/Symantec/Dominator/lib/errors"
	"github.com/Symantec/Dominator/lib/json"
	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/srpc"
	proto "github.com/Symantec/Dominator/proto/hypervisor"
)

type devicesInfo struct {
	Interfaces []interfaceType `xml:"interface"`
	Volumes    []volumeType    `xml:"disk"`
}

type driverType struct {
	Name string `xml:"name,attr"`
	Type string `xml:"type,attr"`
}

type interfaceType struct {
	Mac  macType `xml:"mac"`
	Type string  `xml:"type,attr"`
}

type macType struct {
	Address string `xml:"address,attr"`
}

type memoryInfo struct {
	Value uint64 `xml:",chardata"`
	Unit  string `xml:"unit,attr"`
}

type sourceType struct {
	File string `xml:"file,attr"`
}

type vCpuInfo struct {
	Num       uint   `xml:",chardata"`
	Placement string `xml:"placement,attr"`
}

type virshInfoType struct {
	Devices devicesInfo `xml:"devices"`
	Memory  memoryInfo  `xml:"memory"`
	Name    string      `xml:"name"`
	VCpu    vCpuInfo    `xml:"vcpu"`
}

type volumeType struct {
	Device string     `xml:"device,attr"`
	Driver driverType `xml:"driver"`
	Source sourceType `xml:"source"`
	Type   string     `xml:"type,attr"`
}

func importVirshVmSubcommand(args []string, logger log.DebugLogger) error {
	if err := importVirshVm(args[0], args[1], logger); err != nil {
		return fmt.Errorf("Error importing VM: %s", err)
	}
	return nil
}

func ensureDomainIsStopped(domainName string) error {
	state, err := getDomainState(domainName)
	if err != nil {
		return err
	}
	if state == "shut off" {
		return nil
	}
	if state != "running" {
		return fmt.Errorf("domain is in unsupported state \"%s\"", state)
	}
	response, err := askForInputChoice("Cannot import running VM",
		[]string{"shutdown", "quit"})
	if err != nil {
		return err
	}
	if response == "quit" {
		return fmt.Errorf("domain must be shut off but is \"%s\"", state)
	}
	err = exec.Command("virsh", []string{"shutdown", domainName}...).Run()
	if err != nil {
		return fmt.Errorf("error shutting down VM: %s", err)
	}
	for ; ; time.Sleep(time.Second) {
		state, err := getDomainState(domainName)
		if err != nil {
			return err
		}
		if state == "shut off" {
			return nil
		}
	}
}

func getDomainState(domainName string) (string, error) {
	cmd := exec.Command("virsh", []string{"domstate", domainName}...)
	stdout, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("error getting VM status: %s", err)
	}
	return strings.TrimSpace(string(stdout)), nil
}

func importVirshVm(macAddr, domainName string, logger log.DebugLogger) error {
	ipList, err := net.LookupIP(domainName)
	if err != nil {
		return err
	}
	if len(ipList) != 1 {
		return fmt.Errorf("number of IPs %d != 1", len(ipList))
	}
	tags := vmTags.Copy()
	if _, ok := tags["Name"]; !ok {
		tags["Name"] = domainName
	}
	request := proto.ImportLocalVmRequest{VmInfo: proto.VmInfo{
		Hostname:    domainName,
		OwnerGroups: ownerGroups,
		OwnerUsers:  ownerUsers,
		Tags:        tags,
	}}
	request.VerificationCookie, err = readImportCookie(logger)
	if err != nil {
		return err
	}
	hypervisor := fmt.Sprintf(":%d", *hypervisorPortNum)
	client, err := srpc.DialHTTP("tcp", hypervisor, 0)
	if err != nil {
		return err
	}
	defer client.Close()
	directories, err := listVolumeDirectories(client)
	if err != nil {
		return err
	}
	volumeRoots := make(map[string]string, len(directories))
	for _, dirname := range directories {
		volumeRoots[filepath.Dir(dirname)] = dirname
	}
	cmd := exec.Command("virsh",
		[]string{"dumpxml", "--inactive", domainName}...)
	stdout, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("error getting XML data: %s", err)
	}
	var virshInfo virshInfoType
	if err := xml.Unmarshal(stdout, &virshInfo); err != nil {
		return err
	}
	if macAddr != virshInfo.Devices.Interfaces[0].Mac.Address {
		return fmt.Errorf("MAC address specified: %s != virsh data: %s",
			macAddr, virshInfo.Devices.Interfaces[0].Mac.Address)
	}
	json.WriteWithIndent(os.Stdout, "    ", virshInfo)
	if numIf := len(virshInfo.Devices.Interfaces); numIf != 1 {
		return fmt.Errorf("number of interfaces %d != 1", numIf)
	}
	request.VmInfo.Address = proto.Address{
		IpAddress:  ipList[0],
		MacAddress: virshInfo.Devices.Interfaces[0].Mac.Address,
	}
	switch virshInfo.Memory.Unit {
	case "KiB":
		request.VmInfo.MemoryInMiB = virshInfo.Memory.Value >> 10
	case "MiB":
		request.VmInfo.MemoryInMiB = virshInfo.Memory.Value
	case "GiB":
		request.VmInfo.MemoryInMiB = virshInfo.Memory.Value << 10
	default:
		return fmt.Errorf("unknown memory unit: %s", virshInfo.Memory.Unit)
	}
	request.VmInfo.MilliCPUs = virshInfo.VCpu.Num * 1000
	myPidStr := strconv.Itoa(os.Getpid())
	if err := ensureDomainIsStopped(domainName); err != nil {
		return err
	}
	logger.Debugln(0, "finding volumes")
	for index, inputVolume := range virshInfo.Devices.Volumes {
		if inputVolume.Device != "disk" {
			continue
		}
		var volumeFormat proto.VolumeFormat
		err := volumeFormat.UnmarshalText([]byte(inputVolume.Driver.Type))
		if err != nil {
			return err
		}
		inputFilename := inputVolume.Source.File
		var volumeRoot string
		for dirname := filepath.Dir(inputFilename); ; {
			if vr, ok := volumeRoots[dirname]; ok {
				volumeRoot = vr
				break
			}
			if dirname == "/" {
				break
			}
			dirname = filepath.Dir(dirname)
		}
		if volumeRoot == "" {
			return fmt.Errorf("no Hypervisor directory for: %s", inputFilename)
		}
		outputDirname := filepath.Join(volumeRoot, "import", myPidStr)
		if err := os.MkdirAll(outputDirname, dirPerms); err != nil {
			return err
		}
		defer os.RemoveAll(outputDirname)
		outputFilename := filepath.Join(outputDirname,
			fmt.Sprintf("volume-%d", index))
		if err := os.Link(inputFilename, outputFilename); err != nil {
			return err
		}
		request.VolumeFilenames = append(request.VolumeFilenames,
			outputFilename)
		request.VmInfo.Volumes = append(request.VmInfo.Volumes,
			proto.Volume{Format: volumeFormat})
	}
	requestWithoutSecrets := request
	requestWithoutSecrets.VerificationCookie = nil
	json.WriteWithIndent(os.Stdout, "    ", requestWithoutSecrets)
	var reply proto.GetVmInfoResponse
	logger.Debugln(0, "issuing import RPC")
	err = client.RequestReply("Hypervisor.ImportLocalVm", request, &reply)
	if err != nil {
		return fmt.Errorf("Hypervisor.ImportLocalVm RPC failed: %s", err)
	}
	if err := errors.New(reply.Error); err != nil {
		return fmt.Errorf("Hypervisor failed to import: %s", err)
	}
	logger.Debugln(0, "imported VM")
	for _, dirname := range directories {
		os.RemoveAll(filepath.Join(dirname, "import", myPidStr))
	}
	if err := maybeWatchVm(client, hypervisor, ipList[0], logger); err != nil {
		return err
	}
	if err := askForCommitDecision(client, ipList[0]); err != nil {
		if err == errorCommitAbandoned {
			response, _ := askForInputChoice(
				"Do you want to restart the old VM", []string{"y", "n"})
			if response != "y" {
				return err
			}
			cmd = exec.Command("virsh", "start", domainName)
			if output, err := cmd.CombinedOutput(); err != nil {
				logger.Println(string(output))
				return err
			}
		}
		return err
	}
	defer virshInfo.deleteVolumes()
	cmd = exec.Command("virsh",
		[]string{"undefine", "--managed-save", "--snapshots-metadata",
			"--remove-all-storage", domainName}...)
	if output, err := cmd.CombinedOutput(); err != nil {
		logger.Println(string(output))
		return fmt.Errorf("error destroying old VM: %s", err)
	}
	return nil
}

func (virshInfo virshInfoType) deleteVolumes() {
	for _, inputVolume := range virshInfo.Devices.Volumes {
		if inputVolume.Device != "disk" {
			continue
		}
		os.Remove(inputVolume.Source.File)
	}
}
<commit_msg>Support importing libvirt VMs which disappear when stopped.<commit_after>package main

import (
	"encoding/xml"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/Symantec/Dominator/lib/errors"
	"github.com/Symantec/Dominator/lib/json"
	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/srpc"
	proto "github.com/Symantec/Dominator/proto/hypervisor"
)

type devicesInfo struct {
	Interfaces []interfaceType `xml:"interface"`
	Volumes    []volumeType    `xml:"disk"`
}

type driverType struct {
	Name string `xml:"name,attr"`
	Type string `xml:"type,attr"`
}

type interfaceType struct {
	Mac  macType `xml:"mac"`
	Type string  `xml:"type,attr"`
}

type macType struct {
	Address string `xml:"address,attr"`
}

type memoryInfo struct {
	Value uint64 `xml:",chardata"`
	Unit  string `xml:"unit,attr"`
}

type sourceType struct {
	File string `xml:"file,attr"`
}

type vCpuInfo struct {
	Num       uint   `xml:",chardata"`
	Placement string `xml:"placement,attr"`
}

type virshInfoType struct {
	Devices devicesInfo `xml:"devices"`
	Memory  memoryInfo  `xml:"memory"`
	Name    string      `xml:"name"`
	VCpu    vCpuInfo    `xml:"vcpu"`
}

type volumeType struct {
	Device string     `xml:"device,attr"`
	Driver driverType `xml:"driver"`
	Source sourceType `xml:"source"`
	Type   string     `xml:"type,attr"`
}

func importVirshVmSubcommand(args []string, logger log.DebugLogger) error {
	if err := importVirshVm(args[0], args[1], logger); err != nil {
		return fmt.Errorf("Error importing VM: %s", err)
	}
	return nil
}

func ensureDomainIsStopped(domainName string) error {
	state, err := getDomainState(domainName)
	if err != nil {
		return err
	}
	if state == "shut off" {
		return nil
	}
	if state != "running" {
		return fmt.Errorf("domain is in unsupported state \"%s\"", state)
	}
	response, err := askForInputChoice("Cannot import running VM",
		[]string{"shutdown", "quit"})
	if err != nil {
		return err
	}
	if response == "quit" {
		return fmt.Errorf("domain must be shut off but is \"%s\"", state)
	}
	err = exec.Command("virsh", []string{"shutdown", domainName}...).Run()
	if err != nil {
		return fmt.Errorf("error shutting down VM: %s", err)
	}
	for ; ; time.Sleep(time.Second) {
		state, err := getDomainState(domainName)
		if err != nil {
			if strings.Contains(err.Error(), "Domain not found") {
				return nil
			}
			return err
		}
		if state == "shut off" {
			return nil
		}
	}
}

func getDomainState(domainName string) (string, error) {
	cmd := exec.Command("virsh", []string{"domstate", domainName}...)
	stdout, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("error getting VM status: %s",
			err.(*exec.ExitError).Stderr)
	}
	return strings.TrimSpace(string(stdout)), nil
}

func importVirshVm(macAddr, domainName string, logger log.DebugLogger) error {
	ipList, err := net.LookupIP(domainName)
	if err != nil {
		return err
	}
	if len(ipList) != 1 {
		return fmt.Errorf("number of IPs %d != 1", len(ipList))
	}
	tags := vmTags.Copy()
	if _, ok := tags["Name"]; !ok {
		tags["Name"] = domainName
	}
	request := proto.ImportLocalVmRequest{VmInfo: proto.VmInfo{
		Hostname:    domainName,
		OwnerGroups: ownerGroups,
		OwnerUsers:  ownerUsers,
		Tags:        tags,
	}}
	request.VerificationCookie, err = readImportCookie(logger)
	if err != nil {
		return err
	}
	hypervisor := fmt.Sprintf(":%d", *hypervisorPortNum)
	client, err := srpc.DialHTTP("tcp", hypervisor, 0)
	if err != nil {
		return err
	}
	defer client.Close()
	directories, err := listVolumeDirectories(client)
	if err != nil {
		return err
	}
	volumeRoots := make(map[string]string, len(directories))
	for _, dirname := range directories {
		volumeRoots[filepath.Dir(dirname)] = dirname
	}
	cmd := exec.Command("virsh",
		[]string{"dumpxml", "--inactive", domainName}...)
	stdout, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("error getting XML data: %s", err)
	}
	var virshInfo virshInfoType
	if err := xml.Unmarshal(stdout, &virshInfo); err != nil {
		return err
	}
	if macAddr != virshInfo.Devices.Interfaces[0].Mac.Address {
		return fmt.Errorf("MAC address specified: %s != virsh data: %s",
			macAddr, virshInfo.Devices.Interfaces[0].Mac.Address)
	}
	json.WriteWithIndent(os.Stdout, "    ", virshInfo)
	if numIf := len(virshInfo.Devices.Interfaces); numIf != 1 {
		return fmt.Errorf("number of interfaces %d != 1", numIf)
	}
	request.VmInfo.Address = proto.Address{
		IpAddress:  ipList[0],
		MacAddress: virshInfo.Devices.Interfaces[0].Mac.Address,
	}
	switch virshInfo.Memory.Unit {
	case "KiB":
		request.VmInfo.MemoryInMiB = virshInfo.Memory.Value >> 10
	case "MiB":
		request.VmInfo.MemoryInMiB = virshInfo.Memory.Value
	case "GiB":
		request.VmInfo.MemoryInMiB = virshInfo.Memory.Value << 10
	default:
		return fmt.Errorf("unknown memory unit: %s", virshInfo.Memory.Unit)
	}
	request.VmInfo.MilliCPUs = virshInfo.VCpu.Num * 1000
	myPidStr := strconv.Itoa(os.Getpid())
	if err := ensureDomainIsStopped(domainName); err != nil {
		return err
	}
	logger.Debugln(0, "finding volumes")
	for index, inputVolume := range virshInfo.Devices.Volumes {
		if inputVolume.Device != "disk" {
			continue
		}
		var volumeFormat proto.VolumeFormat
		err := volumeFormat.UnmarshalText([]byte(inputVolume.Driver.Type))
		if err != nil {
			return err
		}
		inputFilename := inputVolume.Source.File
		var volumeRoot string
		for dirname := filepath.Dir(inputFilename); ; {
			if vr, ok := volumeRoots[dirname]; ok {
				volumeRoot = vr
				break
			}
			if dirname == "/" {
				break
			}
			dirname = filepath.Dir(dirname)
		}
		if volumeRoot == "" {
			return fmt.Errorf("no Hypervisor directory for: %s", inputFilename)
		}
		outputDirname := filepath.Join(volumeRoot, "import", myPidStr)
		if err := os.MkdirAll(outputDirname, dirPerms); err != nil {
			return err
		}
		defer os.RemoveAll(outputDirname)
		outputFilename := filepath.Join(outputDirname,
			fmt.Sprintf("volume-%d", index))
		if err := os.Link(inputFilename, outputFilename); err != nil {
			return err
		}
		request.VolumeFilenames = append(request.VolumeFilenames,
			outputFilename)
		request.VmInfo.Volumes = append(request.VmInfo.Volumes,
			proto.Volume{Format: volumeFormat})
	}
	requestWithoutSecrets := request
	requestWithoutSecrets.VerificationCookie = nil
	json.WriteWithIndent(os.Stdout, "    ", requestWithoutSecrets)
	var reply proto.GetVmInfoResponse
	logger.Debugln(0, "issuing import RPC")
	err = client.RequestReply("Hypervisor.ImportLocalVm", request, &reply)
	if err != nil {
		return fmt.Errorf("Hypervisor.ImportLocalVm RPC failed: %s", err)
	}
	if err := errors.New(reply.Error); err != nil {
		return fmt.Errorf("Hypervisor failed to import: %s", err)
	}
	logger.Debugln(0, "imported VM")
	for _, dirname := range directories {
		os.RemoveAll(filepath.Join(dirname, "import", myPidStr))
	}
	if err := maybeWatchVm(client, hypervisor, ipList[0], logger); err != nil {
		return err
	}
	if err := askForCommitDecision(client, ipList[0]); err != nil {
		if err == errorCommitAbandoned {
			response, _ := askForInputChoice(
				"Do you want to restart the old VM", []string{"y", "n"})
			if response != "y" {
				return err
			}
			cmd = exec.Command("virsh", "start", domainName)
			if output, err := cmd.CombinedOutput(); err != nil {
				logger.Println(string(output))
				return err
			}
		}
		return err
	}
	defer virshInfo.deleteVolumes()
	cmd = exec.Command("virsh",
		[]string{"undefine", "--managed-save", "--snapshots-metadata",
			"--remove-all-storage", domainName}...)
	if output, err := cmd.CombinedOutput(); err != nil {
		logger.Println(string(output))
		return fmt.Errorf("error destroying old VM: %s", err)
	}
	return nil
}

func (virshInfo virshInfoType) deleteVolumes() {
	for _, inputVolume := range virshInfo.Devices.Volumes {
		if inputVolume.Device != "disk" {
			continue
		}
		os.Remove(inputVolume.Source.File)
	}
}
<|endoftext|>


<commit_before>package statements

import (
	"bytes"
	"text/template"

	"go/format"

	"github.com/cihangir/gene/generators/common"
	"github.com/cihangir/schema"
)

// GenerateTableName generates a simple table name getter function
func GenerateTableName(s *schema.Schema) ([]byte, error) {
	temp := template.New("table_name_statement.tmpl").Funcs(common.TemplateFuncs)

	if _, err := temp.Parse(TableNameTemplate); err != nil {
		return nil, err
	}

	var buf bytes.Buffer

	if err := temp.ExecuteTemplate(&buf, "table_name_statement.tmpl", s); err != nil {
		return nil, err
	}

	return format.Source(buf.Bytes())
}

// TableNameTemplate holds the template for the TableName function
var TableNameTemplate = `
// TableName returns the table name for {{DepunctWithInitialUpper .Title}}
{{$title := Pointerize .Title}}
func ({{$title}} *{{DepunctWithInitialUpper .Title}}) TableName() string {
    return "{{DepunctWithInitialLower .Title}}"
}
`
<commit_msg>Generator: fix table name creation<commit_after>package statements

import (
	"bytes"
	"text/template"

	"go/format"

	"github.com/cihangir/gene/generators/common"
	"github.com/cihangir/schema"
)

// GenerateTableName generates a simple table name getter function
func GenerateTableName(s *schema.Schema) ([]byte, error) {
	temp := template.New("table_name_statement.tmpl").Funcs(common.TemplateFuncs)

	if _, err := temp.Parse(TableNameTemplate); err != nil {
		return nil, err
	}

	var buf bytes.Buffer

	if err := temp.ExecuteTemplate(&buf, "table_name_statement.tmpl", s); err != nil {
		return nil, err
	}

	return format.Source(buf.Bytes())
}

// TableNameTemplate holds the template for the TableName function
var TableNameTemplate = `
// TableName returns the table name for {{DepunctWithInitialUpper .Title}}
{{$title := Pointerize .Title}}
func ({{$title}} *{{DepunctWithInitialUpper .Title}}) TableName() string {
    return "{{ToLower .Title}}"
}
`
<|endoftext|>


<commit_before>package etwlogrus

import (
	"sort"

	"github.com/Microsoft/go-winio/pkg/etw"
	"github.com/sirupsen/logrus"
)

// Hook is a Logrus hook which logs received events to ETW.
type Hook struct {
	provider      *etw.Provider
	closeProvider bool
}

// NewHook registers a new ETW provider and returns a hook to log from it. The
// provider will be closed when the hook is closed.
func NewHook(providerName string) (*Hook, error) {
	provider, err := etw.NewProvider(providerName, nil)
	if err != nil {
		return nil, err
	}

	return &Hook{provider, true}, nil
}

// NewHookFromProvider creates a new hook based on an existing ETW provider. The
// provider will not be closed when the hook is closed.
func NewHookFromProvider(provider *etw.Provider) (*Hook, error) {
	return &Hook{provider, false}, nil
}

// Levels returns the set of levels that this hook wants to receive log entries
// for.
func (h *Hook) Levels() []logrus.Level {
	return []logrus.Level{
		logrus.TraceLevel,
		logrus.DebugLevel,
		logrus.InfoLevel,
		logrus.WarnLevel,
		logrus.ErrorLevel,
		logrus.FatalLevel,
		logrus.PanicLevel,
	}
}

var logrusToETWLevelMap = map[logrus.Level]etw.Level{
	logrus.PanicLevel: etw.LevelAlways,
	logrus.FatalLevel: etw.LevelCritical,
	logrus.ErrorLevel: etw.LevelError,
	logrus.WarnLevel:  etw.LevelWarning,
	logrus.InfoLevel:  etw.LevelInfo,
	logrus.DebugLevel: etw.LevelVerbose,
	logrus.TraceLevel: etw.LevelVerbose,
}

// Fire receives each Logrus entry as it is logged, and logs it to ETW.
func (h *Hook) Fire(e *logrus.Entry) error {
	// Logrus defines more levels than ETW typically uses, but analysis is
	// easiest when using a consistent set of levels across ETW providers, so we
	// map the Logrus levels to ETW levels.
	level := logrusToETWLevelMap[e.Level]
	if !h.provider.IsEnabledForLevel(level) {
		return nil
	}

	// Sort the fields by name so they are consistent in each instance
	// of an event. Otherwise, the fields don't line up in WPA.
	names := make([]string, 0, len(e.Data))
	hasError := false
	for k := range e.Data {
		names := make([]string, 0, len(e.Data))
		if k == logrus.ErrorKey {
			// Always put the error last because it is optional in some events.
			hasError = true
		} else {
			names = append(names, k)
		}
	}
	sort.Strings(names)

	// Reserve extra space for the message field.
	fields := make([]etw.FieldOpt, 0, len(e.Data)+1)
	fields = append(fields, etw.StringField("Message", e.Message))
	for _, k := range names {
		fields = append(fields, etw.SmartField(k, e.Data[k]))
	}
	if hasError {
		fields = append(fields, etw.SmartField(logrus.ErrorKey, e.Data[logrus.ErrorKey]))
	}

	return h.provider.WriteEvent(
		"LogrusEntry",
		etw.WithEventOpts(etw.WithLevel(level)),
		fields)
}

// Close cleans up the hook and closes the ETW provider. If the provder was
// registered by etwlogrus, it will be closed as part of `Close`. If the
// provider was passed in, it will not be closed.
func (h *Hook) Close() error {
	if h.closeProvider {
		return h.provider.Close()
	}
	return nil
}
<commit_msg>etwlogrus: Fix event logging not including fields<commit_after>package etwlogrus

import (
	"sort"

	"github.com/Microsoft/go-winio/pkg/etw"
	"github.com/sirupsen/logrus"
)

// Hook is a Logrus hook which logs received events to ETW.
type Hook struct {
	provider      *etw.Provider
	closeProvider bool
}

// NewHook registers a new ETW provider and returns a hook to log from it. The
// provider will be closed when the hook is closed.
func NewHook(providerName string) (*Hook, error) {
	provider, err := etw.NewProvider(providerName, nil)
	if err != nil {
		return nil, err
	}

	return &Hook{provider, true}, nil
}

// NewHookFromProvider creates a new hook based on an existing ETW provider. The
// provider will not be closed when the hook is closed.
func NewHookFromProvider(provider *etw.Provider) (*Hook, error) {
	return &Hook{provider, false}, nil
}

// Levels returns the set of levels that this hook wants to receive log entries
// for.
func (h *Hook) Levels() []logrus.Level {
	return []logrus.Level{
		logrus.TraceLevel,
		logrus.DebugLevel,
		logrus.InfoLevel,
		logrus.WarnLevel,
		logrus.ErrorLevel,
		logrus.FatalLevel,
		logrus.PanicLevel,
	}
}

var logrusToETWLevelMap = map[logrus.Level]etw.Level{
	logrus.PanicLevel: etw.LevelAlways,
	logrus.FatalLevel: etw.LevelCritical,
	logrus.ErrorLevel: etw.LevelError,
	logrus.WarnLevel:  etw.LevelWarning,
	logrus.InfoLevel:  etw.LevelInfo,
	logrus.DebugLevel: etw.LevelVerbose,
	logrus.TraceLevel: etw.LevelVerbose,
}

// Fire receives each Logrus entry as it is logged, and logs it to ETW.
func (h *Hook) Fire(e *logrus.Entry) error {
	// Logrus defines more levels than ETW typically uses, but analysis is
	// easiest when using a consistent set of levels across ETW providers, so we
	// map the Logrus levels to ETW levels.
	level := logrusToETWLevelMap[e.Level]
	if !h.provider.IsEnabledForLevel(level) {
		return nil
	}

	// Sort the fields by name so they are consistent in each instance
	// of an event. Otherwise, the fields don't line up in WPA.
	names := make([]string, 0, len(e.Data))
	hasError := false
	for k := range e.Data {
		if k == logrus.ErrorKey {
			// Always put the error last because it is optional in some events.
			hasError = true
		} else {
			names = append(names, k)
		}
	}
	sort.Strings(names)

	// Reserve extra space for the message field.
	fields := make([]etw.FieldOpt, 0, len(e.Data)+1)
	fields = append(fields, etw.StringField("Message", e.Message))
	for _, k := range names {
		fields = append(fields, etw.SmartField(k, e.Data[k]))
	}
	if hasError {
		fields = append(fields, etw.SmartField(logrus.ErrorKey, e.Data[logrus.ErrorKey]))
	}

	return h.provider.WriteEvent(
		"LogrusEntry",
		etw.WithEventOpts(etw.WithLevel(level)),
		fields)
}

// Close cleans up the hook and closes the ETW provider. If the provder was
// registered by etwlogrus, it will be closed as part of `Close`. If the
// provider was passed in, it will not be closed.
func (h *Hook) Close() error {
	if h.closeProvider {
		return h.provider.Close()
	}
	return nil
}
<|endoftext|>


<commit_before>package tls

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	"github.com/rancher/dynamiclistener"
	"github.com/rancher/dynamiclistener/cert"
	"github.com/rancher/dynamiclistener/server"
	"github.com/rancher/dynamiclistener/storage/kubernetes"
	"github.com/rancher/norman/types/convert"
	"github.com/rancher/rancher/pkg/settings"
	"github.com/rancher/wrangler-api/pkg/generated/controllers/core"
	corev1controllers "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
)

const (
	rancherCertFile    = "/etc/rancher/ssl/cert.pem"
	rancherKeyFile     = "/etc/rancher/ssl/key.pem"
	rancherCACertsFile = "/etc/rancher/ssl/cacerts.pem"
)

func ListenAndServe(ctx context.Context, restConfig *rest.Config, handler http.Handler, httpsPort, httpPort int, acmeDomains []string, noCACerts bool) error {
	restConfig = rest.CopyConfig(restConfig)
	restConfig.Timeout = 10 * time.Minute

	core, err := core.NewFactoryFromConfig(restConfig)
	if err != nil {
		return err
	}

	opts, err := SetupListener(core.Core().V1().Secret(), acmeDomains, noCACerts)
	if err != nil {
		return err
	}

	migrateConfig(restConfig, opts)

	if err := server.ListenAndServe(ctx, httpsPort, httpPort, handler, opts); err != nil {
		return err
	}

	if err := core.Start(ctx, 5); err != nil {
		return err
	}

	<-ctx.Done()
	return ctx.Err()

}

func migrateConfig(restConfig *rest.Config, opts *server.ListenOpts) {
	defer func() {
		opts.TLSListenerConfig.MaxSANs += len(opts.TLSListenerConfig.SANs)
	}()

	c, err := dynamic.NewForConfig(restConfig)
	if err != nil {
		return
	}

	config, err := c.Resource(schema.GroupVersionResource{
		Group:    "management.cattle.io",
		Version:  "v3",
		Resource: "listenconfigs",
	}).Get("cli-config", metav1.GetOptions{})
	if err != nil {
		return
	}

	known := convert.ToStringSlice(config.Object["knownIps"])
	for k := range convert.ToMapInterface(config.Object["generatedCerts"]) {
		if strings.HasPrefix(k, "local/") {
			continue
		}
		known = append(known, k)
	}

	for _, k := range known {
		found := false
		for _, san := range opts.TLSListenerConfig.SANs {
			if san == k {
				found = true
				break
			}
		}
		if !found {
			opts.TLSListenerConfig.SANs = append(opts.TLSListenerConfig.SANs, k)
		}
	}
}

func SetupListener(secrets corev1controllers.SecretController, acmeDomains []string, noCACerts bool) (*server.ListenOpts, error) {
	caForAgent, noCACerts, opts, err := readConfig(secrets, acmeDomains, noCACerts)
	if err != nil {
		return nil, err
	}

	if noCACerts {
		caForAgent = ""
	} else if caForAgent == "" {
		caCert, caKey, err := kubernetes.LoadOrGenCA(secrets, opts.CANamespace, opts.CAName)
		if err != nil {
			return nil, err
		}
		caForAgent = string(cert.EncodeCertPEM(caCert))
		opts.CA = caCert
		opts.CAKey = caKey
	}

	caForAgent = strings.TrimSpace(caForAgent)
	if settings.CACerts.Get() != caForAgent {
		if err := settings.CACerts.Set(caForAgent); err != nil {
			return nil, err
		}
	}

	return opts, nil
}

func readConfig(secrets corev1controllers.SecretController, acmeDomains []string, noCACerts bool) (string, bool, *server.ListenOpts, error) {
	var (
		ca  string
		err error
	)

	tlsConfig, err := BaseTLSConfig()
	if err != nil {
		return "", noCACerts, nil, err
	}

	expiration, err := strconv.Atoi(settings.RotateCertsIfExpiringInDays.Get())
	if err != nil {
		return "", noCACerts, nil, errors.Wrapf(err, "parsing %s", settings.RotateCertsIfExpiringInDays.Get())
	}

	opts := &server.ListenOpts{
		Secrets:       secrets,
		CAName:        "tls-rancher",
		CANamespace:   "cattle-system",
		CertNamespace: "cattle-system",
		AcmeDomains:   acmeDomains,
		TLSListenerConfig: dynamiclistener.Config{
			TLSConfig:             tlsConfig,
			ExpirationDaysCheck:   expiration,
			MaxSANs:               6,
			CloseConnOnCertChange: true,
		},
	}

	// ACME / Let's Encrypt
	// If --acme-domain is set, configure and return
	if len(acmeDomains) > 0 {
		return "", true, opts, nil
	}

	// Mounted certificates
	// If certificate file/key are set
	certFileExists := fileExists(rancherCertFile)
	keyFileExists := fileExists(rancherKeyFile)

	// If certificate file exists but not certificate key, or other way around, error out
	if (certFileExists && !keyFileExists) || (!certFileExists && keyFileExists) {
		return "", noCACerts, nil, fmt.Errorf("invalid SSL configuration found, please set both certificate file and certificate key file (one is missing)")
	}

	caFileExists := fileExists(rancherCACertsFile)

	// If certificate file and certificate key file exists, load files into listenConfig
	if certFileExists && keyFileExists {
		cert, err := tls.LoadX509KeyPair(rancherCertFile, rancherKeyFile)
		if err != nil {
			return "", noCACerts, nil, err
		}
		opts.TLSListenerConfig.TLSConfig.Certificates = []tls.Certificate{cert}

		// Selfsigned needs cacerts, recognized CA needs --no-cacerts but can't be used together
		if (caFileExists && noCACerts) || (!caFileExists && !noCACerts) {
			return "", noCACerts, nil, fmt.Errorf("invalid SSL configuration found, please set cacerts when using self signed certificates or use --no-cacerts when using certificates from a recognized Certificate Authority, do not use both at the same time")
		}
		// Load cacerts if exists
		if caFileExists {
			ca, err = readPEM(rancherCACertsFile)
			if err != nil {
				return "", noCACerts, nil, err
			}
		}
		return ca, noCACerts, opts, nil
	}

	// External termination
	// We need to check if cacerts is passed or if --no-cacerts is used (when not providing certificate file and key)
	// If cacerts is passed
	if caFileExists {
		// We can't have --no-cacerts
		if noCACerts {
			return "", noCACerts, nil, fmt.Errorf("invalid SSL configuration found, please set cacerts when using self signed certificates or use --no-cacerts when using certificates from a recognized Certificate Authority, do not use both at the same time")
		}
		ca, err = readPEM(rancherCACertsFile)
		if err != nil {
			return "", noCACerts, nil, err
		}
	}

	// No certificates mounted or only --no-cacerts used
	return ca, noCACerts, opts, nil
}

func fileExists(path string) bool {
	if _, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

func readPEM(path string) (string, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}

	return string(content), nil
}
<commit_msg>Add back default SANs<commit_after>package tls

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	"github.com/rancher/dynamiclistener"
	"github.com/rancher/dynamiclistener/cert"
	"github.com/rancher/dynamiclistener/server"
	"github.com/rancher/dynamiclistener/storage/kubernetes"
	"github.com/rancher/norman/types/convert"
	"github.com/rancher/rancher/pkg/settings"
	"github.com/rancher/wrangler-api/pkg/generated/controllers/core"
	corev1controllers "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
)

const (
	rancherCertFile    = "/etc/rancher/ssl/cert.pem"
	rancherKeyFile     = "/etc/rancher/ssl/key.pem"
	rancherCACertsFile = "/etc/rancher/ssl/cacerts.pem"
)

func ListenAndServe(ctx context.Context, restConfig *rest.Config, handler http.Handler, httpsPort, httpPort int, acmeDomains []string, noCACerts bool) error {
	restConfig = rest.CopyConfig(restConfig)
	restConfig.Timeout = 10 * time.Minute

	core, err := core.NewFactoryFromConfig(restConfig)
	if err != nil {
		return err
	}

	opts, err := SetupListener(core.Core().V1().Secret(), acmeDomains, noCACerts)
	if err != nil {
		return err
	}

	migrateConfig(restConfig, opts)

	if err := server.ListenAndServe(ctx, httpsPort, httpPort, handler, opts); err != nil {
		return err
	}

	if err := core.Start(ctx, 5); err != nil {
		return err
	}

	<-ctx.Done()
	return ctx.Err()

}

func migrateConfig(restConfig *rest.Config, opts *server.ListenOpts) {
	defer func() {
		opts.TLSListenerConfig.MaxSANs += len(opts.TLSListenerConfig.SANs)
	}()

	c, err := dynamic.NewForConfig(restConfig)
	if err != nil {
		return
	}

	config, err := c.Resource(schema.GroupVersionResource{
		Group:    "management.cattle.io",
		Version:  "v3",
		Resource: "listenconfigs",
	}).Get("cli-config", metav1.GetOptions{})
	if err != nil {
		return
	}

	known := convert.ToStringSlice(config.Object["knownIps"])
	for k := range convert.ToMapInterface(config.Object["generatedCerts"]) {
		if strings.HasPrefix(k, "local/") {
			continue
		}
		known = append(known, k)
	}

	for _, k := range known {
		found := false
		for _, san := range opts.TLSListenerConfig.SANs {
			if san == k {
				found = true
				break
			}
		}
		if !found {
			opts.TLSListenerConfig.SANs = append(opts.TLSListenerConfig.SANs, k)
		}
	}
}

func SetupListener(secrets corev1controllers.SecretController, acmeDomains []string, noCACerts bool) (*server.ListenOpts, error) {
	caForAgent, noCACerts, opts, err := readConfig(secrets, acmeDomains, noCACerts)
	if err != nil {
		return nil, err
	}

	if noCACerts {
		caForAgent = ""
	} else if caForAgent == "" {
		caCert, caKey, err := kubernetes.LoadOrGenCA(secrets, opts.CANamespace, opts.CAName)
		if err != nil {
			return nil, err
		}
		caForAgent = string(cert.EncodeCertPEM(caCert))
		opts.CA = caCert
		opts.CAKey = caKey
	}

	caForAgent = strings.TrimSpace(caForAgent)
	if settings.CACerts.Get() != caForAgent {
		if err := settings.CACerts.Set(caForAgent); err != nil {
			return nil, err
		}
	}

	return opts, nil
}

func readConfig(secrets corev1controllers.SecretController, acmeDomains []string, noCACerts bool) (string, bool, *server.ListenOpts, error) {
	var (
		ca  string
		err error
	)

	tlsConfig, err := BaseTLSConfig()
	if err != nil {
		return "", noCACerts, nil, err
	}

	expiration, err := strconv.Atoi(settings.RotateCertsIfExpiringInDays.Get())
	if err != nil {
		return "", noCACerts, nil, errors.Wrapf(err, "parsing %s", settings.RotateCertsIfExpiringInDays.Get())
	}

	sans := []string{"localhost", "127.0.0.1"}
	ip, err := net.ChooseHostInterface()
	if err == nil {
		sans = append(sans, ip.String())
	}

	opts := &server.ListenOpts{
		Secrets:       secrets,
		CAName:        "tls-rancher",
		CANamespace:   "cattle-system",
		CertNamespace: "cattle-system",
		AcmeDomains:   acmeDomains,
		TLSListenerConfig: dynamiclistener.Config{
			TLSConfig:             tlsConfig,
			ExpirationDaysCheck:   expiration,
			SANs:                  sans,
			MaxSANs:               20,
			CloseConnOnCertChange: true,
		},
	}

	// ACME / Let's Encrypt
	// If --acme-domain is set, configure and return
	if len(acmeDomains) > 0 {
		return "", true, opts, nil
	}

	// Mounted certificates
	// If certificate file/key are set
	certFileExists := fileExists(rancherCertFile)
	keyFileExists := fileExists(rancherKeyFile)

	// If certificate file exists but not certificate key, or other way around, error out
	if (certFileExists && !keyFileExists) || (!certFileExists && keyFileExists) {
		return "", noCACerts, nil, fmt.Errorf("invalid SSL configuration found, please set both certificate file and certificate key file (one is missing)")
	}

	caFileExists := fileExists(rancherCACertsFile)

	// If certificate file and certificate key file exists, load files into listenConfig
	if certFileExists && keyFileExists {
		cert, err := tls.LoadX509KeyPair(rancherCertFile, rancherKeyFile)
		if err != nil {
			return "", noCACerts, nil, err
		}
		opts.TLSListenerConfig.TLSConfig.Certificates = []tls.Certificate{cert}

		// Selfsigned needs cacerts, recognized CA needs --no-cacerts but can't be used together
		if (caFileExists && noCACerts) || (!caFileExists && !noCACerts) {
			return "", noCACerts, nil, fmt.Errorf("invalid SSL configuration found, please set cacerts when using self signed certificates or use --no-cacerts when using certificates from a recognized Certificate Authority, do not use both at the same time")
		}
		// Load cacerts if exists
		if caFileExists {
			ca, err = readPEM(rancherCACertsFile)
			if err != nil {
				return "", noCACerts, nil, err
			}
		}
		return ca, noCACerts, opts, nil
	}

	// External termination
	// We need to check if cacerts is passed or if --no-cacerts is used (when not providing certificate file and key)
	// If cacerts is passed
	if caFileExists {
		// We can't have --no-cacerts
		if noCACerts {
			return "", noCACerts, nil, fmt.Errorf("invalid SSL configuration found, please set cacerts when using self signed certificates or use --no-cacerts when using certificates from a recognized Certificate Authority, do not use both at the same time")
		}
		ca, err = readPEM(rancherCACertsFile)
		if err != nil {
			return "", noCACerts, nil, err
		}
	}

	// No certificates mounted or only --no-cacerts used
	return ca, noCACerts, opts, nil
}

func fileExists(path string) bool {
	if _, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

func readPEM(path string) (string, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}

	return string(content), nil
}
<|endoftext|>


<commit_before>package rainsd

import (
	"fmt"
	"rains/rainsSiglib"
	"rains/rainslib"

	log "github.com/inconshreveable/log15"
)

//incoming messages are buffered in one of these channels until they get processed by a worker go routine
//the prioChannel only contains incoming sections in response to a delegation query issued by this server.
var prioChannel chan msgSectionSender
var normalChannel chan msgSectionSender
var notificationChannel chan msgSectionSender

//These channels limit the number of go routines working on the different queues to avoid memory exhaustion.
var prioWorkers chan struct{}
var normalWorkers chan struct{}
var notificationWorkers chan struct{}

//activeTokens stores the tokens of active delegation queries.
var activeTokens activeTokenCache

//capabilities stores known hashes of capabilities and for each connInfo what capability the communication partner has.
var capabilities capabilityCache

func initInbox() error {
	//init Channels
	prioChannel = make(chan msgSectionSender, Config.PrioBufferSize)
	normalChannel = make(chan msgSectionSender, Config.NormalBufferSize)
	notificationChannel = make(chan msgSectionSender, Config.NotificationBufferSize)

	//init max amount of concurrent workers
	prioWorkers = make(chan struct{}, Config.PrioWorkerCount)
	normalWorkers = make(chan struct{}, Config.NormalWorkerCount)
	notificationWorkers = make(chan struct{}, Config.NotificationWorkerCount)

	//init Capability Cache
	var err error
	capabilities, err = createCapabilityCache(Config.CapabilitiesCacheSize, Config.PeerToCapCacheSize)
	if err != nil {
		log.Error("Cannot create connCache", "error", err)
		return err
	}

	activeTokens = createActiveTokenCache(Config.ActiveTokenCacheSize)

	go workPrio()
	go workNotification()
	go workBoth()

	return nil
}

//deliver pushes all incoming messages to the prio or normal channel.
//A message is added to the priority channel if it is the response to a non-expired delegation query
func deliver(message []byte, sender rainslib.ConnInfo) {
	//check message length
	if uint(len(message)) > Config.MaxMsgByteLength {
		token, _ := msgParser.Token(message)
		sendNotificationMsg(token, sender, rainslib.MsgTooLarge)
		return
	}
	//FIXME CFE first extract only SubjectZone to determine if zone is on blacklist and if so drop it instantly
	msg, err := msgParser.Decode(message)
	if err != nil {
		sendNotificationMsg(msg.Token, sender, rainslib.BadMessage)
		return
	}
	log.Info("Parsed Message", "msg", msg)

	processCapability(msg.Capabilities, sender, msg.Token)

	//FIXME CFE get infrastructure key from cache and if not present send a infra query, add a new cache for whole messages to wait for missing public keys
	if !rainsSiglib.CheckMessageSignatures(&msg, rainslib.PublicKey{}, sigEncoder) {
		return
	}

	//handle message content
	for _, m := range msg.Content {
		switch m := m.(type) {
		case *rainslib.AssertionSection, *rainslib.ShardSection, *rainslib.ZoneSection, *rainslib.AddressAssertionSection, *rainslib.AddressZoneSection:
			addMsgSectionToQueue(m, msg.Token, sender)
		case *rainslib.QuerySection:
			addQueryToQueue(m.Token, msg.Token, m, sender)
		case *rainslib.AddressQuerySection:
			addQueryToQueue(m.Token, msg.Token, m, sender)
		case *rainslib.NotificationSection:
			addNotificationToQueue(m, msg.Token, sender)
		default:
			log.Warn(fmt.Sprintf("unsupported message section type %T", m))
			return
		}
	}
}

//processCapability processes capabilities and sends a notification back to the sender if the hash is not understood.
func processCapability(caps []rainslib.Capability, sender rainslib.ConnInfo, token rainslib.Token) {
	log.Debug("Process capabilities", "capabilities", caps)
	if len(caps) > 0 {
		//TODO CFE determine when an incoming capability is represented as a hash
		log.Error("Not yet implemented")
		isHash := false
		if isHash {
			if caps, ok := capabilities.GetFromHash([]byte(caps[0])); ok {
				capabilities.Add(sender, caps)
				handleCapabilities(caps)
			} else {
				sendNotificationMsg(token, sender, rainslib.CapHashNotKnown)
			}
		} else {
			capabilities.Add(sender, caps)
			handleCapabilities(caps)
		}
	}
}

//handleCapabilities takes appropriate actions depending on the capability of the communication partner
func handleCapabilities(caps []rainslib.Capability) {
	log.Warn("Capability handling is not yet implemented")
	/*for _, capa := range caps {
		switch capa {
		case rainslib.TLSOverTCP:
			//TODO CFE impl
		case rainslib.NoCapability:
			//Do nothing
		default:
			log.Warn("Sent capability value does not match know capability", "rcvCaps", capa)
		}
	}*/
}

//sendNotificationMsg sends a notification message to the sender with the given notificationType.
//If an error occurs during parsing no message is sent and the error is logged.
func sendNotificationMsg(token rainslib.Token, sender rainslib.ConnInfo, notificationType rainslib.NotificationType) {
	msg, err := CreateNotificationMsg(token, notificationType, "")
	if err != nil {
		log.Warn("Error during encoding of a message containing a notification section", "error", err)
		return
	}
	sendTo(msg, sender)
}

//addMsgSectionToQueue looks up the token of the msg in the activeTokens cache and if present adds the msg section to the prio cache, otherwise to the normal cache.
func addMsgSectionToQueue(msgSection rainslib.MessageSection, tok rainslib.Token, sender rainslib.ConnInfo) {
	if activeTokens.IsPriority(tok) {
		log.Debug("add section with signature to priority queue", "token", tok)
		prioChannel <- msgSectionSender{Sender: sender, Section: msgSection, Token: tok}
	} else {
		log.Debug("add section with signature to normal queue", "token", tok)
		normalChannel <- msgSectionSender{Sender: sender, Section: msgSection, Token: tok}
	}
}

//addQueryToQueue checks that the token of the message and of the query section are the same and if so adds it to a queue
func addQueryToQueue(queryToken, msgToken rainslib.Token, section rainslib.MessageSection, sender rainslib.ConnInfo) {
	if msgToken == queryToken {
		log.Debug("add query to normal queue")
		normalChannel <- msgSectionSender{Sender: sender, Section: section, Token: msgToken}
	} else {
		log.Warn("Token of message and query section do not match.", "msgToken", msgToken, "querySectionToken", queryToken)
		sendNotificationMsg(msgToken, sender, rainslib.BadMessage)
		sendNotificationMsg(queryToken, sender, rainslib.BadMessage)
	}
}

//addNotificationToQueue adds a notification section to the notification queue
func addNotificationToQueue(msg *rainslib.NotificationSection, tok rainslib.Token, sender rainslib.ConnInfo) {
	log.Info("Add notification to notification queue", "token", tok)
	notificationChannel <- msgSectionSender{Sender: sender, Section: msg, Token: msg.Token}
}

//workBoth works on the prioChannel and on the normalChannel. A worker only fetches a message from the normalChannel if the prioChannel is empty.
//the channel normalWorkers enforces a maximum number of go routines working on the prioChannel and normalChannel.
func workBoth() {
	for {
		normalWorkers <- struct{}{}
		select {
		case msg := <-prioChannel:
			go prioWorkerHandler(msg)
			continue
		default:
			//do nothing
		}
		select {
		case msg := <-normalChannel:
			go normalWorkerHandler(msg)
		default:
			<-normalWorkers
		}
	}
}

//normalWorkerHandler handles sections on the normalChannel
func normalWorkerHandler(msg msgSectionSender) {
	verify(msg)
	<-normalWorkers
}

//workPrio works on the prioChannel. It waits on the prioChannel and creates a new go routine which handles the section.
//the channel prioWorkers enforces a maximum number of go routines working on the prioChannel.
//The prio channel is necessary to avoid a blocking of the server. e.g. in the following unrealistic scenario
//1) normal queue fills up with non delegation queries which all are missing a public key
//2) The non-delegation queries get processed by the normalWorkers and added to the pendingSignature cache
//3) For each non-delegation query that gets taken off the queue a new non-delegation query or expired delegation query wins against all waiting valid delegation-queries.
//4) Then although the server is working all the time, no section is added to the caches.
func workPrio() {
	for {
		prioWorkers <- struct{}{}
		msg := <-prioChannel
		go prioWorkerHandler(msg)
	}
}

//prioWorkerHandler handles sections on the prioChannel
func prioWorkerHandler(msg msgSectionSender) {
	verify(msg)
	<-prioWorkers
}

//workNotification works on the notificationChannel. It waits on the notificationChannel and creates a new go routine which handles the notification.
//the channel notificationWorkers enforces a maximum number of go routines working on the notificationChannel
func workNotification() {
	for {
		notificationWorkers <- struct{}{}
		msg := <-notificationChannel
		go handleNotification(msg)
	}
}

//handleNotification works on notificationChannel.
func handleNotification(msg msgSectionSender) {
	notify(msg)
	<-notificationWorkers
}
<commit_msg>ignore message signatures<commit_after>package rainsd

import (
	"fmt"
	"rains/rainsSiglib"
	"rains/rainslib"

	log "github.com/inconshreveable/log15"
)

//incoming messages are buffered in one of these channels until they get processed by a worker go routine
//the prioChannel only contains incoming sections in response to a delegation query issued by this server.
var prioChannel chan msgSectionSender
var normalChannel chan msgSectionSender
var notificationChannel chan msgSectionSender

//These channels limit the number of go routines working on the different queues to avoid memory exhaustion.
var prioWorkers chan struct{}
var normalWorkers chan struct{}
var notificationWorkers chan struct{}

//activeTokens stores the tokens of active delegation queries.
var activeTokens activeTokenCache

//capabilities stores known hashes of capabilities and for each connInfo what capability the communication partner has.
var capabilities capabilityCache

func initInbox() error {
	//init Channels
	prioChannel = make(chan msgSectionSender, Config.PrioBufferSize)
	normalChannel = make(chan msgSectionSender, Config.NormalBufferSize)
	notificationChannel = make(chan msgSectionSender, Config.NotificationBufferSize)

	//init max amount of concurrent workers
	prioWorkers = make(chan struct{}, Config.PrioWorkerCount)
	normalWorkers = make(chan struct{}, Config.NormalWorkerCount)
	notificationWorkers = make(chan struct{}, Config.NotificationWorkerCount)

	//init Capability Cache
	var err error
	capabilities, err = createCapabilityCache(Config.CapabilitiesCacheSize, Config.PeerToCapCacheSize)
	if err != nil {
		log.Error("Cannot create connCache", "error", err)
		return err
	}

	activeTokens = createActiveTokenCache(Config.ActiveTokenCacheSize)

	go workPrio()
	go workNotification()
	go workBoth()

	return nil
}

//deliver pushes all incoming messages to the prio or normal channel.
//A message is added to the priority channel if it is the response to a non-expired delegation query
func deliver(message []byte, sender rainslib.ConnInfo) {
	//check message length
	if uint(len(message)) > Config.MaxMsgByteLength {
		token, _ := msgParser.Token(message)
		sendNotificationMsg(token, sender, rainslib.MsgTooLarge)
		return
	}
	//FIXME CFE first extract only SubjectZone to determine if zone is on blacklist and if so drop it instantly
	msg, err := msgParser.Decode(message)
	if err != nil {
		sendNotificationMsg(msg.Token, sender, rainslib.BadMessage)
		return
	}
	log.Info("Parsed Message", "msg", msg)

	processCapability(msg.Capabilities, sender, msg.Token)

	//FIXME CFE get infrastructure key from cache and if not present send a infra query, add a new cache for whole messages to wait for missing public keys
	if !rainsSiglib.CheckMessageSignatures(&msg, rainslib.PublicKey{}, sigEncoder) {
		//return TODO CFE uncomment when we can obtain a public key
	}

	//handle message content
	for _, m := range msg.Content {
		switch m := m.(type) {
		case *rainslib.AssertionSection, *rainslib.ShardSection, *rainslib.ZoneSection, *rainslib.AddressAssertionSection, *rainslib.AddressZoneSection:
			addMsgSectionToQueue(m, msg.Token, sender)
		case *rainslib.QuerySection:
			addQueryToQueue(m.Token, msg.Token, m, sender)
		case *rainslib.AddressQuerySection:
			addQueryToQueue(m.Token, msg.Token, m, sender)
		case *rainslib.NotificationSection:
			addNotificationToQueue(m, msg.Token, sender)
		default:
			log.Warn(fmt.Sprintf("unsupported message section type %T", m))
			return
		}
	}
}

//processCapability processes capabilities and sends a notification back to the sender if the hash is not understood.
func processCapability(caps []rainslib.Capability, sender rainslib.ConnInfo, token rainslib.Token) {
	log.Debug("Process capabilities", "capabilities", caps)
	if len(caps) > 0 {
		//TODO CFE determine when an incoming capability is represented as a hash
		log.Error("Not yet implemented")
		isHash := false
		if isHash {
			if caps, ok := capabilities.GetFromHash([]byte(caps[0])); ok {
				capabilities.Add(sender, caps)
				handleCapabilities(caps)
			} else {
				sendNotificationMsg(token, sender, rainslib.CapHashNotKnown)
			}
		} else {
			capabilities.Add(sender, caps)
			handleCapabilities(caps)
		}
	}
}

//handleCapabilities takes appropriate actions depending on the capability of the communication partner
func handleCapabilities(caps []rainslib.Capability) {
	log.Warn("Capability handling is not yet implemented")
	/*for _, capa := range caps {
		switch capa {
		case rainslib.TLSOverTCP:
			//TODO CFE impl
		case rainslib.NoCapability:
			//Do nothing
		default:
			log.Warn("Sent capability value does not match know capability", "rcvCaps", capa)
		}
	}*/
}

//sendNotificationMsg sends a notification message to the sender with the given notificationType.
//If an error occurs during parsing no message is sent and the error is logged.
func sendNotificationMsg(token rainslib.Token, sender rainslib.ConnInfo, notificationType rainslib.NotificationType) {
	msg, err := CreateNotificationMsg(token, notificationType, "")
	if err != nil {
		log.Warn("Error during encoding of a message containing a notification section", "error", err)
		return
	}
	sendTo(msg, sender)
}

//addMsgSectionToQueue looks up the token of the msg in the activeTokens cache and if present adds the msg section to the prio cache, otherwise to the normal cache.
func addMsgSectionToQueue(msgSection rainslib.MessageSection, tok rainslib.Token, sender rainslib.ConnInfo) {
	if activeTokens.IsPriority(tok) {
		log.Debug("add section with signature to priority queue", "token", tok)
		prioChannel <- msgSectionSender{Sender: sender, Section: msgSection, Token: tok}
	} else {
		log.Debug("add section with signature to normal queue", "token", tok)
		normalChannel <- msgSectionSender{Sender: sender, Section: msgSection, Token: tok}
	}
}

//addQueryToQueue checks that the token of the message and of the query section are the same and if so adds it to a queue
func addQueryToQueue(queryToken, msgToken rainslib.Token, section rainslib.MessageSection, sender rainslib.ConnInfo) {
	if msgToken == queryToken {
		log.Debug("add query to normal queue")
		normalChannel <- msgSectionSender{Sender: sender, Section: section, Token: msgToken}
	} else {
		log.Warn("Token of message and query section do not match.", "msgToken", msgToken, "querySectionToken", queryToken)
		sendNotificationMsg(msgToken, sender, rainslib.BadMessage)
		sendNotificationMsg(queryToken, sender, rainslib.BadMessage)
	}
}

//addNotificationToQueue adds a notification section to the notification queue
func addNotificationToQueue(msg *rainslib.NotificationSection, tok rainslib.Token, sender rainslib.ConnInfo) {
	log.Info("Add notification to notification queue", "token", tok)
	notificationChannel <- msgSectionSender{Sender: sender, Section: msg, Token: msg.Token}
}

//workBoth works on the prioChannel and on the normalChannel. A worker only fetches a message from the normalChannel if the prioChannel is empty.
//the channel normalWorkers enforces a maximum number of go routines working on the prioChannel and normalChannel.
func workBoth() {
	for {
		normalWorkers <- struct{}{}
		select {
		case msg := <-prioChannel:
			go prioWorkerHandler(msg)
			continue
		default:
			//do nothing
		}
		select {
		case msg := <-normalChannel:
			go normalWorkerHandler(msg)
		default:
			<-normalWorkers
		}
	}
}

//normalWorkerHandler handles sections on the normalChannel
func normalWorkerHandler(msg msgSectionSender) {
	verify(msg)
	<-normalWorkers
}

//workPrio works on the prioChannel. It waits on the prioChannel and creates a new go routine which handles the section.
//the channel prioWorkers enforces a maximum number of go routines working on the prioChannel.
//The prio channel is necessary to avoid a blocking of the server. e.g. in the following unrealistic scenario
//1) normal queue fills up with non delegation queries which all are missing a public key
//2) The non-delegation queries get processed by the normalWorkers and added to the pendingSignature cache
//3) For each non-delegation query that gets taken off the queue a new non-delegation query or expired delegation query wins against all waiting valid delegation-queries.
//4) Then although the server is working all the time, no section is added to the caches.
func workPrio() {
	for {
		prioWorkers <- struct{}{}
		msg := <-prioChannel
		go prioWorkerHandler(msg)
	}
}

//prioWorkerHandler handles sections on the prioChannel
func prioWorkerHandler(msg msgSectionSender) {
	verify(msg)
	<-prioWorkers
}

//workNotification works on the notificationChannel. It waits on the notificationChannel and creates a new go routine which handles the notification.
//the channel notificationWorkers enforces a maximum number of go routines working on the notificationChannel
func workNotification() {
	for {
		notificationWorkers <- struct{}{}
		msg := <-notificationChannel
		go handleNotification(msg)
	}
}

//handleNotification works on notificationChannel.
func handleNotification(msg msgSectionSender) {
	notify(msg)
	<-notificationWorkers
}
<|endoftext|>


<commit_before>package mqutil

import (
	"fmt"
	"io"
	"log"
	"os"
)

func NewLogger(out io.Writer) *log.Logger {
	return log.New(out, "", (log.Ldate | log.Lmicroseconds | log.Lshortfile))
}

func NewStdLogger() *log.Logger {
	return NewLogger(os.Stdout)
}

func NewFileLogger(path string) *log.Logger {
	f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Printf("Can't open %s, err: %s", path, err.Error())
		return nil
	}
	return NewLogger(f)
}

// There is only one logger per process.
var Logger *log.Logger

// Whether verbose mose is on
var Verbose bool
<commit_msg>truncate log<commit_after>package mqutil

import (
	"fmt"
	"io"
	"log"
	"os"
)

func NewLogger(out io.Writer) *log.Logger {
	return log.New(out, "", (log.Ldate | log.Lmicroseconds | log.Lshortfile))
}

func NewStdLogger() *log.Logger {
	return NewLogger(os.Stdout)
}

func NewFileLogger(path string) *log.Logger {
	f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Printf("Can't open %s, err: %s", path, err.Error())
		return nil
	}
	return NewLogger(f)
}

// There is only one logger per process.
var Logger *log.Logger

// Whether verbose mose is on
var Verbose bool
<|endoftext|>


<commit_before>/*
 * Copyright 2015 1&1 Internet AG, http://1und1.de . All rights reserved. Licensed under the Apache v2 License.
 */

package oneandone_cloudserver_api

import (
	"github.com/docker/machine/log"
	"net/http"
	"time"
)

type FirewallPolicy struct {
	withId
	withName
	withDescription
	// should be fixed: Status        Status                   `json:"status"`
	Status        string                   `json:"state"`
	DefaultPolicy int                      `json:"default"`
	Rules         []FirewallPolicyRules    `json:"rules"`
	ServerIps     []FirewallPolicyServerIp `json:"server_ips"`
	withApi
}

type FirewallPolicyRules struct {
	withId
	Protocol string `json:"protocol"`
	PortFrom *int   `json:"port_from"`
	PortTo   *int   `json:"port_to"`
	SourceIp string `json:"source"`
}

type FirewallPolicyServerIp struct {
	withId
	Ip         string `json:"ip"`
	ServerName string `json:"server_name"`
}

type FirewallPolicyCreateData struct {
	Name        string                          `json:"name"`
	Description string                          `json:"description"`
	Rules       []FirewallPolicyRulesCreateData `json:"rules"`
}

type FirewallPolicyRulesCreateData struct {
	Protocol string `json:"protocol"`
	PortFrom *int   `json:"port_from"`
	PortTo   *int   `json:"port_to"`
	SourceIp string `json:"source"`
}

type FirewallPolicyAddIpsData struct {
	ServerIps []string `json:"server_ips"`
}

// GET /firewall_policies
func (api *API) GetFirewallPolicies() ([]FirewallPolicy, error) {
	log.Debug("requesting information about firewall policies")
	result := []FirewallPolicy{}
	err := api.Client.Get(createUrl(api, "firewall_policies"), &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	for index, _ := range result {
		result[index].api = api
	}
	return result, nil
}

// POST /firewall_policies
func (api *API) CreateFirewallPolicy(configuration FirewallPolicyCreateData) (*FirewallPolicy, error) {
	log.Debug("requesting to create a new firewall policy")
	result := new(FirewallPolicy)
	err := api.Client.Post(createUrl(api, "firewall_policies"), configuration, &result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = api
	return result, nil
}

// GET /firewall_policies/{id}
func (api *API) GetFirewallPolicy(Id string) (*FirewallPolicy, error) {
	log.Debug("requesting to about firewall policy ", Id)
	result := new(FirewallPolicy)
	err := api.Client.Get(createUrl(api, "firewall_policies", Id), &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	result.api = api
	return result, nil

}

// DELETE /firewall_policies/{id}
func (fwp *FirewallPolicy) Delete() (*FirewallPolicy, error) {
	log.Debug("Requested to delete firewall policy ", fwp.Id)
	result := new(FirewallPolicy)
	err := fwp.api.Client.Delete(createUrl(fwp.api, "firewall_policies", fwp.Id), &result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = fwp.api
	return result, nil
}

// PUT /firewall_policies/{id}

// GET /firewall_policies/{id}/server_ips

// PUT /firewall_policies/{id}/server_ips
func (fwp *FirewallPolicy) AddServerIp(ipId string) (*FirewallPolicy, error) {
	log.Debugf("Requested to apply firewall policy '%v' to ip '%v'", fwp.Id, ipId)
	result := new(FirewallPolicy)
	request := FirewallPolicyAddIpsData{
		ServerIps: []string{ipId},
	}
	err := fwp.api.Client.Put(createUrl(fwp.api, "firewall_policies", fwp.Id, "server_ips"), request, result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = fwp.api
	return result, nil
}

// GET /firewall_policies/{id}/server_ips/{id}

// DELETE /firewall_policies/{id}/server_ips/{id}
func (fwp *FirewallPolicy) DeleteServerIp(ipId string) (*FirewallPolicy, error) {
	log.Debugf("Requested to remove firewall policy '%v' from ip '%v'", fwp.Id, ipId)
	result := new(FirewallPolicy)
	err := fwp.api.Client.Delete(createUrl(fwp.api, "firewall_policies", fwp.Id, "server_ips", ipId), result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = fwp.api
	return result, nil
}

// GET /firewall_policies/{id}/rules

// PUT /firewall_policies/{id}/rules

// GET /firewall_policies/{id}/rules/{id}

// DELETE /firewall_policies/{id}/rules/{id}

func (fwp *FirewallPolicy) exists() (bool, error) {
	_, err := fwp.api.GetFirewallPolicy(fwp.Id)
	if err == nil {
		return true, nil
	} else {
		if apiError, ok := err.(ApiError); ok && apiError.httpStatusCode == http.StatusNotFound {
			return false, nil
		} else {
			return false, err
		}
	}
}

func (fwp *FirewallPolicy) WaitUntilDeleted() error {
	exists := true
	var err error
	for exists {
		exists, err = fwp.exists()
		if err != nil {
			return err
		}
		log.Debugf("Wait for firewall policy: '%s' to be deleted", fwp.Id)
		time.Sleep(5 * time.Second)
	}
	log.Infof("The firewall policy: '%s' is now deleted", fwp.Id)
	return nil
}

func (fwp *FirewallPolicy) WaitForState(Id string, State string) error {
	fw, err := fwp.api.GetFirewallPolicy(fwp.Id)
	if err != nil {
		return err
	}
	for fw.Status != State {
		time.Sleep(5 * time.Second)
		fw, err := fwp.api.GetFirewallPolicy(Id)
		if err != nil {
			return err
		}
		if fw.Status == State {
			log.Infof("The firewall policy is now in the expected state: '%s'", State)
			return nil
		} else {
			log.Debugf("Wait for expected status: '%s' current: '%s'", State, fw.Status)
		}
	}
	return nil
}
<commit_msg>Remove old id parameter in WaitForState firewall policy<commit_after>/*
 * Copyright 2015 1&1 Internet AG, http://1und1.de . All rights reserved. Licensed under the Apache v2 License.
 */

package oneandone_cloudserver_api

import (
	"github.com/docker/machine/log"
	"net/http"
	"time"
)

type FirewallPolicy struct {
	withId
	withName
	withDescription
	// should be fixed: Status        Status                   `json:"status"`
	Status        string                   `json:"state"`
	DefaultPolicy int                      `json:"default"`
	Rules         []FirewallPolicyRules    `json:"rules"`
	ServerIps     []FirewallPolicyServerIp `json:"server_ips"`
	withApi
}

type FirewallPolicyRules struct {
	withId
	Protocol string `json:"protocol"`
	PortFrom *int   `json:"port_from"`
	PortTo   *int   `json:"port_to"`
	SourceIp string `json:"source"`
}

type FirewallPolicyServerIp struct {
	withId
	Ip         string `json:"ip"`
	ServerName string `json:"server_name"`
}

type FirewallPolicyCreateData struct {
	Name        string                          `json:"name"`
	Description string                          `json:"description"`
	Rules       []FirewallPolicyRulesCreateData `json:"rules"`
}

type FirewallPolicyRulesCreateData struct {
	Protocol string `json:"protocol"`
	PortFrom *int   `json:"port_from"`
	PortTo   *int   `json:"port_to"`
	SourceIp string `json:"source"`
}

type FirewallPolicyAddIpsData struct {
	ServerIps []string `json:"server_ips"`
}

// GET /firewall_policies
func (api *API) GetFirewallPolicies() ([]FirewallPolicy, error) {
	log.Debug("requesting information about firewall policies")
	result := []FirewallPolicy{}
	err := api.Client.Get(createUrl(api, "firewall_policies"), &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	for index, _ := range result {
		result[index].api = api
	}
	return result, nil
}

// POST /firewall_policies
func (api *API) CreateFirewallPolicy(configuration FirewallPolicyCreateData) (*FirewallPolicy, error) {
	log.Debug("requesting to create a new firewall policy")
	result := new(FirewallPolicy)
	err := api.Client.Post(createUrl(api, "firewall_policies"), configuration, &result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = api
	return result, nil
}

// GET /firewall_policies/{id}
func (api *API) GetFirewallPolicy(Id string) (*FirewallPolicy, error) {
	log.Debug("requesting to about firewall policy ", Id)
	result := new(FirewallPolicy)
	err := api.Client.Get(createUrl(api, "firewall_policies", Id), &result, http.StatusOK)
	if err != nil {
		return nil, err
	}
	result.api = api
	return result, nil

}

// DELETE /firewall_policies/{id}
func (fwp *FirewallPolicy) Delete() (*FirewallPolicy, error) {
	log.Debug("Requested to delete firewall policy ", fwp.Id)
	result := new(FirewallPolicy)
	err := fwp.api.Client.Delete(createUrl(fwp.api, "firewall_policies", fwp.Id), &result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = fwp.api
	return result, nil
}

// PUT /firewall_policies/{id}

// GET /firewall_policies/{id}/server_ips

// PUT /firewall_policies/{id}/server_ips
func (fwp *FirewallPolicy) AddServerIp(ipId string) (*FirewallPolicy, error) {
	log.Debugf("Requested to apply firewall policy '%v' to ip '%v'", fwp.Id, ipId)
	result := new(FirewallPolicy)
	request := FirewallPolicyAddIpsData{
		ServerIps: []string{ipId},
	}
	err := fwp.api.Client.Put(createUrl(fwp.api, "firewall_policies", fwp.Id, "server_ips"), request, result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = fwp.api
	return result, nil
}

// GET /firewall_policies/{id}/server_ips/{id}

// DELETE /firewall_policies/{id}/server_ips/{id}
func (fwp *FirewallPolicy) DeleteServerIp(ipId string) (*FirewallPolicy, error) {
	log.Debugf("Requested to remove firewall policy '%v' from ip '%v'", fwp.Id, ipId)
	result := new(FirewallPolicy)
	err := fwp.api.Client.Delete(createUrl(fwp.api, "firewall_policies", fwp.Id, "server_ips", ipId), result, http.StatusAccepted)
	if err != nil {
		return nil, err
	}
	result.api = fwp.api
	return result, nil
}

// GET /firewall_policies/{id}/rules

// PUT /firewall_policies/{id}/rules

// GET /firewall_policies/{id}/rules/{id}

// DELETE /firewall_policies/{id}/rules/{id}

func (fwp *FirewallPolicy) exists() (bool, error) {
	_, err := fwp.api.GetFirewallPolicy(fwp.Id)
	if err == nil {
		return true, nil
	} else {
		if apiError, ok := err.(ApiError); ok && apiError.httpStatusCode == http.StatusNotFound {
			return false, nil
		} else {
			return false, err
		}
	}
}

func (fwp *FirewallPolicy) WaitUntilDeleted() error {
	exists := true
	var err error
	for exists {
		exists, err = fwp.exists()
		if err != nil {
			return err
		}
		log.Debugf("Wait for firewall policy: '%s' to be deleted", fwp.Id)
		time.Sleep(5 * time.Second)
	}
	log.Infof("The firewall policy: '%s' is now deleted", fwp.Id)
	return nil
}

func (fwp *FirewallPolicy) WaitForState(State string) error {
	fw, err := fwp.api.GetFirewallPolicy(fwp.Id)
	if err != nil {
		return err
	}
	for fw.Status != State {
		time.Sleep(5 * time.Second)
		fw, err := fwp.api.GetFirewallPolicy(fwp.Id)
		if err != nil {
			return err
		}
		if fw.Status == State {
			log.Infof("The firewall policy is now in the expected state: '%s'", State)
			return nil
		} else {
			log.Debugf("Wait for expected status: '%s' current: '%s'", State, fw.Status)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package daemon
import (
	"github.com/subgraph/oz"
	"github.com/subgraph/oz/fs"
	"os/exec"
	"syscall"
	"fmt"
	"io"
	"bufio"
	"os/user"
	"github.com/subgraph/oz/xpra"
	"os"
	"path"
)

const initPath = "/usr/local/bin/oz-init"

type Sandbox struct {
	daemon *daemonState
	id int
	display int
	profile *oz.Profile
	init *exec.Cmd
	cred *syscall.Credential
	fs *fs.Filesystem
	stderr io.ReadCloser
	addr string
	xpra *xpra.Xpra
}

/*
func findSandbox(id int) *Sandbox {
	for _, sb := range sandboxes {
		if sb.id == id {
			return sb
		}
	}
	return nil
}
*/
const initCloneFlags = syscall.CLONE_NEWNS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUTS

func createInitCommand(name, chroot string, uid uint32, display int) *exec.Cmd {
	cmd := exec.Command(initPath)
	cmd.Dir = "/"
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Chroot: chroot,
		Cloneflags: initCloneFlags,
	}
	cmd.Env = []string{
		"INIT_PROFILE="+name,
		fmt.Sprintf("INIT_UID=%d", uid),
	}
	if display > 0 {
		cmd.Env = append(cmd.Env, fmt.Sprintf("INIT_DISPLAY=%d", display))
	}
	return cmd
}

func (d *daemonState) launch(p *oz.Profile, uid,gid uint32) (*Sandbox, error) {
	u,err := user.LookupId(fmt.Sprintf("%d", uid))
	if err != nil {
		return nil, fmt.Errorf("failed to lookup user for uid=%d: %v", uid, err)
	}
	fs := fs.NewFromProfile(p, u, d.log)
	if err := fs.Setup(); err != nil {
		return nil, err
	}
	display := 0
	if p.XServer.Enabled {
		display = d.nextDisplay
		d.nextDisplay += 1
	}

	cmd := createInitCommand(p.Name, fs.Root(), uid, display)
	pp,err := cmd.StderrPipe()
	if err != nil {
		fs.Cleanup()
		return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err)

	}
	if err := cmd.Start(); err != nil {
		fs.Cleanup()
		return nil, err
	}
	sbox := &Sandbox{
		daemon: d,
		id: d.nextSboxId,
		display: display,
		profile: p,
		init: cmd,
		cred: &syscall.Credential{Uid: uid, Gid: gid},
		fs: fs,
		addr: path.Join(fs.Root(), "tmp", "oz-init-control"),
		stderr: pp,
	}
	go sbox.logMessages()
	d.nextSboxId += 1
	d.sandboxes = append(d.sandboxes, sbox)
	return sbox,nil
}

func (sbox *Sandbox) remove() {
	sboxes := []*Sandbox{}
	for _,sb := range sbox.daemon.sandboxes {
		if sb == sbox {
			sb.fs.Cleanup()
		} else {
			sboxes = append(sboxes, sb)
		}
	}
	sbox.daemon.sandboxes = sboxes
}

func (sbox *Sandbox) logMessages() {
	scanner := bufio.NewScanner(sbox.stderr)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "XPRA READY" {
			sbox.daemon.log.Info("Xpra server is ready for connection")
			go sbox.startXpraClient()
		} else if len(line) > 1 {
			sbox.logLine(line)
		}
	}
	sbox.stderr.Close()
}

func (sbox *Sandbox) logLine(line string) {
	if len(line) < 2 {
		return
	}
	f := sbox.getLogFunc(line[0])
	msg := line[2:]
	if f != nil {
		f("[%s] %s", sbox.profile.Name, msg)
	} else {
		sbox.daemon.log.Info("[%s] %s", sbox.profile.Name, line)
	}
}

func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) {
	log := sbox.daemon.log
	switch(c) {
	case 'D':
		return log.Debug
	case 'I':
		return log.Info
	case 'N':
		return log.Notice
	case 'W':
		return log.Warning
	case 'E':
		return log.Error
	case 'C':
		return log.Critical
	}
	return nil
}

func (sbox *Sandbox) startXpraClient() {
	sbox.xpra = xpra.NewClient(
		&sbox.profile.XServer,
		uint64(sbox.display),
		sbox.cred,
		sbox.fs.Xpra(),
		sbox.profile.Name,
		sbox.daemon.log)
	sbox.xpra.Process.Stdout = os.Stdout
	sbox.xpra.Process.Stderr = os.Stdout
	if err := sbox.xpra.Process.Start(); err != nil {
		sbox.daemon.Warning("Failed to start xpra client: %v", err)
	}
}
<commit_msg>use network namespace (CLONE_NEWNET)<commit_after>package daemon
import (
	"github.com/subgraph/oz"
	"github.com/subgraph/oz/fs"
	"os/exec"
	"syscall"
	"fmt"
	"io"
	"bufio"
	"os/user"
	"github.com/subgraph/oz/xpra"
	"os"
	"path"
)

const initPath = "/usr/local/bin/oz-init"

type Sandbox struct {
	daemon *daemonState
	id int
	display int
	profile *oz.Profile
	init *exec.Cmd
	cred *syscall.Credential
	fs *fs.Filesystem
	stderr io.ReadCloser
	addr string
	xpra *xpra.Xpra
}

/*
func findSandbox(id int) *Sandbox {
	for _, sb := range sandboxes {
		if sb.id == id {
			return sb
		}
	}
	return nil
}
*/
const initCloneFlags = syscall.CLONE_NEWNS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUTS | syscall.CLONE_NEWNET

func createInitCommand(name, chroot string, uid uint32, display int) *exec.Cmd {
	cmd := exec.Command(initPath)
	cmd.Dir = "/"
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Chroot: chroot,
		Cloneflags: initCloneFlags,
	}
	cmd.Env = []string{
		"INIT_PROFILE="+name,
		fmt.Sprintf("INIT_UID=%d", uid),
	}
	if display > 0 {
		cmd.Env = append(cmd.Env, fmt.Sprintf("INIT_DISPLAY=%d", display))
	}
	return cmd
}

func (d *daemonState) launch(p *oz.Profile, uid,gid uint32) (*Sandbox, error) {
	u,err := user.LookupId(fmt.Sprintf("%d", uid))
	if err != nil {
		return nil, fmt.Errorf("failed to lookup user for uid=%d: %v", uid, err)
	}
	fs := fs.NewFromProfile(p, u, d.log)
	if err := fs.Setup(); err != nil {
		return nil, err
	}
	display := 0
	if p.XServer.Enabled {
		display = d.nextDisplay
		d.nextDisplay += 1
	}

	cmd := createInitCommand(p.Name, fs.Root(), uid, display)
	pp,err := cmd.StderrPipe()
	if err != nil {
		fs.Cleanup()
		return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err)

	}
	if err := cmd.Start(); err != nil {
		fs.Cleanup()
		return nil, err
	}
	sbox := &Sandbox{
		daemon: d,
		id: d.nextSboxId,
		display: display,
		profile: p,
		init: cmd,
		cred: &syscall.Credential{Uid: uid, Gid: gid},
		fs: fs,
		addr: path.Join(fs.Root(), "tmp", "oz-init-control"),
		stderr: pp,
	}
	go sbox.logMessages()
	d.nextSboxId += 1
	d.sandboxes = append(d.sandboxes, sbox)
	return sbox,nil
}

func (sbox *Sandbox) remove() {
	sboxes := []*Sandbox{}
	for _,sb := range sbox.daemon.sandboxes {
		if sb == sbox {
			sb.fs.Cleanup()
		} else {
			sboxes = append(sboxes, sb)
		}
	}
	sbox.daemon.sandboxes = sboxes
}

func (sbox *Sandbox) logMessages() {
	scanner := bufio.NewScanner(sbox.stderr)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "XPRA READY" {
			sbox.daemon.log.Info("Xpra server is ready for connection")
			go sbox.startXpraClient()
		} else if len(line) > 1 {
			sbox.logLine(line)
		}
	}
	sbox.stderr.Close()
}

func (sbox *Sandbox) logLine(line string) {
	if len(line) < 2 {
		return
	}
	f := sbox.getLogFunc(line[0])
	msg := line[2:]
	if f != nil {
		f("[%s] %s", sbox.profile.Name, msg)
	} else {
		sbox.daemon.log.Info("[%s] %s", sbox.profile.Name, line)
	}
}

func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) {
	log := sbox.daemon.log
	switch(c) {
	case 'D':
		return log.Debug
	case 'I':
		return log.Info
	case 'N':
		return log.Notice
	case 'W':
		return log.Warning
	case 'E':
		return log.Error
	case 'C':
		return log.Critical
	}
	return nil
}

func (sbox *Sandbox) startXpraClient() {
	sbox.xpra = xpra.NewClient(
		&sbox.profile.XServer,
		uint64(sbox.display),
		sbox.cred,
		sbox.fs.Xpra(),
		sbox.profile.Name,
		sbox.daemon.log)
	sbox.xpra.Process.Stdout = os.Stdout
	sbox.xpra.Process.Stderr = os.Stdout
	if err := sbox.xpra.Process.Start(); err != nil {
		sbox.daemon.Warning("Failed to start xpra client: %v", err)
	}
}
<|endoftext|>


<commit_before>package util

import (
	"bytemark.co.uk/client/lib"
	"bytemark.co.uk/client/util/log"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

var configVars = [...]string{
	"endpoint",
	"billing-endpoint",
	"auth-endpoint",
	"user",
	"account",
	"group",
	"token",
	"debug-level",
	"yubikey",
}

// ConfigVar is a struct which contains a name-value-source triplet
// Source is up to two words separated by a space. The first word is the source type: FLAG, ENV, DIR, CODE.
// The second is the name of the flag/file/environment var used.
type ConfigVar struct {
	Name   string
	Value  string
	Source string
}

func (v *ConfigVar) SourceType() string {
	bits := strings.Fields(v.Source)

	return bits[0]
}
func (v *ConfigVar) SourceBaseName() string {
	bits := strings.Split(v.Source, "/")
	return bits[len(bits)-1]
}

// ConfigManager is an interface defining a key->value store that also knows where the values were set from.
type ConfigManager interface {
	Get(string) (string, error)
	GetIgnoreErr(string) string
	GetBool(string) (bool, error)
	GetV(string) (ConfigVar, error)
	GetVirtualMachine() lib.VirtualMachineName
	GetGroup() lib.GroupName
	GetAll() ([]ConfigVar, error)
	Set(string, string, string)
	SetPersistent(string, string, string) error
	Unset(string) error
	GetDebugLevel() int
	Force() bool
	Silent() bool
	EndpointName() string
	PanelURL() string

	ImportFlags(*flag.FlagSet) []string
}

// Params currently used:
// token - an OAuth 2.0 bearer token to use when authenticating
// username - the default username to use - if not present, $USER
// endpoint - the default endpoint to use - if not present, https://uk0.bigv.io
// auth-endpoint - the default auth API endpoint to use - if not present, https://auth.bytemark.co.uk
// account - account to use if not specified elsewhere§
// group - group to use if not specified

// A Config determines the configuration of the Bytemark client.
// It's responsible for handling things like the credentials to use and what endpoints to talk to.
//
// Each configuration item is read from the following places, falling back to successive places:
//
// Per-command command-line flags, global command-line flags, environment variables, configuration directory, hard-coded defaults
//
//The location of the configuration directory is read from global command-line flags, or is otherwise ~/.bytemark
//
type Config struct {
	debugLevel  int
	mainFlags   *flag.FlagSet
	Dir         string
	Memo        map[string]ConfigVar
	Definitions map[string]string
}

type ConfigDirInvalidError struct {
	Path string
}

func (e *ConfigDirInvalidError) Error() string {
	return fmt.Sprintf("The config directory is '%s' but it doesn't seem to be a directory.", e.Path)
}

type CannotLoadDefinitionsError struct {
	Err error
}

func (e *CannotLoadDefinitionsError) Error() string {
	return fmt.Sprintf("Unable to load the definitions file from the Bytemark API.")
}

type ConfigReadError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigReadError) Error() string {
	return fmt.Sprintf("Unable to read config for %s from %s.", e.Name, e.Path)
}

type ConfigWriteError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigWriteError) Error() string {
	return fmt.Sprintf("Unable to write persistent config for %s (%s).", e.Name, e.Path)
}

// Do I really need to have the flags passed in here?
// Yes. Doing commands will be sorted out in a different place, and I don't want to touch it here.

// NewConfig sets up a new config struct. Pass in an empty string to default to ~/.bytemark
func NewConfig(configDir string, flags *flag.FlagSet) (config *Config, err error) {
	config = new(Config)
	config.Memo = make(map[string]ConfigVar)
	home := os.Getenv("HOME")
	if runtime.GOOS == "windows" {
		home = os.Getenv("APPDATA")

	}
	config.Dir = filepath.Join(home, "/.bytemark")
	config.mainFlags = flags
	if os.Getenv("BM_CONFIG_DIR") != "" {
		config.Dir = os.Getenv("BM_CONFIG_DIR")
	}

	if configDir != "" {
		config.Dir = configDir
	}

	err = os.MkdirAll(config.Dir, 0700)
	if err != nil {

		return nil, err
	}

	stat, err := os.Stat(config.Dir)
	if err != nil {
		return nil, err
	}

	if !stat.IsDir() {
		return nil, &ConfigDirInvalidError{config.Dir}
	}

	log.LogFile, err = os.Create(config.GetPath("debug.log"))
	if err != nil {
		log.Errorf("Couldn't open %s for writing\r\n", config.GetPath("debug.log"))
	}

	config.ImportFlags(flags)
	strDL, err := config.Get("debug-level")
	if err != nil {
		log.Error(err)
	} else {
		debugLevel, err := strconv.ParseInt(strDL, 10, 0)
		if err == nil {
			config.debugLevel = int(debugLevel)
			log.DebugLevel = int(debugLevel)
		}
	}
	return config, nil
}

func (config *Config) ImportFlags(flags *flag.FlagSet) []string {
	if flags != nil {
		if flags.Parsed() {
			// dump all the flags into the memo
			// should be reet...reet?
			flags.Visit(func(f *flag.Flag) {
				config.Memo[f.Name] = ConfigVar{
					f.Name,
					f.Value.String(),
					"FLAG " + f.Name,
				}
			})

			if flags != config.mainFlags {

				args := flags.Args()
				for _, arg := range args {
					if strings.HasPrefix(arg, "-") {
						log.Errorf("Flag-like argument '%s' specified after your arguments\r\nBe aware that only flags placed before your arguments are parsed.\r\nSee the help for the command you're calling for invocation examples.\r\n\r\n", arg)
						break
					}
				}
			}

			log.Silent = config.Silent()
			strDL := config.GetIgnoreErr("debug-level")
			debugLevel, err := strconv.ParseInt(strDL, 10, 0)
			if err == nil {
				config.debugLevel = int(debugLevel)
				log.DebugLevel = int(debugLevel)
			}

			return flags.Args()
		}
	}
	return nil
}

// GetDebugLevel returns the current debug-level as an integer. This is used throughout the bytemark.co.uk/client library to determine verbosity of output.
func (config *Config) GetDebugLevel() int {
	return config.debugLevel
}

// GetPath joins the given string onto the end of the Config.Dir path
func (config *Config) GetPath(name string) string {
	return filepath.Join(config.Dir, name)
}

// Get returns the value of a ConfigVar. Used to simplify code when the source is unnecessary.
func (config *Config) Get(name string) (string, error) {
	v, err := config.GetV(name)
	return v.Value, err
}

// GetIgnoreErr returns the value of a ConfigVar or an empty string , if it was unable to read it for whatever reason.
func (config *Config) GetIgnoreErr(name string) string {
	s, _ := config.Get(name)
	return s
}

// GetV returns the ConfigVar for the given key.
func (config *Config) GetV(name string) (ConfigVar, error) {
	// try to read the Memo
	name = strings.ToLower(name)
	if val, ok := config.Memo[name]; ok {
		return val, nil
	}
	return config.read(name)
}

func (config *Config) GetVirtualMachine() (vm lib.VirtualMachineName) {
	vm.Account = config.GetIgnoreErr("account")
	vm.Group = config.GetIgnoreErr("group")
	vm.VirtualMachine = ""
	//TODO(telyn): make it possible to set a default VM?
	return vm
}

func (config *Config) GetGroup() (group lib.GroupName) {
	group.Account = config.GetIgnoreErr("account")
	group.Group = config.GetIgnoreErr("group")
	return group
}

// GetAll returns all of the available ConfigVars in the Config.
func (config *Config) GetAll() (vars []ConfigVar, err error) {
	vars = make([]ConfigVar, len(configVars))
	for i, v := range configVars {
		vars[i], err = config.GetV(v)
		if err != nil {
			return nil, err
		}
	}
	return vars, nil
}

// GetDefault returns the default ConfigVar for the given key.
func (config *Config) GetDefault(name string) ConfigVar {
	// ideally most of these should just be	os.Getenv("BM_"+name.Upcase().Replace("-","_"))
	switch name {
	case "user":
		if os.Getenv("BM_USER") == "" {
			return ConfigVar{"user", os.Getenv("USER"), "ENV USER"}
		}
		return ConfigVar{"user", os.Getenv("BM_USER"), "ENV BM_USER"}
	case "endpoint":
		v := ConfigVar{"endpoint", "https://uk0.bigv.io", "CODE"}

		val := os.Getenv("BM_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BM_ENDPOINT"
		}
		return v
	case "billing-endpoint":
		v := ConfigVar{"billing-endpoint", "https://bmbilling.bytemark.co.uk", "CODE"}
		if val := os.Getenv("BM_BILLING_ENDPOINT"); val != "" {
			v.Value = val
			v.Source = "ENV BM_BILLING_ENDPOINT"
		}
		return v
	case "auth-endpoint":
		v := ConfigVar{"auth-endpoint", "https://auth.bytemark.co.uk", "CODE"}

		val := os.Getenv("BM_AUTH_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BM_AUTH_ENDPOINT"
		}
		return v
	case "account":
		val := os.Getenv("BM_ACCOUNT")
		if val != "" {
			return ConfigVar{
				"account",
				val,
				"ENV BM_ACCOUNT",
			}
		}
		def := config.GetDefault("user")
		def.Name = "account"
		return def
	case "group":
		val := os.Getenv("BM_GROUP")
		if val != "" {
			return ConfigVar{
				"group",
				val,
				"ENV BM_GROUP",
			}
		}
		return ConfigVar{"group", "default", "CODE"}
	case "debug-level":
		v := ConfigVar{"debug-level", "0", "CODE"}
		if val := os.Getenv("BM_DEBUG_LEVEL"); val != "" {
			v.Value = val
		}
		return v
	case "silent":
		return ConfigVar{"silent", "false", "CODE"}
	case "force":
		return ConfigVar{"force", "false", "CODE"}
	}
	return ConfigVar{"", "", ""}
}

func (config *Config) GetBool(name string) (bool, error) {
	v, err := config.Get(name)
	if err != nil {
		return false, err
	}
	return !(v == "" || v == "false"), nil
}

func (config *Config) read(name string) (ConfigVar, error) {
	path := config.GetPath(name)
	contents, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return config.GetDefault(name), nil
		}

		return config.GetDefault(name), &ConfigReadError{Name: name, Path: path, Err: err}
	}

	return ConfigVar{name, strings.TrimSpace(string(contents)), "FILE " + path}, nil
}

// Set stores the given key-value pair in config's Memo. This storage does not persist once the program terminates.
func (config *Config) Set(name, value, source string) {
	config.Memo[name] = ConfigVar{name, value, source}
}

// SetPersistent writes a file to the config directory for the given key-value pair.
func (config *Config) SetPersistent(name, value, source string) error {
	path := config.GetPath(name)
	config.Set(name, value, source)
	err := ioutil.WriteFile(path, []byte(value), 0600)
	if err != nil {
		return &ConfigWriteError{Name: name, Path: path, Err: err}
	}
	return nil
}

// Unset removes the named key from both config's Memo and the user's config directory.
func (config *Config) Unset(name string) error {
	delete(config.Memo, name)
	return os.Remove(config.GetPath(name))
}

func (config *Config) Force() bool {
	force, _ := config.GetBool("force")
	return force
}
func (config *Config) Silent() bool {
	silent, _ := config.GetBool("silent")
	return silent
}

func (config *Config) PanelURL() string {
	endpoint := config.EndpointName()
	if strings.EqualFold(endpoint, "uk0.bigv.io") {
		return "https://panel-beta.bytemark.co.uk"
	}
	if strings.EqualFold(endpoint, "int.bigv.io") {
		// worrying leaky code?
		return "https://panel-int.vlan863.bytemark.uk0.bigv.io"
	}
	panel := config.GetIgnoreErr("panel-address")
	if panel == "" {
		panel = "https://your.panel.address"
	}
	return panel
}

func (config *Config) EndpointName() string {
	endpoint := config.GetIgnoreErr("endpoint")
	endpoint = strings.TrimPrefix(endpoint, "https://")
	endpoint = strings.TrimPrefix(endpoint, "http://") // it never hurts to be prepared
	return endpoint
}
<commit_msg>Check for existence of config var before setting it<commit_after>package util

import (
	"bytemark.co.uk/client/lib"
	"bytemark.co.uk/client/util/log"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

var configVars = [...]string{
	"endpoint",
	"billing-endpoint",
	"auth-endpoint",
	"user",
	"account",
	"group",
	"token",
	"debug-level",
	"yubikey",
}

type InvalidConfigVarError struct {
	ConfigVar string
}

func (e InvalidConfigVarError) Error() string {
	vs := "'" + strings.Join(configVars[:], "','") + "'"
	return fmt.Sprintf("'%s' is not a valid config var. Valid config vars are: %s", e.ConfigVar, vs)
}

// ConfigVar is a struct which contains a name-value-source triplet
// Source is up to two words separated by a space. The first word is the source type: FLAG, ENV, DIR, CODE.
// The second is the name of the flag/file/environment var used.
type ConfigVar struct {
	Name   string
	Value  string
	Source string
}

func (v *ConfigVar) SourceType() string {
	bits := strings.Fields(v.Source)

	return bits[0]
}
func (v *ConfigVar) SourceBaseName() string {
	bits := strings.Split(v.Source, "/")
	return bits[len(bits)-1]
}

// ConfigManager is an interface defining a key->value store that also knows where the values were set from.
type ConfigManager interface {
	Get(string) (string, error)
	GetIgnoreErr(string) string
	GetBool(string) (bool, error)
	GetV(string) (ConfigVar, error)
	GetVirtualMachine() lib.VirtualMachineName
	GetGroup() lib.GroupName
	GetAll() ([]ConfigVar, error)
	Set(string, string, string)
	SetPersistent(string, string, string) error
	Unset(string) error
	GetDebugLevel() int
	Force() bool
	Silent() bool
	EndpointName() string
	PanelURL() string

	ImportFlags(*flag.FlagSet) []string
}

// Params currently used:
// token - an OAuth 2.0 bearer token to use when authenticating
// username - the default username to use - if not present, $USER
// endpoint - the default endpoint to use - if not present, https://uk0.bigv.io
// auth-endpoint - the default auth API endpoint to use - if not present, https://auth.bytemark.co.uk
// account - account to use if not specified elsewhere§
// group - group to use if not specified

// A Config determines the configuration of the Bytemark client.
// It's responsible for handling things like the credentials to use and what endpoints to talk to.
//
// Each configuration item is read from the following places, falling back to successive places:
//
// Per-command command-line flags, global command-line flags, environment variables, configuration directory, hard-coded defaults
//
//The location of the configuration directory is read from global command-line flags, or is otherwise ~/.bytemark
//
type Config struct {
	debugLevel  int
	mainFlags   *flag.FlagSet
	Dir         string
	Memo        map[string]ConfigVar
	Definitions map[string]string
}

type ConfigDirInvalidError struct {
	Path string
}

func (e *ConfigDirInvalidError) Error() string {
	return fmt.Sprintf("The config directory is '%s' but it doesn't seem to be a directory.", e.Path)
}

type CannotLoadDefinitionsError struct {
	Err error
}

func (e *CannotLoadDefinitionsError) Error() string {
	return fmt.Sprintf("Unable to load the definitions file from the Bytemark API.")
}

type ConfigReadError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigReadError) Error() string {
	return fmt.Sprintf("Unable to read config for %s from %s.", e.Name, e.Path)
}

type ConfigWriteError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigWriteError) Error() string {
	return fmt.Sprintf("Unable to write persistent config for %s (%s).", e.Name, e.Path)
}

// Do I really need to have the flags passed in here?
// Yes. Doing commands will be sorted out in a different place, and I don't want to touch it here.

// NewConfig sets up a new config struct. Pass in an empty string to default to ~/.bytemark
func NewConfig(configDir string, flags *flag.FlagSet) (config *Config, err error) {
	config = new(Config)
	config.Memo = make(map[string]ConfigVar)
	home := os.Getenv("HOME")
	if runtime.GOOS == "windows" {
		home = os.Getenv("APPDATA")

	}
	config.Dir = filepath.Join(home, "/.bytemark")
	config.mainFlags = flags
	if os.Getenv("BM_CONFIG_DIR") != "" {
		config.Dir = os.Getenv("BM_CONFIG_DIR")
	}

	if configDir != "" {
		config.Dir = configDir
	}

	err = os.MkdirAll(config.Dir, 0700)
	if err != nil {

		return nil, err
	}

	stat, err := os.Stat(config.Dir)
	if err != nil {
		return nil, err
	}

	if !stat.IsDir() {
		return nil, &ConfigDirInvalidError{config.Dir}
	}

	log.LogFile, err = os.Create(config.GetPath("debug.log"))
	if err != nil {
		log.Errorf("Couldn't open %s for writing\r\n", config.GetPath("debug.log"))
	}

	config.ImportFlags(flags)
	strDL, err := config.Get("debug-level")
	if err != nil {
		log.Error(err)
	} else {
		debugLevel, err := strconv.ParseInt(strDL, 10, 0)
		if err == nil {
			config.debugLevel = int(debugLevel)
			log.DebugLevel = int(debugLevel)
		}
	}
	return config, nil
}

func (config *Config) ImportFlags(flags *flag.FlagSet) []string {
	if flags != nil {
		if flags.Parsed() {
			// dump all the flags into the memo
			// should be reet...reet?
			flags.Visit(func(f *flag.Flag) {
				config.Memo[f.Name] = ConfigVar{
					f.Name,
					f.Value.String(),
					"FLAG " + f.Name,
				}
			})

			if flags != config.mainFlags {

				args := flags.Args()
				for _, arg := range args {
					if strings.HasPrefix(arg, "-") {
						log.Errorf("Flag-like argument '%s' specified after your arguments\r\nBe aware that only flags placed before your arguments are parsed.\r\nSee the help for the command you're calling for invocation examples.\r\n\r\n", arg)
						break
					}
				}
			}

			log.Silent = config.Silent()
			strDL := config.GetIgnoreErr("debug-level")
			debugLevel, err := strconv.ParseInt(strDL, 10, 0)
			if err == nil {
				config.debugLevel = int(debugLevel)
				log.DebugLevel = int(debugLevel)
			}

			return flags.Args()
		}
	}
	return nil
}

// GetDebugLevel returns the current debug-level as an integer. This is used throughout the bytemark.co.uk/client library to determine verbosity of output.
func (config *Config) GetDebugLevel() int {
	return config.debugLevel
}

// GetPath joins the given string onto the end of the Config.Dir path
func (config *Config) GetPath(name string) string {
	return filepath.Join(config.Dir, name)
}

// Get returns the value of a ConfigVar. Used to simplify code when the source is unnecessary.
func (config *Config) Get(name string) (string, error) {
	v, err := config.GetV(name)
	return v.Value, err
}

// GetIgnoreErr returns the value of a ConfigVar or an empty string , if it was unable to read it for whatever reason.
func (config *Config) GetIgnoreErr(name string) string {
	s, _ := config.Get(name)
	return s
}

// GetV returns the ConfigVar for the given key.
func (config *Config) GetV(name string) (ConfigVar, error) {
	// try to read the Memo
	name = strings.ToLower(name)
	if val, ok := config.Memo[name]; ok {
		return val, nil
	}
	return config.read(name)
}

func (config *Config) GetVirtualMachine() (vm lib.VirtualMachineName) {
	vm.Account = config.GetIgnoreErr("account")
	vm.Group = config.GetIgnoreErr("group")
	vm.VirtualMachine = ""
	//TODO(telyn): make it possible to set a default VM?
	return vm
}

func (config *Config) GetGroup() (group lib.GroupName) {
	group.Account = config.GetIgnoreErr("account")
	group.Group = config.GetIgnoreErr("group")
	return group
}

// GetAll returns all of the available ConfigVars in the Config.
func (config *Config) GetAll() (vars []ConfigVar, err error) {
	vars = make([]ConfigVar, len(configVars))
	for i, v := range configVars {
		vars[i], err = config.GetV(v)
		if err != nil {
			return nil, err
		}
	}
	return vars, nil
}

// GetDefault returns the default ConfigVar for the given key.
func (config *Config) GetDefault(name string) ConfigVar {
	// ideally most of these should just be	os.Getenv("BM_"+name.Upcase().Replace("-","_"))
	switch name {
	case "user":
		if os.Getenv("BM_USER") == "" {
			return ConfigVar{"user", os.Getenv("USER"), "ENV USER"}
		}
		return ConfigVar{"user", os.Getenv("BM_USER"), "ENV BM_USER"}
	case "endpoint":
		v := ConfigVar{"endpoint", "https://uk0.bigv.io", "CODE"}

		val := os.Getenv("BM_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BM_ENDPOINT"
		}
		return v
	case "billing-endpoint":
		v := ConfigVar{"billing-endpoint", "https://bmbilling.bytemark.co.uk", "CODE"}
		if val := os.Getenv("BM_BILLING_ENDPOINT"); val != "" {
			v.Value = val
			v.Source = "ENV BM_BILLING_ENDPOINT"
		}
		return v
	case "auth-endpoint":
		v := ConfigVar{"auth-endpoint", "https://auth.bytemark.co.uk", "CODE"}

		val := os.Getenv("BM_AUTH_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BM_AUTH_ENDPOINT"
		}
		return v
	case "account":
		val := os.Getenv("BM_ACCOUNT")
		if val != "" {
			return ConfigVar{
				"account",
				val,
				"ENV BM_ACCOUNT",
			}
		}
		def := config.GetDefault("user")
		def.Name = "account"
		return def
	case "group":
		val := os.Getenv("BM_GROUP")
		if val != "" {
			return ConfigVar{
				"group",
				val,
				"ENV BM_GROUP",
			}
		}
		return ConfigVar{"group", "default", "CODE"}
	case "debug-level":
		v := ConfigVar{"debug-level", "0", "CODE"}
		if val := os.Getenv("BM_DEBUG_LEVEL"); val != "" {
			v.Value = val
		}
		return v
	case "silent":
		return ConfigVar{"silent", "false", "CODE"}
	case "force":
		return ConfigVar{"force", "false", "CODE"}
	}
	return ConfigVar{"", "", ""}
}

func (config *Config) GetBool(name string) (bool, error) {
	v, err := config.Get(name)
	if err != nil {
		return false, err
	}
	return !(v == "" || v == "false"), nil
}

func (config *Config) read(name string) (ConfigVar, error) {
	path := config.GetPath(name)
	contents, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return config.GetDefault(name), nil
		}

		return config.GetDefault(name), &ConfigReadError{Name: name, Path: path, Err: err}
	}

	return ConfigVar{name, strings.TrimSpace(string(contents)), "FILE " + path}, nil
}

// Set stores the given key-value pair in config's Memo. This storage does not persist once the program terminates.
func (config *Config) Set(name, value, source string) {
	config.Memo[name] = ConfigVar{name, value, source}
}

// SetPersistent writes a file to the config directory for the given key-value pair.
func (config *Config) SetPersistent(name, value, source string) error {
	found := false
	for _, v := range configVars {
		if v == name {
			found = true
		}
	}
	if !found {
		return InvalidConfigVarError{name}
	}
	path := config.GetPath(name)
	config.Set(name, value, source)
	err := ioutil.WriteFile(path, []byte(value), 0600)
	if err != nil {
		return &ConfigWriteError{Name: name, Path: path, Err: err}
	}
	return nil
}

// Unset removes the named key from both config's Memo and the user's config directory.
func (config *Config) Unset(name string) error {
	found := false
	for _, v := range configVars {
		if v == name {
			found = true
		}
	}
	if !found {
		return InvalidConfigVarError{name}
	}
	delete(config.Memo, name)
	return os.Remove(config.GetPath(name))
}

func (config *Config) Force() bool {
	force, _ := config.GetBool("force")
	return force
}
func (config *Config) Silent() bool {
	silent, _ := config.GetBool("silent")
	return silent
}

func (config *Config) PanelURL() string {
	endpoint := config.EndpointName()
	if strings.EqualFold(endpoint, "uk0.bigv.io") {
		return "https://panel-beta.bytemark.co.uk"
	}
	if strings.EqualFold(endpoint, "int.bigv.io") {
		// worrying leaky code?
		return "https://panel-int.vlan863.bytemark.uk0.bigv.io"
	}
	panel := config.GetIgnoreErr("panel-address")
	if panel == "" {
		panel = "https://your.panel.address"
	}
	return panel
}

func (config *Config) EndpointName() string {
	endpoint := config.GetIgnoreErr("endpoint")
	endpoint = strings.TrimPrefix(endpoint, "https://")
	endpoint = strings.TrimPrefix(endpoint, "http://") // it never hurts to be prepared
	return endpoint
}
<|endoftext|>