<commit_before>package meep_test

import (
	"fmt"
	"os"
	"runtime"
	"testing"

	"."
	"./fixtures"
)

var cwd, _ = os.Getwd()

var use14fnnames bool

func init() {
	gover := runtime.Version()
	fmt.Fprintf(os.Stderr, "go version reports as %q\n", gover)
	// I have truely minimal desire to parse this "well".
	// If it's not recognized, we'll assume it's new.
	if gover[0:4] != "go1." {
		return
	}
	switch gover[4] {
	case '0', '1', '2', '3', '4':
		use14fnnames = true
	}
}

type stackFrameExpectation struct {
	n      int
	file   string
	line   int
	func14 string
	func15 string
}

func TestStacksStraightforward(t *testing.T) {
	here := 40
	var result meep.Stack
	fn := func() {
		result = *(meep.CaptureStack())
	}
	fixtures.WheeOne(fn)
	expects := []stackFrameExpectation{
		{0, cwd + "/stackinfo_test.go", here + 3, "meep_test.func·001", "meep_test.TestStacksStraightforward.func1"}, // right here, where we call `CaptureStack`
		{1, cwd + "/fixtures/stack1.go", 9, "", "fixtures.wheeTwo"},                                                  // should be in the body of the func
		{2, cwd + "/fixtures/stack1.go", 5, "", "fixtures.WheeOne"},                                                  // should be in the body of the func
		{3, cwd + "/stackinfo_test.go", here + 5, "", "meep_test.TestStacksStraightforward"},                         // right here, where we call `fixtures.*`
		// No need to get overly precise about line numbers in the stdlib:
		//{4, "/usr/local/go/src/testing/testing.go", 447, "", "testing.tRunner"},
		//{5, "/usr/local/go/src/runtime/asm_amd64.s", 2232, "", "runtime.goexit"},
	}
	expectLen := len(expects) + 2
	assertStack(t, result, expects, expectLen)
}

func TestStacksPlusDeferral(t *testing.T) {
	here := 60
	var result meep.Stack
	fn := func() {
		result = *(meep.CaptureStack())
	}
	fixtures.WheeTree(fn)
	expects := []stackFrameExpectation{
		// note the total lack of 'wheeTwo'; it's called, but already returned before the defer path is hit, so of course it's absent here.
		{0, cwd + "/stackinfo_test.go", here + 3, "meep_test.func·002", "meep_test.TestStacksPlusDeferral.func1"}, // right here, where we call `CaptureStack`
		{1, cwd + "/fixtures/stack1.go", 19, "", "fixtures.wheedee"},                                              // should be in the body of the func (natch, the declare location -- the defer location never shows up; that's not a new func)
		{2, cwd + "/fixtures/stack1.go", 16, "", "fixtures.WheeTree"},                                             // golang considers 'defer' to run on the last line of the parent func.  even if that's "}\n".
		{3, cwd + "/stackinfo_test.go", here + 5, "", "meep_test.TestStacksPlusDeferral"},                         // right here, where we call `fixtures.*`
		// No need to get overly precise about line numbers in the stdlib:
		//{4, "/usr/local/go/src/testing/testing.go", 447, "", "testing.tRunner"},
		//{5, "/usr/local/go/src/runtime/asm_amd64.s", 2232, "", "runtime.goexit"},
	}
	expectLen := len(expects) + 2
	assertStack(t, result, expects, expectLen)
}

func TestStacksPanickingInDefersOhMy(t *testing.T) {
	here := 81
	var result meep.Stack
	fixtures.BeesBuzz(func() {
		result = *(meep.CaptureStack())
	})
	expects := []stackFrameExpectation{
		// note the total lack of reference to where "recover" is called.  (That happened after the stack capture... not that that really matters;
		//   if you flip the recover before the BeesBuzz defer'd func's call to our thunk, this thing on line 9 just moves to 10, that's it -- there's no other flow change.)
		{0, cwd + "/stackinfo_test.go", here + 3, "meep_test.func·003", "meep_test.TestStacksPanickingInDefersOhMy.func1"}, // right here, where we call `CaptureStack` in our thunk
		{1, cwd + "/fixtures/stack2.go", 9, "fixtures.func·002", "fixtures.BeesBuzz.func1"},                                // the line in the deferred function that called our thunk
		// No need to get overly precise about line numbers in the stdlib:
		//{2, "/usr/local/go/src/runtime/asm_amd64.s", 401, "", "runtime.call16"}, // if this isn't a single line on some platforms... uff.
		//{3, "/usr/local/go/src/runtime/panic.go", 387, "", "runtime.gopanic"},  // it might be reasonable to detect these and elide everything following from `runtime.*`.
		{4, cwd + "/fixtures/stack2.go", 22, "", "fixtures.buzzkill"},                              // the line that panicked!
		{5, cwd + "/fixtures/stack2.go", 19, "", "fixtures.beesWuz"},                               // the trailing `}` of `beesWuz`, because we left it via defer
		{6, cwd + "/fixtures/stack2.go", 14, "", "fixtures.BeesBuzz"},                              // the body line the calls down to `beesWuz`
		{7, cwd + "/stackinfo_test.go", here + 4, "", "meep_test.TestStacksPanickingInDefersOhMy"}, // obtw!  when we split the `fixtures.*()` *invocation* across lines, this becomes the last one!
		// No need to get overly precise about line numbers in the stdlib",
		//{8, "/usr/local/go/src/testing/testing.go", 447, "", "testing.tRunner"},
		//{9, "/usr/local/go/src/runtime/asm_amd64.s", 2232, "", "runtime.goexit"},
	}
	expectLen := len(expects) + 4
	assertStack(t, result, expects, expectLen)
}

func assertStack(t *testing.T, result meep.Stack, expects []stackFrameExpectation, expectLen int) {
	// Some quick cleanup on the expectations:
	// If no exceptions were specified, the old 1.4 funcname is expected to be same as the new
	for _, ex := range expects {
		if ex.func14 == "" {
			ex.func14 = ex.func15
		}
	}

	// Assertions!
	for _, tr := range expects {
		file, line, fnname := result.Frames[tr.n].Where()
		if file != tr.file {
			t.Errorf("Stack[%d] file should be %q, was %q", tr.n, tr.file, file)
		}
		if line != tr.line {
			t.Errorf("Stack[%d] line should be %d, was %d", tr.n, tr.line, line)
		}
		expectedFnname := tr.func15
		if use14fnnames {
			expectedFnname = tr.func14
		}
		if fnname != expectedFnname {
			t.Errorf("Stack[%d] func name should be %q, was %q", tr.n, expectedFnname, fnname)
		}
	}
	for i, fr := range result.Frames {
		if i < expectLen {
			continue
		}
		t.Errorf("Stack[%d] was expected to be empty, was %q", i, fr.String())
	}
}
<commit_msg>Reach in to struct properly to fix 1.4 expectations.<commit_after>package meep_test

import (
	"fmt"
	"os"
	"runtime"
	"testing"

	"."
	"./fixtures"
)

var cwd, _ = os.Getwd()

var use14fnnames bool

func init() {
	gover := runtime.Version()
	fmt.Fprintf(os.Stderr, "go version reports as %q\n", gover)
	// I have truely minimal desire to parse this "well".
	// If it's not recognized, we'll assume it's new.
	if gover[0:4] != "go1." {
		return
	}
	switch gover[4] {
	case '0', '1', '2', '3', '4':
		use14fnnames = true
	}
}

type stackFrameExpectation struct {
	n      int
	file   string
	line   int
	func14 string
	func15 string
}

func TestStacksStraightforward(t *testing.T) {
	here := 40
	var result meep.Stack
	fn := func() {
		result = *(meep.CaptureStack())
	}
	fixtures.WheeOne(fn)
	expects := []stackFrameExpectation{
		{0, cwd + "/stackinfo_test.go", here + 3, "meep_test.func·001", "meep_test.TestStacksStraightforward.func1"}, // right here, where we call `CaptureStack`
		{1, cwd + "/fixtures/stack1.go", 9, "", "fixtures.wheeTwo"},                                                  // should be in the body of the func
		{2, cwd + "/fixtures/stack1.go", 5, "", "fixtures.WheeOne"},                                                  // should be in the body of the func
		{3, cwd + "/stackinfo_test.go", here + 5, "", "meep_test.TestStacksStraightforward"},                         // right here, where we call `fixtures.*`
		// No need to get overly precise about line numbers in the stdlib:
		//{4, "/usr/local/go/src/testing/testing.go", 447, "", "testing.tRunner"},
		//{5, "/usr/local/go/src/runtime/asm_amd64.s", 2232, "", "runtime.goexit"},
	}
	expectLen := len(expects) + 2
	assertStack(t, result, expects, expectLen)
}

func TestStacksPlusDeferral(t *testing.T) {
	here := 60
	var result meep.Stack
	fn := func() {
		result = *(meep.CaptureStack())
	}
	fixtures.WheeTree(fn)
	expects := []stackFrameExpectation{
		// note the total lack of 'wheeTwo'; it's called, but already returned before the defer path is hit, so of course it's absent here.
		{0, cwd + "/stackinfo_test.go", here + 3, "meep_test.func·002", "meep_test.TestStacksPlusDeferral.func1"}, // right here, where we call `CaptureStack`
		{1, cwd + "/fixtures/stack1.go", 19, "", "fixtures.wheedee"},                                              // should be in the body of the func (natch, the declare location -- the defer location never shows up; that's not a new func)
		{2, cwd + "/fixtures/stack1.go", 16, "", "fixtures.WheeTree"},                                             // golang considers 'defer' to run on the last line of the parent func.  even if that's "}\n".
		{3, cwd + "/stackinfo_test.go", here + 5, "", "meep_test.TestStacksPlusDeferral"},                         // right here, where we call `fixtures.*`
		// No need to get overly precise about line numbers in the stdlib:
		//{4, "/usr/local/go/src/testing/testing.go", 447, "", "testing.tRunner"},
		//{5, "/usr/local/go/src/runtime/asm_amd64.s", 2232, "", "runtime.goexit"},
	}
	expectLen := len(expects) + 2
	assertStack(t, result, expects, expectLen)
}

func TestStacksPanickingInDefersOhMy(t *testing.T) {
	here := 81
	var result meep.Stack
	fixtures.BeesBuzz(func() {
		result = *(meep.CaptureStack())
	})
	expects := []stackFrameExpectation{
		// note the total lack of reference to where "recover" is called.  (That happened after the stack capture... not that that really matters;
		//   if you flip the recover before the BeesBuzz defer'd func's call to our thunk, this thing on line 9 just moves to 10, that's it -- there's no other flow change.)
		{0, cwd + "/stackinfo_test.go", here + 3, "meep_test.func·003", "meep_test.TestStacksPanickingInDefersOhMy.func1"}, // right here, where we call `CaptureStack` in our thunk
		{1, cwd + "/fixtures/stack2.go", 9, "fixtures.func·002", "fixtures.BeesBuzz.func1"},                                // the line in the deferred function that called our thunk
		// No need to get overly precise about line numbers in the stdlib:
		//{2, "/usr/local/go/src/runtime/asm_amd64.s", 401, "", "runtime.call16"}, // if this isn't a single line on some platforms... uff.
		//{3, "/usr/local/go/src/runtime/panic.go", 387, "", "runtime.gopanic"},  // it might be reasonable to detect these and elide everything following from `runtime.*`.
		{4, cwd + "/fixtures/stack2.go", 22, "", "fixtures.buzzkill"},                              // the line that panicked!
		{5, cwd + "/fixtures/stack2.go", 19, "", "fixtures.beesWuz"},                               // the trailing `}` of `beesWuz`, because we left it via defer
		{6, cwd + "/fixtures/stack2.go", 14, "", "fixtures.BeesBuzz"},                              // the body line the calls down to `beesWuz`
		{7, cwd + "/stackinfo_test.go", here + 4, "", "meep_test.TestStacksPanickingInDefersOhMy"}, // obtw!  when we split the `fixtures.*()` *invocation* across lines, this becomes the last one!
		// No need to get overly precise about line numbers in the stdlib",
		//{8, "/usr/local/go/src/testing/testing.go", 447, "", "testing.tRunner"},
		//{9, "/usr/local/go/src/runtime/asm_amd64.s", 2232, "", "runtime.goexit"},
	}
	expectLen := len(expects) + 4
	assertStack(t, result, expects, expectLen)
}

