<commit_before>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/zenoss/serviced/cli/api"
	service "github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/servicedefinition"
)

const (
	IPAddressNotFound = "IP ADDRESS NOT FOUND"
)

var DefaultServiceAPITest = ServiceAPITest{
	services:  DefaultTestServices,
	snapshots: DefaultTestSnapshots,
}

var DefaultTestServices = []*service.Service{
	{
		Id:           "test-service-1",
		Name:         "Zenoss",
		Startup:      "startup command 1",
		Instances:    0,
		ImageId:      "quay.io/zenossinc/tenantid1-core5x",
		PoolId:       "default",
		DesiredState: 1,
		Launch:       "auto",
		DeploymentId: "Zenoss-resmgr",
		Runs: map[string]string{
			"hello":   "echo hello world",
			"goodbye": "echo goodbye world",
		},
	}, {
		Id:           "test-service-2",
		Name:         "Zope",
		Startup:      "startup command 2",
		Instances:    1,
		ImageId:      "quay.io/zenossinc/tenantid2-core5x",
		PoolId:       "default",
		DesiredState: 1,
		Launch:       "auto",
		DeploymentId: "Zenoss-core",
	}, {
		Id:           "test-service-3",
		Name:         "zencommand",
		Startup:      "startup command 3",
		Instances:    2,
		ImageId:      "quay.io/zenossinc/tenantid1-opentsdb",
		PoolId:       "remote",
		DesiredState: 1,
		Launch:       "manual",
		DeploymentId: "Zenoss-core",
	},
}

var (
	ErrNoServiceFound = errors.New("no service found")
	ErrInvalidService = errors.New("invalid service")
	ErrCmdNotFound    = errors.New("command not found")
)

type ServiceAPITest struct {
	api.API
	services  []*service.Service
	snapshots []string
}

func InitServiceAPITest(args ...string) {
	New(DefaultServiceAPITest).Run(args)
}

func (t ServiceAPITest) GetServices() ([]*service.Service, error) {
	return t.services, nil
}

func (t ServiceAPITest) GetService(id string) (*service.Service, error) {
	for i, s := range t.services {
		if s.Id == id {
			return t.services[i], nil
		}
	}

	return nil, ErrNoServiceFound
}

func (t ServiceAPITest) AddService(config api.ServiceConfig) (*service.Service, error) {
	endpoints := make([]servicedefinition.ServiceEndpoint, len(*config.LocalPorts)+len(*config.RemotePorts))
	i := 0
	for _, e := range *config.LocalPorts {
		e.Purpose = "local"
		endpoints[i] = e
		i++
	}
	for _, e := range *config.RemotePorts {
		e.Purpose = "remote"
		endpoints[i] = e
		i++
	}

	s := service.Service{
		Id:        fmt.Sprintf("%s-%s-%s", config.Name, config.PoolID, config.ImageID),
		Name:      config.Name,
		PoolId:    config.PoolID,
		ImageId:   config.ImageID,
		Endpoints: endpoints,
		Startup:   config.Command,
		Instances: 1,
	}

	return &s, nil
}

func (t ServiceAPITest) RemoveService(id string) error {
	_, err := t.GetService(id)
	return err
}

func (t ServiceAPITest) UpdateService(reader io.Reader) (*service.Service, error) {
	var s service.Service

	if err := json.NewDecoder(reader).Decode(&s); err != nil {
		return nil, ErrInvalidService
	}

	if _, err := t.GetService(s.Id); err != nil {
		return nil, err
	}

	return &s, nil
}

func (t ServiceAPITest) StartService(id string) (*host.Host, error) {
	if _, err := t.GetService(id); err != nil {
		return nil, err
	}

	h := host.Host{
		ID: fmt.Sprintf("%s-host", id),
	}

	return &h, nil
}

func (t ServiceAPITest) StopService(id string) error {
	if _, err := t.GetService(id); err != nil {
		return err
	}

	return nil
}

func (t ServiceAPITest) AssignIP(config api.IPConfig) (string, error) {
	if _, err := t.GetService(config.ServiceID); err != nil {
		return "", err
	}

	switch config.IPAddress {
	case IPAddressNotFound:
		return "", nil
	case "":
		return "0.0.0.0", nil
	default:
		return config.IPAddress, nil
	}
}

func (t ServiceAPITest) StartProxy(config api.ProxyConfig) error {
	return nil
}

func (t ServiceAPITest) StartShell(config api.ShellConfig) error {
	return nil
}

func (t ServiceAPITest) RunShell(config api.ShellConfig) error {
	s, err := t.GetService(config.ServiceID)
	if err != nil {
		return err
	}

	command, ok := s.Runs[config.Command]
	if !ok {
		return ErrCmdNotFound
	}

	fmt.Printf("%s %s\n", command, strings.Join(config.Args, " "))
	return nil
}

func (t ServiceAPITest) GetSnapshots() ([]string, error) {
	return t.snapshots, nil
}

func (t ServiceAPITest) GetSnapshotsByServiceID(id string) ([]string, error) {
	var snapshots []string
	for _, s := range t.snapshots {
		if strings.HasPrefix(s, id) {
			snapshots = append(snapshots, s)
		}
	}

	return snapshots, nil
}

func (t ServiceAPITest) AddSnapshot(id string) (string, error) {
	return fmt.Sprintf("%s-snapshot", id), nil
}

func ExampleServicedCli_cmdServiceList() {
	InitServiceAPITest("serviced", "service", "list", "-v")

	// Output:
	// [
	//    {
	//      "Id": "test-service-1",
	//      "Name": "Zenoss",
	//      "Context": "",
	//      "Startup": "startup command 1",
	//      "Description": "",
	//      "Tags": null,
	//      "ConfigFiles": null,
	//      "Instances": 0,
	//      "ImageId": "quay.io/zenossinc/tenantid1-core5x",
	//      "PoolId": "default",
	//      "DesiredState": 1,
	//      "HostPolicy": "",
	//      "Hostname": "",
	//      "Privileged": false,
	//      "Launch": "auto",
	//      "Endpoints": null,
	//      "Tasks": null,
	//      "ParentServiceId": "",
	//      "Volumes": null,
	//      "CreatedAt": "0001-01-01T00:00:00Z",
	//      "UpdatedAt": "0001-01-01T00:00:00Z",
	//      "DeploymentId": "Zenoss-resmgr",
	//      "DisableImage": false,
	//      "LogConfigs": null,
	//      "Snapshot": {
	//        "Pause": "",
	//        "Resume": ""
	//      },
	//      "Runs": {
	//        "goodbye": "echo goodbye world",
	//        "hello": "echo hello world"
	//      },
	//      "RAMCommitment": 0,
	//      "Actions": null,
	//      "HealthCheck": {
	//        "Script": "",
	//        "Interval": 0
	//      }
	//    },
	//    {
	//      "Id": "test-service-2",
	//      "Name": "Zope",
	//      "Context": "",
	//      "Startup": "startup command 2",
	//      "Description": "",
	//      "Tags": null,
	//      "ConfigFiles": null,
	//      "Instances": 1,
	//      "ImageId": "quay.io/zenossinc/tenantid2-core5x",
	//      "PoolId": "default",
	//      "DesiredState": 1,
	//      "HostPolicy": "",
	//      "Hostname": "",
	//      "Privileged": false,
	//      "Launch": "auto",
	//      "Endpoints": null,
	//      "Tasks": null,
	//      "ParentServiceId": "",
	//      "Volumes": null,
	//      "CreatedAt": "0001-01-01T00:00:00Z",
	//      "UpdatedAt": "0001-01-01T00:00:00Z",
	//      "DeploymentId": "Zenoss-core",
	//      "DisableImage": false,
	//      "LogConfigs": null,
	//      "Snapshot": {
	//        "Pause": "",
	//        "Resume": ""
	//      },
	//      "Runs": null,
	//      "RAMCommitment": 0,
	//      "Actions": null,
	//      "HealthCheck": {
	//        "Script": "",
	//        "Interval": 0
	//      }
	//    },
	//    {
	//      "Id": "test-service-3",
	//      "Name": "zencommand",
	//      "Context": "",
	//      "Startup": "startup command 3",
	//      "Description": "",
	//      "Tags": null,
	//      "ConfigFiles": null,
	//      "Instances": 2,
	//      "ImageId": "quay.io/zenossinc/tenantid1-opentsdb",
	//      "PoolId": "remote",
	//      "DesiredState": 1,
	//      "HostPolicy": "",
	//      "Hostname": "",
	//      "Privileged": false,
	//      "Launch": "manual",
	//      "Endpoints": null,
	//      "Tasks": null,
	//      "ParentServiceId": "",
	//      "Volumes": null,
	//      "CreatedAt": "0001-01-01T00:00:00Z",
	//      "UpdatedAt": "0001-01-01T00:00:00Z",
	//      "DeploymentId": "Zenoss-core",
	//      "DisableImage": false,
	//      "LogConfigs": null,
	//      "Snapshot": {
	//        "Pause": "",
	//        "Resume": ""
	//      },
	//      "Runs": null,
	//      "RAMCommitment": 0,
	//      "Actions": null,
	//      "HealthCheck": {
	//        "Script": "",
	//        "Interval": 0
	//      }
	//    }
	//  ]
}

func ExampleServicedCli_cmdServiceAdd() {
	InitServiceAPITest("serviced", "service", "add", "test-service", "test-pool", "test-image", "bash")

	// Output:
	// test-service-test-pool-test-image
}

func ExampleServicedCli_cmdServiceRemove() {
	InitServiceAPITest("serviced", "service", "rm", "test-service-1", "test-service-0")

	// Output:
	// test-service-1
}

func ExampleServicedCLI_CmdServiceRemove_usage() {
	InitServiceAPITest("serviced", "service", "remove")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    remove - Removes an existing service
	//
	// USAGE:
	//    command remove [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced service remove SERVICEID ...
	//
	// OPTIONS:
}

func ExampleServicedCli_cmdServiceEdit() {
	InitServiceAPITest("serviced", "service", "edit", "test-service-1")
}

func ExampleServicedCli_cmdServiceAssignIPs() {
	// Service does not exist
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-0")
	// IP Address not returned
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-3", IPAddressNotFound)
	// Auto-assignment
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-1")
	// Manual-assignment
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-2", "127.0.0.1")

	// Output:
	// 0.0.0.0
	// 127.0.0.1
}

func ExampleServicedCLI_CmdServiceAssignIPs_usage() {
	InitServiceAPITest("serviced", "service", "assign-ip")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    assign-ip - Assigns an IP address to a service's endpoints requiring an explicit IP address
	//
	// USAGE:
	//    command assign-ip [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced service assign-ip SERVICEID [IPADDRESS]
	//
	// OPTIONS:
}

func ExampleServicedCli_cmdServiceStart() {
	InitServiceAPITest("serviced", "service", "start", "test-service-1")

	// Output:
	// Service scheduled to start on host: test-service-1-host
}

func ExampleServicedCli_cmdServiceStop() {
	InitServiceAPITest("serviced", "service", "stop", "test-service-2")

	// Output:
	// Service scheduled to stop.
}

func ExampleServicedCli_cmdServiceProxy() {
}

func ExampleServicedCli_cmdServiceShell() {
}

func ExampleServicedCli_cmdServiceRun_list() {
	InitServiceAPITest("serviced", "service", "run", "test-service-0")
	InitServiceAPITest("serviced", "service", "run", "test-service-1")

	// Output:
	// hello
	// goodbye
}

