<commit_before>package command

import (
	"fmt"
	"strings"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/jobspec"
	"github.com/hashicorp/nomad/scheduler"
	"github.com/mitchellh/colorstring"
)

type PlanCommand struct {
	Meta
	color *colorstring.Colorize
}

func (c *PlanCommand) Help() string {
	helpText := `
Usage: nomad plan [options] <file>

General Options:

  ` + generalOptionsUsage() + `

Run Options:

  -diff
    Defaults to true, but can be toggled off to omit diff output.

  -no-color
    Disable colored output.

  -verbose
    Increased diff verbosity

`
	return strings.TrimSpace(helpText)
}

func (c *PlanCommand) Synopsis() string {
	return "Dry-run a job update to determine its effects"
}

func (c *PlanCommand) Run(args []string) int {
	var diff, verbose bool

	flags := c.Meta.FlagSet("plan", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&diff, "diff", true, "")
	flags.BoolVar(&verbose, "verbose", false, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Check that we got exactly one job
	args = flags.Args()
	if len(args) != 1 {
		c.Ui.Error(c.Help())
		return 1
	}
	file := args[0]

	// Parse the job file
	job, err := jobspec.ParseFile(file)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err))
		return 1
	}

	// Initialize any fields that need to be.
	job.InitFields()

	// Check that the job is valid
	if err := job.Validate(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
		return 1
	}

	// Convert it to something we can use
	apiJob, err := convertStructJob(job)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
		return 1
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Submit the job
	resp, _, err := client.Jobs().Plan(apiJob, diff, nil)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error during plan: %s", err))
		return 1
	}

	if diff {
		c.Ui.Output(c.Colorize().Color(strings.TrimSpace(formatJobDiff(resp.Diff, verbose))))
	}

	return 0
}

func formatJobDiff(job *api.JobDiff, verbose bool) string {
	out := fmt.Sprintf("%s[bold]Job: %q\n", getDiffString(job.Type), job.ID)

	if job.Type == "Edited" || verbose {
		for _, field := range job.Fields {
			out += fmt.Sprintf("%s\n", formatFieldDiff(field, "", verbose))
		}

		for _, object := range job.Objects {
			out += fmt.Sprintf("%s\n", formatObjectDiff(object, "", verbose))
		}
	}

	for _, tg := range job.TaskGroups {
		out += fmt.Sprintf("%s\n", formatTaskGroupDiff(tg, verbose))
	}

	return out
}

func formatTaskGroupDiff(tg *api.TaskGroupDiff, verbose bool) string {
	out := fmt.Sprintf("%s[bold]Task Group: %q", getDiffString(tg.Type), tg.Name)

	// Append the updates
	if l := len(tg.Updates); l > 0 {
		updates := make([]string, 0, l)
		for updateType, count := range tg.Updates {
			var color string
			switch updateType {
			case scheduler.UpdateTypeIgnore:
			case scheduler.UpdateTypeCreate:
				color = "[green]"
			case scheduler.UpdateTypeDestroy:
				color = "[red]"
			case scheduler.UpdateTypeMigrate:
				color = "[blue]"
			case scheduler.UpdateTypeInplaceUpdate:
				color = "[cyan]"
			case scheduler.UpdateTypeDestructiveUpdate:
				color = "[yellow]"
			}
			updates = append(updates, fmt.Sprintf("[reset]%s%d %s", color, count, updateType))
		}
		out += fmt.Sprintf(" (%s[reset])\n", strings.Join(updates, ", "))
	} else {
		out += "[reset]\n"
	}

	if tg.Type == "Edited" || verbose {
		for _, field := range tg.Fields {
			out += fmt.Sprintf("%s\n", formatFieldDiff(field, "  ", verbose))
		}

		for _, object := range tg.Objects {
			out += fmt.Sprintf("%s\n", formatObjectDiff(object, "  ", verbose))
		}
	}

	for _, task := range tg.Tasks {
		out += fmt.Sprintf("%s\n", formatTaskDiff(task, verbose))
	}

	return out
}

func formatTaskDiff(task *api.TaskDiff, verbose bool) string {
	out := fmt.Sprintf("  %s[bold]Task: %q", getDiffString(task.Type), task.Name)
	if len(task.Annotations) != 0 {
		out += fmt.Sprintf(" [reset](%s)", strings.Join(task.Annotations, ", "))
	}

	if task.Type == "None" {
		return out
	} else if (task.Type == "Deleted" || task.Type == "Added") && !verbose {
		return out
	} else {
		out += "\n"
	}

	for _, field := range task.Fields {
		out += fmt.Sprintf("%s\n", formatFieldDiff(field, "    ", verbose))
	}

	for _, object := range task.Objects {
		out += fmt.Sprintf("%s\n", formatObjectDiff(object, "    ", verbose))
	}

	return out
}

func formatFieldDiff(diff *api.FieldDiff, prefix string, verbose bool) string {
	out := prefix
	switch diff.Type {
	case "Added":
		out += fmt.Sprintf("%s%s: %q", getDiffString(diff.Type), diff.Name, diff.New)
	case "Deleted":
		out += fmt.Sprintf("%s%s: %q", getDiffString(diff.Type), diff.Name, diff.Old)
	case "Edited":
		out += fmt.Sprintf("%s%s: %q => %q", getDiffString(diff.Type), diff.Name, diff.Old, diff.New)
	default:
		out += fmt.Sprintf("%s: %q", diff.Name, diff.New)
	}

	if len(diff.Annotations) != 0 {
		out += fmt.Sprintf(" (%s)", strings.Join(diff.Annotations, ", "))
	}

	return out
}

func formatObjectDiff(diff *api.ObjectDiff, prefix string, verbose bool) string {
	out := fmt.Sprintf("%s%s%s {\n", prefix, getDiffString(diff.Type), diff.Name)

	newPrefix := prefix + "  "
	numFields := len(diff.Fields)
	numObjects := len(diff.Objects)
	haveObjects := numObjects != 0
	for i, field := range diff.Fields {
		out += formatFieldDiff(field, newPrefix, verbose)
		if i+1 != numFields || haveObjects {
			out += "\n"
		}
	}

	for i, object := range diff.Objects {
		out += formatObjectDiff(object, newPrefix, verbose)
		if i+1 != numObjects {
			out += "\n"
		}
	}

	return fmt.Sprintf("%s\n%s}", out, prefix)
}

func getDiffString(diffType string) string {
	switch diffType {
	case "Added":
		return "[green]+[reset] "
	case "Deleted":
		return "[red]-[reset] "
	case "Edited":
		return "[light_yellow]+/-[reset] "
	default:
		return ""
	}
}
<commit_msg>better colors and verify help text<commit_after>package command

import (
	"fmt"
	"strings"

	"github.com/hashicorp/nomad/api"
	"github.com/hashicorp/nomad/jobspec"
	"github.com/hashicorp/nomad/scheduler"
	"github.com/mitchellh/colorstring"
)

const (
	casHelp = `To submit the job with version verification run:

nomad run -verify %d %s

When running the job with the verify flag, the job will only be run if the server side
version matches the the verify index returned. If the index has changed, another user has
modified the job and the plan's results are potentially invalid.`
)

type PlanCommand struct {
	Meta
	color *colorstring.Colorize
}

func (c *PlanCommand) Help() string {
	helpText := `
Usage: nomad plan [options] <file>

General Options:

  ` + generalOptionsUsage() + `

Run Options:

  -diff
    Defaults to true, but can be toggled off to omit diff output.

  -no-color
    Disable colored output.

  -verbose
    Increased diff verbosity

`
	return strings.TrimSpace(helpText)
}

func (c *PlanCommand) Synopsis() string {
	return "Dry-run a job update to determine its effects"
}

func (c *PlanCommand) Run(args []string) int {
	var diff, verbose bool

	flags := c.Meta.FlagSet("plan", FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&diff, "diff", true, "")
	flags.BoolVar(&verbose, "verbose", false, "")

	if err := flags.Parse(args); err != nil {
		return 1
	}

	// Check that we got exactly one job
	args = flags.Args()
	if len(args) != 1 {
		c.Ui.Error(c.Help())
		return 1
	}
	file := args[0]

	// Parse the job file
	job, err := jobspec.ParseFile(file)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err))
		return 1
	}

	// Initialize any fields that need to be.
	job.InitFields()

	// Check that the job is valid
	if err := job.Validate(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
		return 1
	}

	// Convert it to something we can use
	apiJob, err := convertStructJob(job)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
		return 1
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Submit the job
	resp, _, err := client.Jobs().Plan(apiJob, diff, nil)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error during plan: %s", err))
		return 1
	}

	if diff {
		c.Ui.Output(fmt.Sprintf("%s\n",
			c.Colorize().Color(strings.TrimSpace(formatJobDiff(resp.Diff, verbose)))))
	}

	c.Ui.Output(c.Colorize().Color("[bold]Scheduler dry-run:[reset]"))
	c.Ui.Output(c.Colorize().Color(formatDryRun(resp.CreatedEvals)))

	c.Ui.Output(c.Colorize().Color(formatCas(resp.Cas, file)))
	return 0
}

func formatCas(cas uint64, jobName string) string {
	help := fmt.Sprintf(casHelp, cas, jobName)
	out := fmt.Sprintf("[reset][bold]Job Verify Index: %d[reset]\n%s", cas, help)
	return out
}