func assertStack(t *testing.T, result meep.Stack, expects []stackFrameExpectation, expectLen int) {
	// Some quick cleanup on the expectations:
	// If no exceptions were specified, the old 1.4 funcname is expected to be same as the new
	for i, ex := range expects {
		if ex.func14 == "" {
			expects[i].func14 = ex.func15
		}
	}

	// Assertions!
	for _, tr := range expects {
		file, line, fnname := result.Frames[tr.n].Where()
		if file != tr.file {
			t.Errorf("Stack[%d] file should be %q, was %q", tr.n, tr.file, file)
		}
		if line != tr.line {
			t.Errorf("Stack[%d] line should be %d, was %d", tr.n, tr.line, line)
		}
		expectedFnname := tr.func15
		if use14fnnames {
			expectedFnname = tr.func14
		}
		if fnname != expectedFnname {
			t.Errorf("Stack[%d] func name should be %q, was %q", tr.n, expectedFnname, fnname)
		}
	}
	for i, fr := range result.Frames {
		if i < expectLen {
			continue
		}
		t.Errorf("Stack[%d] was expected to be empty, was %q", i, fr.String())
	}
}
<|endoftext|>


<commit_before>package mpool

import (
	"errors"
)

var ErrorMessageOverflow = errors.New("message overflow")

// Message encapsulates the messages that we exchange back and forth.
type Message struct {
	Body    []byte
	bodyBuf []byte

	slabSize int
	offset   int
}

type slabClass struct {
	maxSize int
	ch      chan *Message
}

var messagePool = []slabClass{
	{maxSize: 256, ch: make(chan *Message, 20<<10)},     // 5MB   = 256 * 20K
	{maxSize: 1024, ch: make(chan *Message, 50<<10)},    // 50MB  = 1K * 50K
	{maxSize: 2 << 10, ch: make(chan *Message, 50<<10)}, // 100MB = 2K * 50K
	{maxSize: 4 << 10, ch: make(chan *Message, 50<<10)}, // 200MB = 4K * 50K
	{maxSize: 8 << 10, ch: make(chan *Message, 1<<10)},  // 8MB   = 8K * 1K
	{maxSize: 64 << 10, ch: make(chan *Message, 1<<8)},  // 16MB  = 64K * 256
	{maxSize: 256 << 10, ch: make(chan *Message, 1<<7)}, // 32MB  = 256K * 128
}

// Free decrements the reference count on a message, and releases its
// resources if no further references remain.  While this is not
// strictly necessary thanks to GC, doing so allows for the resources to
// be recycled without engaging GC.  This can have rather substantial
// benefits for performance.
func (this *Message) Free() (recycled bool) {
	var ch chan *Message
	for _, slab := range messagePool {
		if this.slabSize == slab.maxSize {
			ch = slab.ch
			break
		}
	}

	select {
	case ch <- this:
	default:
		// this slab class pool is full, silently drop
	}

	return true
}

func (this *Message) Reset() {
	this.offset = 0
}

// WriteString is a costly function. Use it only when you know its underhood.
// Currently, kateway is not using this function.
func (this *Message) WriteString(s string) error {
	if len(s)+this.offset > cap(this.bodyBuf) {
		return ErrorMessageOverflow
	}

	this.Body = this.Body[0 : this.offset+len(s)]
	copy(this.Body[this.offset:], s)
	this.offset += len(s)
	return nil
}

func (this *Message) Bytes() []byte {
	return this.Body[0:]
}

// NewMessage is the supported way to obtain a new Message.  This makes
// use of a "slab allocator" which greatly reduces the load on the
// garbage collector.
func NewMessage(size int) *Message {
	var msg *Message
	var ch chan *Message
	for _, slabClass := range messagePool { // TODO improve perf
		if size <= slabClass.maxSize {
			ch = slabClass.ch
			size = slabClass.maxSize
			break
		}
	}

	select {
	case msg = <-ch:
	default:
		// message pool empty:
		// too busy or size greater than largest slab class
		msg = &Message{}
		msg.slabSize = size
		msg.bodyBuf = make([]byte, 0, msg.slabSize)
	}

	msg.Body = msg.bodyBuf
	return msg
}
<commit_msg>tweak of mem pool slab classes<commit_after>package mpool

import (
	"errors"
)

var ErrorMessageOverflow = errors.New("message overflow")

// Message encapsulates the messages that we exchange back and forth.
type Message struct {
	Body    []byte
	bodyBuf []byte

	slabSize int
	offset   int
}

type slabClass struct {
	maxSize int
	ch      chan *Message
}

var messagePool = []slabClass{
	{maxSize: 256, ch: make(chan *Message, 20<<10)},     // 5MB   = 256 * 20K
	{maxSize: 1024, ch: make(chan *Message, 50<<10)},    // 50MB  = 1K * 50K
	{maxSize: 2 << 10, ch: make(chan *Message, 50<<10)}, // 100MB = 2K * 50K
	{maxSize: 4 << 10, ch: make(chan *Message, 50<<10)}, // 200MB = 4K * 50K
	{maxSize: 8 << 10, ch: make(chan *Message, 4<<10)},  // 32MB  = 8K * 4K
	{maxSize: 64 << 10, ch: make(chan *Message, 1<<10)}, // 64MB  = 64K * 1K
	{maxSize: 256 << 10, ch: make(chan *Message, 1<<7)}, // 32MB  = 256K * 128
}

// Free decrements the reference count on a message, and releases its
// resources if no further references remain.  While this is not
// strictly necessary thanks to GC, doing so allows for the resources to
// be recycled without engaging GC.  This can have rather substantial
// benefits for performance.
func (this *Message) Free() (recycled bool) {
	var ch chan *Message
	for _, slab := range messagePool {
		if this.slabSize == slab.maxSize {
			ch = slab.ch
			break
		}
	}

	select {
	case ch <- this:
	default:
		// this slab class pool is full, silently drop
	}

	return true
}

func (this *Message) Reset() {
	this.offset = 0
}

// WriteString is a costly function. Use it only when you know its underhood.
// Currently, kateway is not using this function.
func (this *Message) WriteString(s string) error {
	if len(s)+this.offset > cap(this.bodyBuf) {
		return ErrorMessageOverflow
	}

	this.Body = this.Body[0 : this.offset+len(s)]
	copy(this.Body[this.offset:], s)
	this.offset += len(s)
	return nil
}

func (this *Message) Bytes() []byte {
	return this.Body[0:]
}

// NewMessage is the supported way to obtain a new Message.  This makes
// use of a "slab allocator" which greatly reduces the load on the
// garbage collector.
func NewMessage(size int) *Message {
	var msg *Message
	var ch chan *Message
	for _, slabClass := range messagePool { // TODO improve perf
		if size <= slabClass.maxSize {
			ch = slabClass.ch
			size = slabClass.maxSize
			break
		}
	}

	select {
	case msg = <-ch:
	default:
		// message pool empty:
		// too busy or size greater than largest slab class
		msg = &Message{}
		msg.slabSize = size
		msg.bodyBuf = make([]byte, 0, msg.slabSize)
	}

	msg.Body = msg.bodyBuf
	return msg
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"io"
	"sort"
	"strconv"
	"strings"

	"time"

	"github.com/docker/docker/pkg/units"
	"github.com/spf13/cobra"

	kapi "k8s.io/kubernetes/pkg/api"
	kerrors "k8s.io/kubernetes/pkg/api/errors"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/fields"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"

	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/cli/describe"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deployutil "github.com/openshift/origin/pkg/deploy/util"
)

// DeployOptions holds all the options for the `deploy` command
type DeployOptions struct {
	out             io.Writer
	osClient        client.Interface
	kubeClient      kclient.Interface
	builder         *resource.Builder
	namespace       string
	baseCommandName string

	deploymentConfigName string
	deployLatest         bool
	retryDeploy          bool
	cancelDeploy         bool
	enableTriggers       bool
}

const (
	deployLong = `
View, start, cancel, or retry a deployment

This command allows you to control a deployment config. Each individual deployment is exposed
as a new replication controller, and the deployment process manages scaling down old deployments
and scaling up new ones. You can rollback to any previous deployment, or even scale multiple
deployments up at the same time.

There are several deployment strategies defined:

* Rolling (default) - scales up the new deployment in stages, gradually reducing the number
  of old deployments. If one of the new deployed pods never becomes "ready", the new deployment
  will be rolled back (scaled down to zero). Use when your application can tolerate two versions
  of code running at the same time (many web applications, scalable databases)
* Recreate - scales the old deployment down to zero, then scales the new deployment up to full.
  Use when your application cannot tolerate two versions of code running at the same time
* Custom - run your own deployment process inside a Docker container using your own scripts.

If a deployment fails, you may opt to retry it (if the error was transient). Some deployments may
never successfully complete - in which case you can use the '--latest' flag to force a redeployment.
When rolling back to a previous deployment, a new deployment will be created with an identical copy
of your config at the latest position.

If no options are given, shows information about the latest deployment.`

	deployExample = `  # Display the latest deployment for the 'database' deployment config
  $ %[1]s deploy database

  # Start a new deployment based on the 'database'
  $ %[1]s deploy database --latest

  # Retry the latest failed deployment based on 'frontend'
  # The deployer pod and any hook pods are deleted for the latest failed deployment
  $ %[1]s deploy frontend --retry

  # Cancel the in-progress deployment based on 'frontend'
  $ %[1]s deploy frontend --cancel`
)

// NewCmdDeploy creates a new `deploy` command.
func NewCmdDeploy(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
	options := &DeployOptions{
		baseCommandName: fullName,
	}

	cmd := &cobra.Command{
		Use:        "deploy DEPLOYMENTCONFIG [--latest|--retry|--cancel|--enable-triggers]",
		Short:      "View, start, cancel, or retry a deployment",
		Long:       deployLong,
		Example:    fmt.Sprintf(deployExample, fullName),
		SuggestFor: []string{"deployment"},
		Run: func(cmd *cobra.Command, args []string) {
			if err := options.Complete(f, args, out); err != nil {
				cmdutil.CheckErr(err)
			}

			if err := options.Validate(); err != nil {
				cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
			}

			if err := options.RunDeploy(); err != nil {
				cmdutil.CheckErr(err)
			}
		},
	}

	cmd.Flags().BoolVar(&options.deployLatest, "latest", false, "Start a new deployment now.")
	cmd.Flags().BoolVar(&options.retryDeploy, "retry", false, "Retry the latest failed deployment.")
	cmd.Flags().BoolVar(&options.cancelDeploy, "cancel", false, "Cancel the in-progress deployment.")
	cmd.Flags().BoolVar(&options.enableTriggers, "enable-triggers", false, "Enables all image triggers for the deployment config.")

	return cmd
}

func (o *DeployOptions) Complete(f *clientcmd.Factory, args []string, out io.Writer) error {
	if len(args) > 1 {
		return errors.New("only one deployment config name is supported as argument.")
	}
	var err error

	o.osClient, o.kubeClient, err = f.Clients()
	if err != nil {
		return err
	}
	o.namespace, _, err = f.DefaultNamespace()
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	o.builder = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand())

	o.out = out

	if len(args) > 0 {
		o.deploymentConfigName = args[0]
	}

	return nil
}

func (o DeployOptions) Validate() error {
	if len(o.deploymentConfigName) == 0 {
		return errors.New("a deployment config name is required.")
	}
	numOptions := 0
	if o.deployLatest {
		numOptions++
	}
	if o.retryDeploy {
		numOptions++
	}
	if o.cancelDeploy {
		numOptions++
	}
	if o.enableTriggers {
		numOptions++
	}
	if numOptions > 1 {
		return errors.New("only one of --latest, --retry, --cancel, or --enable-triggers is allowed.")
	}
	return nil
}