func ExampleServicedCli_cmdServiceRun_exec() {
	InitServiceAPITest("serviced", "service", "run", "test-service-1", "notfound")
	InitServiceAPITest("serviced", "service", "run", "test-service-1", "hello", "-i")

	// Output:
	// echo hello world -i
}

func ExampleServicedCLI_CmdServiceRun_usage() {
	InitServiceAPITest("serviced", "service", "run")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    run - Runs a service command in a service instance
	//
	// USAGE:
	//    command run [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced service run SERVICEID [COMMAND]
	//
	// OPTIONS:
	//    --saveas, -s 	saves the service instance with the given name
	//    --interactive, -i	runs the service instance as a tty
}
<commit_msg>fix brittle test<commit_after>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/zenoss/serviced/cli/api"
	service "github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/domain/host"
	"github.com/zenoss/serviced/domain/servicedefinition"
)

const (
	IPAddressNotFound = "IP ADDRESS NOT FOUND"
)

var DefaultServiceAPITest = ServiceAPITest{
	services:  DefaultTestServices,
	snapshots: DefaultTestSnapshots,
}

var DefaultTestServices = []*service.Service{
	{
		Id:           "test-service-1",
		Name:         "Zenoss",
		Startup:      "startup command 1",
		Instances:    0,
		ImageId:      "quay.io/zenossinc/tenantid1-core5x",
		PoolId:       "default",
		DesiredState: 1,
		Launch:       "auto",
		DeploymentId: "Zenoss-resmgr",
		Runs: map[string]string{
			"hello":   "echo hello world",
			"goodbye": "echo goodbye world",
		},
	}, {
		Id:           "test-service-2",
		Name:         "Zope",
		Startup:      "startup command 2",
		Instances:    1,
		ImageId:      "quay.io/zenossinc/tenantid2-core5x",
		PoolId:       "default",
		DesiredState: 1,
		Launch:       "auto",
		DeploymentId: "Zenoss-core",
	}, {
		Id:           "test-service-3",
		Name:         "zencommand",
		Startup:      "startup command 3",
		Instances:    2,
		ImageId:      "quay.io/zenossinc/tenantid1-opentsdb",
		PoolId:       "remote",
		DesiredState: 1,
		Launch:       "manual",
		DeploymentId: "Zenoss-core",
	},
}

var (
	ErrNoServiceFound = errors.New("no service found")
	ErrInvalidService = errors.New("invalid service")
	ErrCmdNotFound    = errors.New("command not found")
)

type ServiceAPITest struct {
	api.API
	services  []*service.Service
	snapshots []string
}

func InitServiceAPITest(args ...string) {
	New(DefaultServiceAPITest).Run(args)
}

func (t ServiceAPITest) GetServices() ([]*service.Service, error) {
	return t.services, nil
}

func (t ServiceAPITest) GetService(id string) (*service.Service, error) {
	for i, s := range t.services {
		if s.Id == id {
			return t.services[i], nil
		}
	}

	return nil, ErrNoServiceFound
}

func (t ServiceAPITest) AddService(config api.ServiceConfig) (*service.Service, error) {
	endpoints := make([]servicedefinition.ServiceEndpoint, len(*config.LocalPorts)+len(*config.RemotePorts))
	i := 0
	for _, e := range *config.LocalPorts {
		e.Purpose = "local"
		endpoints[i] = e
		i++
	}
	for _, e := range *config.RemotePorts {
		e.Purpose = "remote"
		endpoints[i] = e
		i++
	}

	s := service.Service{
		Id:        fmt.Sprintf("%s-%s-%s", config.Name, config.PoolID, config.ImageID),
		Name:      config.Name,
		PoolId:    config.PoolID,
		ImageId:   config.ImageID,
		Endpoints: endpoints,
		Startup:   config.Command,
		Instances: 1,
	}

	return &s, nil
}

func (t ServiceAPITest) RemoveService(id string) error {
	_, err := t.GetService(id)
	return err
}

func (t ServiceAPITest) UpdateService(reader io.Reader) (*service.Service, error) {
	var s service.Service

	if err := json.NewDecoder(reader).Decode(&s); err != nil {
		return nil, ErrInvalidService
	}

	if _, err := t.GetService(s.Id); err != nil {
		return nil, err
	}

	return &s, nil
}

func (t ServiceAPITest) StartService(id string) (*host.Host, error) {
	if _, err := t.GetService(id); err != nil {
		return nil, err
	}

	h := host.Host{
		ID: fmt.Sprintf("%s-host", id),
	}

	return &h, nil
}

func (t ServiceAPITest) StopService(id string) error {
	if _, err := t.GetService(id); err != nil {
		return err
	}

	return nil
}

func (t ServiceAPITest) AssignIP(config api.IPConfig) (string, error) {
	if _, err := t.GetService(config.ServiceID); err != nil {
		return "", err
	}

	switch config.IPAddress {
	case IPAddressNotFound:
		return "", nil
	case "":
		return "0.0.0.0", nil
	default:
		return config.IPAddress, nil
	}
}

func (t ServiceAPITest) StartProxy(config api.ProxyConfig) error {
	return nil
}

func (t ServiceAPITest) StartShell(config api.ShellConfig) error {
	return nil
}

func (t ServiceAPITest) RunShell(config api.ShellConfig) error {
	s, err := t.GetService(config.ServiceID)
	if err != nil {
		return err
	}

	command, ok := s.Runs[config.Command]
	if !ok {
		return ErrCmdNotFound
	}

	fmt.Printf("%s %s\n", command, strings.Join(config.Args, " "))
	return nil
}

func (t ServiceAPITest) GetSnapshots() ([]string, error) {
	return t.snapshots, nil
}

func (t ServiceAPITest) GetSnapshotsByServiceID(id string) ([]string, error) {
	var snapshots []string
	for _, s := range t.snapshots {
		if strings.HasPrefix(s, id) {
			snapshots = append(snapshots, s)
		}
	}

	return snapshots, nil
}

func (t ServiceAPITest) AddSnapshot(id string) (string, error) {
	return fmt.Sprintf("%s-snapshot", id), nil
}

func ExampleServicedCli_cmdServiceList() {
	InitServiceAPITest("serviced", "service", "list", "-v")

	// Output:
	// [
	//    {
	//      "Id": "test-service-1",
	//      "Name": "Zenoss",
	//      "Context": "",
	//      "Startup": "startup command 1",
	//      "Description": "",
	//      "Tags": null,
	//      "ConfigFiles": null,
	//      "Instances": 0,
	//      "ImageId": "quay.io/zenossinc/tenantid1-core5x",
	//      "PoolId": "default",
	//      "DesiredState": 1,
	//      "HostPolicy": "",
	//      "Hostname": "",
	//      "Privileged": false,
	//      "Launch": "auto",
	//      "Endpoints": null,
	//      "Tasks": null,
	//      "ParentServiceId": "",
	//      "Volumes": null,
	//      "CreatedAt": "0001-01-01T00:00:00Z",
	//      "UpdatedAt": "0001-01-01T00:00:00Z",
	//      "DeploymentId": "Zenoss-resmgr",
	//      "DisableImage": false,
	//      "LogConfigs": null,
	//      "Snapshot": {
	//        "Pause": "",
	//        "Resume": ""
	//      },
	//      "Runs": {
	//        "goodbye": "echo goodbye world",
	//        "hello": "echo hello world"
	//      },
	//      "RAMCommitment": 0,
	//      "Actions": null,
	//      "HealthChecks": null
	//    },
	//    {
	//      "Id": "test-service-2",
	//      "Name": "Zope",
	//      "Context": "",
	//      "Startup": "startup command 2",
	//      "Description": "",
	//      "Tags": null,
	//      "ConfigFiles": null,
	//      "Instances": 1,
	//      "ImageId": "quay.io/zenossinc/tenantid2-core5x",
	//      "PoolId": "default",
	//      "DesiredState": 1,
	//      "HostPolicy": "",
	//      "Hostname": "",
	//      "Privileged": false,
	//      "Launch": "auto",
	//      "Endpoints": null,
	//      "Tasks": null,
	//      "ParentServiceId": "",
	//      "Volumes": null,
	//      "CreatedAt": "0001-01-01T00:00:00Z",
	//      "UpdatedAt": "0001-01-01T00:00:00Z",
	//      "DeploymentId": "Zenoss-core",
	//      "DisableImage": false,
	//      "LogConfigs": null,
	//      "Snapshot": {
	//        "Pause": "",
	//        "Resume": ""
	//      },
	//      "Runs": null,
	//      "RAMCommitment": 0,
	//      "Actions": null,
	//      "HealthChecks": null
	//    },
	//    {
	//      "Id": "test-service-3",
	//      "Name": "zencommand",
	//      "Context": "",
	//      "Startup": "startup command 3",
	//      "Description": "",
	//      "Tags": null,
	//      "ConfigFiles": null,
	//      "Instances": 2,
	//      "ImageId": "quay.io/zenossinc/tenantid1-opentsdb",
	//      "PoolId": "remote",
	//      "DesiredState": 1,
	//      "HostPolicy": "",
	//      "Hostname": "",
	//      "Privileged": false,
	//      "Launch": "manual",
	//      "Endpoints": null,
	//      "Tasks": null,
	//      "ParentServiceId": "",
	//      "Volumes": null,
	//      "CreatedAt": "0001-01-01T00:00:00Z",
	//      "UpdatedAt": "0001-01-01T00:00:00Z",
	//      "DeploymentId": "Zenoss-core",
	//      "DisableImage": false,
	//      "LogConfigs": null,
	//      "Snapshot": {
	//        "Pause": "",
	//        "Resume": ""
	//      },
	//      "Runs": null,
	//      "RAMCommitment": 0,
	//      "Actions": null,
	//      "HealthChecks": null
	//    }
	//  ]
}

func ExampleServicedCli_cmdServiceAdd() {
	InitServiceAPITest("serviced", "service", "add", "test-service", "test-pool", "test-image", "bash")

	// Output:
	// test-service-test-pool-test-image
}

func ExampleServicedCli_cmdServiceRemove() {
	InitServiceAPITest("serviced", "service", "rm", "test-service-1", "test-service-0")

	// Output:
	// test-service-1
}

func ExampleServicedCLI_CmdServiceRemove_usage() {
	InitServiceAPITest("serviced", "service", "remove")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    remove - Removes an existing service
	//
	// USAGE:
	//    command remove [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced service remove SERVICEID ...
	//
	// OPTIONS:
}

func ExampleServicedCli_cmdServiceEdit() {
	InitServiceAPITest("serviced", "service", "edit", "test-service-1")
}

func ExampleServicedCli_cmdServiceAssignIPs() {
	// Service does not exist
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-0")
	// IP Address not returned
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-3", IPAddressNotFound)
	// Auto-assignment
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-1")
	// Manual-assignment
	InitServiceAPITest("serviced", "service", "assign-ip", "test-service-2", "127.0.0.1")

	// Output:
	// 0.0.0.0
	// 127.0.0.1
}

func ExampleServicedCLI_CmdServiceAssignIPs_usage() {
	InitServiceAPITest("serviced", "service", "assign-ip")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    assign-ip - Assigns an IP address to a service's endpoints requiring an explicit IP address
	//
	// USAGE:
	//    command assign-ip [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced service assign-ip SERVICEID [IPADDRESS]
	//
	// OPTIONS:
}

func ExampleServicedCli_cmdServiceStart() {
	InitServiceAPITest("serviced", "service", "start", "test-service-1")

	// Output:
	// Service scheduled to start on host: test-service-1-host
}

func ExampleServicedCli_cmdServiceStop() {
	InitServiceAPITest("serviced", "service", "stop", "test-service-2")

	// Output:
	// Service scheduled to stop.
}