func formatDryRun(evals []*api.Evaluation) string {
	// "- All tasks successfully allocated." bold and green

	var rolling *api.Evaluation
	var blocked *api.Evaluation
	for _, eval := range evals {
		if eval.TriggeredBy == "rolling-update" {
			rolling = eval
		} else if eval.Status == "blocked" {
			blocked = eval
		}
	}

	var out string
	if blocked == nil {
		out = "[bold][green]  - All tasks successfully allocated.[reset]\n"
	} else {
		out = "[bold][yellow]  - WARNING: Failed to place all allocations.[reset]\n"
	}

	if rolling != nil {
		out += fmt.Sprintf("[green]  - Rolling update, next evaluation will be in %s.\n", rolling.Wait)
	}

	return out
}

func formatJobDiff(job *api.JobDiff, verbose bool) string {
	out := fmt.Sprintf("%s[bold]Job: %q\n", getDiffString(job.Type), job.ID)

	if job.Type == "Edited" || verbose {
		for _, field := range job.Fields {
			out += fmt.Sprintf("%s\n", formatFieldDiff(field, "", verbose))
		}

		for _, object := range job.Objects {
			out += fmt.Sprintf("%s\n", formatObjectDiff(object, "", verbose))
		}
	}

	for _, tg := range job.TaskGroups {
		out += fmt.Sprintf("%s\n", formatTaskGroupDiff(tg, verbose))
	}

	return out
}

func formatTaskGroupDiff(tg *api.TaskGroupDiff, verbose bool) string {
	out := fmt.Sprintf("%s[bold]Task Group: %q", getDiffString(tg.Type), tg.Name)

	// Append the updates
	if l := len(tg.Updates); l > 0 {
		updates := make([]string, 0, l)
		for updateType, count := range tg.Updates {
			var color string
			switch updateType {
			case scheduler.UpdateTypeIgnore:
			case scheduler.UpdateTypeCreate:
				color = "[green]"
			case scheduler.UpdateTypeDestroy:
				color = "[red]"
			case scheduler.UpdateTypeMigrate:
				color = "[blue]"
			case scheduler.UpdateTypeInplaceUpdate:
				color = "[cyan]"
			case scheduler.UpdateTypeDestructiveUpdate:
				color = "[yellow]"
			}
			updates = append(updates, fmt.Sprintf("[reset]%s%d %s", color, count, updateType))
		}
		out += fmt.Sprintf(" (%s[reset])\n", strings.Join(updates, ", "))
	} else {
		out += "[reset]\n"
	}

	if tg.Type == "Edited" || verbose {
		for _, field := range tg.Fields {
			out += fmt.Sprintf("%s\n", formatFieldDiff(field, "  ", verbose))
		}

		for _, object := range tg.Objects {
			out += fmt.Sprintf("%s\n", formatObjectDiff(object, "  ", verbose))
		}
	}

	for _, task := range tg.Tasks {
		out += fmt.Sprintf("%s\n", formatTaskDiff(task, verbose))
	}

	return out
}

func formatTaskDiff(task *api.TaskDiff, verbose bool) string {
	out := fmt.Sprintf("  %s[bold]Task: %q", getDiffString(task.Type), task.Name)
	if len(task.Annotations) != 0 {
		out += fmt.Sprintf(" [reset](%s)", colorAnnotations(task.Annotations))
	}

	if task.Type == "None" {
		return out
	} else if (task.Type == "Deleted" || task.Type == "Added") && !verbose {
		return out
	} else {
		out += "\n"
	}

	for _, field := range task.Fields {
		out += fmt.Sprintf("%s\n", formatFieldDiff(field, "    ", verbose))
	}

	for _, object := range task.Objects {
		out += fmt.Sprintf("%s\n", formatObjectDiff(object, "    ", verbose))
	}

	return out
}

func formatFieldDiff(diff *api.FieldDiff, prefix string, verbose bool) string {
	out := prefix
	switch diff.Type {
	case "Added":
		out += fmt.Sprintf("%s%s: %q", getDiffString(diff.Type), diff.Name, diff.New)
	case "Deleted":
		out += fmt.Sprintf("%s%s: %q", getDiffString(diff.Type), diff.Name, diff.Old)
	case "Edited":
		out += fmt.Sprintf("%s%s: %q => %q", getDiffString(diff.Type), diff.Name, diff.Old, diff.New)
	default:
		out += fmt.Sprintf("%s: %q", diff.Name, diff.New)
	}

	// Color the annotations where possible
	if l := len(diff.Annotations); l != 0 {
		out += fmt.Sprintf(" (%s)", colorAnnotations(diff.Annotations))
	}

	return out
}

func colorAnnotations(annotations []string) string {
	l := len(annotations)
	if l == 0 {
		return ""
	}

	colored := make([]string, l)
	for i, annotation := range annotations {
		switch annotation {
		case "forces create":
			colored[i] = fmt.Sprintf("[green]%s[reset]", annotation)
		case "forces destroy":
			colored[i] = fmt.Sprintf("[red]%s[reset]", annotation)
		case "forces in-place update":
			colored[i] = fmt.Sprintf("[cyan]%s[reset]", annotation)
		case "forces create/destroy update":
			colored[i] = fmt.Sprintf("[yellow]%s[reset]", annotation)
		default:
			colored[i] = annotation
		}
	}

	return strings.Join(colored, ", ")
}

func formatObjectDiff(diff *api.ObjectDiff, prefix string, verbose bool) string {
	out := fmt.Sprintf("%s%s%s {\n", prefix, getDiffString(diff.Type), diff.Name)

	newPrefix := prefix + "  "
	numFields := len(diff.Fields)
	numObjects := len(diff.Objects)
	haveObjects := numObjects != 0
	for i, field := range diff.Fields {
		out += formatFieldDiff(field, newPrefix, verbose)
		if i+1 != numFields || haveObjects {
			out += "\n"
		}
	}

	for i, object := range diff.Objects {
		out += formatObjectDiff(object, newPrefix, verbose)
		if i+1 != numObjects {
			out += "\n"
		}
	}

	return fmt.Sprintf("%s\n%s}", out, prefix)
}

func getDiffString(diffType string) string {
	switch diffType {
	case "Added":
		return "[green]+[reset] "
	case "Deleted":
		return "[red]-[reset] "
	case "Edited":
		return "[light_yellow]+/-[reset] "
	default:
		return ""
	}
}
<|endoftext|>


<commit_before>package popgun

import (
	"io/ioutil"
	"net"
	"regexp"
	"testing"

	"github.com/DevelHell/popgun/backends"
)

type cmdTestCase struct {
	cmd            Executable
	initialState   int
	args           []string
	expectedState  int
	expectedErr    bool
	expectedOutput string
}

func commandTest(t *testing.T, tc cmdTestCase) {
	s, c := net.Pipe()
	defer c.Close()

	go func(t *testing.T) {
		backend := backends.DummyBackend{}
		authorizator := backends.DummyAuthorizator{}
		client := newClient(authorizator, backend)
		client.currentState = tc.initialState

		client.printer = NewPrinter(s)
		state, err := tc.cmd.Run(client, tc.args)
		if state != tc.expectedState {
			t.Errorf("Expected state '%d', but got '%d'", tc.expectedState, state)
		}
		if tc.expectedErr && err == nil {
			t.Error("Expected error, but got none")
		} else if !tc.expectedErr && err != nil {
			t.Error("Error not expected, but got one")
		}
		s.Close()
	}(t)

	buf, err := ioutil.ReadAll(c)
	if err != nil {
		t.Fatal(err)
	}
	response := string(buf[:])
	matched, err := regexp.MatchString(tc.expectedOutput, response)
	if err != nil {
		t.Fatal(err)
	}
	if !matched {
		t.Errorf("Expected to match '%s', but got '%s'", tc.expectedOutput, response)
	}
}

func TestQuitCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            QuitCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "^\\+OK Goodbye",
		},
		{
			cmd:            QuitCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_UPDATE,
			expectedErr:    false,
			expectedOutput: "^\\+OK Goodbye",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestUserCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            UserCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            UserCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            UserCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{"john"},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "^\\+OK",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestPassCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            PassCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{ // USER was not called before
			cmd:            PassCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestStatCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            StatCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            StatCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 5 50",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestListCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            ListCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"a"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: a",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 1 10",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"6"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\-ERR no such message",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 5 messages\r\n0 10\r\n1 10\r\n2 10\r\n3 10\r\n4 10\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestRetrCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            RetrCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            RetrCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Missing argument for RETR command",
		},
		{
			cmd:            RetrCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"a"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: a",
		},
		{
			cmd:            RetrCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK \r\nthis is dummy message\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestDeleCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            DeleCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            DeleCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Missing argument for DELE command",
		},
		{
			cmd:            DeleCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"foo"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: foo",
		},
		{
			cmd:            DeleCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK Message 1 deleted",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestNoopCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            NoopCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK",
		},
		{
			cmd:            NoopCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestRsetCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            RsetCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            RsetCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK ",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestUidlCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            UidlCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"a"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: a",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"6"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\-ERR no such message",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 1 2",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 5 messages\r\n0 1\r\n1 2\r\n2 3\r\n3 4\r\n4 5\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestCapaCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            CapaCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK \r\nUSER\r\nUIDL\r\n\\.",
		},
		{
			cmd:            CapaCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "^\\+OK \r\nUSER\r\nUIDL\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}
<commit_msg>fix failing tests because of changed msg numbering<commit_after>package popgun

import (
	"io/ioutil"
	"net"
	"regexp"
	"testing"

	"github.com/DevelHell/popgun/backends"
)

type cmdTestCase struct {
	cmd            Executable
	initialState   int
	args           []string
	expectedState  int
	expectedErr    bool
	expectedOutput string
}

func commandTest(t *testing.T, tc cmdTestCase) {
	s, c := net.Pipe()
	defer c.Close()

	go func(t *testing.T) {
		backend := backends.DummyBackend{}
		authorizator := backends.DummyAuthorizator{}
		client := newClient(authorizator, backend)
		client.currentState = tc.initialState

		client.printer = NewPrinter(s)
		state, err := tc.cmd.Run(client, tc.args)
		if state != tc.expectedState {
			t.Errorf("Expected state '%d', but got '%d'", tc.expectedState, state)
		}
		if tc.expectedErr && err == nil {
			t.Error("Expected error, but got none")
		} else if !tc.expectedErr && err != nil {
			t.Error("Error not expected, but got one")
		}
		s.Close()
	}(t)

	buf, err := ioutil.ReadAll(c)
	if err != nil {
		t.Fatal(err)
	}
	response := string(buf[:])
	matched, err := regexp.MatchString(tc.expectedOutput, response)
	if err != nil {
		t.Fatal(err)
	}
	if !matched {
		t.Errorf("Expected to match '%s', but got '%s'", tc.expectedOutput, response)
	}
}

func TestQuitCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            QuitCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "^\\+OK Goodbye",
		},
		{
			cmd:            QuitCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_UPDATE,
			expectedErr:    false,
			expectedOutput: "^\\+OK Goodbye",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestUserCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            UserCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            UserCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            UserCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{"john"},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "^\\+OK",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestPassCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            PassCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{ // USER was not called before
			cmd:            PassCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestStatCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            StatCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            StatCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 5 50",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestListCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            ListCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"a"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: a",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 1 10",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"6"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\-ERR no such message",
		},
		{
			cmd:            ListCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 5 messages\r\n1 10\r\n2 10\r\n3 10\r\n4 10\r\n5 10\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestRetrCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            RetrCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            RetrCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Missing argument for RETR command",
		},
		{
			cmd:            RetrCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"a"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: a",
		},
		{
			cmd:            RetrCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK \r\nthis is dummy message\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestDeleCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            DeleCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            DeleCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Missing argument for DELE command",
		},
		{
			cmd:            DeleCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"foo"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: foo",
		},
		{
			cmd:            DeleCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK Message 1 deleted",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestNoopCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            NoopCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK",
		},
		{
			cmd:            NoopCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestRsetCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            RsetCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            RsetCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK ",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestUidlCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            UidlCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"a"},
			expectedState:  0,
			expectedErr:    true,
			expectedOutput: "^\\-ERR Invalid argument: a",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"6"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\-ERR no such message",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{"1"},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 1 2",
		},
		{
			cmd:            UidlCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK 5 messages\r\n1 1\r\n2 2\r\n3 3\r\n4 4\r\n5 5\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}

func TestCapaCommand_Run(t *testing.T) {
	testCases := []cmdTestCase{
		{
			cmd:            CapaCommand{},
			initialState:   STATE_TRANSACTION,
			args:           []string{},
			expectedState:  STATE_TRANSACTION,
			expectedErr:    false,
			expectedOutput: "^\\+OK \r\nUSER\r\nUIDL\r\n\\.",
		},
		{
			cmd:            CapaCommand{},
			initialState:   STATE_AUTHORIZATION,
			args:           []string{},
			expectedState:  STATE_AUTHORIZATION,
			expectedErr:    false,
			expectedOutput: "^\\+OK \r\nUSER\r\nUIDL\r\n\\.",
		},
	}

	for _, testCase := range testCases {
		commandTest(t, testCase)
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"html/template"
	"net/http"

	"encoding/json"

	"github.com/codegangsta/martini"
	"github.com/spf13/kaiju/models"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

func index() template.HTML {
	return template.HTML(`
<html>
<body>
<form action="/comment" method="POST">
  <input type="hidden" name="userId" value="5346e476331583002c7de60d" />
  <input type="hidden" name="forum" value="5346e494331583002c7de60e" />
  <input type="hidden" name="parent" value="534718de080fcc8979000002" />
  <input type="hidden" name="page" value="dans sc2 blog post" />
  <input type="hidden" name="body" value="sc2 is awesome. Toss OP" />
  <input type="hidden" name="timestamp" value="123456789" />
  <input type="submit" />
</form>
</body>
</html>
`)
}

func GetAllCommentsResource(db *mgo.Database, parms martini.Params) string {
	forumStr := parms["forum"]
	if bson.IsObjectIdHex(forumStr) == false {
		return fmt.Sprintf("`forum` is not valid. Received: `%v`", forumStr)
	}
	forum := bson.ObjectIdHex(forumStr)
	page := parms["page"]

	comments, err := GetAllComments(db, forum, page)
	if err != nil {
		return fmt.Sprintf("Error: %v", err)
	}

	bytes, err := json.Marshal(comments)
	if err != nil {
		return fmt.Sprintf("Error: %v", err)
	}

	return string(bytes)
}

func PostCommentHandler(request *http.Request, db *mgo.Database) string {
	if err := request.ParseForm(); err != nil {
		return err.Error()
	}

	userIdStr := request.FormValue("userId")
	forumIdStr := request.FormValue("forum")
	page := request.FormValue("page")
	body := request.FormValue("body")
	parentIdStr := request.FormValue("parent")

	userId, forumId, page, body, parentId, err := PostCommentResource(userIdStr,
		forumIdStr,
		page,
		body,
		parentIdStr)

	if err != nil {
		return err.Error()
	}

	comment, err := PostComment(db,
		userId,
		forumId,
		page,
		body,
		parentId)

	if err != nil {
		return err.Error()
	}

	return fmt.Sprintf("Accepted. Comment ID: %v", comment.Id)
}

func PostCommentResource(userIdStr string, forumIdStr string, pageStr string, bodyStr string, parentIdStr string) (userId bson.ObjectId, forumId bson.ObjectId, page string, body string, parentId *bson.ObjectId, err error) {
	page = pageStr
	body = bodyStr

	if bson.IsObjectIdHex(userIdStr) == false {
		err = fmt.Errorf("`userId` is not valid. Received: `%v`", userIdStr)
		return
	}
	userId = bson.ObjectIdHex(userIdStr)

	if bson.IsObjectIdHex(forumIdStr) == false {
		err = fmt.Errorf("`forum` is not valid. Received: `%v`", forumIdStr)
		return
	}
	forumId = bson.ObjectIdHex(forumIdStr)

	switch {
	case parentIdStr == "":
	case bson.IsObjectIdHex(parentIdStr):
		parentIdObj := bson.ObjectIdHex(parentIdStr)
		parentId = &parentIdObj
	default:
		err = fmt.Errorf("`parent` is not valid. Received: `%v`", parentIdStr)
		return
	}

	return
}

func PostComment(db *mgo.Database, userId bson.ObjectId, forumId bson.ObjectId,
	page string, body string, parentId *bson.ObjectId) (*models.Comment, error) {

	users := db.C("users")
	forums := db.C("forums")
	comments := db.C("comments")

	user := &models.User{}
	err := users.Find(bson.M{"_id": userId}).One(user)
	switch err {
	case nil:
	case mgo.ErrNotFound:
		return nil, fmt.Errorf("User not found. Id: `%v`", userId)
	default:
		return nil, fmt.Errorf("Error finding user. Err: %v", err)
	}

	err = forums.Find(bson.M{"_id": forumId}).One(make(bson.M))
	switch err {
	case nil:
	case mgo.ErrNotFound:
		return nil, fmt.Errorf("Forum not found. Id: `%v`", forumId)
	default:
		return nil, fmt.Errorf("Error finding forum. Err: %v", err)
	}

	ancestors := make([]bson.ObjectId, 0, 0)
	if parentId != nil {
		parentComment := &models.Comment{}
		if err := comments.Find(bson.M{"_id": *parentId}).One(parentComment); err != nil {
			return nil, fmt.Errorf("Parent comment not found. Id: `%v`", parentId)
		}

		ancestors = parentComment.Ancestors
		ancestors = append(ancestors, *parentId)
	}

	commentId := bson.NewObjectId()
	comment := &models.Comment{
		Id: commentId,
		User: models.CommentUser{
			Id:       userId,
			FullName: user.FullName,
			Email:    user.Email,
		},
		Forum:     forumId,
		Page:      page,
		Body:      body,
		Parent:    parentId,
		Ancestors: ancestors,
	}

	if err := comments.Insert(comment); err != nil {
		return nil, fmt.Errorf("Database error. Err: %v", err)
	}

	return comment, nil
}

func _getAllCommentsWithQuery(db *mgo.Database, query bson.M) ([]*models.Comment, error) {
	ret := make([]*models.Comment, 0, 0)
	comments := db.C("comments")
	if err := comments.Find(query).All(&ret); err != nil {
		return nil, err
	}

	return ret, nil
}

func GetAllComments(db *mgo.Database,
	forumId bson.ObjectId,
	page string) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db, nil)
}

func GetTopLevelComments(db *mgo.Database,
	forumId bson.ObjectId,
	page string) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db,
		bson.M{
			"Forum":  forumId,
			"Page":   page,
			"Parent": nil,
		})
}

func GetCommentsWithAncestor(db *mgo.Database,
	forumId bson.ObjectId,
	page string,
	ancestorId bson.ObjectId) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db,
		bson.M{
			"Forum":     forumId,
			"Page":      page,
			"Ancestors": ancestorId,
		})
}

func GetCommentsSinceTime(db *mgo.Database,
	forumId bson.ObjectId,
	page string,
	since bson.ObjectId) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db,
		bson.M{
			"Forum": forumId,
			"Page":  page,
			"_id":   bson.M{"$gt": since},
		})
}
<commit_msg>Api request fixes<commit_after>package commands