func (o DeployOptions) RunDeploy() error {
	r := o.builder.
		NamespaceParam(o.namespace).
		ResourceNames("deploymentconfigs", o.deploymentConfigName).
		SingleResourceType().
		Do()
	resultObj, err := r.Object()
	if err != nil {
		return err
	}
	config, ok := resultObj.(*deployapi.DeploymentConfig)
	if !ok {
		return fmt.Errorf("%s is not a valid deployment config", o.deploymentConfigName)
	}

	switch {
	case o.deployLatest:
		err = o.deploy(config, o.out)
	case o.retryDeploy:
		err = o.retry(config, o.out)
	case o.cancelDeploy:
		err = o.cancel(config, o.out)
	case o.enableTriggers:
		err = o.reenableTriggers(config, o.out)
	default:
		describer := describe.NewLatestDeploymentsDescriber(o.osClient, o.kubeClient, -1)
		desc, err := describer.Describe(config.Namespace, config.Name)
		if err != nil {
			return err
		}
		fmt.Fprint(o.out, desc)
	}

	return err
}

// deploy launches a new deployment unless there's already a deployment
// process in progress for config.
func (o DeployOptions) deploy(config *deployapi.DeploymentConfig, out io.Writer) error {
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err == nil {
		// Reject attempts to start a concurrent deployment.
		status := deployutil.DeploymentStatusFor(deployment)
		if status != deployapi.DeploymentStatusComplete && status != deployapi.DeploymentStatusFailed {
			return fmt.Errorf("#%d is already in progress (%s).\nOptionally, you can cancel this deployment using the --cancel option.", config.Status.LatestVersion, status)
		}
	} else {
		if !kerrors.IsNotFound(err) {
			return err
		}
	}

	config.Status.LatestVersion++
	_, err = o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err == nil {
		fmt.Fprintf(out, "Started deployment #%d\n", config.Status.LatestVersion)
	}
	return err
}

// retry resets the status of the latest deployment to New, which will cause
// the deployment to be retried. An error is returned if the deployment is not
// currently in a failed state.
func (o DeployOptions) retry(config *deployapi.DeploymentConfig, out io.Writer) error {
	if config.Status.LatestVersion == 0 {
		return fmt.Errorf("no deployments found for %s/%s", config.Namespace, config.Name)
	}
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			return fmt.Errorf("unable to find the latest deployment (#%d).\nYou can start a new deployment using the --latest option.", config.Status.LatestVersion)
		}
		return err
	}

	if status := deployutil.DeploymentStatusFor(deployment); status != deployapi.DeploymentStatusFailed {
		message := fmt.Sprintf("#%d is %s; only failed deployments can be retried.\n", config.Status.LatestVersion, status)
		if status == deployapi.DeploymentStatusComplete {
			message += "You can start a new deployment using the --latest option."
		} else {
			message += "Optionally, you can cancel this deployment using the --cancel option."
		}

		return fmt.Errorf(message)
	}

	// Delete the deployer pod as well as the deployment hooks pods, if any
	pods, err := o.kubeClient.Pods(config.Namespace).List(deployutil.DeployerPodSelector(deploymentName), fields.Everything())
	if err != nil {
		return fmt.Errorf("failed to list deployer/hook pods for deployment #%d: %v", config.Status.LatestVersion, err)
	}
	for _, pod := range pods.Items {
		err := o.kubeClient.Pods(pod.Namespace).Delete(pod.Name, kapi.NewDeleteOptions(0))
		if err != nil {
			return fmt.Errorf("failed to delete deployer/hook pod %s for deployment #%d: %v", pod.Name, config.Status.LatestVersion, err)
		}
	}

	deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew)
	// clear out the cancellation flag as well as any previous status-reason annotation
	delete(deployment.Annotations, deployapi.DeploymentStatusReasonAnnotation)
	delete(deployment.Annotations, deployapi.DeploymentCancelledAnnotation)
	_, err = o.kubeClient.ReplicationControllers(deployment.Namespace).Update(deployment)
	if err == nil {
		fmt.Fprintf(out, "Retried #%d\n", config.Status.LatestVersion)
	}
	return err
}

// cancel cancels any deployment process in progress for config.
func (o DeployOptions) cancel(config *deployapi.DeploymentConfig, out io.Writer) error {
	deployments, err := o.kubeClient.ReplicationControllers(config.Namespace).List(deployutil.ConfigSelector(config.Name), fields.Everything())
	if err != nil {
		return err
	}
	if len(deployments.Items) == 0 {
		fmt.Fprintf(out, "There have been no deployments for %s/%s\n", config.Namespace, config.Name)
		return nil
	}
	sort.Sort(deployutil.ByLatestVersionDesc(deployments.Items))
	failedCancellations := []string{}
	anyCancelled := false
	for _, deployment := range deployments.Items {
		status := deployutil.DeploymentStatusFor(&deployment)
		switch status {
		case deployapi.DeploymentStatusNew,
			deployapi.DeploymentStatusPending,
			deployapi.DeploymentStatusRunning:

			if deployutil.IsDeploymentCancelled(&deployment) {
				continue
			}

			deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue
			deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser
			_, err := o.kubeClient.ReplicationControllers(deployment.Namespace).Update(&deployment)
			if err == nil {
				fmt.Fprintf(out, "Cancelled deployment #%d\n", config.Status.LatestVersion)
				anyCancelled = true
			} else {
				fmt.Fprintf(out, "Couldn't cancel deployment #%d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err)
				failedCancellations = append(failedCancellations, strconv.Itoa(deployutil.DeploymentVersionFor(&deployment)))
			}
		}
	}
	if len(failedCancellations) > 0 {
		return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", "))
	}
	if !anyCancelled {
		latest := &deployments.Items[0]
		timeAt := strings.ToLower(units.HumanDuration(time.Now().Sub(latest.CreationTimestamp.Time)))
		fmt.Fprintf(out, "No deployments are in progress (latest deployment #%d %s %s ago)\n",
			deployutil.DeploymentVersionFor(latest),
			strings.ToLower(string(deployutil.DeploymentStatusFor(latest))),
			timeAt)
	}
	return nil
}

// reenableTriggers enables all image triggers and then persists config.
func (o DeployOptions) reenableTriggers(config *deployapi.DeploymentConfig, out io.Writer) error {
	enabled := []string{}
	for _, trigger := range config.Spec.Triggers {
		if trigger.Type == deployapi.DeploymentTriggerOnImageChange {
			trigger.ImageChangeParams.Automatic = true
			enabled = append(enabled, trigger.ImageChangeParams.From.Name)
		}
	}
	if len(enabled) == 0 {
		fmt.Fprintln(out, "No image triggers found to enable")
		return nil
	}
	_, err := o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "Enabled image triggers: %s\n", strings.Join(enabled, ","))
	return nil
}
<commit_msg>	modified:   cmd/cli/cmd/deploy.go<commit_after>package cmd

import (
	"errors"
	"fmt"
	"io"
	"sort"
	"strconv"
	"strings"

	"time"

	"github.com/docker/docker/pkg/units"
	"github.com/spf13/cobra"

	kapi "k8s.io/kubernetes/pkg/api"
	kerrors "k8s.io/kubernetes/pkg/api/errors"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/fields"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"

	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/cli/describe"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deployutil "github.com/openshift/origin/pkg/deploy/util"
)

// DeployOptions holds all the options for the `deploy` command
type DeployOptions struct {
	out             io.Writer
	osClient        client.Interface
	kubeClient      kclient.Interface
	builder         *resource.Builder
	namespace       string
	baseCommandName string

	deploymentConfigName string
	deployLatest         bool
	retryDeploy          bool
	cancelDeploy         bool
	enableTriggers       bool
}

const (
	deployLong = `
hahahaha  xiugaile View, start, cancel, or retry a deployment

This command allows you to control a deployment config. Each individual deployment is exposed
as a new replication controller, and the deployment process manages scaling down old deployments
and scaling up new ones. You can rollback to any previous deployment, or even scale multiple
deployments up at the same time.

There are several deployment strategies defined:

* Rolling (default) - scales up the new deployment in stages, gradually reducing the number
  of old deployments. If one of the new deployed pods never becomes "ready", the new deployment
  will be rolled back (scaled down to zero). Use when your application can tolerate two versions
  of code running at the same time (many web applications, scalable databases)
* Recreate - scales the old deployment down to zero, then scales the new deployment up to full.
  Use when your application cannot tolerate two versions of code running at the same time
* Custom - run your own deployment process inside a Docker container using your own scripts.

If a deployment fails, you may opt to retry it (if the error was transient). Some deployments may
never successfully complete - in which case you can use the '--latest' flag to force a redeployment.
When rolling back to a previous deployment, a new deployment will be created with an identical copy
of your config at the latest position.

If no options are given, shows information about the latest deployment.`

	deployExample = `  # Display the latest deployment for the 'database' deployment config
  $ %[1]s deploy database

  # Start a new deployment based on the 'database'
  $ %[1]s deploy database --latest

  # Retry the latest failed deployment based on 'frontend'
  # The deployer pod and any hook pods are deleted for the latest failed deployment
  $ %[1]s deploy frontend --retry

  # Cancel the in-progress deployment based on 'frontend'
  $ %[1]s deploy frontend --cancel`
)

// NewCmdDeploy creates a new `deploy` command.
func NewCmdDeploy(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
	options := &DeployOptions{
		baseCommandName: fullName,
	}

	cmd := &cobra.Command{
		Use:        "deploy DEPLOYMENTCONFIG [--latest|--retry|--cancel|--enable-triggers]",
		Short:      "View, start, cancel, or retry a deployment",
		Long:       deployLong,
		Example:    fmt.Sprintf(deployExample, fullName),
		SuggestFor: []string{"deployment"},
		Run: func(cmd *cobra.Command, args []string) {
			if err := options.Complete(f, args, out); err != nil {
				cmdutil.CheckErr(err)
			}

			if err := options.Validate(); err != nil {
				cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
			}

			if err := options.RunDeploy(); err != nil {
				cmdutil.CheckErr(err)
			}
		},
	}

	cmd.Flags().BoolVar(&options.deployLatest, "latest", false, "Start a new deployment now.")
	cmd.Flags().BoolVar(&options.retryDeploy, "retry", false, "Retry the latest failed deployment.")
	cmd.Flags().BoolVar(&options.cancelDeploy, "cancel", false, "Cancel the in-progress deployment.")
	cmd.Flags().BoolVar(&options.enableTriggers, "enable-triggers", false, "Enables all image triggers for the deployment config.")

	return cmd
}

func (o *DeployOptions) Complete(f *clientcmd.Factory, args []string, out io.Writer) error {
	if len(args) > 1 {
		return errors.New("only one deployment config name is supported as argument.")
	}
	var err error

	o.osClient, o.kubeClient, err = f.Clients()
	if err != nil {
		return err
	}
	o.namespace, _, err = f.DefaultNamespace()
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	o.builder = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand())

	o.out = out

	if len(args) > 0 {
		o.deploymentConfigName = args[0]
	}

	return nil
}

func (o DeployOptions) Validate() error {
	if len(o.deploymentConfigName) == 0 {
		return errors.New("a deployment config name is required.")
	}
	numOptions := 0
	if o.deployLatest {
		numOptions++
	}
	if o.retryDeploy {
		numOptions++
	}
	if o.cancelDeploy {
		numOptions++
	}
	if o.enableTriggers {
		numOptions++
	}
	if numOptions > 1 {
		return errors.New("only one of --latest, --retry, --cancel, or --enable-triggers is allowed.")
	}
	return nil
}