func ExampleServicedCli_cmdServiceProxy() {
}

func ExampleServicedCli_cmdServiceShell() {
}

func ExampleServicedCli_cmdServiceRun_list() {
	InitServiceAPITest("serviced", "service", "run", "test-service-0")
	InitServiceAPITest("serviced", "service", "run", "test-service-1")

	// Output:
	// hello
	// goodbye
}

func ExampleServicedCli_cmdServiceRun_exec() {
	InitServiceAPITest("serviced", "service", "run", "test-service-1", "notfound")
	InitServiceAPITest("serviced", "service", "run", "test-service-1", "hello", "-i")

	// Output:
	// echo hello world -i
}

func ExampleServicedCLI_CmdServiceRun_usage() {
	InitServiceAPITest("serviced", "service", "run")

	// Output:
	// Incorrect Usage.
	//
	// NAME:
	//    run - Runs a service command in a service instance
	//
	// USAGE:
	//    command run [command options] [arguments...]
	//
	// DESCRIPTION:
	//    serviced service run SERVICEID [COMMAND]
	//
	// OPTIONS:
	//    --saveas, -s 	saves the service instance with the given name
	//    --interactive, -i	runs the service instance as a tty
}
<|endoftext|>


<commit_before>/**
 * @file main.go
 * @author Mikhail Klementyev jollheef<AT>riseup.net
 * @license GNU AGPLv3
 * @date December, 2015
 * @brief contest checking system CLI
 *
 * Entry point for contest checking system CLI
 */

package main

import (
	"database/sql"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sort"

	"github.com/jollheef/henhouse/config"
	"github.com/jollheef/henhouse/db"
	"github.com/olekukonko/tablewriter"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

var (
	configPath = kingpin.Flag("config", "Path to configuration file.").String()

	// Task
	task = kingpin.Command("task", "Work with tasks.")

	taskList        = task.Command("list", "List tasks.")
	taskListWOFlags = taskList.Flag("without-flags", "Do not include flags in output.").Bool()

	taskAdd    = task.Command("add", "Add task.")
	taskAddXML = taskAdd.Arg("xml", "Path to xml.").Required().String()

	taskUpdate    = task.Command("update", "Update task.")
	taskUpdateID  = taskUpdate.Arg("id", "ID of task.").Required().Int()
	taskUpdateXML = taskUpdate.Arg("xml", "Path to xml.").Required().String()

	taskOpen   = task.Command("open", "Open task.")
	taskOpenID = taskOpen.Arg("id", "ID of task").Required().Int()

	taskClose   = task.Command("close", "Close task.")
	taskCloseID = taskClose.Arg("id", "ID of task").Required().Int()

	taskDump   = task.Command("dump", "Dump task to xml.")
	taskDumpID = taskDump.Arg("id", "ID of task").Required().Int()

	// Category
	category = kingpin.Command("category", "Work with categories.")

	categoryList = category.Command("list", "List categories.")

	categoryAdd  = category.Command("add", "Add category.")
	categoryName = categoryAdd.Arg("name", "Name.").Required().String()

	// Team
	team = kingpin.Command("team", "Work with teams.")

	teamList = team.Command("list", "List teams.")
)

var (
	// CommitID fill in ./build.sh
	CommitID string
	// BuildDate fill in ./build.sh
	BuildDate string
	// BuildTime fill in ./build.sh
	BuildTime string
)

func getCategoryByID(categoryID int, categories []db.Category) string {
	for _, cat := range categories {
		if cat.ID == categoryID {
			return cat.Name
		}
	}
	return "Unknown"
}

func getCategoryByName(name string, categories []db.Category) (id int, err error) {
	for _, cat := range categories {
		if cat.Name == name {
			return cat.ID, nil
		}
	}

	return 0, errors.New("Category " + name + " not found")
}

type byID []db.Task

func (t byID) Len() int           { return len(t) }
func (t byID) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
func (t byID) Less(i, j int) bool { return t[i].ID < t[j].ID }

func parseTask(path string, categories []db.Category) (t db.Task, err error) {

	content, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	task, err := config.ParseXMLTask(content)
	if err != nil {
		return
	}

	t.Name = task.Name
	t.Desc = task.Description
	t.NameEn = task.NameEn
	t.DescEn = task.DescriptionEn
	t.CategoryID, err = getCategoryByName(task.Category, categories)
	if err != nil {
		log.Println("Cant find category")
		return
	}

	t.Level = task.Level
	t.Flag = task.Flag
	t.Price = 500         // TODO support non-shared task
	t.Shared = true       // TODO support non-shared task
	t.MaxSharePrice = 500 // TODO support value from xml
	t.MinSharePrice = 100 // TODO support value from xml
	t.Opened = false      // by default task is closed
	t.Author = task.Author
	t.Tags = task.Tags
	t.ForceClosed = task.ForceClosed

	return
}

var cfgFiles = []string{"/etc/henhouse/cli.toml", "/etc/henhouse.toml",
	"cli.toml", "henhouse.toml"}

func taskUpdateCmd(database *sql.DB, categories []db.Category) (err error) {
	task, err := db.GetTask(database, *taskUpdateID)
	if err != nil {
		return
	}

	id := task.ID

	task, err = parseTask(*taskUpdateXML, categories)
	if err != nil {
		return
	}

	task.ID = id

	err = db.UpdateTask(database, &task)
	if err != nil {
		return
	}

	return
}

func taskAddCmd(database *sql.DB, categories []db.Category) (err error) {
	t, err := parseTask(*taskAddXML, categories)
	if err != nil {
		return
	}

	err = db.AddTask(database, &t)
	if err != nil {
		return
	}

	return
}

func taskListCmd(database *sql.DB, categories []db.Category) (err error) {
	tasks, err := db.GetTasks(database)
	if err != nil {
		return
	}

	sort.Sort(byID(tasks))

	table := tablewriter.NewWriter(os.Stdout)
	var header []string
	if *taskListWOFlags {
		header = []string{"ID", "Name", "Category", "Opened", "Solved by"}
	} else {
		header = []string{"ID", "Name", "Category", "Flag", "Opened", "Solved by"}
	}
	table.SetHeader(header)

	for _, task := range tasks {
		var solvedBy int
		solvedBy, err = db.GetSolvedCount(database, task.ID)
		if err != nil {
			return
		}

		var row []string

		row = append(row, fmt.Sprintf("%d", task.ID))
		row = append(row, task.Name)
		row = append(row, getCategoryByID(task.CategoryID, categories))
		if !*taskListWOFlags {
			row = append(row, task.Flag)
		}
		row = append(row, fmt.Sprintf("%v", task.Opened))
		row = append(row, fmt.Sprintf("%d", solvedBy))

		table.Append(row)
	}

	table.Render()

	return
}

func taskDumpCmd(database *sql.DB, categories []db.Category) (err error) {
	task, err := db.GetTask(database, *taskDumpID)
	if err != nil {
		return
	}

	xmlTask := config.Task{
		Name:          task.Name,
		NameEn:        task.NameEn,
		Description:   task.Desc,
		DescriptionEn: task.DescEn,
		Category:      getCategoryByID(task.CategoryID, categories),
		Level:         task.Level,
		Flag:          task.Flag,
		Author:        task.Author,
		ForceClosed:   task.ForceClosed,
		Tags:          task.Tags,
	}

	output, err := xml.MarshalIndent(xmlTask, "", "	")
	if err != nil {
		return

	}

	fmt.Fprintln(os.Stdout, string(output))

	return
}

func categoryListCmd(database *sql.DB) (err error) {
	categories, err := db.GetCategories(database)
	if err != nil {
		return
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"ID", "Name"})

	for _, cat := range categories {
		row := []string{fmt.Sprintf("%d", cat.ID), cat.Name}
		table.Append(row)
	}

	table.Render()

	return
}

func teamListCmd(database *sql.DB) (err error) {
	teams, err := db.GetTeams(database)
	if err != nil {
		return
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"ID", "Name"})

	for _, t := range teams {
		row := []string{fmt.Sprintf("%d", t.ID), t.Name}
		table.Append(row)
	}

	table.Render()

	return
}

func runCommandLine(database *sql.DB, categories []db.Category) (err error) {
	switch kingpin.Parse() {
	case "task add":
		err = taskAddCmd(database, categories)
	case "task update":
		err = taskUpdateCmd(database, categories)
	case "task list":
		err = taskListCmd(database, categories)
	case "task open":
		err = db.SetOpened(database, *taskOpenID, true)
	case "task close":
		err = db.SetOpened(database, *taskCloseID, false)
	case "task dump":
		err = taskDumpCmd(database, categories)
	case "category add":
		err = db.AddCategory(database, &db.Category{Name: *categoryName})
	case "category list":
		err = categoryListCmd(database)
	case "team list":
		err = teamListCmd(database)
	}

	return
}

func main() {

	if len(CommitID) > 7 {
		CommitID = CommitID[:7] // abbreviated commit hash
	}

	kingpin.Version(BuildDate + " " + CommitID +
		" (Mikhail Klementyev <jollheef@riseup.net>)")

	kingpin.Parse()

	var cfgPath string

	if *configPath != "" {
		cfgPath = *configPath
	} else {

		for _, cfgFile := range cfgFiles {
			_, err := os.Stat(cfgFile)
			if err == nil {
				cfgPath = cfgFile
				break
			}
		}
	}

	if cfgPath == "" {
		log.Fatalln("Config not found")
	}

	cfg, err := config.ReadConfig(cfgPath)
	if err != nil {
		log.Fatalln("Cannot open config:", err)
	}

	database, err := db.OpenDatabase(cfg.Database.Connection)
	if err != nil {
		log.Fatalln("Error:", err)
	}

	defer database.Close()

	database.SetMaxOpenConns(cfg.Database.MaxConnections)

	categories, err := db.GetCategories(database)
	if err != nil {
		log.Fatalln("Error:", err)
	}

	err = runCommandLine(database, categories)
	if err != nil {
		log.Fatalln("Error:", err)
	}
}
<commit_msg>Implements team info cli command<commit_after>/**
 * @file main.go
 * @author Mikhail Klementyev jollheef<AT>riseup.net
 * @license GNU AGPLv3
 * @date December, 2015
 * @brief contest checking system CLI
 *
 * Entry point for contest checking system CLI
 */

package main