import (
	"fmt"
	"html/template"
	"net/http"

	"encoding/json"

	"github.com/codegangsta/martini"
	"github.com/spf13/kaiju/models"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

func index() template.HTML {
	return template.HTML(`
<html>
<body>
<form action="/comment" method="POST">
  <input type="hidden" name="userId" value="5346e476331583002c7de60d" />
  <input type="hidden" name="forum" value="5346e494331583002c7de60e" />
  <input type="hidden" name="parent" value="534718de080fcc8979000002" />
  <input type="hidden" name="page" value="dans sc2 blog post" />
  <input type="hidden" name="body" value="sc2 is awesome. Toss OP" />
  <input type="hidden" name="timestamp" value="123456789" />
  <input type="submit" />
</form>
</body>
</html>
`)
}

func GetAllCommentsResource(db *mgo.Database, parms martini.Params) string {
	forumStr := parms["forum"]
	if bson.IsObjectIdHex(forumStr) == false {
		return fmt.Sprintf("`forum` is not valid. Received: `%v`", forumStr)
	}
	forum := bson.ObjectIdHex(forumStr)
	page := parms["page"]

	comments, err := GetAllComments(db, forum, page)
	if err != nil {
		return fmt.Sprintf("Error: %v", err)
	}

	bytes, err := json.Marshal(comments)
	if err != nil {
		return fmt.Sprintf("Error: %v", err)
	}

	return string(bytes)
}

func PostCommentHandler(request *http.Request, db *mgo.Database) string {
	if err := request.ParseForm(); err != nil {
		return err.Error()
	}

	userIdStr := request.FormValue("userId")
	forumIdStr := request.FormValue("forum")
	page := request.FormValue("page")
	body := request.FormValue("body")
	parentIdStr := request.FormValue("parent")

	userId, forumId, page, body, parentId, err := PostCommentResource(userIdStr,
		forumIdStr,
		page,
		body,
		parentIdStr)

	if err != nil {
		return err.Error()
	}

	comment, err := PostComment(db,
		userId,
		forumId,
		page,
		body,
		parentId)

	if err != nil {
		return err.Error()
	}

	return fmt.Sprintf("Accepted. Comment ID: %v", comment.Id)
}

func PostCommentResource(userIdStr string, forumIdStr string, pageStr string, bodyStr string, parentIdStr string) (userId bson.ObjectId, forumId bson.ObjectId, page string, body string, parentId *bson.ObjectId, err error) {
	page = pageStr
	body = bodyStr

	if bson.IsObjectIdHex(userIdStr) == false {
		err = fmt.Errorf("`userId` is not valid. Received: `%v`", userIdStr)
		return
	}
	userId = bson.ObjectIdHex(userIdStr)

	if bson.IsObjectIdHex(forumIdStr) == false {
		err = fmt.Errorf("`forum` is not valid. Received: `%v`", forumIdStr)
		return
	}
	forumId = bson.ObjectIdHex(forumIdStr)

	switch {
	case parentIdStr == "":
	case bson.IsObjectIdHex(parentIdStr):
		parentIdObj := bson.ObjectIdHex(parentIdStr)
		parentId = &parentIdObj
	default:
		err = fmt.Errorf("`parent` is not valid. Received: `%v`", parentIdStr)
		return
	}

	return
}

func PostComment(db *mgo.Database, userId bson.ObjectId, forumId bson.ObjectId,
	page string, body string, parentId *bson.ObjectId) (*models.Comment, error) {

	users := db.C("users")
	forums := db.C("forums")
	comments := db.C("comments")

	user := &models.User{}
	err := users.Find(bson.M{"_id": userId}).One(user)
	switch err {
	case nil:
	case mgo.ErrNotFound:
		return nil, fmt.Errorf("User not found. Id: `%v`", userId)
	default:
		return nil, fmt.Errorf("Error finding user. Err: %v", err)
	}

	err = forums.Find(bson.M{"_id": forumId}).One(make(bson.M))
	switch err {
	case nil:
	case mgo.ErrNotFound:
		return nil, fmt.Errorf("Forum not found. Id: `%v`", forumId)
	default:
		return nil, fmt.Errorf("Error finding forum. Err: %v", err)
	}

	ancestors := make([]bson.ObjectId, 0, 0)
	if parentId != nil {
		parentComment := &models.Comment{}
		if err := comments.Find(bson.M{"_id": *parentId}).One(parentComment); err != nil {
			return nil, fmt.Errorf("Parent comment not found. Id: `%v`", parentId)
		}

		ancestors = parentComment.Ancestors
		ancestors = append(ancestors, *parentId)
	}

	commentId := bson.NewObjectId()
	comment := &models.Comment{
		Id: commentId,
		User: models.CommentUser{
			Id:       userId,
			FullName: user.FullName,
			Email:    user.Email,
		},
		Forum:     forumId,
		Page:      page,
		Body:      body,
		Parent:    parentId,
		Ancestors: ancestors,
	}

	if err := comments.Insert(comment); err != nil {
		return nil, fmt.Errorf("Database error. Err: %v", err)
	}

	return comment, nil
}

func _getAllCommentsWithQuery(db *mgo.Database, query bson.M) ([]*models.Comment, error) {
	ret := make([]*models.Comment, 0, 0)
	comments := db.C("comments")
	if err := comments.Find(query).All(&ret); err != nil {
		return nil, err
	}

	return ret, nil
}

func GetAllComments(db *mgo.Database,
	forumId bson.ObjectId,
	page string) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db, bson.M{
		"forum": forumId,
		"page":  page,
	})
}

func GetTopLevelComments(db *mgo.Database,
	forumId bson.ObjectId,
	page string) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db,
		bson.M{
			"forum":  forumId,
			"page":   page,
			"parent": nil,
		})
}

func GetCommentsWithAncestor(db *mgo.Database,
	forumId bson.ObjectId,
	page string,
	ancestorId bson.ObjectId) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db,
		bson.M{
			"forum":     forumId,
			"page":      page,
			"ancestors": ancestorId,
		})
}

func GetCommentsSinceTime(db *mgo.Database,
	forumId bson.ObjectId,
	page string,
	since bson.ObjectId) ([]*models.Comment, error) {

	return _getAllCommentsWithQuery(db,
		bson.M{
			"forum": forumId,
			"page":  page,
			"_id":   bson.M{"$gt": since},
		})
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 (
	"github.com/ghodss/yaml"

	"github.com/kubernetes/deployment-manager/expandybird/expander"
	"github.com/kubernetes/deployment-manager/manager/manager"
	"github.com/kubernetes/deployment-manager/registry"
	"github.com/kubernetes/deployment-manager/util"

	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

var (
	deployment_name   = flag.String("name", "", "Name of deployment, used for deploy and update commands (defaults to template name)")
	stdin             = flag.Bool("stdin", false, "Reads a configuration from the standard input")
	properties        = flag.String("properties", "", "Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)")
	template_registry = flag.String("registry", "kubernetes/deployment-manager/templates", "Github based template registry (owner/repo[/path])")
	service           = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/dm/services/manager-service:manager", "URL for deployment manager")
	binary            = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
	timeout           = flag.Int("timeout", 10, "Time in seconds to wait for response")
)

var commands = []string{
	"expand \t\t\t Expands the supplied configuration(s)",
	"deploy \t\t\t Deploys the named template or the supplied configuration(s)",
	"list \t\t\t Lists the deployments in the cluster",
	"get \t\t\t Retrieves the supplied deployment",
	"delete \t\t\t Deletes the supplied deployment",
	"update \t\t\t Updates a deployment using the supplied configuration(s)",
	"deployed-types \t\t Lists the types deployed in the cluster",
	"deployed-instances \t Lists the instances of the named type deployed in the cluster",
	"templates \t\t Lists the templates in a given template registry",
	"describe \t\t Describes the named template in a given template registry",
}

var usage = func() {
	message := "Usage: %s [<flags>] <command> (<template-name> | <deployment-name> | (<configuration> [<import1>...<importN>]))\n"
	fmt.Fprintf(os.Stderr, message, os.Args[0])
	fmt.Fprintln(os.Stderr, "Commands:")
	for _, command := range commands {
		fmt.Fprintln(os.Stderr, command)
	}

	fmt.Fprintln(os.Stderr)
	fmt.Fprintln(os.Stderr, "Flags:")
	flag.PrintDefaults()
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}

func getGitRegistry() *registry.GithubRegistry {
	s := strings.Split(*template_registry, "/")
	if len(s) < 2 {
		log.Fatalf("invalid template registry: %s", *template_registry)
	}

	var path = ""
	if len(s) > 2 {
		path = strings.Join(s[2:], "/")
	}

	return registry.NewGithubRegistry(s[0], s[1], path)
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		fmt.Fprintln(os.Stderr, "No command supplied")
		usage()
	}

	if *stdin {
		fmt.Printf("reading from stdin is not yet implemented")
		os.Exit(0)
	}

	command := args[0]
	switch command {
	case "templates":
		git := getGitRegistry()
		templates, err := git.List()
		if err != nil {
			log.Fatalf("Cannot list %v", err)
		}

		fmt.Printf("Templates:\n")
		for _, t := range templates {
			fmt.Printf("%s:%s\n", t.Name, t.Version)
			downloadURL := getDownloadUrl(t)

			fmt.Printf("\tdownload URL: %s\n", downloadURL)
		}
	case "describe":
		describeType(args)
	case "expand":
		template := loadTemplate(args)
		callService("expand", "POST", "expand configuration", marshalTemplate(template))
	case "deploy":
		template := loadTemplate(args)
		action := fmt.Sprintf("deploy configuration named %s", template.Name)
		callService("deployments", "POST", action, marshalTemplate(template))
	case "list":
		callService("deployments", "GET", "list deployments", nil)
	case "get":
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, "No deployment name supplied")
			usage()
		}

		path := fmt.Sprintf("deployments/%s", args[1])
		action := fmt.Sprintf("get deployment named %s", args[1])
		callService(path, "GET", action, nil)
	case "delete":
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, "No deployment name supplied")
			usage()
		}

		path := fmt.Sprintf("deployments/%s", args[1])
		action := fmt.Sprintf("delete deployment named %s", args[1])
		callService(path, "DELETE", action, nil)
	case "update":
		template := loadTemplate(args)
		path := fmt.Sprintf("deployments/%s", template.Name)
		action := fmt.Sprintf("delete deployment named %s", template.Name)
		callService(path, "PUT", action, marshalTemplate(template))
	case "deployed-types":
		callService("types", "GET", "list deployed types", nil)
	case "deployed-instances":
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, "No type name supplied")
			usage()
		}

		tUrl := getTypeUrl(args[1])
		if tUrl == "" {
			// Type is most likely a primitive.
			tUrl = args[1]
		}
		path := fmt.Sprintf("types/%s/instances", url.QueryEscape(tUrl))
		action := fmt.Sprintf("list deployed instances of type %s", tUrl)
		callService(path, "GET", action, nil)
	default:
		usage()
	}
}