func (o DeployOptions) RunDeploy() error {
	r := o.builder.
		NamespaceParam(o.namespace).
		ResourceNames("deploymentconfigs", o.deploymentConfigName).
		SingleResourceType().
		Do()
	resultObj, err := r.Object()
	if err != nil {
		return err
	}
	config, ok := resultObj.(*deployapi.DeploymentConfig)
	if !ok {
		return fmt.Errorf("%s is not a valid deployment config", o.deploymentConfigName)
	}

	switch {
	case o.deployLatest:
		err = o.deploy(config, o.out)
	case o.retryDeploy:
		err = o.retry(config, o.out)
	case o.cancelDeploy:
		err = o.cancel(config, o.out)
	case o.enableTriggers:
		err = o.reenableTriggers(config, o.out)
	default:
		describer := describe.NewLatestDeploymentsDescriber(o.osClient, o.kubeClient, -1)
		desc, err := describer.Describe(config.Namespace, config.Name)
		if err != nil {
			return err
		}
		fmt.Fprint(o.out, desc)
	}

	return err
}

// deploy launches a new deployment unless there's already a deployment
// process in progress for config.
func (o DeployOptions) deploy(config *deployapi.DeploymentConfig, out io.Writer) error {
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err == nil {
		// Reject attempts to start a concurrent deployment.
		status := deployutil.DeploymentStatusFor(deployment)
		if status != deployapi.DeploymentStatusComplete && status != deployapi.DeploymentStatusFailed {
			return fmt.Errorf("#%d is already in progress (%s).\nOptionally, you can cancel this deployment using the --cancel option.", config.Status.LatestVersion, status)
		}
	} else {
		if !kerrors.IsNotFound(err) {
			return err
		}
	}

	config.Status.LatestVersion++
	_, err = o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err == nil {
		fmt.Fprintf(out, "Started deployment #%d\n", config.Status.LatestVersion)
	}
	return err
}

// retry resets the status of the latest deployment to New, which will cause
// the deployment to be retried. An error is returned if the deployment is not
// currently in a failed state.
func (o DeployOptions) retry(config *deployapi.DeploymentConfig, out io.Writer) error {
	if config.Status.LatestVersion == 0 {
		return fmt.Errorf("no deployments found for %s/%s", config.Namespace, config.Name)
	}
	deploymentName := deployutil.LatestDeploymentNameForConfig(config)
	deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			return fmt.Errorf("unable to find the latest deployment (#%d).\nYou can start a new deployment using the --latest option.", config.Status.LatestVersion)
		}
		return err
	}

	if status := deployutil.DeploymentStatusFor(deployment); status != deployapi.DeploymentStatusFailed {
		message := fmt.Sprintf("#%d is %s; only failed deployments can be retried.\n", config.Status.LatestVersion, status)
		if status == deployapi.DeploymentStatusComplete {
			message += "You can start a new deployment using the --latest option."
		} else {
			message += "Optionally, you can cancel this deployment using the --cancel option."
		}

		return fmt.Errorf(message)
	}

	// Delete the deployer pod as well as the deployment hooks pods, if any
	pods, err := o.kubeClient.Pods(config.Namespace).List(deployutil.DeployerPodSelector(deploymentName), fields.Everything())
	if err != nil {
		return fmt.Errorf("failed to list deployer/hook pods for deployment #%d: %v", config.Status.LatestVersion, err)
	}
	for _, pod := range pods.Items {
		err := o.kubeClient.Pods(pod.Namespace).Delete(pod.Name, kapi.NewDeleteOptions(0))
		if err != nil {
			return fmt.Errorf("failed to delete deployer/hook pod %s for deployment #%d: %v", pod.Name, config.Status.LatestVersion, err)
		}
	}

	deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew)
	// clear out the cancellation flag as well as any previous status-reason annotation
	delete(deployment.Annotations, deployapi.DeploymentStatusReasonAnnotation)
	delete(deployment.Annotations, deployapi.DeploymentCancelledAnnotation)
	_, err = o.kubeClient.ReplicationControllers(deployment.Namespace).Update(deployment)
	if err == nil {
		fmt.Fprintf(out, "Retried #%d\n", config.Status.LatestVersion)
	}
	return err
}

// cancel cancels any deployment process in progress for config.
func (o DeployOptions) cancel(config *deployapi.DeploymentConfig, out io.Writer) error {
	deployments, err := o.kubeClient.ReplicationControllers(config.Namespace).List(deployutil.ConfigSelector(config.Name), fields.Everything())
	if err != nil {
		return err
	}
	if len(deployments.Items) == 0 {
		fmt.Fprintf(out, "There have been no deployments for %s/%s\n", config.Namespace, config.Name)
		return nil
	}
	sort.Sort(deployutil.ByLatestVersionDesc(deployments.Items))
	failedCancellations := []string{}
	anyCancelled := false
	for _, deployment := range deployments.Items {
		status := deployutil.DeploymentStatusFor(&deployment)
		switch status {
		case deployapi.DeploymentStatusNew,
			deployapi.DeploymentStatusPending,
			deployapi.DeploymentStatusRunning:

			if deployutil.IsDeploymentCancelled(&deployment) {
				continue
			}

			deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue
			deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser
			_, err := o.kubeClient.ReplicationControllers(deployment.Namespace).Update(&deployment)
			if err == nil {
				fmt.Fprintf(out, "Cancelled deployment #%d\n", config.Status.LatestVersion)
				anyCancelled = true
			} else {
				fmt.Fprintf(out, "Couldn't cancel deployment #%d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err)
				failedCancellations = append(failedCancellations, strconv.Itoa(deployutil.DeploymentVersionFor(&deployment)))
			}
		}
	}
	if len(failedCancellations) > 0 {
		return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", "))
	}
	if !anyCancelled {
		latest := &deployments.Items[0]
		timeAt := strings.ToLower(units.HumanDuration(time.Now().Sub(latest.CreationTimestamp.Time)))
		fmt.Fprintf(out, "No deployments are in progress (latest deployment #%d %s %s ago)\n",
			deployutil.DeploymentVersionFor(latest),
			strings.ToLower(string(deployutil.DeploymentStatusFor(latest))),
			timeAt)
	}
	return nil
}

// reenableTriggers enables all image triggers and then persists config.
func (o DeployOptions) reenableTriggers(config *deployapi.DeploymentConfig, out io.Writer) error {
	enabled := []string{}
	for _, trigger := range config.Spec.Triggers {
		if trigger.Type == deployapi.DeploymentTriggerOnImageChange {
			trigger.ImageChangeParams.Automatic = true
			enabled = append(enabled, trigger.ImageChangeParams.From.Name)
		}
	}
	if len(enabled) == 0 {
		fmt.Fprintln(out, "No image triggers found to enable")
		return nil
	}
	_, err := o.osClient.DeploymentConfigs(config.Namespace).Update(config)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "Enabled image triggers: %s\n", strings.Join(enabled, ","))
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"github.com/Symantec/Dominator/dom/herd"
	"github.com/Symantec/Dominator/dom/mdb"
	"github.com/Symantec/Dominator/lib/constants"
	"github.com/Symantec/Dominator/lib/logbuf"
	"log"
	"os"
	"os/user"
	"path"
	"runtime"
	"strconv"
	"syscall"
	"time"
)

var (
	caFile = flag.String("CAfile", "/etc/ssl/CA.pem",
		"Name of file containing the root of trust")
	certDir = flag.String("certDir", "/etc/ssl/Dominator",
		"Name of file (relative to certDir) containing the SSL certificate")
	certFile = flag.String("certFile", "cert.pem",
		"Name of file (relative to stateDir) containing the SSL certificate")
	debug = flag.Bool("debug", false,
		"If true, show debugging output")
	fdLimit = flag.Uint64("fdLimit", getFdLimit(),
		"Maximum number of open file descriptors (this limits concurrent connection attempts)")
	imageServerHostname = flag.String("imageServerHostname", "localhost",
		"Hostname of image server")
	imageServerPortNum = flag.Uint("imageServerPortNum",
		constants.ImageServerPortNumber,
		"Port number of image server")
	keyFile = flag.String("keyFile", "key.pem",
		"Name of file (relative to certDir) containing the SSL certificate")
	logbufLines = flag.Uint("logbufLines", 1024,
		"Number of lines to store in the log buffer")
	minInterval = flag.Uint("minInterval", 1,
		"Minimum interval between loops (in seconds)")
	portNum = flag.Uint("portNum", constants.DomPortNumber,
		"Port number to allocate and listen on for HTTP/RPC")
	stateDir = flag.String("stateDir", "/var/lib/Dominator",
		"Name of dominator state directory.")
	username = flag.String("username", "",
		"If running as root, username to switch to.")
)

func showMdb(mdb *mdb.Mdb) {
	fmt.Println()
	mdb.DebugWrite(os.Stdout)
	fmt.Println()
}

func getFdLimit() uint64 {
	var rlim syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlim); err != nil {
		panic(err)
	}
	return rlim.Max
}

func setUser(username string) error {
	if username == "" {
		return errors.New("-username argument missing")
	}
	newUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid, err := strconv.Atoi(newUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(newUser.Gid)
	if err != nil {
		return err
	}
	if uid == 0 {
		return errors.New("Do not run the Dominator as root")
		os.Exit(1)
	}
	if err := syscall.Setresgid(gid, gid, gid); err != nil {
		return err
	}
	return syscall.Setresuid(uid, uid, uid)
}

func pathJoin(first, second string) string {
	if path.IsAbs(second) {
		return path.Clean(second)
	}
	return path.Join(first, second)
}

func main() {
	flag.Parse()
	setupTls(*caFile,
		pathJoin(*certDir, *certFile), pathJoin(*certDir, *keyFile))
	rlim := syscall.Rlimit{*fdLimit, *fdLimit}
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim); err != nil {
		fmt.Fprintf(os.Stderr, "Cannot set FD limit\t%s\n", err)
		os.Exit(1)
	}
	if os.Geteuid() == 0 {
		if err := setUser(*username); err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
	fi, err := os.Lstat(*stateDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Cannot stat: %s\t%s\n", *stateDir, err)
		os.Exit(1)
	}
	if !fi.IsDir() {
		fmt.Fprintf(os.Stderr, "%s is not a directory\n", *stateDir)
		os.Exit(1)
	}
	interval := time.Duration(*minInterval) * time.Second
	circularBuffer := logbuf.New(*logbufLines)
	logger := log.New(circularBuffer, "", log.LstdFlags)
	mdbChannel := mdb.StartMdbDaemon(path.Join(*stateDir, "mdb"), logger)
	herd := herd.NewHerd(fmt.Sprintf("%s:%d", *imageServerHostname,
		*imageServerPortNum), logger)
	herd.AddHtmlWriter(circularBuffer)
	if err = herd.StartServer(*portNum, true); err != nil {
		fmt.Fprintf(os.Stderr, "Unable to create http server\t%s\n", err)
		os.Exit(1)
	}
	nextCycleStopTime := time.Now().Add(interval)
	for {
		select {
		case mdb := <-mdbChannel:
			herd.MdbUpdate(mdb)
			if *debug {
				showMdb(mdb)
			}
			runtime.GC() // An opportune time to take out the garbage.
		default:
			// Do work.
			if herd.PollNextSub() {
				if *debug {
					fmt.Print(".")
				}
				sleepTime := nextCycleStopTime.Sub(time.Now())
				time.Sleep(sleepTime)
				nextCycleStopTime = time.Now().Add(interval)
				if sleepTime < 0 { // There was no time to rest.
					runtime.GC() // An opportune time to take out the garbage.
				}
			}
		}
	}
}
<commit_msg>Add -mdbFile flag to dominator.<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"github.com/Symantec/Dominator/dom/herd"
	"github.com/Symantec/Dominator/dom/mdb"
	"github.com/Symantec/Dominator/lib/constants"
	"github.com/Symantec/Dominator/lib/logbuf"
	"log"
	"os"
	"os/user"
	"path"
	"runtime"
	"strconv"
	"syscall"
	"time"
)