import (
	"database/sql"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sort"

	"github.com/jollheef/henhouse/config"
	"github.com/jollheef/henhouse/db"
	"github.com/olekukonko/tablewriter"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

var (
	configPath = kingpin.Flag("config", "Path to configuration file.").String()

	// Task
	task = kingpin.Command("task", "Work with tasks.")

	taskList        = task.Command("list", "List tasks.")
	taskListWOFlags = taskList.Flag("without-flags", "Do not include flags in output.").Bool()

	taskAdd    = task.Command("add", "Add task.")
	taskAddXML = taskAdd.Arg("xml", "Path to xml.").Required().String()

	taskUpdate    = task.Command("update", "Update task.")
	taskUpdateID  = taskUpdate.Arg("id", "ID of task.").Required().Int()
	taskUpdateXML = taskUpdate.Arg("xml", "Path to xml.").Required().String()

	taskOpen   = task.Command("open", "Open task.")
	taskOpenID = taskOpen.Arg("id", "ID of task").Required().Int()

	taskClose   = task.Command("close", "Close task.")
	taskCloseID = taskClose.Arg("id", "ID of task").Required().Int()

	taskDump   = task.Command("dump", "Dump task to xml.")
	taskDumpID = taskDump.Arg("id", "ID of task").Required().Int()

	// Category
	category = kingpin.Command("category", "Work with categories.")

	categoryList = category.Command("list", "List categories.")

	categoryAdd  = category.Command("add", "Add category.")
	categoryName = categoryAdd.Arg("name", "Name.").Required().String()

	// Team
	team = kingpin.Command("team", "Work with teams.")

	teamList   = team.Command("list", "List teams.")
	teamInfo   = team.Command("info", "Information about team.")
	teamInfoID = teamInfo.Arg("id", "ID of task").Required().Int()
)

var (
	// CommitID fill in ./build.sh
	CommitID string
	// BuildDate fill in ./build.sh
	BuildDate string
	// BuildTime fill in ./build.sh
	BuildTime string
)

func getCategoryByID(categoryID int, categories []db.Category) string {
	for _, cat := range categories {
		if cat.ID == categoryID {
			return cat.Name
		}
	}
	return "Unknown"
}

func getCategoryByName(name string, categories []db.Category) (id int, err error) {
	for _, cat := range categories {
		if cat.Name == name {
			return cat.ID, nil
		}
	}

	return 0, errors.New("Category " + name + " not found")
}

type byID []db.Task

func (t byID) Len() int           { return len(t) }
func (t byID) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
func (t byID) Less(i, j int) bool { return t[i].ID < t[j].ID }

func parseTask(path string, categories []db.Category) (t db.Task, err error) {

	content, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	task, err := config.ParseXMLTask(content)
	if err != nil {
		return
	}

	t.Name = task.Name
	t.Desc = task.Description
	t.NameEn = task.NameEn
	t.DescEn = task.DescriptionEn
	t.CategoryID, err = getCategoryByName(task.Category, categories)
	if err != nil {
		log.Println("Cant find category")
		return
	}

	t.Level = task.Level
	t.Flag = task.Flag
	t.Price = 500         // TODO support non-shared task
	t.Shared = true       // TODO support non-shared task
	t.MaxSharePrice = 500 // TODO support value from xml
	t.MinSharePrice = 100 // TODO support value from xml
	t.Opened = false      // by default task is closed
	t.Author = task.Author
	t.Tags = task.Tags
	t.ForceClosed = task.ForceClosed

	return
}

var cfgFiles = []string{"/etc/henhouse/cli.toml", "/etc/henhouse.toml",
	"cli.toml", "henhouse.toml"}

func taskUpdateCmd(database *sql.DB, categories []db.Category) (err error) {
	task, err := db.GetTask(database, *taskUpdateID)
	if err != nil {
		return
	}

	id := task.ID

	task, err = parseTask(*taskUpdateXML, categories)
	if err != nil {
		return
	}

	task.ID = id

	err = db.UpdateTask(database, &task)
	if err != nil {
		return
	}

	return
}

func taskAddCmd(database *sql.DB, categories []db.Category) (err error) {
	t, err := parseTask(*taskAddXML, categories)
	if err != nil {
		return
	}

	err = db.AddTask(database, &t)
	if err != nil {
		return
	}

	return
}

func taskListCmd(database *sql.DB, categories []db.Category) (err error) {
	tasks, err := db.GetTasks(database)
	if err != nil {
		return
	}

	sort.Sort(byID(tasks))

	table := tablewriter.NewWriter(os.Stdout)
	var header []string
	if *taskListWOFlags {
		header = []string{"ID", "Name", "Category", "Opened", "Solved by"}
	} else {
		header = []string{"ID", "Name", "Category", "Flag", "Opened", "Solved by"}
	}
	table.SetHeader(header)

	for _, task := range tasks {
		var solvedBy int
		solvedBy, err = db.GetSolvedCount(database, task.ID)
		if err != nil {
			return
		}

		var row []string

		row = append(row, fmt.Sprintf("%d", task.ID))
		row = append(row, task.Name)
		row = append(row, getCategoryByID(task.CategoryID, categories))
		if !*taskListWOFlags {
			row = append(row, task.Flag)
		}
		row = append(row, fmt.Sprintf("%v", task.Opened))
		row = append(row, fmt.Sprintf("%d", solvedBy))

		table.Append(row)
	}

	table.Render()

	return
}

func taskDumpCmd(database *sql.DB, categories []db.Category) (err error) {
	task, err := db.GetTask(database, *taskDumpID)
	if err != nil {
		return
	}

	xmlTask := config.Task{
		Name:          task.Name,
		NameEn:        task.NameEn,
		Description:   task.Desc,
		DescriptionEn: task.DescEn,
		Category:      getCategoryByID(task.CategoryID, categories),
		Level:         task.Level,
		Flag:          task.Flag,
		Author:        task.Author,
		ForceClosed:   task.ForceClosed,
		Tags:          task.Tags,
	}

	output, err := xml.MarshalIndent(xmlTask, "", "	")
	if err != nil {
		return

	}

	fmt.Fprintln(os.Stdout, string(output))

	return
}

func categoryListCmd(database *sql.DB) (err error) {
	categories, err := db.GetCategories(database)
	if err != nil {
		return
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"ID", "Name"})

	for _, cat := range categories {
		row := []string{fmt.Sprintf("%d", cat.ID), cat.Name}
		table.Append(row)
	}

	table.Render()

	return
}

func teamListCmd(database *sql.DB) (err error) {
	teams, err := db.GetTeams(database)
	if err != nil {
		return
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"ID", "Name"})

	for _, t := range teams {
		row := []string{fmt.Sprintf("%d", t.ID), t.Name}
		table.Append(row)
	}

	table.Render()

	return
}

func teamInfoCmd(database *sql.DB) (err error) {
	teams, err := db.GetTeams(database)
	if err != nil {
		return
	}

	flags, err := db.GetFlags(database)
	if err != nil {
		return
	}

	tasks, err := db.GetTasks(database)
	if err != nil {
		return
	}

	for _, t := range teams {
		if t.ID == *teamInfoID {
			fmt.Println("ID:", t.ID)
			fmt.Println("Name:", t.Name)
			fmt.Println("Email:", t.Email)
			fmt.Println("Description:", t.Desc)
			fmt.Println("Token:", t.Token)
			fmt.Println("Test:", t.Test)
			fmt.Print("Solved: ")
			solvedCount := 0
			for _, f := range flags {
				if f.TeamID == t.ID {
					var task db.Task
					task, err = db.GetTask(database, f.TaskID)
					if err != nil {
						return
					}
					solvedCount += 1
					fmt.Printf("%s (%d), ", task.Name, task.ID)
				}
			}
			fmt.Println()
			fmt.Printf("Solved: %d/%d\n", solvedCount, len(tasks))
		}
	}

	return
}

func runCommandLine(database *sql.DB, categories []db.Category) (err error) {
	switch kingpin.Parse() {
	case "task add":
		err = taskAddCmd(database, categories)
	case "task update":
		err = taskUpdateCmd(database, categories)
	case "task list":
		err = taskListCmd(database, categories)
	case "task open":
		err = db.SetOpened(database, *taskOpenID, true)
	case "task close":
		err = db.SetOpened(database, *taskCloseID, false)
	case "task dump":
		err = taskDumpCmd(database, categories)
	case "category add":
		err = db.AddCategory(database, &db.Category{Name: *categoryName})
	case "category list":
		err = categoryListCmd(database)
	case "team list":
		err = teamListCmd(database)
	case "team info":
		err = teamInfoCmd(database)
	}

	return
}

func main() {

	if len(CommitID) > 7 {
		CommitID = CommitID[:7] // abbreviated commit hash
	}

	kingpin.Version(BuildDate + " " + CommitID +
		" (Mikhail Klementyev <jollheef@riseup.net>)")

	kingpin.Parse()

	var cfgPath string

	if *configPath != "" {
		cfgPath = *configPath
	} else {

		for _, cfgFile := range cfgFiles {
			_, err := os.Stat(cfgFile)
			if err == nil {
				cfgPath = cfgFile
				break
			}
		}
	}

	if cfgPath == "" {
		log.Fatalln("Config not found")
	}

	cfg, err := config.ReadConfig(cfgPath)
	if err != nil {
		log.Fatalln("Cannot open config:", err)
	}

	database, err := db.OpenDatabase(cfg.Database.Connection)
	if err != nil {
		log.Fatalln("Error:", err)
	}

	defer database.Close()

	database.SetMaxOpenConns(cfg.Database.MaxConnections)

	categories, err := db.GetCategories(database)
	if err != nil {
		log.Fatalln("Error:", err)
	}

	err = runCommandLine(database, categories)
	if err != nil {
		log.Fatalln("Error:", err)
	}
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2014-2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"net/url"
	"path"
	"sort"
	"sync"
	"time"
)

// localAdminClient - represents admin operation to be executed locally.
type localAdminClient struct {
}

// remoteAdminClient - represents admin operation to be executed
// remotely, via RPC.
type remoteAdminClient struct {
	*AuthRPCClient
}

// adminCmdRunner - abstracts local and remote execution of admin
// commands like service stop and service restart.
type adminCmdRunner interface {
	Restart() error
	ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error)
	ReInitDisks() error
	Uptime() (time.Duration, error)
}

// Restart - Sends a message over channel to the go-routine
// responsible for restarting the process.
func (lc localAdminClient) Restart() error {
	globalServiceSignalCh <- serviceRestart
	return nil
}

// ListLocks - Fetches lock information from local lock instrumentation.
func (lc localAdminClient) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
	return listLocksInfo(bucket, prefix, duration), nil
}

// Restart - Sends restart command to remote server via RPC.
func (rc remoteAdminClient) Restart() error {
	args := AuthRPCArgs{}
	reply := AuthRPCReply{}
	return rc.Call("Admin.Restart", &args, &reply)
}

// ListLocks - Sends list locks command to remote server via RPC.
func (rc remoteAdminClient) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
	listArgs := ListLocksQuery{
		bucket:   bucket,
		prefix:   prefix,
		duration: duration,
	}
	var reply ListLocksReply
	if err := rc.Call("Admin.ListLocks", &listArgs, &reply); err != nil {
		return nil, err
	}
	return reply.volLocks, nil
}

// ReInitDisks - There is nothing to do here, heal format REST API
// handler has already formatted and reinitialized the local disks.
func (lc localAdminClient) ReInitDisks() error {
	return nil
}

// ReInitDisks - Signals peers via RPC to reinitialize their disks and
// object layer.
func (rc remoteAdminClient) ReInitDisks() error {
	args := AuthRPCArgs{}
	reply := AuthRPCReply{}
	return rc.Call("Admin.ReInitDisks", &args, &reply)
}

// Uptime - Returns the uptime of this server. Timestamp is taken
// after object layer is initialized.
func (lc localAdminClient) Uptime() (time.Duration, error) {
	if globalBootTime.IsZero() {
		return time.Duration(0), errServerNotInitialized
	}

	return time.Now().UTC().Sub(globalBootTime), nil
}

// Uptime - returns the uptime of the server to which the RPC call is made.
func (rc remoteAdminClient) Uptime() (time.Duration, error) {
	args := AuthRPCArgs{}
	reply := UptimeReply{}
	err := rc.Call("Admin.Uptime", &args, &reply)
	if err != nil {
		return time.Duration(0), err
	}

	return reply.Uptime, nil
}

// adminPeer - represents an entity that implements Restart methods.
type adminPeer struct {
	addr      string
	cmdRunner adminCmdRunner
}

// type alias for a collection of adminPeer.
type adminPeers []adminPeer