func callService(path, method, action string, reader io.ReadCloser) {
	u := fmt.Sprintf("%s/%s", *service, path)

	resp := callHttp(u, method, action, reader)
	var j interface{}
	if err := json.Unmarshal([]byte(resp), &j); err != nil {
		log.Fatalf("Failed to parse JSON response from service: %s", resp)
	}

	y, err := yaml.Marshal(j)
	if err != nil {
		log.Fatalf("Failed to serialize JSON response from service: %s", resp)
	}

	fmt.Println(string(y))
}

func callHttp(path, method, action string, reader io.ReadCloser) string {
	request, err := http.NewRequest(method, path, reader)
	request.Header.Add("Content-Type", "application/json")

	client := http.Client{
		Timeout: time.Duration(time.Duration(*timeout) * time.Second),
	}

	response, err := client.Do(request)
	if err != nil {
		log.Fatalf("cannot %s: %s\n", action, err)
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatalf("cannot %s: %s\n", action, err)
	}

	if response.StatusCode < http.StatusOK ||
		response.StatusCode >= http.StatusMultipleChoices {
		message := fmt.Sprintf("status code: %d status: %s : %s", response.StatusCode, response.Status, body)
		log.Fatalf("cannot %s: %s\n", action, message)
	}

	return string(body)
}

// describeType prints the schema for a type specified by either a
// template URL or a fully qualified registry type name (e.g.,
// <type-name>:<version>)
func describeType(args []string) {
	if len(args) != 2 {
		fmt.Fprintln(os.Stderr, "No type name or URL supplied")
		usage()
	}

	tUrl := getTypeUrl(args[1])
	if tUrl == "" {
		log.Fatalf("Invalid type name, must be a template URL or in the form \"<type-name>:<version>\": %s", args[1])
	}
	schemaUrl := tUrl + ".schema"
	fmt.Println(callHttp(schemaUrl, "GET", "get schema for type ("+tUrl+")", nil))
}

// getTypeUrl returns URL or empty if a primitive type.
func getTypeUrl(tName string) string {
	if util.IsHttpUrl(tName) {
		// User can pass raw URL to template.
		return tName
	}

	// User can pass registry type.
	t := getRegistryType(tName)
	if t == nil {
		// Primitive types have no associated URL.
		return ""
	}

	return getDownloadUrl(*t)
}

func getDownloadUrl(t registry.Type) string {
	git := getGitRegistry()
	url, err := git.GetURL(t)
	if err != nil {
		log.Fatalf("Failed to fetch type information for \"%s:%s\": %s", t.Name, t.Version, err)
	}

	return url
}

func isHttp(t string) bool {
	return strings.HasPrefix(t, "http://") || strings.HasPrefix(t, "https://")
}

func loadTemplate(args []string) *expander.Template {
	var template *expander.Template
	var err error
	if len(args) < 2 {
		fmt.Fprintln(os.Stderr, "No template name or configuration(s) supplied")
		usage()
	}

	if len(args) < 3 {
		if t := getRegistryType(args[1]); t != nil {
			template = buildTemplateFromType(*t)
		} else {
			template, err = expander.NewTemplateFromRootTemplate(args[1])
		}
	} else {
		template, err = expander.NewTemplateFromFileNames(args[1], args[2:])
	}

	if err != nil {
		log.Fatalf("cannot create configuration from supplied arguments: %s\n", err)
	}

	// Override name if set from flags.
	if *deployment_name != "" {
		template.Name = *deployment_name
	}

	return template
}

// TODO: needs better validation that this is actually a registry type.
func getRegistryType(fullType string) *registry.Type {
	tList := strings.Split(fullType, ":")
	if len(tList) != 2 {
		return nil
	}

	return &registry.Type{
		Name:    tList[0],
		Version: tList[1],
	}
}

func buildTemplateFromType(t registry.Type) *expander.Template {
	downloadURL := getDownloadUrl(t)

	props := make(map[string]interface{})
	if *properties != "" {
		plist := strings.Split(*properties, ",")
		for _, p := range plist {
			ppair := strings.Split(p, "=")
			if len(ppair) != 2 {
				log.Fatalf("--properties must be in the form \"p1=v1,p2=v2,...\": %s\n", p)
			}

			// support ints
			// TODO: needs to support other types.
			i, err := strconv.Atoi(ppair[1])
			if err != nil {
				props[ppair[0]] = ppair[1]
			} else {
				props[ppair[0]] = i
			}
		}
	}

	// Name the deployment after the type name.
	name := fmt.Sprintf("%s:%s", t.Name, t.Version)

	config := manager.Configuration{Resources: []*manager.Resource{&manager.Resource{
		Name:       name,
		Type:       downloadURL,
		Properties: props,
	}}}

	y, err := yaml.Marshal(config)
	if err != nil {
		log.Fatalf("error: %s\ncannot create configuration for deployment: %v\n", err, config)
	}

	return &expander.Template{
		Name:    name,
		Content: string(y),
		// No imports, as this is a single type from repository.
	}
}

func marshalTemplate(template *expander.Template) io.ReadCloser {
	j, err := json.Marshal(template)
	if err != nil {
		log.Fatalf("cannot deploy configuration %s: %s\n", template.Name, err)
	}

	return ioutil.NopCloser(bytes.NewReader(j))
}

func getRandomName() string {
	return fmt.Sprintf("manifest-%d", time.Now().UTC().UnixNano())
}
<commit_msg>Adding support for listing and getting manifests in dm client.<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 (
	"github.com/ghodss/yaml"

	"github.com/kubernetes/deployment-manager/expandybird/expander"
	"github.com/kubernetes/deployment-manager/manager/manager"
	"github.com/kubernetes/deployment-manager/registry"
	"github.com/kubernetes/deployment-manager/util"

	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

var (
	deployment_name   = flag.String("name", "", "Name of deployment, used for deploy and update commands (defaults to template name)")
	stdin             = flag.Bool("stdin", false, "Reads a configuration from the standard input")
	properties        = flag.String("properties", "", "Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)")
	template_registry = flag.String("registry", "kubernetes/deployment-manager/templates", "Github based template registry (owner/repo[/path])")
	service           = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/dm/services/manager-service:manager", "URL for deployment manager")
	binary            = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary")
	timeout           = flag.Int("timeout", 10, "Time in seconds to wait for response")
)

var commands = []string{
	"expand \t\t\t Expands the supplied configuration(s)",
	"deploy \t\t\t Deploys the named template or the supplied configuration(s)",
	"list \t\t\t Lists the deployments in the cluster",
	"get \t\t\t Retrieves the supplied deployment",
	"delete \t\t\t Deletes the supplied deployment",
	"update \t\t\t Updates a deployment using the supplied configuration(s)",
	"deployed-types \t\t Lists the types deployed in the cluster",
	"deployed-instances \t Lists the instances of the named type deployed in the cluster",
	"templates \t\t Lists the templates in a given template registry",
	"describe \t\t Describes the named template in a given template registry",
}

var usage = func() {
	message := "Usage: %s [<flags>] <command> (<template-name> | <deployment-name> | (<configuration> [<import1>...<importN>]))\n"
	fmt.Fprintf(os.Stderr, message, os.Args[0])
	fmt.Fprintln(os.Stderr, "Commands:")
	for _, command := range commands {
		fmt.Fprintln(os.Stderr, command)
	}

	fmt.Fprintln(os.Stderr)
	fmt.Fprintln(os.Stderr, "Flags:")
	flag.PrintDefaults()
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}

func getGitRegistry() *registry.GithubRegistry {
	s := strings.Split(*template_registry, "/")
	if len(s) < 2 {
		log.Fatalf("invalid template registry: %s", *template_registry)
	}

	var path = ""
	if len(s) > 2 {
		path = strings.Join(s[2:], "/")
	}

	return registry.NewGithubRegistry(s[0], s[1], path)
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		fmt.Fprintln(os.Stderr, "No command supplied")
		usage()
	}

	if *stdin {
		fmt.Printf("reading from stdin is not yet implemented")
		os.Exit(0)
	}

	command := args[0]
	switch command {
	case "templates":
		git := getGitRegistry()
		templates, err := git.List()
		if err != nil {
			log.Fatalf("Cannot list %v", err)
		}

		fmt.Printf("Templates:\n")
		for _, t := range templates {
			fmt.Printf("%s:%s\n", t.Name, t.Version)
			downloadURL := getDownloadUrl(t)

			fmt.Printf("\tdownload URL: %s\n", downloadURL)
		}
	case "describe":
		describeType(args)
	case "expand":
		template := loadTemplate(args)
		callService("expand", "POST", "expand configuration", marshalTemplate(template))
	case "deploy":
		template := loadTemplate(args)
		action := fmt.Sprintf("deploy configuration named %s", template.Name)
		callService("deployments", "POST", action, marshalTemplate(template))
	case "list":
		callService("deployments", "GET", "list deployments", nil)
	case "get":
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, "No deployment name supplied")
			usage()
		}

		path := fmt.Sprintf("deployments/%s", args[1])
		action := fmt.Sprintf("get deployment named %s", args[1])
		callService(path, "GET", action, nil)
	case "manifest":
		msg := "Must specify manifest in the form <deployment>/<manifest> or <deployment> to list."
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, msg)
			usage()
		}

		s := strings.Split(args[1], "/")
		ls := len(s)
		if ls < 1 || ls > 2 {
			fmt.Fprintln(os.Stderr, fmt.Sprintf("Invalid manifest (%s), %s", args[1], msg))
			usage()
		}

		path := fmt.Sprintf("deployments/%s/manifests", s[0])
		if ls == 2 {
			path = path + fmt.Sprintf("/%s", s[1])
		}

		action := fmt.Sprintf("get manifest %s", args[1])
		callService(path, "GET", action, nil)
	case "delete":
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, "No deployment name supplied")
			usage()
		}

		path := fmt.Sprintf("deployments/%s", args[1])
		action := fmt.Sprintf("delete deployment named %s", args[1])
		callService(path, "DELETE", action, nil)
	case "update":
		template := loadTemplate(args)
		path := fmt.Sprintf("deployments/%s", template.Name)
		action := fmt.Sprintf("delete deployment named %s", template.Name)
		callService(path, "PUT", action, marshalTemplate(template))
	case "deployed-types":
		callService("types", "GET", "list deployed types", nil)
	case "deployed-instances":
		if len(args) < 2 {
			fmt.Fprintln(os.Stderr, "No type name supplied")
			usage()
		}

		tUrl := getTypeUrl(args[1])
		if tUrl == "" {
			// Type is most likely a primitive.
			tUrl = args[1]
		}
		path := fmt.Sprintf("types/%s/instances", url.QueryEscape(tUrl))
		action := fmt.Sprintf("list deployed instances of type %s", tUrl)
		callService(path, "GET", action, nil)
	default:
		usage()
	}
}