var (
	caFile = flag.String("CAfile", "/etc/ssl/CA.pem",
		"Name of file containing the root of trust")
	certDir = flag.String("certDir", "/etc/ssl/Dominator",
		"Name of file (relative to certDir) containing the SSL certificate")
	certFile = flag.String("certFile", "cert.pem",
		"Name of file (relative to stateDir) containing the SSL certificate")
	debug = flag.Bool("debug", false,
		"If true, show debugging output")
	fdLimit = flag.Uint64("fdLimit", getFdLimit(),
		"Maximum number of open file descriptors (this limits concurrent connection attempts)")
	imageServerHostname = flag.String("imageServerHostname", "localhost",
		"Hostname of image server")
	imageServerPortNum = flag.Uint("imageServerPortNum",
		constants.ImageServerPortNumber,
		"Port number of image server")
	keyFile = flag.String("keyFile", "key.pem",
		"Name of file (relative to certDir) containing the SSL certificate")
	logbufLines = flag.Uint("logbufLines", 1024,
		"Number of lines to store in the log buffer")
	mdbFile = flag.String("mdbFile", "mdb",
		"File to read MDB data from, relative to stateDir (default format is JSON)")
	minInterval = flag.Uint("minInterval", 1,
		"Minimum interval between loops (in seconds)")
	portNum = flag.Uint("portNum", constants.DomPortNumber,
		"Port number to allocate and listen on for HTTP/RPC")
	stateDir = flag.String("stateDir", "/var/lib/Dominator",
		"Name of dominator state directory.")
	username = flag.String("username", "",
		"If running as root, username to switch to.")
)

func showMdb(mdb *mdb.Mdb) {
	fmt.Println()
	mdb.DebugWrite(os.Stdout)
	fmt.Println()
}

func getFdLimit() uint64 {
	var rlim syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlim); err != nil {
		panic(err)
	}
	return rlim.Max
}