// makeAdminPeers - helper function to construct a collection of adminPeer.
func makeAdminPeers(eps []*url.URL) adminPeers {
	var servicePeers []adminPeer

	// map to store peers that are already added to ret
	seenAddr := make(map[string]bool)

	// add local (self) as peer in the array
	servicePeers = append(servicePeers, adminPeer{
		globalMinioAddr,
		localAdminClient{},
	})
	seenAddr[globalMinioAddr] = true

	serverCred := serverConfig.GetCredential()
	// iterate over endpoints to find new remote peers and add
	// them to ret.
	for _, ep := range eps {
		if ep.Host == "" {
			continue
		}

		// Check if the remote host has been added already
		if !seenAddr[ep.Host] {
			cfg := authConfig{
				accessKey:       serverCred.AccessKey,
				secretKey:       serverCred.SecretKey,
				serverAddr:      ep.Host,
				secureConn:      globalIsSSL,
				serviceEndpoint: path.Join(reservedBucket, adminPath),
				serviceName:     "Admin",
			}

			servicePeers = append(servicePeers, adminPeer{
				addr:      ep.Host,
				cmdRunner: &remoteAdminClient{newAuthRPCClient(cfg)},
			})
			seenAddr[ep.Host] = true
		}
	}

	return servicePeers
}

// Initialize global adminPeer collection.
func initGlobalAdminPeers(eps []*url.URL) {
	globalAdminPeers = makeAdminPeers(eps)
}

// invokeServiceCmd - Invoke Restart command.
func invokeServiceCmd(cp adminPeer, cmd serviceSignal) (err error) {
	switch cmd {
	case serviceRestart:
		err = cp.cmdRunner.Restart()
	}
	return err
}

// sendServiceCmd - Invoke Restart command on remote peers
// adminPeer followed by on the local peer.
func sendServiceCmd(cps adminPeers, cmd serviceSignal) {
	// Send service command like stop or restart to all remote nodes and finally run on local node.
	errs := make([]error, len(cps))
	var wg sync.WaitGroup
	remotePeers := cps[1:]
	for i := range remotePeers {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			// we use idx+1 because remotePeers slice is 1 position shifted w.r.t cps
			errs[idx+1] = invokeServiceCmd(remotePeers[idx], cmd)
		}(i)
	}
	wg.Wait()
	errs[0] = invokeServiceCmd(cps[0], cmd)
}

// listPeerLocksInfo - fetch list of locks held on the given bucket,
// matching prefix held longer than duration from all peer servers.
func listPeerLocksInfo(peers adminPeers, bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
	// Used to aggregate volume lock information from all nodes.
	allLocks := make([][]VolumeLockInfo, len(peers))
	errs := make([]error, len(peers))
	var wg sync.WaitGroup
	localPeer := peers[0]
	remotePeers := peers[1:]
	for i, remotePeer := range remotePeers {
		wg.Add(1)
		go func(idx int, remotePeer adminPeer) {
			defer wg.Done()
			// `remotePeers` is right-shifted by one position relative to `peers`
			allLocks[idx], errs[idx] = remotePeer.cmdRunner.ListLocks(bucket, prefix, duration)
		}(i+1, remotePeer)
	}
	wg.Wait()
	allLocks[0], errs[0] = localPeer.cmdRunner.ListLocks(bucket, prefix, duration)

	// Summarizing errors received for ListLocks RPC across all
	// nodes.  N B the possible unavailability of quorum in errors
	// applies only to distributed setup.
	errCount, err := reduceErrs(errs, []error{})
	if err != nil {
		if errCount >= (len(peers)/2 + 1) {
			return nil, err
		}
		return nil, InsufficientReadQuorum{}
	}

	// Group lock information across nodes by (bucket, object)
	// pair. For readability only.
	paramLockMap := make(map[nsParam][]VolumeLockInfo)
	for _, nodeLocks := range allLocks {
		for _, lockInfo := range nodeLocks {
			param := nsParam{
				volume: lockInfo.Bucket,
				path:   lockInfo.Object,
			}
			paramLockMap[param] = append(paramLockMap[param], lockInfo)
		}
	}
	groupedLockInfos := []VolumeLockInfo{}
	for _, volLocks := range paramLockMap {
		groupedLockInfos = append(groupedLockInfos, volLocks...)
	}
	return groupedLockInfos, nil
}

// reInitPeerDisks - reinitialize disks and object layer on peer servers to use the new format.
func reInitPeerDisks(peers adminPeers) error {
	errs := make([]error, len(peers))

	// Send ReInitDisks RPC call to all nodes.
	// for local adminPeer this is a no-op.
	wg := sync.WaitGroup{}
	for i, peer := range peers {
		wg.Add(1)
		go func(idx int, peer adminPeer) {
			defer wg.Done()
			errs[idx] = peer.cmdRunner.ReInitDisks()
		}(i, peer)
	}
	wg.Wait()
	return nil
}

// uptimeSlice - used to sort uptimes in chronological order.
type uptimeSlice []struct {
	err    error
	uptime time.Duration
}

func (ts uptimeSlice) Len() int {
	return len(ts)
}

func (ts uptimeSlice) Less(i, j int) bool {
	return ts[i].uptime < ts[j].uptime
}

func (ts uptimeSlice) Swap(i, j int) {
	ts[i], ts[j] = ts[j], ts[i]
}

// getPeerUptimes - returns the uptime since the last time read quorum
// was established on success. Otherwise returns errXLReadQuorum.
func getPeerUptimes(peers adminPeers) (time.Duration, error) {
	uptimes := make(uptimeSlice, len(peers))

	// Get up time of all servers.
	wg := sync.WaitGroup{}
	for i, peer := range peers {
		wg.Add(1)
		go func(idx int, peer adminPeer) {
			defer wg.Done()
			uptimes[idx].uptime, uptimes[idx].err = peer.cmdRunner.Uptime()
		}(i, peer)
	}
	wg.Wait()

	// Sort uptimes in chronological order.
	sort.Sort(uptimes)

	// Pick the readQuorum'th uptime in chronological order. i.e,
	// the time at which read quorum was (re-)established.
	readQuorum := len(uptimes) / 2
	validCount := 0
	latestUptime := time.Duration(0)
	for _, uptime := range uptimes {
		if uptime.err != nil {
			continue
		}

		validCount++
		if validCount >= readQuorum {
			latestUptime = uptime.uptime
			break
		}
	}

	// This implies there weren't read quorum number of servers up.
	if latestUptime == time.Duration(0) {
		return time.Duration(0), InsufficientReadQuorum{}
	}

	return latestUptime, nil
}
<commit_msg>Handle uptime for single-node instances differnt from distributed setup (#3726)<commit_after>/*
 * Minio Cloud Storage, (C) 2014-2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"net/url"
	"path"
	"sort"
	"sync"
	"time"
)

// localAdminClient - represents admin operation to be executed locally.
type localAdminClient struct {
}

// remoteAdminClient - represents admin operation to be executed
// remotely, via RPC.
type remoteAdminClient struct {
	*AuthRPCClient
}

// adminCmdRunner - abstracts local and remote execution of admin
// commands like service stop and service restart.
type adminCmdRunner interface {
	Restart() error
	ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error)
	ReInitDisks() error
	Uptime() (time.Duration, error)
}

// Restart - Sends a message over channel to the go-routine
// responsible for restarting the process.
func (lc localAdminClient) Restart() error {
	globalServiceSignalCh <- serviceRestart
	return nil
}

// ListLocks - Fetches lock information from local lock instrumentation.
func (lc localAdminClient) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
	return listLocksInfo(bucket, prefix, duration), nil
}

// Restart - Sends restart command to remote server via RPC.
func (rc remoteAdminClient) Restart() error {
	args := AuthRPCArgs{}
	reply := AuthRPCReply{}
	return rc.Call("Admin.Restart", &args, &reply)
}

// ListLocks - Sends list locks command to remote server via RPC.
func (rc remoteAdminClient) ListLocks(bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
	listArgs := ListLocksQuery{
		bucket:   bucket,
		prefix:   prefix,
		duration: duration,
	}
	var reply ListLocksReply
	if err := rc.Call("Admin.ListLocks", &listArgs, &reply); err != nil {
		return nil, err
	}
	return reply.volLocks, nil
}

// ReInitDisks - There is nothing to do here, heal format REST API
// handler has already formatted and reinitialized the local disks.
func (lc localAdminClient) ReInitDisks() error {
	return nil
}

// ReInitDisks - Signals peers via RPC to reinitialize their disks and
// object layer.
func (rc remoteAdminClient) ReInitDisks() error {
	args := AuthRPCArgs{}
	reply := AuthRPCReply{}
	return rc.Call("Admin.ReInitDisks", &args, &reply)
}

// Uptime - Returns the uptime of this server. Timestamp is taken
// after object layer is initialized.
func (lc localAdminClient) Uptime() (time.Duration, error) {
	if globalBootTime.IsZero() {
		return time.Duration(0), errServerNotInitialized
	}

	return time.Now().UTC().Sub(globalBootTime), nil
}

// Uptime - returns the uptime of the server to which the RPC call is made.
func (rc remoteAdminClient) Uptime() (time.Duration, error) {
	args := AuthRPCArgs{}
	reply := UptimeReply{}
	err := rc.Call("Admin.Uptime", &args, &reply)
	if err != nil {
		return time.Duration(0), err
	}

	return reply.Uptime, nil
}

// adminPeer - represents an entity that implements Restart methods.
type adminPeer struct {
	addr      string
	cmdRunner adminCmdRunner
}

// type alias for a collection of adminPeer.
type adminPeers []adminPeer

// makeAdminPeers - helper function to construct a collection of adminPeer.
func makeAdminPeers(eps []*url.URL) adminPeers {
	var servicePeers []adminPeer

	// map to store peers that are already added to ret
	seenAddr := make(map[string]bool)

	// add local (self) as peer in the array
	servicePeers = append(servicePeers, adminPeer{
		globalMinioAddr,
		localAdminClient{},
	})
	seenAddr[globalMinioAddr] = true

	serverCred := serverConfig.GetCredential()
	// iterate over endpoints to find new remote peers and add
	// them to ret.
	for _, ep := range eps {
		if ep.Host == "" {
			continue
		}

		// Check if the remote host has been added already
		if !seenAddr[ep.Host] {
			cfg := authConfig{
				accessKey:       serverCred.AccessKey,
				secretKey:       serverCred.SecretKey,
				serverAddr:      ep.Host,
				secureConn:      globalIsSSL,
				serviceEndpoint: path.Join(reservedBucket, adminPath),
				serviceName:     "Admin",
			}

			servicePeers = append(servicePeers, adminPeer{
				addr:      ep.Host,
				cmdRunner: &remoteAdminClient{newAuthRPCClient(cfg)},
			})
			seenAddr[ep.Host] = true
		}
	}

	return servicePeers
}

// Initialize global adminPeer collection.
func initGlobalAdminPeers(eps []*url.URL) {
	globalAdminPeers = makeAdminPeers(eps)
}

// invokeServiceCmd - Invoke Restart command.
func invokeServiceCmd(cp adminPeer, cmd serviceSignal) (err error) {
	switch cmd {
	case serviceRestart:
		err = cp.cmdRunner.Restart()
	}
	return err
}

// sendServiceCmd - Invoke Restart command on remote peers
// adminPeer followed by on the local peer.
func sendServiceCmd(cps adminPeers, cmd serviceSignal) {
	// Send service command like stop or restart to all remote nodes and finally run on local node.
	errs := make([]error, len(cps))
	var wg sync.WaitGroup
	remotePeers := cps[1:]
	for i := range remotePeers {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			// we use idx+1 because remotePeers slice is 1 position shifted w.r.t cps
			errs[idx+1] = invokeServiceCmd(remotePeers[idx], cmd)
		}(i)
	}
	wg.Wait()
	errs[0] = invokeServiceCmd(cps[0], cmd)
}