func callService(path, method, action string, reader io.ReadCloser) {
	u := fmt.Sprintf("%s/%s", *service, path)

	resp := callHttp(u, method, action, reader)
	var j interface{}
	if err := json.Unmarshal([]byte(resp), &j); err != nil {
		log.Fatalf("Failed to parse JSON response from service: %s", resp)
	}

	y, err := yaml.Marshal(j)
	if err != nil {
		log.Fatalf("Failed to serialize JSON response from service: %s", resp)
	}

	fmt.Println(string(y))
}

func callHttp(path, method, action string, reader io.ReadCloser) string {
	request, err := http.NewRequest(method, path, reader)
	request.Header.Add("Content-Type", "application/json")

	client := http.Client{
		Timeout: time.Duration(time.Duration(*timeout) * time.Second),
	}

	response, err := client.Do(request)
	if err != nil {
		log.Fatalf("cannot %s: %s\n", action, err)
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatalf("cannot %s: %s\n", action, err)
	}

	if response.StatusCode < http.StatusOK ||
		response.StatusCode >= http.StatusMultipleChoices {
		message := fmt.Sprintf("status code: %d status: %s : %s", response.StatusCode, response.Status, body)
		log.Fatalf("cannot %s: %s\n", action, message)
	}

	return string(body)
}

// describeType prints the schema for a type specified by either a
// template URL or a fully qualified registry type name (e.g.,
// <type-name>:<version>)
func describeType(args []string) {
	if len(args) != 2 {
		fmt.Fprintln(os.Stderr, "No type name or URL supplied")
		usage()
	}

	tUrl := getTypeUrl(args[1])
	if tUrl == "" {
		log.Fatalf("Invalid type name, must be a template URL or in the form \"<type-name>:<version>\": %s", args[1])
	}
	schemaUrl := tUrl + ".schema"
	fmt.Println(callHttp(schemaUrl, "GET", "get schema for type ("+tUrl+")", nil))
}

// getTypeUrl returns URL or empty if a primitive type.
func getTypeUrl(tName string) string {
	if util.IsHttpUrl(tName) {
		// User can pass raw URL to template.
		return tName
	}

	// User can pass registry type.
	t := getRegistryType(tName)
	if t == nil {
		// Primitive types have no associated URL.
		return ""
	}

	return getDownloadUrl(*t)
}

func getDownloadUrl(t registry.Type) string {
	git := getGitRegistry()
	url, err := git.GetURL(t)
	if err != nil {
		log.Fatalf("Failed to fetch type information for \"%s:%s\": %s", t.Name, t.Version, err)
	}

	return url
}

func isHttp(t string) bool {
	return strings.HasPrefix(t, "http://") || strings.HasPrefix(t, "https://")
}

func loadTemplate(args []string) *expander.Template {
	var template *expander.Template
	var err error
	if len(args) < 2 {
		fmt.Fprintln(os.Stderr, "No template name or configuration(s) supplied")
		usage()
	}

	if len(args) < 3 {
		if t := getRegistryType(args[1]); t != nil {
			template = buildTemplateFromType(*t)
		} else {
			template, err = expander.NewTemplateFromRootTemplate(args[1])
		}
	} else {
		template, err = expander.NewTemplateFromFileNames(args[1], args[2:])
	}

	if err != nil {
		log.Fatalf("cannot create configuration from supplied arguments: %s\n", err)
	}

	// Override name if set from flags.
	if *deployment_name != "" {
		template.Name = *deployment_name
	}

	return template
}

// TODO: needs better validation that this is actually a registry type.
func getRegistryType(fullType string) *registry.Type {
	tList := strings.Split(fullType, ":")
	if len(tList) != 2 {
		return nil
	}

	return &registry.Type{
		Name:    tList[0],
		Version: tList[1],
	}
}

func buildTemplateFromType(t registry.Type) *expander.Template {
	downloadURL := getDownloadUrl(t)

	props := make(map[string]interface{})
	if *properties != "" {
		plist := strings.Split(*properties, ",")
		for _, p := range plist {
			ppair := strings.Split(p, "=")
			if len(ppair) != 2 {
				log.Fatalf("--properties must be in the form \"p1=v1,p2=v2,...\": %s\n", p)
			}

			// support ints
			// TODO: needs to support other types.
			i, err := strconv.Atoi(ppair[1])
			if err != nil {
				props[ppair[0]] = ppair[1]
			} else {
				props[ppair[0]] = i
			}
		}
	}

	// Name the deployment after the type name.
	name := fmt.Sprintf("%s:%s", t.Name, t.Version)

	config := manager.Configuration{Resources: []*manager.Resource{&manager.Resource{
		Name:       name,
		Type:       downloadURL,
		Properties: props,
	}}}

	y, err := yaml.Marshal(config)
	if err != nil {
		log.Fatalf("error: %s\ncannot create configuration for deployment: %v\n", err, config)
	}

	return &expander.Template{
		Name:    name,
		Content: string(y),
		// No imports, as this is a single type from repository.
	}
}

func marshalTemplate(template *expander.Template) io.ReadCloser {
	j, err := json.Marshal(template)
	if err != nil {
		log.Fatalf("cannot deploy configuration %s: %s\n", template.Name, err)
	}

	return ioutil.NopCloser(bytes.NewReader(j))
}

func getRandomName() string {
	return fmt.Sprintf("manifest-%d", time.Now().UTC().UnixNano())
}
<|endoftext|>


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

package sshstorage

import (
	"bufio"
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils"

	"github.com/juju/juju/utils/ssh"
)

var logger = loggo.GetLogger("juju.environs.sshstorage")

// base64LineLength is the default line length for wrapping
// output generated by the base64 command line utility.
const base64LineLength = 76

// SSHStorage implements storage.Storage.
//
// The storage is created under sudo, and ownership given over to the
// login uid/gid. This is done so that we don't require sudo, and by
// consequence, don't require a pty, so we can interact with a script
// via stdin.
type SSHStorage struct {
	host       string
	remotepath string
	tmpdir     string

	cmd     *ssh.Cmd
	stdin   io.WriteCloser
	stdout  io.ReadCloser
	scanner *bufio.Scanner
}

var sshCommand = func(host string, command ...string) *ssh.Cmd {
	return ssh.Command(host, command, nil)
}

type flockmode string

const (
	flockShared    flockmode = "-s"
	flockExclusive flockmode = "-x"
)

type NewSSHStorageParams struct {
	// Host is the host to connect to, in the format [user@]hostname.
	Host string

	// StorageDir is the root of the remote storage directory.
	StorageDir string

	// TmpDir is the remote temporary directory for storage.
	// A temporary directory must be specified, and should be located on the
	// same filesystem as the storage directory to ensure atomic writes.
	// The temporary directory will be created when NewSSHStorage is invoked
	// if it doesn't already exist; it will never be removed. NewSSHStorage
	// will attempt to reassign ownership to the login user, and will return
	// an error if it cannot do so.
	TmpDir string
}