func setUser(username string) error {
	if username == "" {
		return errors.New("-username argument missing")
	}
	newUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid, err := strconv.Atoi(newUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(newUser.Gid)
	if err != nil {
		return err
	}
	if uid == 0 {
		return errors.New("Do not run the Dominator as root")
		os.Exit(1)
	}
	if err := syscall.Setresgid(gid, gid, gid); err != nil {
		return err
	}
	return syscall.Setresuid(uid, uid, uid)
}

func pathJoin(first, second string) string {
	if path.IsAbs(second) {
		return path.Clean(second)
	}
	return path.Join(first, second)
}

func main() {
	flag.Parse()
	setupTls(*caFile,
		pathJoin(*certDir, *certFile), pathJoin(*certDir, *keyFile))
	rlim := syscall.Rlimit{*fdLimit, *fdLimit}
	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim); err != nil {
		fmt.Fprintf(os.Stderr, "Cannot set FD limit\t%s\n", err)
		os.Exit(1)
	}
	if os.Geteuid() == 0 {
		if err := setUser(*username); err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
	fi, err := os.Lstat(*stateDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Cannot stat: %s\t%s\n", *stateDir, err)
		os.Exit(1)
	}
	if !fi.IsDir() {
		fmt.Fprintf(os.Stderr, "%s is not a directory\n", *stateDir)
		os.Exit(1)
	}
	interval := time.Duration(*minInterval) * time.Second
	circularBuffer := logbuf.New(*logbufLines)
	logger := log.New(circularBuffer, "", log.LstdFlags)
	mdbChannel := mdb.StartMdbDaemon(path.Join(*stateDir, *mdbFile), logger)
	herd := herd.NewHerd(fmt.Sprintf("%s:%d", *imageServerHostname,
		*imageServerPortNum), logger)
	herd.AddHtmlWriter(circularBuffer)
	if err = herd.StartServer(*portNum, true); err != nil {
		fmt.Fprintf(os.Stderr, "Unable to create http server\t%s\n", err)
		os.Exit(1)
	}
	nextCycleStopTime := time.Now().Add(interval)
	for {
		select {
		case mdb := <-mdbChannel:
			herd.MdbUpdate(mdb)
			if *debug {
				showMdb(mdb)
			}
			runtime.GC() // An opportune time to take out the garbage.
		default:
			// Do work.
			if herd.PollNextSub() {
				if *debug {
					fmt.Print(".")
				}
				sleepTime := nextCycleStopTime.Sub(time.Now())
				time.Sleep(sleepTime)
				nextCycleStopTime = time.Now().Add(interval)
				if sleepTime < 0 { // There was no time to rest.
					runtime.GC() // An opportune time to take out the garbage.
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strings"

	"github.com/exoscale/egoscale"
	"github.com/go-ini/ini"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	defaultConfigFileName  = "exoscale"
	defaultComputeEndpoint = "https://api.exoscale.ch/compute"
)

// configCmd represents the config command
var configCmd = &cobra.Command{
	Use:   "config",
	Short: "Generate config file for this cli",
}

func configCmdRun(cmd *cobra.Command, args []string) error {
	if viper.ConfigFileUsed() != "" {
		fmt.Println("Good day! exo is already configured with accounts:")
		listAccounts()
		return addNewAccount(false)
	}
	csPath, ok := isCloudstackINIFileExist()
	if ok {
		resp, ok, err := askCloudstackINIMigration(csPath)
		if err != nil {
			return err
		}
		if !ok {
			return addNewAccount(true)
		}

		cfgPath, err := createConfigFile(defaultConfigFileName)
		if err != nil {
			return err
		}
		if err := importCloudstackINI(resp, csPath, cfgPath); err != nil {
			return err
		}
		return addNewAccount(false)
	}
	fmt.Print(`
Hi happy Exoscalian, some configuration is required to use exo.

We now need some very important information, find them there.
	<https://portal.exoscale.com/account/profile/api>

`)
	return addNewAccount(true)
}

func addNewAccount(firstRun bool) error {

	config := &config{}

	if firstRun {
		filePath, err := createConfigFile(defaultConfigFileName)
		if err != nil {
			return err
		}

		viper.SetConfigFile(filePath)

		newAccount, err := getAccount()
		if err != nil {
			return err
		}
		config.DefaultAccount = newAccount.Name
		config.Accounts = []account{*newAccount}
		viper.Set("defaultAccount", newAccount.Name)
	}

	for askQuestion("Do you wish to add another account?") {
		newAccount, err := getAccount()
		if err != nil {
			return err
		}
		config.Accounts = append(config.Accounts, *newAccount)
		if askQuestion("Make [" + newAccount.Name + "] your default profile?") {
			config.DefaultAccount = newAccount.Name
			viper.Set("defaultAccount", newAccount.Name)
		}
	}

	if len(config.Accounts) == 0 {
		return nil
	}

	return addAccount(viper.ConfigFileUsed(), config)
}

func getAccount() (*account, error) {
	reader := bufio.NewReader(os.Stdin)

	var client *egoscale.Client

	account := &account{
		Endpoint: defaultComputeEndpoint,
		Key:      "",
		Secret:   "",
	}

	for i := 0; ; i++ {
		if i > 0 {
			endpoint, err := readInput(reader, "API Endpoint", account.Endpoint)
			if err != nil {
				return nil, err
			}
			if endpoint != account.Endpoint {
				account.Endpoint = endpoint
			}
		}

		apiKey, err := readInput(reader, "API Key", account.Key)
		if err != nil {
			return nil, err
		}
		if apiKey != account.Key {
			account.Key = apiKey
		}

		secret := ""
		if account.Secret != "" && len(account.Secret) > 10 {
			secret = account.Secret[0:7] + "..."
		}
		secretKey, err := readInput(reader, "Secret Key", secret)
		if err != nil {
			return nil, err
		}
		if secretKey != account.Secret && secretKey != secret {
			account.Secret = secretKey
		}

		client = egoscale.NewClient(account.Endpoint, account.Key, account.Secret)

		fmt.Printf("Checking the credentials of %q...", account.Key)
		acc := &egoscale.Account{}
		err = client.GetWithContext(gContext, acc)
		if err != nil {
			fmt.Print(` failure.

Let's start over.

`)
		} else {
			fmt.Print(" success!\n\n")
			account.Name = acc.Name
			account.Account = acc.Name
			break
		}
	}

	name, err := readInput(reader, "Account name", account.Name)
	if err != nil {
		return nil, err
	}
	if name != "" {
		account.Name = name
	}

	for isAccountExist(account.Name) {
		fmt.Printf("Account name [%s] already exist\n", name)
		name, err = readInput(reader, "Account name", account.Name)
		if err != nil {
			return nil, err
		}

		account.Name = name
	}

	defaultZone, err := chooseZone(account.Name, client)
	if err != nil {
		return nil, err
	}

	account.DefaultZone = defaultZone

	return account, nil
}

func addAccount(filePath string, newAccounts *config) error {

	accountsSize := 0
	currentAccounts := []account{}
	if gAllAccount != nil {
		accountsSize = len(gAllAccount.Accounts)
		currentAccounts = gAllAccount.Accounts
	}

	newAccountsSize := 0

	if newAccounts != nil {
		newAccountsSize = len(newAccounts.Accounts)
	}

	accounts := make([]map[string]string, accountsSize+newAccountsSize)

	conf := &config{}

	for i, acc := range currentAccounts {

		accounts[i] = map[string]string{}

		accounts[i]["name"] = acc.Name
		accounts[i]["endpoint"] = acc.Endpoint
		accounts[i]["key"] = acc.Key
		accounts[i]["secret"] = acc.Secret
		accounts[i]["defaultZone"] = acc.DefaultZone
		accounts[i]["account"] = acc.Account

		conf.Accounts = append(conf.Accounts, acc)
	}

	if newAccounts != nil {

		for i, acc := range newAccounts.Accounts {

			accounts[accountsSize+i] = map[string]string{}

			accounts[accountsSize+i]["name"] = acc.Name
			accounts[accountsSize+i]["endpoint"] = acc.Endpoint
			accounts[accountsSize+i]["key"] = acc.Key
			accounts[accountsSize+i]["secret"] = acc.Secret
			accounts[accountsSize+i]["defaultZone"] = acc.DefaultZone
			accounts[accountsSize+i]["account"] = acc.Account
			conf.Accounts = append(conf.Accounts, acc)
		}
	}

	viper.SetConfigType("toml")
	viper.SetConfigFile(filePath)

	viper.Set("accounts", accounts)

	if err := viper.WriteConfig(); err != nil {
		return err
	}

	conf.DefaultAccount = viper.Get("defaultAccount").(string)
	gAllAccount = conf

	return nil

}

func isCloudstackINIFileExist() (string, bool) {

	envConfigPath := os.Getenv("CLOUDSTACK_CONFIG")

	usr, _ := user.Current()

	localConfig, _ := filepath.Abs("cloudstack.ini")
	inis := []string{
		localConfig,
		filepath.Join(usr.HomeDir, ".cloudstack.ini"),
		filepath.Join(gConfigFolder, "cloudstack.ini"),
		envConfigPath,
	}

	cfgPath := ""

	for _, i := range inis {
		if _, err := os.Stat(i); err != nil {
			continue
		}
		cfgPath = i
		break
	}

	if cfgPath == "" {
		return "", false
	}
	return cfgPath, true
}

func askCloudstackINIMigration(csFilePath string) (string, bool, error) {

	cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, csFilePath)
	if err != nil {
		return "", false, err
	}

	if len(cfg.Sections()) <= 0 {
		return "", false, nil
	}

	fmt.Printf("We've found a %q configuration file with the following configurations:\n", "cloudstack.ini")
	for i, acc := range cfg.Sections() {
		if i == 0 {
			continue
		}
		fmt.Printf("- [%s] %s\n", acc.Name(), acc.Key("key").String())
	}
	fmt.Println("")

	reader := bufio.NewReader(os.Stdin)

	resp, err := readInput(reader, "Which one should we import?", "All, some, none")
	if err != nil {
		return "", false, err
	}

	resp = strings.ToLower(resp)
	if resp == "" {
		resp = "all"
	}

	return resp, (resp == "all" || resp == "some"), nil
}

func importCloudstackINI(option, csPath, cfgPath string) error {
	cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, csPath)
	if err != nil {
		return err
	}

	reader := bufio.NewReader(os.Stdin)

	config := &config{}

	for i, acc := range cfg.Sections() {
		if i == 0 {
			continue
		}

		if option == "some" {
			if !askQuestion(fmt.Sprintf("Do you want to import [%s] %s?", acc.Name(), acc.Key("key").String())) {
				continue
			}
		}

		csAccount := account{
			Name:     acc.Name(),
			Endpoint: acc.Key("endpoint").String(),
			Key:      acc.Key("key").String(),
			Secret:   acc.Key("secret").String(),
		}

		csClient := egoscale.NewClient(csAccount.Endpoint, csAccount.Key, csAccount.Secret)

		fmt.Printf("Checking the credentials of %q...", csAccount.Key)
		a := &egoscale.Account{}
		err := csClient.GetWithContext(gContext, a)
		if err != nil {
			fmt.Println(" failure.")
			if !askQuestion(fmt.Sprintf("Do you want to keep %s?", acc.Name())) {
				continue
			}
		} else {
			fmt.Println(" success!")
			csAccount.Name = a.Name
			csAccount.Account = a.Name
		}
		fmt.Println("")

		name, err := readInput(reader, "Account name", csAccount.Name)
		if err != nil {
			return err
		}
		if name != "" {
			csAccount.Name = name
		}

		for isAccountExist(csAccount.Name) {
			fmt.Printf("Account name [%s] already exist\n", csAccount.Name)
			name, err = readInput(reader, "Account name", csAccount.Name)
			if err != nil {
				return err
			}

			csAccount.Name = name
		}

		defaultZone, err := chooseZone(csAccount.Name, csClient)
		if err != nil {
			return err
		}

		csAccount.DefaultZone = defaultZone

		isDefault := false
		if askQuestion(fmt.Sprintf("Is %q your default profile?", csAccount.Name)) {
			isDefault = true
		}

		config.Accounts = append(config.Accounts, csAccount)

		if i == 1 || isDefault {
			config.DefaultAccount = acc.Name()
			viper.Set("defaultAccount", acc.Name())
		}
	}

	return addAccount(cfgPath, config)
}

func isAccountExist(name string) bool {

	if gAllAccount == nil {
		return false
	}

	for _, acc := range gAllAccount.Accounts {
		if acc.Name == name {
			return true
		}
	}

	return false
}

func createConfigFile(fileName string) (string, error) {
	if _, err := os.Stat(gConfigFolder); os.IsNotExist(err) {
		if err := os.MkdirAll(gConfigFolder, os.ModePerm); err != nil {
			return "", err
		}
	}

	filepath := path.Join(gConfigFolder, fileName+".toml")

	if _, err := os.Stat(filepath); !os.IsNotExist(err) {
		return "", fmt.Errorf("%q exists already", filepath)
	}
	return filepath, nil
}

func readInput(reader *bufio.Reader, text, def string) (string, error) {
	if def == "" {
		fmt.Printf("[+] %s [%s]: ", text, "none")
	} else {
		fmt.Printf("[+] %s [%s]: ", text, def)
	}
	c := make(chan bool)
	defer close(c)

	input := ""
	var err error
	go func() {
		input, err = reader.ReadString('\n')
		c <- true
	}()

	select {
	case <-c:
	case <-gContext.Done():
		err = fmt.Errorf("")
	}

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

	input = strings.TrimSpace(input)
	if input == "" {
		input = def
	}
	return input, nil
}

func askQuestion(text string) bool {

	reader := bufio.NewReader(os.Stdin)

	resp, err := readInput(reader, text, "yN")
	if err != nil {
		log.Fatal(err)
	}

	return (strings.ToLower(resp) == "y" || strings.ToLower(resp) == "yes")
}

func listAccounts() {
	if gAllAccount == nil {
		return
	}
	for _, acc := range gAllAccount.Accounts {
		print("- ", acc.Name)
		if acc.Name == gAllAccount.DefaultAccount {
			print(" [Default]")
		}
		fmt.Println("")
	}
}

func getAccountByName(name string) *account {
	if gAllAccount == nil {
		return nil
	}
	for i, acc := range gAllAccount.Accounts {
		if acc.Name == name {
			return &gAllAccount.Accounts[i]
		}
	}
	return nil
}

func getSelectedZone(number string, zones map[string]string) (string, bool) {
	zName, ok := zones[number]
	if !ok {
		return "", false
	}
	return zName, true
}

func chooseZone(accountName string, cs *egoscale.Client) (string, error) {

	reader := bufio.NewReader(os.Stdin)

	zonesResp, err := cs.List(&egoscale.Zone{})
	if err != nil {
		return "", err
	}

	zones := map[string]string{}

	if len(zonesResp) == 0 {
		return "", fmt.Errorf("no zones were found")
	}

	fmt.Printf("Choose %q default zone:\n", accountName)

	for i, z := range zonesResp {
		zone := z.(*egoscale.Zone)

		zName := strings.ToLower(zone.Name)

		n := fmt.Sprintf("%d", i+1)

		zones[n] = zName

		fmt.Printf("%d: %s\n", i+1, zName)
	}

	zoneNumber, err := readInput(reader, "Select", "1")
	if err != nil {
		return "", err
	}

	defaultZone, ok := getSelectedZone(zoneNumber, zones)
	for !ok {
		fmt.Println("Error: Invalid zone number")
		defaultZone, err = chooseZone(accountName, cs)
		if err == nil {
			break
		}
	}
	return defaultZone, nil
}

func init() {

	configCmd.RunE = configCmdRun
	RootCmd.AddCommand(configCmd)
}
<commit_msg>exo: fix panic on cloudstack.ini import config<commit_after>package cmd

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strings"

	"github.com/exoscale/egoscale"
	"github.com/go-ini/ini"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	defaultConfigFileName  = "exoscale"
	defaultComputeEndpoint = "https://api.exoscale.ch/compute"
)

// configCmd represents the config command
var configCmd = &cobra.Command{
	Use:   "config",
	Short: "Generate config file for this cli",
}

func configCmdRun(cmd *cobra.Command, args []string) error {
	if viper.ConfigFileUsed() != "" {
		fmt.Println("Good day! exo is already configured with accounts:")
		listAccounts()
		return addNewAccount(false)
	}
	csPath, ok := isCloudstackINIFileExist()
	if ok {
		resp, ok, err := askCloudstackINIMigration(csPath)
		if err != nil {
			return err
		}
		if !ok {
			return addNewAccount(true)
		}

		cfgPath, err := createConfigFile(defaultConfigFileName)
		if err != nil {
			return err
		}
		if err := importCloudstackINI(resp, csPath, cfgPath); err != nil {
			return err
		}
		return addNewAccount(false)
	}
	fmt.Print(`
Hi happy Exoscalian, some configuration is required to use exo.

We now need some very important information, find them there.
	<https://portal.exoscale.com/account/profile/api>

`)
	return addNewAccount(true)
}

func addNewAccount(firstRun bool) error {

	config := &config{}

	if firstRun {
		filePath, err := createConfigFile(defaultConfigFileName)
		if err != nil {
			return err
		}

		viper.SetConfigFile(filePath)

		newAccount, err := getAccount()
		if err != nil {
			return err
		}
		config.DefaultAccount = newAccount.Name
		config.Accounts = []account{*newAccount}
		viper.Set("defaultAccount", newAccount.Name)
	}

	for askQuestion("Do you wish to add another account?") {
		newAccount, err := getAccount()
		if err != nil {
			return err
		}
		config.Accounts = append(config.Accounts, *newAccount)
		if askQuestion("Make [" + newAccount.Name + "] your default profile?") {
			config.DefaultAccount = newAccount.Name
			viper.Set("defaultAccount", newAccount.Name)
		}
	}

	if len(config.Accounts) == 0 {
		return nil
	}

	return addAccount(viper.ConfigFileUsed(), config)
}

func getAccount() (*account, error) {
	reader := bufio.NewReader(os.Stdin)

	var client *egoscale.Client

	account := &account{
		Endpoint: defaultComputeEndpoint,
		Key:      "",
		Secret:   "",
	}

	for i := 0; ; i++ {
		if i > 0 {
			endpoint, err := readInput(reader, "API Endpoint", account.Endpoint)
			if err != nil {
				return nil, err
			}
			if endpoint != account.Endpoint {
				account.Endpoint = endpoint
			}
		}

		apiKey, err := readInput(reader, "API Key", account.Key)
		if err != nil {
			return nil, err
		}
		if apiKey != account.Key {
			account.Key = apiKey
		}

		secret := ""
		if account.Secret != "" && len(account.Secret) > 10 {
			secret = account.Secret[0:7] + "..."
		}
		secretKey, err := readInput(reader, "Secret Key", secret)
		if err != nil {
			return nil, err
		}
		if secretKey != account.Secret && secretKey != secret {
			account.Secret = secretKey
		}

		client = egoscale.NewClient(account.Endpoint, account.Key, account.Secret)

		fmt.Printf("Checking the credentials of %q...", account.Key)
		acc := &egoscale.Account{}
		err = client.GetWithContext(gContext, acc)
		if err != nil {
			fmt.Print(` failure.

Let's start over.

`)
		} else {
			fmt.Print(" success!\n\n")
			account.Name = acc.Name
			account.Account = acc.Name
			break
		}
	}

	name, err := readInput(reader, "Account name", account.Name)
	if err != nil {
		return nil, err
	}
	if name != "" {
		account.Name = name
	}

	for isAccountExist(account.Name) {
		fmt.Printf("Account name [%s] already exist\n", name)
		name, err = readInput(reader, "Account name", account.Name)
		if err != nil {
			return nil, err
		}

		account.Name = name
	}

	defaultZone, err := chooseZone(account.Name, client)
	if err != nil {
		return nil, err
	}

	account.DefaultZone = defaultZone

	return account, nil
}

func addAccount(filePath string, newAccounts *config) error {

	accountsSize := 0
	currentAccounts := []account{}
	if gAllAccount != nil {
		accountsSize = len(gAllAccount.Accounts)
		currentAccounts = gAllAccount.Accounts
	}

	newAccountsSize := 0

	if newAccounts != nil {
		newAccountsSize = len(newAccounts.Accounts)
	}

	accounts := make([]map[string]string, accountsSize+newAccountsSize)

	conf := &config{}

	for i, acc := range currentAccounts {

		accounts[i] = map[string]string{}

		accounts[i]["name"] = acc.Name
		accounts[i]["endpoint"] = acc.Endpoint
		accounts[i]["key"] = acc.Key
		accounts[i]["secret"] = acc.Secret
		accounts[i]["defaultZone"] = acc.DefaultZone
		accounts[i]["account"] = acc.Account

		conf.Accounts = append(conf.Accounts, acc)
	}

	if newAccounts != nil {

		for i, acc := range newAccounts.Accounts {

			accounts[accountsSize+i] = map[string]string{}

			accounts[accountsSize+i]["name"] = acc.Name
			accounts[accountsSize+i]["endpoint"] = acc.Endpoint
			accounts[accountsSize+i]["key"] = acc.Key
			accounts[accountsSize+i]["secret"] = acc.Secret
			accounts[accountsSize+i]["defaultZone"] = acc.DefaultZone
			accounts[accountsSize+i]["account"] = acc.Account
			conf.Accounts = append(conf.Accounts, acc)
		}
	}

	viper.SetConfigType("toml")
	viper.SetConfigFile(filePath)

	viper.Set("accounts", accounts)

	if err := viper.WriteConfig(); err != nil {
		return err
	}

	conf.DefaultAccount = viper.Get("defaultAccount").(string)
	gAllAccount = conf

	return nil

}

func isCloudstackINIFileExist() (string, bool) {

	envConfigPath := os.Getenv("CLOUDSTACK_CONFIG")

	usr, _ := user.Current()

	localConfig, _ := filepath.Abs("cloudstack.ini")
	inis := []string{
		localConfig,
		filepath.Join(usr.HomeDir, ".cloudstack.ini"),
		filepath.Join(gConfigFolder, "cloudstack.ini"),
		envConfigPath,
	}

	cfgPath := ""

	for _, i := range inis {
		if _, err := os.Stat(i); err != nil {
			continue
		}
		cfgPath = i
		break
	}

	if cfgPath == "" {
		return "", false
	}
	return cfgPath, true
}

func askCloudstackINIMigration(csFilePath string) (string, bool, error) {

	cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, csFilePath)
	if err != nil {
		return "", false, err
	}

	if len(cfg.Sections()) <= 0 {
		return "", false, nil
	}

	fmt.Printf("We've found a %q configuration file with the following configurations:\n", "cloudstack.ini")
	for i, acc := range cfg.Sections() {
		if i == 0 {
			continue
		}
		fmt.Printf("- [%s] %s\n", acc.Name(), acc.Key("key").String())
	}
	fmt.Println("")

	reader := bufio.NewReader(os.Stdin)

	resp, err := readInput(reader, "Which one should we import?", "All, some, none")
	if err != nil {
		return "", false, err
	}

	resp = strings.ToLower(resp)
	if resp == "" {
		resp = "all"
	}

	return resp, (resp == "all" || resp == "some"), nil
}

func importCloudstackINI(option, csPath, cfgPath string) error {
	cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, csPath)
	if err != nil {
		return err
	}

	reader := bufio.NewReader(os.Stdin)

	config := &config{}

	setdefaultAccount := 1
	for i, acc := range cfg.Sections() {
		if i == 0 {
			continue
		}

		if option == "some" {
			if !askQuestion(fmt.Sprintf("Do you want to import [%s] %s?", acc.Name(), acc.Key("key").String())) {
				if viper.Get("defaultAccount") == nil {
					setdefaultAccount = i + 1
				}
				continue
			}
		}

		csAccount := account{
			Name:     acc.Name(),
			Endpoint: acc.Key("endpoint").String(),
			Key:      acc.Key("key").String(),
			Secret:   acc.Key("secret").String(),
		}

		csClient := egoscale.NewClient(csAccount.Endpoint, csAccount.Key, csAccount.Secret)

		fmt.Printf("Checking the credentials of %q...", csAccount.Key)
		a := &egoscale.Account{}
		err := csClient.GetWithContext(gContext, a)
		if err != nil {
			fmt.Println(" failure.")
			if !askQuestion(fmt.Sprintf("Do you want to keep %s?", acc.Name())) {
				continue
			}
		} else {
			fmt.Println(" success!")
			csAccount.Name = a.Name
			csAccount.Account = a.Name
		}
		fmt.Println("")

		name, err := readInput(reader, "Account name", csAccount.Name)
		if err != nil {
			return err
		}
		if name != "" {
			csAccount.Name = name
		}

		for isAccountExist(csAccount.Name) {
			fmt.Printf("Account name [%s] already exist\n", csAccount.Name)
			name, err = readInput(reader, "Account name", csAccount.Name)
			if err != nil {
				return err
			}

			csAccount.Name = name
		}

		defaultZone, err := chooseZone(csAccount.Name, csClient)
		if err != nil {
			return err
		}

		csAccount.DefaultZone = defaultZone

		isDefault := false
		if askQuestion(fmt.Sprintf("Is %q your default profile?", csAccount.Name)) {
			isDefault = true
		}

		config.Accounts = append(config.Accounts, csAccount)

		if i == setdefaultAccount || isDefault {
			config.DefaultAccount = csAccount.Name
			viper.Set("defaultAccount", acc.Name())
		}
	}

	return addAccount(cfgPath, config)
}