// listPeerLocksInfo - fetch list of locks held on the given bucket,
// matching prefix held longer than duration from all peer servers.
func listPeerLocksInfo(peers adminPeers, bucket, prefix string, duration time.Duration) ([]VolumeLockInfo, error) {
	// Used to aggregate volume lock information from all nodes.
	allLocks := make([][]VolumeLockInfo, len(peers))
	errs := make([]error, len(peers))
	var wg sync.WaitGroup
	localPeer := peers[0]
	remotePeers := peers[1:]
	for i, remotePeer := range remotePeers {
		wg.Add(1)
		go func(idx int, remotePeer adminPeer) {
			defer wg.Done()
			// `remotePeers` is right-shifted by one position relative to `peers`
			allLocks[idx], errs[idx] = remotePeer.cmdRunner.ListLocks(bucket, prefix, duration)
		}(i+1, remotePeer)
	}
	wg.Wait()
	allLocks[0], errs[0] = localPeer.cmdRunner.ListLocks(bucket, prefix, duration)

	// Summarizing errors received for ListLocks RPC across all
	// nodes.  N B the possible unavailability of quorum in errors
	// applies only to distributed setup.
	errCount, err := reduceErrs(errs, []error{})
	if err != nil {
		if errCount >= (len(peers)/2 + 1) {
			return nil, err
		}
		return nil, InsufficientReadQuorum{}
	}

	// Group lock information across nodes by (bucket, object)
	// pair. For readability only.
	paramLockMap := make(map[nsParam][]VolumeLockInfo)
	for _, nodeLocks := range allLocks {
		for _, lockInfo := range nodeLocks {
			param := nsParam{
				volume: lockInfo.Bucket,
				path:   lockInfo.Object,
			}
			paramLockMap[param] = append(paramLockMap[param], lockInfo)
		}
	}
	groupedLockInfos := []VolumeLockInfo{}
	for _, volLocks := range paramLockMap {
		groupedLockInfos = append(groupedLockInfos, volLocks...)
	}
	return groupedLockInfos, nil
}

// reInitPeerDisks - reinitialize disks and object layer on peer servers to use the new format.
func reInitPeerDisks(peers adminPeers) error {
	errs := make([]error, len(peers))

	// Send ReInitDisks RPC call to all nodes.
	// for local adminPeer this is a no-op.
	wg := sync.WaitGroup{}
	for i, peer := range peers {
		wg.Add(1)
		go func(idx int, peer adminPeer) {
			defer wg.Done()
			errs[idx] = peer.cmdRunner.ReInitDisks()
		}(i, peer)
	}
	wg.Wait()
	return nil
}

// uptimeSlice - used to sort uptimes in chronological order.
type uptimeSlice []struct {
	err    error
	uptime time.Duration
}

func (ts uptimeSlice) Len() int {
	return len(ts)
}

func (ts uptimeSlice) Less(i, j int) bool {
	return ts[i].uptime < ts[j].uptime
}

func (ts uptimeSlice) Swap(i, j int) {
	ts[i], ts[j] = ts[j], ts[i]
}

// getPeerUptimes - returns the uptime since the last time read quorum
// was established on success. Otherwise returns errXLReadQuorum.
func getPeerUptimes(peers adminPeers) (time.Duration, error) {
	// In a single node Erasure or FS backend setup the uptime of
	// the setup is the uptime of the single minio server
	// instance.
	if !globalIsDistXL {
		return time.Now().UTC().Sub(globalBootTime), nil
	}

	uptimes := make(uptimeSlice, len(peers))

	// Get up time of all servers.
	wg := sync.WaitGroup{}
	for i, peer := range peers {
		wg.Add(1)
		go func(idx int, peer adminPeer) {
			defer wg.Done()
			uptimes[idx].uptime, uptimes[idx].err = peer.cmdRunner.Uptime()
		}(i, peer)
	}
	wg.Wait()

	// Sort uptimes in chronological order.
	sort.Sort(uptimes)

	// Pick the readQuorum'th uptime in chronological order. i.e,
	// the time at which read quorum was (re-)established.
	readQuorum := len(uptimes) / 2
	validCount := 0
	latestUptime := time.Duration(0)
	for _, uptime := range uptimes {
		if uptime.err != nil {
			continue
		}

		validCount++
		if validCount >= readQuorum {
			latestUptime = uptime.uptime
			break
		}
	}

	// Less than readQuorum "Admin.Uptime" RPC call returned
	// successfully, so read-quorum unavailable.
	if validCount < readQuorum {
		return time.Duration(0), InsufficientReadQuorum{}
	}

	return latestUptime, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/asdine/storm"

	lumberjack "gopkg.in/natefinch/lumberjack.v2"

	"github.com/hacdias/filemanager"
	"github.com/hacdias/filemanager/bolt"
	h "github.com/hacdias/filemanager/http"
	"github.com/hacdias/filemanager/staticgen"
	"github.com/hacdias/fileutils"
	flag "github.com/spf13/pflag"
	"github.com/spf13/viper"
)

var (
	addr          string
	config        string
	database      string
	scope         string
	commands      string
	logfile       string
	staticg       string
	locale        string
	baseurl       string
	prefixurl     string
	viewMode      string
	port          int
	noAuth        bool
	allowCommands bool
	allowEdit     bool
	allowNew      bool
	allowPublish  bool
	showVer       bool
)

func init() {
	flag.StringVarP(&config, "config", "c", "", "Configuration file")
	flag.IntVarP(&port, "port", "p", 0, "HTTP Port (default is random)")
	flag.StringVarP(&addr, "address", "a", "", "Address to listen to (default is all of them)")
	flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
	flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
	flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
	flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
	flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
	flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
	flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
	flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
	flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
	flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
	flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
	flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
	flag.StringVar(&locale, "locale", "en", "Default locale for new users")
	flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
	flag.BoolVarP(&showVer, "version", "v", false, "Show version")
}

func setupViper() {
	viper.SetDefault("Address", "")
	viper.SetDefault("Port", "0")
	viper.SetDefault("Database", "./filemanager.db")
	viper.SetDefault("Scope", ".")
	viper.SetDefault("Logger", "stdout")
	viper.SetDefault("Commands", []string{"git", "svn", "hg"})
	viper.SetDefault("AllowCommmands", true)
	viper.SetDefault("AllowEdit", true)
	viper.SetDefault("AllowNew", true)
	viper.SetDefault("AllowPublish", true)
	viper.SetDefault("StaticGen", "")
	viper.SetDefault("Locale", "en")
	viper.SetDefault("NoAuth", false)
	viper.SetDefault("BaseURL", "")
	viper.SetDefault("PrefixURL", "")
	viper.SetDefault("ViewMode", "mosaic")

	viper.BindPFlag("Port", flag.Lookup("port"))
	viper.BindPFlag("Address", flag.Lookup("address"))
	viper.BindPFlag("Database", flag.Lookup("database"))
	viper.BindPFlag("Scope", flag.Lookup("scope"))
	viper.BindPFlag("Logger", flag.Lookup("log"))
	viper.BindPFlag("Commands", flag.Lookup("commands"))
	viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
	viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
	viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
	viper.BindPFlag("AllowPublish", flag.Lookup("allow-publish"))
	viper.BindPFlag("Locale", flag.Lookup("locale"))
	viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
	viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
	viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
	viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
	viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))

	viper.SetConfigName("filemanager")
	viper.AddConfigPath(".")
}

func printVersion() {
	fmt.Println("filemanager version", filemanager.Version)
	os.Exit(0)
}

func main() {
	setupViper()
	flag.Parse()

	if showVer {
		printVersion()
	}

	// Add a configuration file if set.
	if config != "" {
		ext := filepath.Ext(config)
		dir := filepath.Dir(config)
		config = strings.TrimSuffix(config, ext)

		if dir != "" {
			viper.AddConfigPath(dir)
			config = strings.TrimPrefix(config, dir)
		}

		viper.SetConfigName(config)
	}

	// Read configuration from a file if exists.
	err := viper.ReadInConfig()
	if err != nil {
		if _, ok := err.(viper.ConfigParseError); ok {
			panic(err)
		}
	}

	// Set up process log before anything bad happens.
	switch viper.GetString("Logger") {
	case "stdout":
		log.SetOutput(os.Stdout)
	case "stderr":
		log.SetOutput(os.Stderr)
	case "":
		log.SetOutput(ioutil.Discard)
	default:
		log.SetOutput(&lumberjack.Logger{
			Filename:   logfile,
			MaxSize:    100,
			MaxAge:     14,
			MaxBackups: 10,
		})
	}

	// Builds the address and a listener.
	laddr := viper.GetString("Address") + ":" + viper.GetString("Port")
	listener, err := net.Listen("tcp", laddr)
	if err != nil {
		log.Fatal(err)
	}

	// Tell the user the port in which is listening.
	fmt.Println("Listening on", listener.Addr().String())

	// Starts the server.
	if err := http.Serve(listener, handler()); err != nil {
		log.Fatal(err)
	}
}

func handler() http.Handler {
	db, err := storm.Open(viper.GetString("Database"))
	if err != nil {
		log.Fatal(err)
	}

	fm := &filemanager.FileManager{
		NoAuth:    viper.GetBool("NoAuth"),
		BaseURL:   viper.GetString("BaseURL"),
		PrefixURL: viper.GetString("PrefixURL"),
		DefaultUser: &filemanager.User{
			AllowCommands: viper.GetBool("AllowCommands"),
			AllowEdit:     viper.GetBool("AllowEdit"),
			AllowNew:      viper.GetBool("AllowNew"),
			AllowPublish:  viper.GetBool("AllowPublish"),
			Commands:      viper.GetStringSlice("Commands"),
			Rules:         []*filemanager.Rule{},
			Locale:        viper.GetString("Locale"),
			CSS:           "",
			Scope:         viper.GetString("Scope"),
			FileSystem:    fileutils.Dir(viper.GetString("Scope")),
			ViewMode:      viper.GetString("ViewMode"),
		},
		Store: &filemanager.Store{
			Config: bolt.ConfigStore{DB: db},
			Users:  bolt.UsersStore{DB: db},
			Share:  bolt.ShareStore{DB: db},
		},
		NewFS: func(scope string) filemanager.FileSystem {
			return fileutils.Dir(scope)
		},
	}

	err = fm.Setup()
	if err != nil {
		log.Fatal(err)
	}

	switch viper.GetString("StaticGen") {
	case "hugo":
		hugo := &staticgen.Hugo{
			Root:        viper.GetString("Scope"),
			Public:      filepath.Join(viper.GetString("Scope"), "public"),
			Args:        []string{},
			CleanPublic: true,
		}

		if err = fm.Attach(hugo); err != nil {
			log.Fatal(err)
		}
	case "jekyll":
		jekyll := &staticgen.Jekyll{
			Root:        viper.GetString("Scope"),
			Public:      filepath.Join(viper.GetString("Scope"), "_site"),
			Args:        []string{"build"},
			CleanPublic: true,
		}

		if err = fm.Attach(jekyll); err != nil {
			log.Fatal(err)
		}
	}

	return h.Handler(fm)
}
<commit_msg>Add ReCaptcha to main<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/asdine/storm"

	lumberjack "gopkg.in/natefinch/lumberjack.v2"

	"github.com/hacdias/filemanager"
	"github.com/hacdias/filemanager/bolt"
	h "github.com/hacdias/filemanager/http"
	"github.com/hacdias/filemanager/staticgen"
	"github.com/hacdias/fileutils"
	flag "github.com/spf13/pflag"
	"github.com/spf13/viper"
)