// NewSSHStorage creates a new SSHStorage, connected to the
// specified host, managing state under the specified remote path.
func NewSSHStorage(params NewSSHStorageParams) (*SSHStorage, error) {
	if params.StorageDir == "" {
		return nil, errors.New("storagedir must be specified and non-empty")
	}
	if params.TmpDir == "" {
		return nil, errors.New("tmpdir must be specified and non-empty")
	}

	script := fmt.Sprintf(
		"install -d -g $SUDO_GID -o $SUDO_UID %s %s",
		utils.ShQuote(params.StorageDir),
		utils.ShQuote(params.TmpDir),
	)

	cmd := sshCommand(params.Host, "sudo", "-n", "/bin/bash")
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	cmd.Stdin = strings.NewReader(script)
	if err := cmd.Run(); err != nil {
		err = fmt.Errorf("failed to create storage dir: %v (%v)", err, strings.TrimSpace(stderr.String()))
		return nil, err
	}

	// We could use sftp, but then we'd be at the mercy of
	// sftp's output messages for checking errors. Instead,
	// we execute an interactive bash shell.
	cmd = sshCommand(params.Host, "bash")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		stdin.Close()
		return nil, err
	}
	// Combine stdout and stderr, so we can easily
	// get at catastrophic failure messages.
	cmd.Stderr = cmd.Stdout
	stor := &SSHStorage{
		host:       params.Host,
		remotepath: params.StorageDir,
		tmpdir:     params.TmpDir,
		cmd:        cmd,
		stdin:      stdin,
		stdout:     stdout,
		scanner:    bufio.NewScanner(stdout),
	}
	cmd.Start()

	// Verify we have write permissions.
	_, err = stor.runf(flockExclusive, "touch %s", utils.ShQuote(params.StorageDir))
	if err != nil {
		stdin.Close()
		stdout.Close()
		cmd.Wait()
		return nil, err
	}
	return stor, nil
}

// Close cleanly terminates the underlying SSH connection.
func (s *SSHStorage) Close() error {
	s.stdin.Close()
	s.stdout.Close()
	return s.cmd.Wait()
}

func (s *SSHStorage) runf(flockmode flockmode, command string, args ...interface{}) (string, error) {
	command = fmt.Sprintf(command, args...)
	return s.run(flockmode, command, nil, 0)
}

// terminate closes the stdin, and appends any output to the input error.
func (s *SSHStorage) terminate(err error) error {
	s.stdin.Close()
	var output string
	for s.scanner.Scan() {
		if len(output) > 0 {
			output += "\n"
		}
		output += s.scanner.Text()
	}
	if len(output) > 0 {
		err = fmt.Errorf("%v (output: %q)", err, output)
	}
	return err
}

func (s *SSHStorage) run(flockmode flockmode, command string, input io.Reader, inputlen int64) (string, error) {
	const rcPrefix = "JUJU-RC: "
	command = fmt.Sprintf(
		"SHELL=/bin/bash flock %s %s -c %s",
		flockmode,
		utils.ShQuote(s.remotepath),
		utils.ShQuote(command),
	)
	stdin := bufio.NewWriter(s.stdin)
	if input != nil {
		command = fmt.Sprintf("base64 -d << '@EOF' | (%s)", command)
	}
	command = fmt.Sprintf("(%s) 2>&1; echo %s$?", command, rcPrefix)
	if _, err := stdin.WriteString(command + "\n"); err != nil {
		return "", fmt.Errorf("failed to write command: %v", err)
	}
	if input != nil {
		if err := copyAsBase64(stdin, input); err != nil {
			return "", s.terminate(fmt.Errorf("failed to write input: %v", err))
		}
	}
	if err := stdin.Flush(); err != nil {
		return "", s.terminate(fmt.Errorf("failed to write input: %v", err))
	}
	var output []string
	for s.scanner.Scan() {
		line := s.scanner.Text()
		if strings.HasPrefix(line, rcPrefix) {
			line := line[len(rcPrefix):]
			rc, err := strconv.Atoi(line)
			if err != nil {
				return "", fmt.Errorf("failed to parse exit code %q: %v", line, err)
			}
			outputJoined := strings.Join(output, "\n")
			if rc == 0 {
				return outputJoined, nil
			}
			return "", SSHStorageError{outputJoined, rc}
		} else {
			output = append(output, line)
		}
	}

	err := fmt.Errorf("failed to locate %q", rcPrefix)
	if len(output) > 0 {
		err = fmt.Errorf("%v (output: %q)", err, strings.Join(output, "\n"))
	}
	if scannerErr := s.scanner.Err(); scannerErr != nil {
		err = fmt.Errorf("%v (scanner error: %v)", err, scannerErr)
	}
	return "", err
}

func copyAsBase64(w *bufio.Writer, r io.Reader) error {
	wrapper := newLineWrapWriter(w, base64LineLength)
	encoder := base64.NewEncoder(base64.StdEncoding, wrapper)
	if _, err := io.Copy(encoder, r); err != nil {
		return err
	}
	if err := encoder.Close(); err != nil {
		return err
	}
	if _, err := w.WriteString("\n@EOF\n"); err != nil {
		return err
	}
	return nil
}

// path returns a remote absolute path for a storage object name.
func (s *SSHStorage) path(name string) (string, error) {
	remotepath := path.Clean(path.Join(s.remotepath, name))
	if !strings.HasPrefix(remotepath, s.remotepath) {
		return "", fmt.Errorf("%q escapes storage directory", name)
	}
	return remotepath, nil
}

// Get implements storage.StorageReader.Get.
func (s *SSHStorage) Get(name string) (io.ReadCloser, error) {
	logger.Debugf("getting %q from storage", name)
	path, err := s.path(name)
	if err != nil {
		return nil, err
	}
	out, err := s.runf(flockShared, "base64 < %s", utils.ShQuote(path))
	if err != nil {
		err := err.(SSHStorageError)
		if strings.Contains(err.Output, "No such file") {
			return nil, errors.NewNotFound(err, "")
		}
		return nil, err
	}
	decoded, err := base64.StdEncoding.DecodeString(out)
	if err != nil {
		return nil, err
	}
	return ioutil.NopCloser(bytes.NewBuffer(decoded)), nil
}

// List implements storage.StorageReader.List.
func (s *SSHStorage) List(prefix string) ([]string, error) {
	remotepath, err := s.path(prefix)
	if err != nil {
		return nil, err
	}
	dir, prefix := path.Split(remotepath)
	quotedDir := utils.ShQuote(dir)
	out, err := s.runf(flockShared, "(test -d %s && find %s -type f) || true", quotedDir, quotedDir)
	if err != nil {
		return nil, err
	}
	if out == "" {
		return nil, nil
	}
	var names []string
	for _, name := range strings.Split(out, "\n") {
		if strings.HasPrefix(name[len(dir):], prefix) {
			names = append(names, name[len(s.remotepath)+1:])
		}
	}
	sort.Strings(names)
	return names, nil
}

// URL implements storage.StorageReader.URL.
func (s *SSHStorage) URL(name string) (string, error) {
	path, err := s.path(name)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("sftp://%s/%s", s.host, path), nil
}

// DefaultConsistencyStrategy implements storage.StorageReader.ConsistencyStrategy.
func (s *SSHStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
	return utils.AttemptStrategy{}
}

// ShouldRetry is specified in the StorageReader interface.
func (s *SSHStorage) ShouldRetry(err error) bool {
	return false
}

// Put implements storage.StorageWriter.Put
func (s *SSHStorage) Put(name string, r io.Reader, length int64) error {
	logger.Debugf("putting %q (len %d) to storage", name, length)
	path, err := s.path(name)
	if err != nil {
		return err
	}
	path = utils.ShQuote(path)
	tmpdir := utils.ShQuote(s.tmpdir)

	// Write to a temporary file ($TMPFILE), then mv atomically.
	command := fmt.Sprintf("mkdir -p `dirname %s` && cat > $TMPFILE", path)
	command = fmt.Sprintf(
		"TMPFILE=`mktemp --tmpdir=%s` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)",
		tmpdir, command, path,
	)

	_, err = s.run(flockExclusive, command+"\n", r, length)
	return err
}

// Remove implements storage.StorageWriter.Remove
func (s *SSHStorage) Remove(name string) error {
	path, err := s.path(name)
	if err != nil {
		return err
	}
	path = utils.ShQuote(path)
	_, err = s.runf(flockExclusive, "rm -f %s", path)
	return err
}

// RemoveAll implements storage.StorageWriter.RemoveAll
func (s *SSHStorage) RemoveAll() error {
	_, err := s.runf(flockExclusive, "rm -fr %s/*", utils.ShQuote(s.remotepath))
	return err
}
<commit_msg>Partial fix for LP-1351089<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package sshstorage

import (
	"bufio"
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/utils"

	"github.com/juju/juju/utils/ssh"
)

var logger = loggo.GetLogger("juju.environs.sshstorage")

// base64LineLength is the default line length for wrapping
// output generated by the base64 command line utility.
const base64LineLength = 76

// SSHStorage implements storage.Storage.
//
// The storage is created under sudo, and ownership given over to the
// login uid/gid. This is done so that we don't require sudo, and by
// consequence, don't require a pty, so we can interact with a script
// via stdin.
type SSHStorage struct {
	host       string
	remotepath string
	tmpdir     string

	cmd     *ssh.Cmd
	stdin   io.WriteCloser
	stdout  io.ReadCloser
	scanner *bufio.Scanner
}