func isAccountExist(name string) bool {

	if gAllAccount == nil {
		return false
	}

	for _, acc := range gAllAccount.Accounts {
		if acc.Name == name {
			return true
		}
	}

	return false
}

func createConfigFile(fileName string) (string, error) {
	if _, err := os.Stat(gConfigFolder); os.IsNotExist(err) {
		if err := os.MkdirAll(gConfigFolder, os.ModePerm); err != nil {
			return "", err
		}
	}

	filepath := path.Join(gConfigFolder, fileName+".toml")

	if _, err := os.Stat(filepath); !os.IsNotExist(err) {
		return "", fmt.Errorf("%q exists already", filepath)
	}
	return filepath, nil
}

func readInput(reader *bufio.Reader, text, def string) (string, error) {
	if def == "" {
		fmt.Printf("[+] %s [%s]: ", text, "none")
	} else {
		fmt.Printf("[+] %s [%s]: ", text, def)
	}
	c := make(chan bool)
	defer close(c)

	input := ""
	var err error
	go func() {
		input, err = reader.ReadString('\n')
		c <- true
	}()

	select {
	case <-c:
	case <-gContext.Done():
		err = fmt.Errorf("")
	}

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

	input = strings.TrimSpace(input)
	if input == "" {
		input = def
	}
	return input, nil
}

func askQuestion(text string) bool {

	reader := bufio.NewReader(os.Stdin)

	resp, err := readInput(reader, text, "yN")
	if err != nil {
		log.Fatal(err)
	}

	return (strings.ToLower(resp) == "y" || strings.ToLower(resp) == "yes")
}

func listAccounts() {
	if gAllAccount == nil {
		return
	}
	for _, acc := range gAllAccount.Accounts {
		print("- ", acc.Name)
		if acc.Name == gAllAccount.DefaultAccount {
			print(" [Default]")
		}
		fmt.Println("")
	}
}

func getAccountByName(name string) *account {
	if gAllAccount == nil {
		return nil
	}
	for i, acc := range gAllAccount.Accounts {
		if acc.Name == name {
			return &gAllAccount.Accounts[i]
		}
	}
	return nil
}

func getSelectedZone(number string, zones map[string]string) (string, bool) {
	zName, ok := zones[number]
	if !ok {
		return "", false
	}
	return zName, true
}

func chooseZone(accountName string, cs *egoscale.Client) (string, error) {

	reader := bufio.NewReader(os.Stdin)

	zonesResp, err := cs.List(&egoscale.Zone{})
	if err != nil {
		return "", err
	}

	zones := map[string]string{}

	if len(zonesResp) == 0 {
		return "", fmt.Errorf("no zones were found")
	}

	fmt.Printf("Choose %q default zone:\n", accountName)

	for i, z := range zonesResp {
		zone := z.(*egoscale.Zone)

		zName := strings.ToLower(zone.Name)

		n := fmt.Sprintf("%d", i+1)

		zones[n] = zName

		fmt.Printf("%d: %s\n", i+1, zName)
	}

	zoneNumber, err := readInput(reader, "Select", "1")
	if err != nil {
		return "", err
	}

	defaultZone, ok := getSelectedZone(zoneNumber, zones)
	for !ok {
		fmt.Println("Error: Invalid zone number")
		defaultZone, err = chooseZone(accountName, cs)
		if err == nil {
			break
		}
	}
	return defaultZone, nil
}

func init() {

	configCmd.RunE = configCmdRun
	RootCmd.AddCommand(configCmd)
}
<|endoftext|>