var (
	addr            string
	config          string
	database        string
	scope           string
	commands        string
	logfile         string
	staticg         string
	locale          string
	baseurl         string
	prefixurl       string
	viewMode        string
	recaptchakey    string
	recaptchasecret string
	port            int
	noAuth          bool
	allowCommands   bool
	allowEdit       bool
	allowNew        bool
	allowPublish    bool
	showVer         bool
)

func init() {
	flag.StringVarP(&config, "config", "c", "", "Configuration file")
	flag.IntVarP(&port, "port", "p", 0, "HTTP Port (default is random)")
	flag.StringVarP(&addr, "address", "a", "", "Address to listen to (default is all of them)")
	flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
	flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
	flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
	flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
	flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
	flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
	flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
	flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
	flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
	flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
	flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
	flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
	flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
	flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
	flag.StringVar(&locale, "locale", "en", "Default locale for new users")
	flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
	flag.BoolVarP(&showVer, "version", "v", false, "Show version")
}

func setupViper() {
	viper.SetDefault("Address", "")
	viper.SetDefault("Port", "0")
	viper.SetDefault("Database", "./filemanager.db")
	viper.SetDefault("Scope", ".")
	viper.SetDefault("Logger", "stdout")
	viper.SetDefault("Commands", []string{"git", "svn", "hg"})
	viper.SetDefault("AllowCommmands", true)
	viper.SetDefault("AllowEdit", true)
	viper.SetDefault("AllowNew", true)
	viper.SetDefault("AllowPublish", true)
	viper.SetDefault("StaticGen", "")
	viper.SetDefault("Locale", "en")
	viper.SetDefault("NoAuth", false)
	viper.SetDefault("BaseURL", "")
	viper.SetDefault("PrefixURL", "")
	viper.SetDefault("ViewMode", "mosaic")
	viper.SetDefault("ReCaptchaKey", "")
	viper.SetDefault("ReCaptchaSecret", "")

	viper.BindPFlag("Port", flag.Lookup("port"))
	viper.BindPFlag("Address", flag.Lookup("address"))
	viper.BindPFlag("Database", flag.Lookup("database"))
	viper.BindPFlag("Scope", flag.Lookup("scope"))
	viper.BindPFlag("Logger", flag.Lookup("log"))
	viper.BindPFlag("Commands", flag.Lookup("commands"))
	viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
	viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
	viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
	viper.BindPFlag("AllowPublish", flag.Lookup("allow-publish"))
	viper.BindPFlag("Locale", flag.Lookup("locale"))
	viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
	viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
	viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
	viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
	viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
	viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
	viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))

	viper.SetConfigName("filemanager")
	viper.AddConfigPath(".")
}

func printVersion() {
	fmt.Println("filemanager version", filemanager.Version)
	os.Exit(0)
}

func main() {
	setupViper()
	flag.Parse()

	if showVer {
		printVersion()
	}

	// Add a configuration file if set.
	if config != "" {
		ext := filepath.Ext(config)
		dir := filepath.Dir(config)
		config = strings.TrimSuffix(config, ext)

		if dir != "" {
			viper.AddConfigPath(dir)
			config = strings.TrimPrefix(config, dir)
		}

		viper.SetConfigName(config)
	}

	// Read configuration from a file if exists.
	err := viper.ReadInConfig()
	if err != nil {
		if _, ok := err.(viper.ConfigParseError); ok {
			panic(err)
		}
	}

	// Set up process log before anything bad happens.
	switch viper.GetString("Logger") {
	case "stdout":
		log.SetOutput(os.Stdout)
	case "stderr":
		log.SetOutput(os.Stderr)
	case "":
		log.SetOutput(ioutil.Discard)
	default:
		log.SetOutput(&lumberjack.Logger{
			Filename:   logfile,
			MaxSize:    100,
			MaxAge:     14,
			MaxBackups: 10,
		})
	}

	// Builds the address and a listener.
	laddr := viper.GetString("Address") + ":" + viper.GetString("Port")
	listener, err := net.Listen("tcp", laddr)
	if err != nil {
		log.Fatal(err)
	}

	// Tell the user the port in which is listening.
	fmt.Println("Listening on", listener.Addr().String())

	// Starts the server.
	if err := http.Serve(listener, handler()); err != nil {
		log.Fatal(err)
	}
}

func handler() http.Handler {
	db, err := storm.Open(viper.GetString("Database"))
	if err != nil {
		log.Fatal(err)
	}

	fm := &filemanager.FileManager{
		NoAuth:          viper.GetBool("NoAuth"),
		BaseURL:         viper.GetString("BaseURL"),
		PrefixURL:       viper.GetString("PrefixURL"),
		ReCaptchaKey:    viper.GetString("ReCaptchaKey"),
		ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
		DefaultUser: &filemanager.User{
			AllowCommands: viper.GetBool("AllowCommands"),
			AllowEdit:     viper.GetBool("AllowEdit"),
			AllowNew:      viper.GetBool("AllowNew"),
			AllowPublish:  viper.GetBool("AllowPublish"),
			Commands:      viper.GetStringSlice("Commands"),
			Rules:         []*filemanager.Rule{},
			Locale:        viper.GetString("Locale"),
			CSS:           "",
			Scope:         viper.GetString("Scope"),
			FileSystem:    fileutils.Dir(viper.GetString("Scope")),
			ViewMode:      viper.GetString("ViewMode"),
		},
		Store: &filemanager.Store{
			Config: bolt.ConfigStore{DB: db},
			Users:  bolt.UsersStore{DB: db},
			Share:  bolt.ShareStore{DB: db},
		},
		NewFS: func(scope string) filemanager.FileSystem {
			return fileutils.Dir(scope)
		},
	}

	err = fm.Setup()
	if err != nil {
		log.Fatal(err)
	}

	switch viper.GetString("StaticGen") {
	case "hugo":
		hugo := &staticgen.Hugo{
			Root:        viper.GetString("Scope"),
			Public:      filepath.Join(viper.GetString("Scope"), "public"),
			Args:        []string{},
			CleanPublic: true,
		}

		if err = fm.Attach(hugo); err != nil {
			log.Fatal(err)
		}
	case "jekyll":
		jekyll := &staticgen.Jekyll{
			Root:        viper.GetString("Scope"),
			Public:      filepath.Join(viper.GetString("Scope"), "_site"),
			Args:        []string{"build"},
			CleanPublic: true,
		}

		if err = fm.Attach(jekyll); err != nil {
			log.Fatal(err)
		}
	}

	return h.Handler(fm)
}
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"math"
	"net"
	"sort"
	"strings"
	"time"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/termui"
	"github.com/nsf/termbox-go"
)

type Zktop struct {
	Ui  cli.Ui
	Cmd string
}

func (this *Zktop) Run(args []string) (exitCode int) {
	var (
		zone            string
		graph           bool
		refreshInterval time.Duration
	)
	cmdFlags := flag.NewFlagSet("zktop", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&zone, "z", "", "")
	cmdFlags.DurationVar(&refreshInterval, "r", time.Second*5, "")
	cmdFlags.BoolVar(&graph, "g", false, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 2
	}

	if graph {
		var zkzones = make([]*zk.ZkZone, 0)
		if zone == "" {
			forSortedZones(func(zkzone *zk.ZkZone) {
				zkzones = append(zkzones, zkzone)
			})
		} else {
			zkzone := zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
			zkzones = append(zkzones, zkzone)
		}

		this.draw(zkzones)
		return
	}

	for {
		refreshScreen()

		if zone == "" {
			forSortedZones(func(zkzone *zk.ZkZone) {
				this.displayZoneTop(zkzone)
			})
		} else {
			zkzone := zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
			this.displayZoneTop(zkzone)
		}

		time.Sleep(refreshInterval)
	}

	return
}

func (this *Zktop) displayZoneTop(zkzone *zk.ZkZone) {
	this.Ui.Output(color.Green(zkzone.Name()))
	header := "SERVER           PORT M      OUTST        RECVD         SENT CONNS ZNODES LAT(MIN/AVG/MAX)"
	this.Ui.Output(header)

	stats := zkzone.RunZkFourLetterCommand("stat")
	sortedHosts := make([]string, 0, len(stats))
	for hp, _ := range stats {
		sortedHosts = append(sortedHosts, hp)
	}
	sort.Strings(sortedHosts)

	for _, hostPort := range sortedHosts {
		host, port, err := net.SplitHostPort(hostPort)
		if err != nil {
			panic(err)
		}

		stat := this.parsedStat(stats[hostPort])
		this.Ui.Output(fmt.Sprintf("%-15s %5s %1s %10s %12s %12s %5s %6s %s",
			host, port,
			stat.mode,
			stat.outstanding,
			stat.received,
			stat.sent,
			stat.connections,
			stat.znodes,
			stat.latency,
		))
	}
}

func (this *Zktop) draw(zkzones []*zk.ZkZone) {
	err := termui.Init()
	if err != nil {
		panic(err)
	}
	defer termui.Close()

	termui.UseTheme("helloworld")

	sinps := (func() []float64 {
		n := 220
		ps := make([]float64, n)
		for i := range ps {
			ps[i] = 1 + math.Sin(float64(i)/5)
		}
		return ps
	})()

	lc0 := termui.NewLineChart()
	lc0.Border.Label = "zk"
	lc0.Data = sinps
	lc0.Width = 50
	lc0.Height = 12
	lc0.X = 0
	lc0.Y = 0
	lc0.AxesColor = termui.ColorWhite
	lc0.LineColor = termui.ColorGreen | termui.AttrBold

	termui.Render(lc0)
	termbox.PollEvent()
}

type zkStat struct {
	latency        string
	connections    string
	outstanding    string
	mode           string
	znodes         string
	received, sent string
}

func (this *Zktop) parsedStat(s string) (stat zkStat) {
	lines := strings.Split(s, "\n")
	for _, l := range lines {
		switch {
		case strings.HasPrefix(l, "Latency"):
			stat.latency = this.extractStatValue(l)

		case strings.HasPrefix(l, "Sent"):
			stat.sent = this.extractStatValue(l)

		case strings.HasPrefix(l, "Received"):
			stat.received = this.extractStatValue(l)

		case strings.HasPrefix(l, "Connections"):
			stat.connections = this.extractStatValue(l)

		case strings.HasPrefix(l, "Mode"):
			stat.mode = strings.ToUpper(this.extractStatValue(l)[:1])

		case strings.HasPrefix(l, "Node count"):
			stat.znodes = this.extractStatValue(l)

		case strings.HasPrefix(l, "Outstanding"):
			stat.outstanding = this.extractStatValue(l)

		}
	}
	return
}

func (this *Zktop) extractStatValue(l string) string {
	p := strings.SplitN(l, ":", 2)
	return strings.TrimSpace(p[1])
}

func (*Zktop) Synopsis() string {
	return "Unix “top” like utility for ZooKeeper"
}

func (this *Zktop) Help() string {
	help := fmt.Sprintf(`
Usage: %s zktop [options]

    Unix “top” like utility for ZooKeeper

Options:

    -z zone

    -g
      Draws zk connections in graph. TODO

    -r refresh duration
      e,g. 5s

`, this.Cmd)
	return strings.TrimSpace(help)
}
<commit_msg>red alert when zk down<commit_after>package command