var sshCommand = func(host string, command ...string) *ssh.Cmd {
	return ssh.Command(host, command, nil)
}

type flockmode string

const (
	flockShared    flockmode = "-s"
	flockExclusive flockmode = "-x"
)

type NewSSHStorageParams struct {
	// Host is the host to connect to, in the format [user@]hostname.
	Host string

	// StorageDir is the root of the remote storage directory.
	StorageDir string

	// TmpDir is the remote temporary directory for storage.
	// A temporary directory must be specified, and should be located on the
	// same filesystem as the storage directory to ensure atomic writes.
	// The temporary directory will be created when NewSSHStorage is invoked
	// if it doesn't already exist; it will never be removed. NewSSHStorage
	// will attempt to reassign ownership to the login user, and will return
	// an error if it cannot do so.
	TmpDir string
}

// NewSSHStorage creates a new SSHStorage, connected to the
// specified host, managing state under the specified remote path.
func NewSSHStorage(params NewSSHStorageParams) (*SSHStorage, error) {
	if params.StorageDir == "" {
		return nil, errors.New("storagedir must be specified and non-empty")
	}
	if params.TmpDir == "" {
		return nil, errors.New("tmpdir must be specified and non-empty")
	}

	script := fmt.Sprintf(
		"install -d -g $SUDO_GID -o $SUDO_UID %s %s",
		utils.ShQuote(params.StorageDir),
		utils.ShQuote(params.TmpDir),
	)

	cmd := sshCommand(params.Host, "sudo", "-n", "/bin/bash")
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	cmd.Stdin = strings.NewReader(script)
	if err := cmd.Run(); err != nil {
		err = fmt.Errorf("failed to create storage dir: %v (%v)", err, strings.TrimSpace(stderr.String()))
		return nil, err
	}

	// We could use sftp, but then we'd be at the mercy of
	// sftp's output messages for checking errors. Instead,
	// we execute an interactive bash shell.
	cmd = sshCommand(params.Host, "bash")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		stdin.Close()
		return nil, err
	}
	// Combine stdout and stderr, so we can easily
	// get at catastrophic failure messages.
	cmd.Stderr = cmd.Stdout
	stor := &SSHStorage{
		host:       params.Host,
		remotepath: params.StorageDir,
		tmpdir:     params.TmpDir,
		cmd:        cmd,
		stdin:      stdin,
		stdout:     stdout,
		scanner:    bufio.NewScanner(stdout),
	}
	cmd.Start()

	// Verify we have write permissions.
	_, err = stor.runf(flockExclusive, "touch %s", utils.ShQuote(params.StorageDir))
	if err != nil {
		stdin.Close()
		stdout.Close()
		cmd.Wait()
		return nil, err
	}
	return stor, nil
}

// Close cleanly terminates the underlying SSH connection.
func (s *SSHStorage) Close() error {
	s.stdin.Close()
	s.stdout.Close()
	return s.cmd.Wait()
}

func (s *SSHStorage) runf(flockmode flockmode, command string, args ...interface{}) (string, error) {
	command = fmt.Sprintf(command, args...)
	return s.run(flockmode, command, nil, 0)
}

// terminate closes the stdin, and appends any output to the input error.
func (s *SSHStorage) terminate(err error) error {
	s.stdin.Close()
	var output string
	for s.scanner.Scan() {
		if len(output) > 0 {
			output += "\n"
		}
		output += s.scanner.Text()
	}
	if len(output) > 0 {
		err = fmt.Errorf("%v (output: %q)", err, output)
	}
	return err
}

func (s *SSHStorage) run(flockmode flockmode, command string, input io.Reader, inputlen int64) (string, error) {
	const rcPrefix = "JUJU-RC: "
	command = fmt.Sprintf(
		"SHELL=/bin/bash flock %s %s -c %s",
		flockmode,
		utils.ShQuote(s.remotepath),
		utils.ShQuote(command),
	)
	stdin := bufio.NewWriter(s.stdin)
	if input != nil {
		command = fmt.Sprintf("base64 -d << '@EOF' | (%s)", command)
	}
	command = fmt.Sprintf("(%s) 2>&1; echo %s$?", command, rcPrefix)
	if _, err := stdin.WriteString(command + "\n"); err != nil {
		return "", fmt.Errorf("failed to write command: %v", err)
	}
	if input != nil {
		if err := copyAsBase64(stdin, input); err != nil {
			return "", s.terminate(fmt.Errorf("failed to write input: %v", err))
		}
	}
	if err := stdin.Flush(); err != nil {
		return "", s.terminate(fmt.Errorf("failed to write input: %v", err))
	}
	var output []string
	for s.scanner.Scan() {
		line := s.scanner.Text()
		if strings.HasPrefix(line, rcPrefix) {
			line := line[len(rcPrefix):]
			rc, err := strconv.Atoi(line)
			if err != nil {
				return "", fmt.Errorf("failed to parse exit code %q: %v", line, err)
			}
			outputJoined := strings.Join(output, "\n")
			if rc == 0 {
				return outputJoined, nil
			}
			return "", SSHStorageError{outputJoined, rc}
		} else {
			output = append(output, line)
		}
	}

	err := fmt.Errorf("failed to locate %q", rcPrefix)
	if len(output) > 0 {
		err = fmt.Errorf("%v (output: %q)", err, strings.Join(output, "\n"))
	}
	if scannerErr := s.scanner.Err(); scannerErr != nil {
		err = fmt.Errorf("%v (scanner error: %v)", err, scannerErr)
	}
	return "", err
}

func copyAsBase64(w *bufio.Writer, r io.Reader) error {
	wrapper := newLineWrapWriter(w, base64LineLength)
	encoder := base64.NewEncoder(base64.StdEncoding, wrapper)
	if _, err := io.Copy(encoder, r); err != nil {
		return err
	}
	if err := encoder.Close(); err != nil {
		return err
	}
	if _, err := w.WriteString("\n@EOF\n"); err != nil {
		return err
	}
	return nil
}

// path returns a remote absolute path for a storage object name.
func (s *SSHStorage) path(name string) (string, error) {
	remotepath := path.Clean(path.Join(s.remotepath, name))
	if !strings.HasPrefix(remotepath, s.remotepath) {
		return "", fmt.Errorf("%q escapes storage directory", name)
	}
	return remotepath, nil
}

// Get implements storage.StorageReader.Get.
func (s *SSHStorage) Get(name string) (io.ReadCloser, error) {
	logger.Debugf("getting %q from storage", name)
	path, err := s.path(name)
	if err != nil {
		return nil, err
	}
	out, err := s.runf(flockShared, "base64 < %s", utils.ShQuote(path))
	if err != nil {
		err := err.(SSHStorageError)
		if strings.Contains(err.Output, "No such file") {
			return nil, errors.NewNotFound(err, "")
		}
		return nil, err
	}
	decoded, err := base64.StdEncoding.DecodeString(out)
	if err != nil {
		return nil, err
	}
	return ioutil.NopCloser(bytes.NewBuffer(decoded)), nil
}

// List implements storage.StorageReader.List.
func (s *SSHStorage) List(prefix string) ([]string, error) {
	remotepath, err := s.path(prefix)
	if err != nil {
		return nil, err
	}
	dir, prefix := path.Split(remotepath)
	quotedDir := utils.ShQuote(dir)
	out, err := s.runf(flockShared, "(test -d %s && find %s -type f) || true", quotedDir, quotedDir)
	if err != nil {
		return nil, err
	}
	if out == "" {
		return nil, nil
	}
	var names []string
	for _, name := range strings.Split(out, "\n") {
		if strings.HasPrefix(name[len(dir):], prefix) {
			names = append(names, name[len(s.remotepath)+1:])
		}
	}
	sort.Strings(names)
	return names, nil
}

// URL implements storage.StorageReader.URL.
func (s *SSHStorage) URL(name string) (string, error) {
	path, err := s.path(name)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("sftp://%s/%s", s.host, path), nil
}

// DefaultConsistencyStrategy implements storage.StorageReader.ConsistencyStrategy.
func (s *SSHStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
	return utils.AttemptStrategy{}
}

// ShouldRetry is specified in the StorageReader interface.
func (s *SSHStorage) ShouldRetry(err error) bool {
	return false
}

// Put implements storage.StorageWriter.Put
func (s *SSHStorage) Put(name string, r io.Reader, length int64) error {
	logger.Debugf("putting %q (len %d) to storage", name, length)
	path, err := s.path(name)
	if err != nil {
		return err
	}
	path = utils.ShQuote(path)
	tmpdir := utils.ShQuote(s.tmpdir)

	// Write to a temporary file ($TMPFILE), then mv atomically.
	command := fmt.Sprintf("mkdir -p `dirname %s` && cat >| $TMPFILE", path)
	command = fmt.Sprintf(
		"TMPFILE=`mktemp --tmpdir=%s` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)",
		tmpdir, command, path,
	)

	_, err = s.run(flockExclusive, command+"\n", r, length)
	return err
}

// Remove implements storage.StorageWriter.Remove
func (s *SSHStorage) Remove(name string) error {
	path, err := s.path(name)
	if err != nil {
		return err
	}
	path = utils.ShQuote(path)
	_, err = s.runf(flockExclusive, "rm -f %s", path)
	return err
}

// RemoveAll implements storage.StorageWriter.RemoveAll
func (s *SSHStorage) RemoveAll() error {
	_, err := s.runf(flockExclusive, "rm -fr %s/*", utils.ShQuote(s.remotepath))
	return err
}
<|endoftext|>