<commit_before>/*
 * errors.go - File which contains common error handling code for fscrypt
 * commands. This includes handling for bad usage, invalid commands, and errors
 * from the other packages
 *
 * Copyright 2017 Google Inc.
 * Author: Joe Richey (joerichey@google.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"unicode/utf8"

	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/google/fscrypt/actions"
	"github.com/google/fscrypt/crypto"
	"github.com/google/fscrypt/filesystem"
	"github.com/google/fscrypt/metadata"
	"github.com/google/fscrypt/util"
)

// failureExitCode is the value fscrypt will return on failure.
const failureExitCode = 1

// Various errors used for the top level user interface
var (
	ErrCanceled           = errors.New("operation canceled")
	ErrNoDesctructiveOps  = errors.New("operation would be destructive")
	ErrMaxPassphrase      = util.SystemError("max passphrase length exceeded")
	ErrInvalidSource      = errors.New("invalid source type")
	ErrPassphraseMismatch = errors.New("entered passphrases do not match")
	ErrSpecifyProtector   = errors.New("multiple protectors available")
	ErrWrongKey           = errors.New("incorrect key provided")
	ErrSpecifyKeyFile     = errors.New("no key file specified")
	ErrKeyFileLength      = errors.Errorf("key file must be %d bytes", metadata.InternalKeyLen)
	ErrAllLoadsFailed     = errors.New("could not load any protectors")
	ErrMustBeRoot         = errors.New("this command must be run as root")
	ErrPolicyUnlocked     = errors.New("this file or directory is already unlocked")
	ErrBadOwners          = errors.New("you do not own this directory")
	ErrNotEmptyDir        = errors.New("not an empty directory")
	ErrNotPassphrase      = errors.New("protector does not use a passphrase")
	ErrUnknownUser        = errors.New("unknown user")
	ErrDropCachesPerm     = errors.New("inode cache can only be dropped as root")
	ErrSpecifyUser        = errors.New("user must be specified when run as root")
)

var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag))

// getFullName returns the full name of the application or command being used.
func getFullName(c *cli.Context) string {
	if c.Command.HelpName != "" {
		return c.Command.HelpName
	}
	return c.App.HelpName
}

// getErrorSuggestions returns a string containing suggestions about how to fix
// an error. If no suggestion is necessary or available, return empty string.
func getErrorSuggestions(err error) string {
	switch errors.Cause(err) {
	case filesystem.ErrNotSetup:
		return fmt.Sprintf(`Run "fscrypt setup %s" to use fscrypt on this filesystem.`, mountpointArg)
	case crypto.ErrKeyLock:
		return `Too much memory was requested to be locked in RAM. The
			current limit for this user can be checked with "ulimit
			-l". The limit can be modified by either changing the
			"memlock" item in /etc/security/limits.conf or by
			changing the "LimitMEMLOCK" value in systemd.`
	case metadata.ErrEncryptionNotSupported:
		return `Encryption for this type of filesystem is not supported
			on this kernel version.`
	case metadata.ErrEncryptionNotEnabled:
		return `Encryption is either disabled in the kernel config, or
			needs to be enabled for this filesystem. See the
			documentation on how to enable encryption on ext4
			systems (and the risks of doing so).`
	case actions.ErrBadConfigFile:
		return `Run "sudo fscrypt setup" to recreate the file.`
	case actions.ErrNoConfigFile:
		return `Run "sudo fscrypt setup" to create the file.`
	case actions.ErrMissingPolicyMetadata:
		return `This file or directory has either been encrypted with
			another tool (such as e4crypt) or the corresponding
			filesystem metadata has been deleted.`
	case actions.ErrPolicyMetadataMismatch:
		return `The metadata for this encrypted directory is in an
			inconsistent state. This most likely means the filesystem
			metadata is corrupted.`
	case actions.ErrMissingProtectorName:
		return fmt.Sprintf("Use %s to specify a protector name.", shortDisplay(nameFlag))
	case ErrNoDesctructiveOps:
		return fmt.Sprintf("Use %s to automatically run destructive operations.", shortDisplay(forceFlag))
	case ErrSpecifyProtector:
		return fmt.Sprintf("Use %s to specify a protector.", shortDisplay(protectorFlag))
	case ErrSpecifyKeyFile:
		return fmt.Sprintf("Use %s to specify a key file.", shortDisplay(keyFileFlag))
	case ErrBadOwners:
		return `Encryption can only be setup on directories you own,
			even if you have write permission for the directory.`
	case ErrNotEmptyDir:
		return `Encryption can only be setup on empty directories; files
			cannot be encrypted in-place. Instead, encrypt an empty
			directory, copy the files into that encrypted directory,
			and securely delete the originals with "shred".`
	case ErrDropCachesPerm:
		return fmt.Sprintf(`Either this command should be run as root to
			properly clear the inode cache, or it should be run with
			%s=false (this may leave encrypted files and directories
			in an accessible state).`, shortDisplay(dropCachesFlag))
	case ErrSpecifyUser:
		return fmt.Sprintf(`When running this command as root, you
			usually still want to provision/remove keys for a normal
			user's keyring and use a normal user's login passphrase
			as a protector (so the corresponding files will be
			accessible for that user). This can be done with %s. To
			use the root user's keyring or passphrase, use
			--%s=root.`, shortDisplay(userFlag), userFlag.GetName())
	case ErrAllLoadsFailed:
		return loadHelpText
	default:
		return ""
	}
}

// newExitError creates a new error for a given context and normal error. The
// returned error prepends the name of the relevant command and will make
// fscrypt return a non-zero exit value.
func newExitError(c *cli.Context, err error) error {
	// Prepend the full name and append suggestions (if any)
	fullNamePrefix := getFullName(c) + ": "
	message := fullNamePrefix + wrapText(err.Error(), utf8.RuneCountInString(fullNamePrefix))

	if suggestion := getErrorSuggestions(err); suggestion != "" {
		message += "\n\n" + wrapText(suggestion, 0)
	}

	return cli.NewExitError(message, failureExitCode)
}

// usageError implements cli.ExitCoder to will print the usage and the return a
// non-zero value. This error should be used when a command is used incorrectly.
type usageError struct {
	c       *cli.Context
	message string
}

func (u *usageError) Error() string {
	return fmt.Sprintf("%s: %s", getFullName(u.c), u.message)
}

// We get the help to print after the error by having it run right before the
// application exits. This is very nasty, but there isn't a better way to do it
// with the constraints of urfave/cli.
func (u *usageError) ExitCode() int {
	// Redirect help output to a buffer, so we can customize it.
	buf := new(bytes.Buffer)
	oldWriter := u.c.App.Writer
	u.c.App.Writer = buf

	// Get the appropriate help
	if getFullName(u.c) == filepath.Base(os.Args[0]) {
		cli.ShowAppHelp(u.c)
	} else {
		cli.ShowCommandHelp(u.c, u.c.Command.Name)
	}

	// Remove first line from help and print it out
	buf.ReadBytes('\n')
	buf.WriteTo(oldWriter)
	u.c.App.Writer = oldWriter
	return failureExitCode
}

// expectedArgsErr creates a usage error for the incorrect number of arguments
// being specified. atMost should be true only if any number of arguments from 0
// to expectedArgs would be acceptable.
func expectedArgsErr(c *cli.Context, expectedArgs int, atMost bool) error {
	message := "expected "
	if atMost {
		message += "at most "
	}
	message += fmt.Sprintf("%s, got %s",
		pluralize(expectedArgs, "argument"), pluralize(c.NArg(), "argument"))
	return &usageError{c, message}
}

// onUsageError is a function handler for the application and each command.
func onUsageError(c *cli.Context, err error, _ bool) error {
	return &usageError{c, err.Error()}
}

// checkRequiredFlags makes sure that all of the specified string flags have
// been given nonempty values. Returns a usage error on failure.
func checkRequiredFlags(c *cli.Context, flags []*stringFlag) error {
	for _, flag := range flags {
		if flag.Value == "" {
			message := fmt.Sprintf("required flag %s not provided", shortDisplay(flag))
			return &usageError{c, message}
		}
	}
	return nil
}
<commit_msg>cmd/fscrypt: Add explanations for keyring failures<commit_after>/*
 * errors.go - File which contains common error handling code for fscrypt
 * commands. This includes handling for bad usage, invalid commands, and errors
 * from the other packages
 *
 * Copyright 2017 Google Inc.
 * Author: Joe Richey (joerichey@google.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"unicode/utf8"

	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/google/fscrypt/actions"
	"github.com/google/fscrypt/crypto"
	"github.com/google/fscrypt/filesystem"
	"github.com/google/fscrypt/metadata"
	"github.com/google/fscrypt/security"
	"github.com/google/fscrypt/util"
)

// failureExitCode is the value fscrypt will return on failure.
const failureExitCode = 1

// Various errors used for the top level user interface
var (
	ErrCanceled           = errors.New("operation canceled")
	ErrNoDesctructiveOps  = errors.New("operation would be destructive")
	ErrMaxPassphrase      = util.SystemError("max passphrase length exceeded")
	ErrInvalidSource      = errors.New("invalid source type")
	ErrPassphraseMismatch = errors.New("entered passphrases do not match")
	ErrSpecifyProtector   = errors.New("multiple protectors available")
	ErrWrongKey           = errors.New("incorrect key provided")
	ErrSpecifyKeyFile     = errors.New("no key file specified")
	ErrKeyFileLength      = errors.Errorf("key file must be %d bytes", metadata.InternalKeyLen)
	ErrAllLoadsFailed     = errors.New("could not load any protectors")
	ErrMustBeRoot         = errors.New("this command must be run as root")
	ErrPolicyUnlocked     = errors.New("this file or directory is already unlocked")
	ErrBadOwners          = errors.New("you do not own this directory")
	ErrNotEmptyDir        = errors.New("not an empty directory")
	ErrNotPassphrase      = errors.New("protector does not use a passphrase")
	ErrUnknownUser        = errors.New("unknown user")
	ErrDropCachesPerm     = errors.New("inode cache can only be dropped as root")
	ErrSpecifyUser        = errors.New("user must be specified when run as root")
)

var loadHelpText = fmt.Sprintf("You may need to mount a linked filesystem. Run with %s for more information.", shortDisplay(verboseFlag))

// getFullName returns the full name of the application or command being used.
func getFullName(c *cli.Context) string {
	if c.Command.HelpName != "" {
		return c.Command.HelpName
	}
	return c.App.HelpName
}

// getErrorSuggestions returns a string containing suggestions about how to fix
// an error. If no suggestion is necessary or available, return empty string.
func getErrorSuggestions(err error) string {
	switch errors.Cause(err) {
	case filesystem.ErrNotSetup:
		return fmt.Sprintf(`Run "fscrypt setup %s" to use fscrypt on this filesystem.`, mountpointArg)
	case crypto.ErrKeyLock:
		return `Too much memory was requested to be locked in RAM. The
			current limit for this user can be checked with "ulimit
			-l". The limit can be modified by either changing the
			"memlock" item in /etc/security/limits.conf or by
			changing the "LimitMEMLOCK" value in systemd.`
	case metadata.ErrEncryptionNotSupported:
		return `Encryption for this type of filesystem is not supported
			on this kernel version.`
	case metadata.ErrEncryptionNotEnabled:
		return `Encryption is either disabled in the kernel config, or
			needs to be enabled for this filesystem. See the
			documentation on how to enable encryption on ext4
			systems (and the risks of doing so).`
	case security.ErrSessionUserKeying:
		return `This is usually the result of a bad PAM configuration.
			Either correct the problem in your PAM stack, enable
			pam_keyinit.so, or run "keyctl link @u @s".`
	case security.ErrAccessUserKeyring:
		return fmt.Sprintf(`You can only use %s to access the user
			keyring of another user if you are running as root.`,
			shortDisplay(userFlag))
	case actions.ErrBadConfigFile:
		return `Run "sudo fscrypt setup" to recreate the file.`
	case actions.ErrNoConfigFile:
		return `Run "sudo fscrypt setup" to create the file.`
	case actions.ErrMissingPolicyMetadata:
		return `This file or directory has either been encrypted with
			another tool (such as e4crypt) or the corresponding
			filesystem metadata has been deleted.`
	case actions.ErrPolicyMetadataMismatch:
		return `The metadata for this encrypted directory is in an
			inconsistent state. This most likely means the filesystem
			metadata is corrupted.`
	case actions.ErrMissingProtectorName:
		return fmt.Sprintf("Use %s to specify a protector name.", shortDisplay(nameFlag))
	case ErrNoDesctructiveOps:
		return fmt.Sprintf("Use %s to automatically run destructive operations.", shortDisplay(forceFlag))
	case ErrSpecifyProtector:
		return fmt.Sprintf("Use %s to specify a protector.", shortDisplay(protectorFlag))
	case ErrSpecifyKeyFile:
		return fmt.Sprintf("Use %s to specify a key file.", shortDisplay(keyFileFlag))
	case ErrBadOwners:
		return `Encryption can only be setup on directories you own,
			even if you have write permission for the directory.`
	case ErrNotEmptyDir:
		return `Encryption can only be setup on empty directories; files
			cannot be encrypted in-place. Instead, encrypt an empty
			directory, copy the files into that encrypted directory,
			and securely delete the originals with "shred".`
	case ErrDropCachesPerm:
		return fmt.Sprintf(`Either this command should be run as root to
			properly clear the inode cache, or it should be run with
			%s=false (this may leave encrypted files and directories
			in an accessible state).`, shortDisplay(dropCachesFlag))
	case ErrSpecifyUser:
		return fmt.Sprintf(`When running this command as root, you
			usually still want to provision/remove keys for a normal
			user's keyring and use a normal user's login passphrase
			as a protector (so the corresponding files will be
			accessible for that user). This can be done with %s. To
			use the root user's keyring or passphrase, use
			--%s=root.`, shortDisplay(userFlag), userFlag.GetName())
	case ErrAllLoadsFailed:
		return loadHelpText
	default:
		return ""
	}
}

// newExitError creates a new error for a given context and normal error. The
// returned error prepends the name of the relevant command and will make
// fscrypt return a non-zero exit value.
func newExitError(c *cli.Context, err error) error {
	// Prepend the full name and append suggestions (if any)
	fullNamePrefix := getFullName(c) + ": "
	message := fullNamePrefix + wrapText(err.Error(), utf8.RuneCountInString(fullNamePrefix))

	if suggestion := getErrorSuggestions(err); suggestion != "" {
		message += "\n\n" + wrapText(suggestion, 0)
	}

	return cli.NewExitError(message, failureExitCode)
}

// usageError implements cli.ExitCoder to will print the usage and the return a
// non-zero value. This error should be used when a command is used incorrectly.
type usageError struct {
	c       *cli.Context
	message string
}

func (u *usageError) Error() string {
	return fmt.Sprintf("%s: %s", getFullName(u.c), u.message)
}

// We get the help to print after the error by having it run right before the
// application exits. This is very nasty, but there isn't a better way to do it
// with the constraints of urfave/cli.
func (u *usageError) ExitCode() int {
	// Redirect help output to a buffer, so we can customize it.
	buf := new(bytes.Buffer)
	oldWriter := u.c.App.Writer
	u.c.App.Writer = buf

	// Get the appropriate help
	if getFullName(u.c) == filepath.Base(os.Args[0]) {
		cli.ShowAppHelp(u.c)
	} else {
		cli.ShowCommandHelp(u.c, u.c.Command.Name)
	}

	// Remove first line from help and print it out
	buf.ReadBytes('\n')
	buf.WriteTo(oldWriter)
	u.c.App.Writer = oldWriter
	return failureExitCode
}

// expectedArgsErr creates a usage error for the incorrect number of arguments
// being specified. atMost should be true only if any number of arguments from 0
// to expectedArgs would be acceptable.
func expectedArgsErr(c *cli.Context, expectedArgs int, atMost bool) error {
	message := "expected "
	if atMost {
		message += "at most "
	}
	message += fmt.Sprintf("%s, got %s",
		pluralize(expectedArgs, "argument"), pluralize(c.NArg(), "argument"))
	return &usageError{c, message}
}

// onUsageError is a function handler for the application and each command.
func onUsageError(c *cli.Context, err error, _ bool) error {
	return &usageError{c, err.Error()}
}

// checkRequiredFlags makes sure that all of the specified string flags have
// been given nonempty values. Returns a usage error on failure.
func checkRequiredFlags(c *cli.Context, flags []*stringFlag) error {
	for _, flag := range flags {
		if flag.Value == "" {
			message := fmt.Sprintf("required flag %s not provided", shortDisplay(flag))
			return &usageError{c, message}
		}
	}
	return nil
}
<|endoftext|>