import (
	"flag"
	"fmt"
	"math"
	"net"
	"sort"
	"strings"
	"time"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/termui"
	"github.com/nsf/termbox-go"
)

type Zktop struct {
	Ui  cli.Ui
	Cmd string
}

func (this *Zktop) Run(args []string) (exitCode int) {
	var (
		zone            string
		graph           bool
		refreshInterval time.Duration
	)
	cmdFlags := flag.NewFlagSet("zktop", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&zone, "z", "", "")
	cmdFlags.DurationVar(&refreshInterval, "r", time.Second*5, "")
	cmdFlags.BoolVar(&graph, "g", false, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 2
	}

	if graph {
		var zkzones = make([]*zk.ZkZone, 0)
		if zone == "" {
			forSortedZones(func(zkzone *zk.ZkZone) {
				zkzones = append(zkzones, zkzone)
			})
		} else {
			zkzone := zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
			zkzones = append(zkzones, zkzone)
		}

		this.draw(zkzones)
		return
	}

	for {
		refreshScreen()

		if zone == "" {
			forSortedZones(func(zkzone *zk.ZkZone) {
				this.displayZoneTop(zkzone)
			})
		} else {
			zkzone := zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
			this.displayZoneTop(zkzone)
		}

		time.Sleep(refreshInterval)
	}

	return
}

func (this *Zktop) displayZoneTop(zkzone *zk.ZkZone) {
	this.Ui.Output(color.Green(zkzone.Name()))
	header := "SERVER           PORT M      OUTST        RECVD         SENT CONNS ZNODES LAT(MIN/AVG/MAX)"
	this.Ui.Output(header)

	stats := zkzone.RunZkFourLetterCommand("stat")
	sortedHosts := make([]string, 0, len(stats))
	for hp, _ := range stats {
		sortedHosts = append(sortedHosts, hp)
	}
	sort.Strings(sortedHosts)

	for _, hostPort := range sortedHosts {
		host, port, err := net.SplitHostPort(hostPort)
		if err != nil {
			panic(err)
		}

		stat := this.parsedStat(stats[hostPort])
		if stat.mode == "" {
			stat.mode = color.Red("E")
		}
		this.Ui.Output(fmt.Sprintf("%-15s %5s %1s %10s %12s %12s %5s %6s %s",
			host, port,
			stat.mode,
			stat.outstanding,
			stat.received,
			stat.sent,
			stat.connections,
			stat.znodes,
			stat.latency,
		))
	}
}

func (this *Zktop) draw(zkzones []*zk.ZkZone) {
	err := termui.Init()
	if err != nil {
		panic(err)
	}
	defer termui.Close()

	termui.UseTheme("helloworld")

	sinps := (func() []float64 {
		n := 220
		ps := make([]float64, n)
		for i := range ps {
			ps[i] = 1 + math.Sin(float64(i)/5)
		}
		return ps
	})()

	lc0 := termui.NewLineChart()
	lc0.Border.Label = "zk"
	lc0.Data = sinps
	lc0.Width = 50
	lc0.Height = 12
	lc0.X = 0
	lc0.Y = 0
	lc0.AxesColor = termui.ColorWhite
	lc0.LineColor = termui.ColorGreen | termui.AttrBold

	termui.Render(lc0)
	termbox.PollEvent()
}

type zkStat struct {
	latency        string
	connections    string
	outstanding    string
	mode           string
	znodes         string
	received, sent string
}

func (this *Zktop) parsedStat(s string) (stat zkStat) {
	lines := strings.Split(s, "\n")
	for _, l := range lines {
		switch {
		case strings.HasPrefix(l, "Latency"):
			stat.latency = this.extractStatValue(l)

		case strings.HasPrefix(l, "Sent"):
			stat.sent = this.extractStatValue(l)

		case strings.HasPrefix(l, "Received"):
			stat.received = this.extractStatValue(l)

		case strings.HasPrefix(l, "Connections"):
			stat.connections = this.extractStatValue(l)

		case strings.HasPrefix(l, "Mode"):
			stat.mode = strings.ToUpper(this.extractStatValue(l)[:1])

		case strings.HasPrefix(l, "Node count"):
			stat.znodes = this.extractStatValue(l)

		case strings.HasPrefix(l, "Outstanding"):
			stat.outstanding = this.extractStatValue(l)

		}
	}
	return
}

func (this *Zktop) extractStatValue(l string) string {
	p := strings.SplitN(l, ":", 2)
	return strings.TrimSpace(p[1])
}

func (*Zktop) Synopsis() string {
	return "Unix “top” like utility for ZooKeeper"
}

func (this *Zktop) Help() string {
	help := fmt.Sprintf(`
Usage: %s zktop [options]

    Unix “top” like utility for ZooKeeper

Options:

    -z zone

    -g
      Draws zk connections in graph. TODO

    -r refresh duration
      e,g. 5s

`, this.Cmd)
	return strings.TrimSpace(help)
}
<|endoftext|>


<commit_before>package opt

import (
	"encoding/json"
	"flag"
	"log"

	"github.com/nelhage/taktician/ai"
)

type Minimax struct {
	Seed         int64
	Debug        int
	Depth        int
	MaxEvals     uint64
	Sort         bool
	TableMem     int64
	NullMove     bool
	ExtendForces bool
	ReduceSlides bool
	MultiCut     bool
	Precise      bool
	Weights      string
	ModWeights   string
	LogCuts      string
	Symmetry     bool
}

func (o *Minimax) AddFlags(flags *flag.FlagSet) {
	flags.IntVar(&o.Debug, "debug", 1, "debug level")
	flags.Int64Var(&o.Seed, "seed", 0, "specify a seed")
	flags.IntVar(&o.Depth, "depth", 0, "minimax depth")
	flags.Uint64Var(&o.MaxEvals, "max-evals", 0, "Limit the search by number of nodes evaluated")
	flags.BoolVar(&o.Sort, "sort", true, "sort moves via history heuristic")
	flags.Int64Var(&o.TableMem, "table-mem", 0, "set table size")
	flags.BoolVar(&o.NullMove, "null-move", true, "use null-move pruning")
	flags.BoolVar(&o.ExtendForces, "extend-forces", true, "extend forced moves")
	flags.BoolVar(&o.ReduceSlides, "reduce-slides", true, "reduce trivial slides")
	flags.BoolVar(&o.MultiCut, "multi-cut", false, "use multi-cut pruning")
	flags.BoolVar(&o.Precise, "precise", false, "Limit to optimizations that provably preserve the game-theoretic value")
	flags.StringVar(&o.Weights, "weights", "", "JSON-encoded evaluation weights")
	flags.StringVar(&o.ModWeights, "mod-weights", "", "JSON-encoded evaluation weights applied on top of defaults")
	flags.StringVar(&o.LogCuts, "log-cuts", "", "log all cuts")
	flags.BoolVar(&o.Symmetry, "symmetry", false, "ignore symmetries")
}

func (o *Minimax) BuildConfig(size int) ai.MinimaxConfig {
	var w ai.Weights
	var err error
	if o.Weights == "" && o.ModWeights == "" {
		w = ai.DefaultWeights[size]
	} else if o.Weights != "" && o.ModWeights != "" {
		log.Fatalf("Can't combine -mod-weights and -weights")
	} else if o.Weights != "" {
		err = json.Unmarshal([]byte(o.Weights), &w)

	} else if o.ModWeights != "" {
		w = ai.DefaultWeights[size]
		err = json.Unmarshal([]byte(o.ModWeights), &w)
	}
	if err != nil {
		log.Fatalf("parse weights: %s", err.Error())
	}
	cfg := ai.MinimaxConfig{
		Size:     size,
		Depth:    o.Depth,
		MaxEvals: o.MaxEvals,
		Seed:     o.Seed,
		Debug:    o.Debug,

		NoSort:         !o.Sort,
		TableMem:       o.TableMem,
		NoNullMove:     !o.NullMove,
		NoExtendForces: !o.ExtendForces,
		NoReduceSlides: !o.ReduceSlides,
		MultiCut:       o.MultiCut,

		CutLog:        o.LogCuts,
		DedupSymmetry: o.Symmetry,

		Evaluate: ai.MakeEvaluator(size, &w),
	}
	if o.Precise {
		cfg.MakePrecise()
	}
	return cfg
}
<commit_msg>Default to -debug=0<commit_after>package opt

import (
	"encoding/json"
	"flag"
	"log"

	"github.com/nelhage/taktician/ai"
)

type Minimax struct {
	Seed         int64
	Debug        int
	Depth        int
	MaxEvals     uint64
	Sort         bool
	TableMem     int64
	NullMove     bool
	ExtendForces bool
	ReduceSlides bool
	MultiCut     bool
	Precise      bool
	Weights      string
	ModWeights   string
	LogCuts      string
	Symmetry     bool
}

func (o *Minimax) AddFlags(flags *flag.FlagSet) {
	flags.IntVar(&o.Debug, "debug", 0, "debug level")
	flags.Int64Var(&o.Seed, "seed", 0, "specify a seed")
	flags.IntVar(&o.Depth, "depth", 0, "minimax depth")
	flags.Uint64Var(&o.MaxEvals, "max-evals", 0, "Limit the search by number of nodes evaluated")
	flags.BoolVar(&o.Sort, "sort", true, "sort moves via history heuristic")
	flags.Int64Var(&o.TableMem, "table-mem", 0, "set table size")
	flags.BoolVar(&o.NullMove, "null-move", true, "use null-move pruning")
	flags.BoolVar(&o.ExtendForces, "extend-forces", true, "extend forced moves")
	flags.BoolVar(&o.ReduceSlides, "reduce-slides", true, "reduce trivial slides")
	flags.BoolVar(&o.MultiCut, "multi-cut", false, "use multi-cut pruning")
	flags.BoolVar(&o.Precise, "precise", false, "Limit to optimizations that provably preserve the game-theoretic value")
	flags.StringVar(&o.Weights, "weights", "", "JSON-encoded evaluation weights")
	flags.StringVar(&o.ModWeights, "mod-weights", "", "JSON-encoded evaluation weights applied on top of defaults")
	flags.StringVar(&o.LogCuts, "log-cuts", "", "log all cuts")
	flags.BoolVar(&o.Symmetry, "symmetry", false, "ignore symmetries")
}

func (o *Minimax) BuildConfig(size int) ai.MinimaxConfig {
	var w ai.Weights
	var err error
	if o.Weights == "" && o.ModWeights == "" {
		w = ai.DefaultWeights[size]
	} else if o.Weights != "" && o.ModWeights != "" {
		log.Fatalf("Can't combine -mod-weights and -weights")
	} else if o.Weights != "" {
		err = json.Unmarshal([]byte(o.Weights), &w)

	} else if o.ModWeights != "" {
		w = ai.DefaultWeights[size]
		err = json.Unmarshal([]byte(o.ModWeights), &w)
	}
	if err != nil {
		log.Fatalf("parse weights: %s", err.Error())
	}
	cfg := ai.MinimaxConfig{
		Size:     size,
		Depth:    o.Depth,
		MaxEvals: o.MaxEvals,
		Seed:     o.Seed,
		Debug:    o.Debug,

		NoSort:         !o.Sort,
		TableMem:       o.TableMem,
		NoNullMove:     !o.NullMove,
		NoExtendForces: !o.ExtendForces,
		NoReduceSlides: !o.ReduceSlides,
		MultiCut:       o.MultiCut,

		CutLog:        o.LogCuts,
		DedupSymmetry: o.Symmetry,

		Evaluate: ai.MakeEvaluator(size, &w),
	}
	if o.Precise {
		cfg.MakePrecise()
	}
	return cfg
}
<|endoftext|>