<commit_before>package cmd

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/stretchr/testify/assert"
)

func TestTryToAddBigName(t *testing.T) {

	containerName := "lorem-ipsum-container-big-name"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, Messages["field-name-too-big"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToAddExistentContainer(t *testing.T) {
	containerName := "my-container-exists"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, Messages["same-name"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddError(t *testing.T) {

	guard := monkey.PatchInstanceMethod(reflect.TypeOf(model.Container{}), "Add",
		func(container model.Container) error {
			return fmt.Errorf("simulated error")
		})
	defer guard.Unpatch()

	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, "simulated error")
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainer(t *testing.T) {
	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := fmt.Sprintf(Messages["added-to-channel"], containerName, channelName)
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainerWithUserMention(t *testing.T) {
	containerName := "<@user>"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := Messages["shouldnt-mention-user"]
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}
<commit_msg>Checking mentions when adding container<commit_after>package cmd

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/stretchr/testify/assert"
)

func TestTryToAddBigName(t *testing.T) {

	containerName := "lorem-ipsum-container-big-name"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, Messages["field-name-too-big"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToAddExistentContainer(t *testing.T) {
	containerName := "my-container-exists"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, Messages["same-name"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddError(t *testing.T) {

	guard := monkey.PatchInstanceMethod(reflect.TypeOf(model.Container{}), "Add",
		func(container model.Container) error {
			return fmt.Errorf("simulated error")
		})
	defer guard.Unpatch()

	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, "simulated error")
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainer(t *testing.T) {
	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := fmt.Sprintf(Messages["added-to-channel"], containerName, channelName)
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainerWithUserMention(t *testing.T) {
	containerName := "<@user>"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := Messages["shouldnt-mention-user"]
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainerWithChannelMention(t *testing.T) {
	containerName := "<#CHANNEL>"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := Messages["shouldnt-mention-channel"]
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}
<|endoftext|>


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

package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// dbbackupCmd represents the dbbackup command
var dbbackupCmd = &cobra.Command{
	Use:   "dbbackup",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		// TODO: Work your own magic here
		fmt.Println("dbbackup called")
	},
}

func init() {
	RootCmd.AddCommand(dbbackupCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// dbbackupCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// dbbackupCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

}
<commit_msg>Default<commit_after>// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// dbbackupCmd represents the dbbackup command
var dbbackupCmd = &cobra.Command{
	Use:   "dbbackup",
	Short: "Backs up the Database(s)",
	Long: `Backs up all of the databases listed in the setting files.`,
	Run: func(cmd *cobra.Command, args []string) {
		// TODO: Work your own magic here
		fmt.Println("dbbackup called")
	},
}

func init() {
	RootCmd.AddCommand(dbbackupCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// dbbackupCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// dbbackupCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"

	sys "golang.org/x/sys/unix"

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/rpc"
	"github.com/derekparker/delve/terminal"
	"github.com/spf13/cobra"
)

const version string = "0.6.0.beta"

var (
	Log      bool
	Headless bool
	Addr     string
)

func main() {
	// Main dlv root command.
	rootCommand := &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
		Long: `Delve is a source level debugger for Go programs.

Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.

The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.
`,
	}
	rootCommand.Flags().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
	rootCommand.Flags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
	rootCommand.Flags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")

	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Delve version: " + version)
		},
	}
	rootCommand.AddCommand(versionCommand)

	// 'run' subcommand.
	runCommand := &cobra.Command{
		Use:   "run",
		Short: "Compile and begin debugging program.",
		Long: `Compiles your program with optimizations disabled,
starts and attaches to it, and enables you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			const debugname = "debug"
			goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
			goBuild.Stderr = os.Stderr
			err := goBuild.Run()
			if err != nil {
				os.Exit(1)
			}
			fp, err := filepath.Abs("./" + debugname)
			if err != nil {
				fmt.Fprintf(os.Stderr, err.Error())
				os.Exit(1)
			}

			processArgs := append([]string{"./" + debugname}, args...)
			status := execute(0, processArgs)
			os.Remove(fp)
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(runCommand)

	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
		Long: `Compiles a test binary with optimizations disabled,
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			wd, err := os.Getwd()
			if err != nil {
				fmt.Fprintf(os.Stderr, err.Error())
				os.Exit(1)
			}
			base := filepath.Base(wd)
			goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
			goTest.Stderr = os.Stderr
			err = goTest.Run()
			if err != nil {
				os.Exit(1)
			}
			debugname := "./" + base + ".test"
			processArgs := append([]string{debugname}, args...)

			status := execute(0, processArgs)
			os.Remove(debugname)
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(testCommand)

	// 'attach' subcommand.
	attachCommand := &cobra.Command{
		Use:   "attach [pid]",
		Short: "Attach to running process and begin debugging.",
		Long:  "Attach to running process and begin debugging.",
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
				fmt.Fprintf(os.Stderr, "Invalid pid: %d", args[0])
				os.Exit(1)
			}
			os.Exit(execute(pid, nil))
		},
	}
	rootCommand.AddCommand(attachCommand)

	rootCommand.Execute()
}

func execute(attachPid int, processArgs []string) int {
	// Make a TCP listener
	listener, err := net.Listen("tcp", Addr)
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
		return 1
	}
	defer listener.Close()

	// Create and start a debugger server
	var server service.Server
	server = rpc.NewServer(&service.Config{
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
	}, Log)
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	var status int
	if !Headless {
		// Create and start a terminal
		var client service.Client
		client = rpc.NewClient(listener.Addr().String())
		term := terminal.New(client)
		err, status = term.Run()
	} else {
		ch := make(chan os.Signal)
		signal.Notify(ch, sys.SIGINT)
		<-ch
		err = server.Stop(true)
	}

	if err != nil {
		fmt.Println(err)
	}

	return status
}
<commit_msg>Prefer anonymous functions with return status for defers<commit_after>package main

import (
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"

	sys "golang.org/x/sys/unix"

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/rpc"
	"github.com/derekparker/delve/terminal"
	"github.com/spf13/cobra"
)

const version string = "0.6.0.beta"

var (
	Log      bool
	Headless bool
	Addr     string
)

func main() {
	// Main dlv root command.
	rootCommand := &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
		Long: `Delve is a source level debugger for Go programs.

Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.

The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.
`,
	}
	rootCommand.Flags().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
	rootCommand.Flags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
	rootCommand.Flags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")

	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Delve version: " + version)
		},
	}
	rootCommand.AddCommand(versionCommand)

	// 'run' subcommand.
	runCommand := &cobra.Command{
		Use:   "run",
		Short: "Compile and begin debugging program.",
		Long: `Compiles your program with optimizations disabled,
starts and attaches to it, and enables you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			status := func() int {
				const debugname = "debug"
				goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
				goBuild.Stderr = os.Stderr
				err := goBuild.Run()
				if err != nil {
					return 1
				}
				fp, err := filepath.Abs("./" + debugname)
				if err != nil {
					fmt.Fprintf(os.Stderr, err.Error())
					return 1
				}
				defer os.Remove(fp)

				processArgs := append([]string{"./" + debugname}, args...)
				return execute(0, processArgs)
			}()
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(runCommand)

	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
		Long: `Compiles a test binary with optimizations disabled,
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			status := func() int {
				wd, err := os.Getwd()
				if err != nil {
					fmt.Fprintf(os.Stderr, err.Error())
					return 1
				}
				base := filepath.Base(wd)
				goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
				goTest.Stderr = os.Stderr
				err = goTest.Run()
				if err != nil {
					return 1
				}
				debugname := "./" + base + ".test"
				defer os.Remove(debugname)
				processArgs := append([]string{debugname}, args...)

				return execute(0, processArgs)
			}()
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(testCommand)

	// 'attach' subcommand.
	attachCommand := &cobra.Command{
		Use:   "attach [pid]",
		Short: "Attach to running process and begin debugging.",
		Long:  "Attach to running process and begin debugging.",
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
				fmt.Fprintf(os.Stderr, "Invalid pid: %d", args[0])
				os.Exit(1)
			}
			os.Exit(execute(pid, nil))
		},
	}
	rootCommand.AddCommand(attachCommand)

	rootCommand.Execute()
}

func execute(attachPid int, processArgs []string) int {
	// Make a TCP listener
	listener, err := net.Listen("tcp", Addr)
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
		return 1
	}
	defer listener.Close()

	// Create and start a debugger server
	server := rpc.NewServer(&service.Config{
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
	}, Log)
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	var status int
	if !Headless {
		// Create and start a terminal
		var client service.Client
		client = rpc.NewClient(listener.Addr().String())
		term := terminal.New(client)
		err, status = term.Run()
	} else {
		ch := make(chan os.Signal)
		signal.Notify(ch, sys.SIGINT)
		<-ch
		err = server.Stop(true)
	}

	if err != nil {
		fmt.Println(err)
	}

	return status
}
<|endoftext|>


<commit_before>// Copyright 2015 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"time"

	"github.com/vdobler/ht/ht"
)

var cmdQuick = &Command{
	RunArgs:     runQuick,
	Usage:       "quick <URL>...",
	Description: "do quick checking of HTML pages",
	Flag:        flag.NewFlagSet("run", flag.ContinueOnError),
	Help: `
Quick performs a set of standard checks on the given URLs which must
be HTML pages (and not images, JSON files, scripts or that like).
	`,
}

var defaultHeader = http.Header{}
var fullChecksFlag = false

func init() {
	addSkiptlsverifyFlag(cmdQuick.Flag)
	addVerbosityFlag(cmdQuick.Flag)
	addOutputFlag(cmdQuick.Flag)
	cmdQuick.Flag.BoolVar(&fullChecksFlag, "full", false,
		"check links, latency and resilience too")

	defaultHeader.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
	defaultHeader.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/47.0.2526.73 Chrome/47.0.2526.73 Safari/537.36")
	defaultHeader.Set("Encoding", "gzip")
	defaultHeader.Set("Accept-Language", "en-US,en;q=0.8,de;q=0.6")
	defaultHeader.Set("Cache-Control", "max-age=0")
	defaultHeader.Set("Upgrade-Insecure-Requests", "1")
}

func sameHostCondition(s string) ht.Condition {
	u, err := url.Parse(s)
	if err != nil {
		log.Fatalf("Cannot parse URL %q: %s", s, err)
	}
	u.Path = "/"
	u.RawPath = ""
	u.RawQuery = ""
	u.Fragment = ""

	absolute := `/.*`
	relative := `\.`
	full := regexp.QuoteMeta(u.String())

	re := fmt.Sprintf("^(%s|%s|%s)", absolute, relative, full)
	return ht.Condition{Regexp: re}
}

func makeChecks(u string) ht.CheckList {
	cl := ht.CheckList{
		ht.StatusCode{Expect: 200},
		ht.ContentType{Is: "html"},
		ht.UTF8Encoded{},
		ht.ValidHTML{},
		ht.ResponseTime{Lower: ht.Duration(1500 * time.Millisecond)},
	}

	if fullChecksFlag {
		cl = append(cl,
			&ht.Links{
				Which:       "a img link script",
				Concurrency: 8,
				OnlyLinks:   []ht.Condition{sameHostCondition(u)},
			})

		cl = append(cl,
			ht.Resilience{
				ModParam:  "all",
				ModHeader: "drop twice nonsense empty none",
			})

		cl = append(cl,
			&ht.Latency{
				Concurrent: 1,
				N:          31,
				SkipChecks: true,
				Limits:     "25% ≤ 1ms; 50% ≤ 2ms; 80% ≤ 3ms; 90% ≤ 4ms; 95% ≤ 5ms",
			})

		cl = append(cl,
			&ht.Latency{
				Concurrent: 4,
				N:          51,
				SkipChecks: true,
				Limits:     "25% ≤ 1ms; 50% ≤ 2ms; 80% ≤ 3ms; 90% ≤ 4ms; 95% ≤ 5ms",
			})

		cl = append(cl,
			&ht.Latency{
				Concurrent: 8,
				N:          71,
				SkipChecks: true,
				Limits:     "25% ≤ 1ms; 50% ≤ 2ms; 80% ≤ 3ms; 90% ≤ 4ms; 95% ≤ 5ms",
			})
	}

	return cl
}

func runQuick(cmd *Command, urls []string) {
	logger := log.New(os.Stdout, "", log.LstdFlags)
	suite := &ht.Suite{
		Name: "Quick Check",
		Log:  logger,
	}

	for _, u := range urls {
		test := &ht.Test{
			Name:        u,
			Description: "Quick test for " + u,
			Request: ht.Request{
				Method:          "GET",
				URL:             u,
				Header:          defaultHeader,
				FollowRedirects: true,
			},
			Checks: makeChecks(u),
		}
		suite.Tests = append(suite.Tests, test)
	}

	err := suite.Prepare()
	if err != nil {
		log.Println(err.Error())
		os.Exit(3)
	}
	if verbosity != -99 {
		for i := range suite.Tests {
			suite.Tests[i].Verbosity = verbosity
		}
	}
	suite.Variables = variablesFlag
	runExecute(cmd, []*ht.Suite{suite})
}
<commit_msg>cmd/ht: improve latency checks in quick testing<commit_after>// Copyright 2015 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"time"

	"github.com/vdobler/ht/ht"
)

var cmdQuick = &Command{
	RunArgs:     runQuick,
	Usage:       "quick <URL>...",
	Description: "do quick checking of HTML pages",
	Flag:        flag.NewFlagSet("run", flag.ContinueOnError),
	Help: `
Quick performs a set of standard checks on the given URLs which must
be HTML pages (and not images, JSON files, scripts or that like).
	`,
}

var defaultHeader = http.Header{}
var fullChecksFlag = false

func init() {
	addSkiptlsverifyFlag(cmdQuick.Flag)
	addVerbosityFlag(cmdQuick.Flag)
	addOutputFlag(cmdQuick.Flag)
	cmdQuick.Flag.BoolVar(&fullChecksFlag, "full", false,
		"check links, latency and resilience too")

	defaultHeader.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
	defaultHeader.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/47.0.2526.73 Chrome/47.0.2526.73 Safari/537.36")
	defaultHeader.Set("Encoding", "gzip")
	defaultHeader.Set("Accept-Language", "en-US,en;q=0.8,de;q=0.6")
	defaultHeader.Set("Cache-Control", "max-age=0")
	defaultHeader.Set("Upgrade-Insecure-Requests", "1")
}

func sameHostCondition(s string) ht.Condition {
	u, err := url.Parse(s)
	if err != nil {
		log.Fatalf("Cannot parse URL %q: %s", s, err)
	}
	u.Path = "/"
	u.RawPath = ""
	u.RawQuery = ""
	u.Fragment = ""

	absolute := `/.*`
	relative := `\.`
	full := regexp.QuoteMeta(u.String())

	re := fmt.Sprintf("^(%s|%s|%s)", absolute, relative, full)
	return ht.Condition{Regexp: re}
}

func makeChecks(u string) ht.CheckList {
	cl := ht.CheckList{
		ht.StatusCode{Expect: 200},
		ht.ContentType{Is: "html"},
		ht.UTF8Encoded{},
		ht.ValidHTML{},
		ht.ResponseTime{Lower: ht.Duration(1500 * time.Millisecond)},
	}

	if fullChecksFlag {
		cl = append(cl,
			&ht.Links{
				Which:       "a img link script",
				Concurrency: 8,
				OnlyLinks:   []ht.Condition{sameHostCondition(u)},
			})

		cl = append(cl,
			ht.Resilience{
				ModParam:  "all",
				ModHeader: "drop twice nonsense empty none",
			})

		cl = append(cl,
			&ht.Latency{
				Concurrent:         1,
				N:                  31,
				SkipChecks:         true,
				Limits:             "1% ≤ 1ms; 25% ≤ 2ms; 50% ≤ 3ms; 80% ≤ 4ms; 90% ≤ 5ms; 95% ≤ 6ms",
				IndividualSessions: true,
			})

		cl = append(cl,
			&ht.Latency{
				Concurrent:         3,
				N:                  51,
				SkipChecks:         true,
				Limits:             "1% ≤ 1ms; 25% ≤ 2ms; 50% ≤ 3ms; 80% ≤ 4ms; 90% ≤ 5ms; 95% ≤ 6ms",
				IndividualSessions: true,
			})

		cl = append(cl,
			&ht.Latency{
				Concurrent:         6,
				N:                  71,
				SkipChecks:         true,
				Limits:             "1% ≤ 1ms; 25% ≤ 2ms; 50% ≤ 3ms; 80% ≤ 4ms; 90% ≤ 5ms; 95% ≤ 6ms",
				IndividualSessions: true,
			})
	}

	return cl
}

func runQuick(cmd *Command, urls []string) {
	logger := log.New(os.Stdout, "", log.LstdFlags)
	suite := &ht.Suite{
		Name: "Quick Check",
		Log:  logger,
	}

	for _, u := range urls {
		test := &ht.Test{
			Name:        u,
			Description: "Quick test for " + u,
			Request: ht.Request{
				Method:          "GET",
				URL:             u,
				Header:          defaultHeader,
				FollowRedirects: true,
			},
			Checks: makeChecks(u),
		}
		suite.Tests = append(suite.Tests, test)
	}

	err := suite.Prepare()
	if err != nil {
		log.Println(err.Error())
		os.Exit(3)
	}
	if verbosity != -99 {
		for i := range suite.Tests {
			suite.Tests[i].Verbosity = verbosity
		}
	}
	suite.Variables = variablesFlag
	runExecute(cmd, []*ht.Suite{suite})
}
<|endoftext|>


<commit_before>package main

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

	"github.com/ivpusic/golog"
	"github.com/ivpusic/neo"
	"github.com/ivpusic/neo/cmd/neo/scripts"
	"gopkg.in/alecthomas/kingpin.v1"
)

var (
	logger = golog.GetLogger("github.com/ivpusic/neo")
	app    = kingpin.New("Neo", "Command line tool to managing your Neo application")

	// run command
	run        = app.Command("run", "Run Neo application")
	mainFile   = run.Arg("program", "Main file").String()
	configFile = run.Flag("config", "Path to configuration file").Short('c').String()
	verbose    = run.Flag("verbose", "Run in verbose mode").Short('v').Bool()

	// new commmand
	newCmd       = app.Command("new", "Create new Neo application")
	projectName  = newCmd.Arg("name", "Project name").String()
	templateName = newCmd.Flag("tpl", "Create new Neo application based on some template.").Short('t').String()
)

///////////////////////////////////////////////////////////////////
// `run` command
///////////////////////////////////////////////////////////////////

func handleRunCommand() {
	config := &neo.Conf{}

	// check if provided main file exists
	if len(*mainFile) > 0 {
		stat, err := os.Stat(*mainFile)
		if err != nil {
			logger.Errorf("Please provide valid main file. `%s` not found!", *mainFile)
			os.Exit(0)
		} else if stat.IsDir() {
			logger.Errorf("Please provide valid main file. `%s` not valid!", *mainFile)
			os.Exit(0)
		}
	}

	// find project root
	mainFileAbs, err := filepath.Abs(*mainFile)
	if err != nil {
		panic(err)
	}

	// if configuration is not provided by CLI,
	// try to figure out where it could be
	if len(*configFile) == 0 {
		if len(*mainFile) > 0 {
			projectRoot := filepath.Dir(mainFileAbs)
			*configFile = path.Join(projectRoot, "config.toml")
		} else {
			cwd, _ := os.Getwd()
			*configFile = path.Join(cwd, "config.toml")
		}

		// if config file doesn't exists,
		// fallback to empty configuration file -> defaults will be used
		_, err := os.Stat(*configFile)
		if err != nil {
			logger.Warnf("Configuration file at %s not found!", *configFile)
			*configFile = ""
		}
	}

	// parse configuration
	config.Parse(*configFile)

	arguments := []string{}

	// set project root
	arguments = append(arguments, "-r", filepath.Dir(mainFileAbs))

	// ignore files
	if len(config.Hotreload.Ignore) > 0 {
		arguments = append(arguments, "-i", strings.Join(config.Hotreload.Ignore, ","))
	}

	// suffixes to watch
	if len(config.Hotreload.Suffixes) > 0 {
		arguments = append(arguments, "-s", strings.Join(config.Hotreload.Suffixes, ","))
	}

	// app arguments
	config.App.Args = append(config.App.Args, "--config", *configFile)
	arguments = append(arguments, "-a"+strings.Join(config.App.Args, ","))

	// verbose
	if *verbose {
		arguments = append(arguments, "-v")
	}

	runCmd("rerun", arguments)
}

///////////////////////////////////////////////////////////////////
// `new` command
///////////////////////////////////////////////////////////////////

func handleNewCommand() {
	currentLocation, err := os.Getwd()
	if err != nil {
		logger.Error(err.Error())
		os.Exit(0)
	}
	projectLocation := currentLocation + "/" + *projectName

	var template scripts.Template = nil

	if len(*templateName) == 0 {
		logger.Info("Creating Neo project")
		template = &scripts.NeoTemplate{}
	} else {
		switch *templateName {
		case "angular":
			logger.Info("Creating Neo Angular project")
			template = &scripts.AngularTemplate{}
		case "html":
			logger.Info("Creating Neo HTML project")
			template = &scripts.NeoHtmlTemplate{}
		}
	}

	if template == nil {
		logger.Errorf("Unkonown template %s!", *templateName)
	} else {
		template.Build(projectLocation)
	}
}

///////////////////////////////////////////////////////////////////
// main
///////////////////////////////////////////////////////////////////

func main() {
	logger.Level = golog.INFO

	app.Version("1.0.0")

	// install dependencies
	outputCmd("go", []string{"get", "github.com/ivpusic/reload"})

	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
	case run.FullCommand():
		handleRunCommand()
	case newCmd.FullCommand():
		handleNewCommand()
	default:
		app.Usage(os.Stdout)
	}
}
<commit_msg>remove project root flag<commit_after>package main

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

	"github.com/ivpusic/golog"
	"github.com/ivpusic/neo"
	"github.com/ivpusic/neo/cmd/neo/scripts"
	"gopkg.in/alecthomas/kingpin.v1"
)

var (
	logger = golog.GetLogger("github.com/ivpusic/neo")
	app    = kingpin.New("Neo", "Command line tool to managing your Neo application")

	// run command
	run        = app.Command("run", "Run Neo application")
	mainFile   = run.Arg("program", "Main file").String()
	configFile = run.Flag("config", "Path to configuration file").Short('c').String()
	verbose    = run.Flag("verbose", "Run in verbose mode").Short('v').Bool()

	// new commmand
	newCmd       = app.Command("new", "Create new Neo application")
	projectName  = newCmd.Arg("name", "Project name").String()
	templateName = newCmd.Flag("tpl", "Create new Neo application based on some template.").Short('t').String()
)

///////////////////////////////////////////////////////////////////
// `run` command
///////////////////////////////////////////////////////////////////

func handleRunCommand() {
	config := &neo.Conf{}

	// check if provided main file exists
	if len(*mainFile) > 0 {
		stat, err := os.Stat(*mainFile)
		if err != nil {
			logger.Errorf("Please provide valid main file. `%s` not found!", *mainFile)
			os.Exit(0)
		} else if stat.IsDir() {
			logger.Errorf("Please provide valid main file. `%s` not valid!", *mainFile)
			os.Exit(0)
		}
	}

	// find project root
	mainFileAbs, err := filepath.Abs(*mainFile)
	if err != nil {
		panic(err)
	}

	// if configuration is not provided by CLI,
	// try to figure out where it could be
	if len(*configFile) == 0 {
		if len(*mainFile) > 0 {
			projectRoot := filepath.Dir(mainFileAbs)
			*configFile = path.Join(projectRoot, "config.toml")
		} else {
			cwd, _ := os.Getwd()
			*configFile = path.Join(cwd, "config.toml")
		}

		// if config file doesn't exists,
		// fallback to empty configuration file -> defaults will be used
		_, err := os.Stat(*configFile)
		if err != nil {
			logger.Warnf("Configuration file at %s not found!", *configFile)
			*configFile = ""
		}
	}

	// parse configuration
	config.Parse(*configFile)

	arguments := []string{}

	// ignore files
	if len(config.Hotreload.Ignore) > 0 {
		arguments = append(arguments, "-i", strings.Join(config.Hotreload.Ignore, ","))
	}

	// suffixes to watch
	if len(config.Hotreload.Suffixes) > 0 {
		arguments = append(arguments, "-s", strings.Join(config.Hotreload.Suffixes, ","))
	}

	// app arguments
	config.App.Args = append(config.App.Args, "--config", *configFile)
	arguments = append(arguments, "-a"+strings.Join(config.App.Args, ","))

	// verbose
	if *verbose {
		arguments = append(arguments, "-v")
	}

	runCmd("rerun", arguments)
}

///////////////////////////////////////////////////////////////////
// `new` command
///////////////////////////////////////////////////////////////////

func handleNewCommand() {
	currentLocation, err := os.Getwd()
	if err != nil {
		logger.Error(err.Error())
		os.Exit(0)
	}
	projectLocation := currentLocation + "/" + *projectName

	var template scripts.Template = nil

	if len(*templateName) == 0 {
		logger.Info("Creating Neo project")
		template = &scripts.NeoTemplate{}
	} else {
		switch *templateName {
		case "angular":
			logger.Info("Creating Neo Angular project")
			template = &scripts.AngularTemplate{}
		case "html":
			logger.Info("Creating Neo HTML project")
			template = &scripts.NeoHtmlTemplate{}
		}
	}

	if template == nil {
		logger.Errorf("Unkonown template %s!", *templateName)
	} else {
		template.Build(projectLocation)
	}
}

///////////////////////////////////////////////////////////////////
// main
///////////////////////////////////////////////////////////////////

func main() {
	logger.Level = golog.INFO

	app.Version("1.0.0")

	// install dependencies
	outputCmd("go", []string{"get", "github.com/ivpusic/reload"})

	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
	case run.FullCommand():
		handleRunCommand()
	case newCmd.FullCommand():
		handleNewCommand()
	default:
		app.Usage(os.Stdout)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.

package cmd

import (
	"archive/zip"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/renstrom/dedent"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/wso2/wum-uc/util"
	"github.com/wso2/wum-uc/constant"
)

var (
	validateCmdUse = "validate <update_loc> <dist_loc>"
	validateCmdShortDesc = "A brief description of your command"
	validateCmdLongDesc = dedent.Dedent(`
		A longer description that spans multiple lines and likely contains
		examples and usage of using your command.`)
)

// validateCmd represents the validate command
var validateCmd = &cobra.Command{
	Use: validateCmdUse,
	Short: validateCmdShortDesc,
	Long: validateCmdLongDesc,
	Run: initializeValidateCommand,
}

func init() {
	RootCmd.AddCommand(validateCmd)
	validateCmd.Flags().BoolVarP(&isDebugLogsEnabled, "debug", "d", false, "Enable debug logs")
	validateCmd.Flags().BoolVarP(&isTraceLogsEnabled, "trace", "t", false, "Enable trace logs")
}

func initializeValidateCommand(cmd *cobra.Command, args []string) {
	if len(args) < 2 || len(args) > 2 {
		util.PrintErrorAndExit("Invalid number of argumants. Run 'wum-uc validate --help' to view help.")
	}
	startValidation(args[0], args[1])
}

//Entry point of  the validate command
func startValidation(updateFilePath, distributionLocation string) {

	updateFileMap := make(map[string]bool)
	distributionFileMap := make(map[string]bool)

	//Check update location
	//Check 1
	if !strings.HasSuffix(updateFilePath, ".zip") {
		util.PrintErrorAndExit("Entered update location does not have a 'zip' extention.")
	}
	//Check 2
	exists, err := util.IsFileExists(updateFilePath)
	util.HandleError(err, "")
	if !exists {
		util.PrintErrorAndExit("Update file '" + updateFilePath + "' does not exist.")
	}

	exists, err = util.IsDistributionExists(distributionLocation)
	util.HandleError(err, "Error occurred while checking '" + distributionLocation + "'")
	if !exists {
		util.PrintErrorAndExit("Distribution does not exist at ", distributionLocation)
	}

	//Check 3
	locationInfo, err := os.Stat(updateFilePath)
	util.HandleError(err, "")
	match, err := regexp.MatchString(constant.FILENAME_REGEX, locationInfo.Name())
	if !match {
		util.PrintErrorAndExit("Update file name does not match '" + constant.FILENAME_REGEX + "' regular expression.")
	}
	updateName := strings.TrimSuffix(locationInfo.Name(), ".zip")
	viper.Set(constant.UPDATE_NAME, updateName)

	updateFileMap, err = readUpdateZip(updateFilePath)
	util.HandleError(err)
	logger.Debug(updateFileMap)

	//Check dist location
	if strings.HasSuffix(distributionLocation, ".zip") {
		locationInfo, err := os.Stat(distributionLocation)
		util.HandleError(err, "")
		viper.Set(constant.PRODUCT_NAME, strings.TrimSuffix(locationInfo.Name(), ".zip"))

		distributionFileMap, err = readDistributionZip(distributionLocation)
	} else {

	}

	err = compare(updateFileMap, distributionFileMap)
	util.HandleError(err)

	util.PrintInfo("'" + updateName + "' validation successfully finished. No issues found.")
}

func compare(updateFileMap, distributionFileMap map[string]bool) error {
	for filePath := range updateFileMap {
		logger.Debug("Searching:", filePath)
		_, found := distributionFileMap[filePath]
		if !found {
			return &util.CustomError{What: "File not found in the distribution: '" + filePath + "'" }
		}
	}
	return nil
}

func readUpdateZip(filename string) (map[string]bool, error) {
	fileMap := make(map[string]bool)
	// Create a reader out of the zip archive
	zipReader, err := zip.OpenReader(filename)
	if err != nil {
	}
	defer zipReader.Close()

	updateName := viper.GetString(constant.UPDATE_NAME)
	logger.Debug("updateName:", updateName)
	// Iterate through each file/dir found in
	for _, file := range zipReader.Reader.File {
		if file.FileInfo().IsDir() {
			logger.Debug("dir:", file.Name)
			logger.Debug("dir:", file.FileInfo().Name())
			if file.FileInfo().Name() != updateName {
				logger.Debug("Checking:", file.FileInfo().Name())
				//Check 4
				prefix := filepath.Join(updateName, constant.CARBON_HOME)
				hasPrefix := strings.HasPrefix(file.Name, prefix)
				if !hasPrefix {
					return nil, &util.CustomError{What: "Unknown directory found: '" + file.Name + "'" }
				}
			}
		} else {
			logger.Debug("file:", file.Name)
			logger.Debug("file:", file.FileInfo().Name())
			fullPath := filepath.Join(updateName, file.FileInfo().Name())
			logger.Debug("fullPath:", fullPath)
			switch file.FileInfo().Name() {
			case constant.UPDATE_DESCRIPTOR_FILE:
				//todo: read update descriptor and validate
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.UPDATE_DESCRIPTOR_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			case constant.LICENSE_FILE:
				//todo: read license and validate
				//This security update is licensed by WSO2 Inc. under Apache License 2.0.
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.LICENSE_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			case constant.INSTRUCTIONS_FILE:
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.INSTRUCTIONS_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			case constant.NOT_A_CONTRIBUTION_FILE:
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.NOT_A_CONTRIBUTION_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			default:
				prefix := filepath.Join(updateName, constant.CARBON_HOME)
				hasPrefix := strings.HasPrefix(file.Name, prefix)
				if !hasPrefix {
					return nil, &util.CustomError{What: "Unknown file found: '" + file.Name + "'" }
				}
				relativePath := strings.TrimPrefix(file.Name, prefix)
				fileMap[relativePath] = false
			}
		}
	}
	return fileMap, nil
}

func readDistributionZip(filename string) (map[string]bool, error) {
	fileMap := make(map[string]bool)
	// Create a reader out of the zip archive
	zipReader, err := zip.OpenReader(filename)
	if err != nil {
		return nil, err
	}
	defer zipReader.Close()

	productName := viper.GetString(constant.PRODUCT_NAME)
	// Iterate through each file/dir found in
	for _, file := range zipReader.Reader.File {
		relativePath := strings.TrimPrefix(file.Name, productName)
		if !file.FileInfo().IsDir() {
			fileMap[relativePath] = false
		}
	}
	return fileMap, nil
}<commit_msg>Add 'update-description' validation<commit_after>// Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.

package cmd

import (
	"archive/zip"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/renstrom/dedent"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/wso2/wum-uc/util"
	"github.com/wso2/wum-uc/constant"
	"gopkg.in/yaml.v2"
)

var (
	validateCmdUse = "validate <update_loc> <dist_loc>"
	validateCmdShortDesc = "A brief description of your command"
	validateCmdLongDesc = dedent.Dedent(`
		A longer description that spans multiple lines and likely contains
		examples and usage of using your command.`)
)

// validateCmd represents the validate command
var validateCmd = &cobra.Command{
	Use: validateCmdUse,
	Short: validateCmdShortDesc,
	Long: validateCmdLongDesc,
	Run: initializeValidateCommand,
}

func init() {
	RootCmd.AddCommand(validateCmd)
	validateCmd.Flags().BoolVarP(&isDebugLogsEnabled, "debug", "d", false, "Enable debug logs")
	validateCmd.Flags().BoolVarP(&isTraceLogsEnabled, "trace", "t", false, "Enable trace logs")
}

func initializeValidateCommand(cmd *cobra.Command, args []string) {
	if len(args) < 2 || len(args) > 2 {
		util.PrintErrorAndExit("Invalid number of argumants. Run 'wum-uc validate --help' to view help.")
	}
	startValidation(args[0], args[1])
}

//Entry point of  the validate command
func startValidation(updateFilePath, distributionLocation string) {

	setLogLevel()
	logger.Debug("validate command called")

	updateFileMap := make(map[string]bool)
	distributionFileMap := make(map[string]bool)

	//Check update location
	//Check 1
	if !strings.HasSuffix(updateFilePath, ".zip") {
		util.PrintErrorAndExit("Entered update location does not have a 'zip' extention.")
	}
	//Check 2
	exists, err := util.IsFileExists(updateFilePath)
	util.HandleError(err, "")
	if !exists {
		util.PrintErrorAndExit("Update file '" + updateFilePath + "' does not exist.")
	}

	exists, err = util.IsDistributionExists(distributionLocation)
	util.HandleError(err, "Error occurred while checking '" + distributionLocation + "'")
	if !exists {
		util.PrintErrorAndExit("Distribution does not exist at ", distributionLocation)
	}

	//Check 3
	locationInfo, err := os.Stat(updateFilePath)
	util.HandleError(err, "")
	match, err := regexp.MatchString(constant.FILENAME_REGEX, locationInfo.Name())
	if !match {
		util.PrintErrorAndExit("Update file name does not match '" + constant.FILENAME_REGEX + "' regular expression.")
	}
	updateName := strings.TrimSuffix(locationInfo.Name(), ".zip")
	viper.Set(constant.UPDATE_NAME, updateName)

	updateFileMap, err = readUpdateZip(updateFilePath)
	util.HandleError(err)
	logger.Debug(updateFileMap)

	//Check dist location
	if strings.HasSuffix(distributionLocation, ".zip") {
		locationInfo, err := os.Stat(distributionLocation)
		util.HandleError(err, "")
		viper.Set(constant.PRODUCT_NAME, strings.TrimSuffix(locationInfo.Name(), ".zip"))

		distributionFileMap, err = readDistributionZip(distributionLocation)
	} else {

	}

	err = compare(updateFileMap, distributionFileMap)
	util.HandleError(err)

	util.PrintInfo("'" + updateName + "' validation successfully finished. No issues found.")
}

func compare(updateFileMap, distributionFileMap map[string]bool) error {
	for filePath := range updateFileMap {
		logger.Debug("Searching:", filePath)
		_, found := distributionFileMap[filePath]
		if !found {
			return &util.CustomError{What: "File not found in the distribution: '" + filePath + "'" }
		}
	}
	return nil
}

func readUpdateZip(filename string) (map[string]bool, error) {
	fileMap := make(map[string]bool)
	updateDescriptor := util.UpdateDescriptor{}

	// Create a reader out of the zip archive
	zipReader, err := zip.OpenReader(filename)
	if err != nil {
	}
	defer zipReader.Close()

	updateName := viper.GetString(constant.UPDATE_NAME)
	logger.Debug("updateName:", updateName)
	// Iterate through each file/dir found in
	for _, file := range zipReader.Reader.File {
		if file.FileInfo().IsDir() {
			logger.Debug("dir:", file.Name)
			logger.Debug("dir:", file.FileInfo().Name())
			if file.FileInfo().Name() != updateName {
				logger.Debug("Checking:", file.FileInfo().Name())
				//Check 4
				prefix := filepath.Join(updateName, constant.CARBON_HOME)
				hasPrefix := strings.HasPrefix(file.Name, prefix)
				if !hasPrefix {
					return nil, &util.CustomError{What: "Unknown directory found: '" + file.Name + "'" }
				}
			}
		} else {
			logger.Debug("file:", file.Name)
			logger.Debug("file:", file.FileInfo().Name())
			fullPath := filepath.Join(updateName, file.FileInfo().Name())
			logger.Debug("fullPath:", fullPath)
			switch file.FileInfo().Name() {
			case constant.UPDATE_DESCRIPTOR_FILE:
				//todo: read update descriptor and validate
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.UPDATE_DESCRIPTOR_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
				zippedFile, err := file.Open()
				if err != nil {
					return nil, err
				}
				data, err := ioutil.ReadAll(zippedFile)
				if err != nil {
					return nil, err
				}
				//fmt.Print(string(data))

				zippedFile.Close()

				err = yaml.Unmarshal(data, &updateDescriptor)
				if err != nil {
					return nil, err
				}
				err = util.ValidateUpdateDescriptor(&updateDescriptor)
				if err != nil {
					return nil, &util.CustomError{What: "'" + constant.UPDATE_DESCRIPTOR_FILE + "' is invalid. " + err.Error() }
				}
			case constant.LICENSE_FILE:
				//todo: read license and validate
				//This security update is licensed by WSO2 Inc. under Apache License 2.0.
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.LICENSE_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			case constant.INSTRUCTIONS_FILE:
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.INSTRUCTIONS_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			case constant.NOT_A_CONTRIBUTION_FILE:
				if file.Name != fullPath {
					parent := strings.TrimSuffix(file.Name, file.FileInfo().Name())
					return nil, &util.CustomError{What: "'" + constant.NOT_A_CONTRIBUTION_FILE + "' found at '" + parent + "'. It should be in the '" + updateName + "' directory" }
				}
			default:
				prefix := filepath.Join(updateName, constant.CARBON_HOME)
				hasPrefix := strings.HasPrefix(file.Name, prefix)
				if !hasPrefix {
					return nil, &util.CustomError{What: "Unknown file found: '" + file.Name + "'" }
				}
				relativePath := strings.TrimPrefix(file.Name, prefix)
				fileMap[relativePath] = false
			}
		}
	}
	return fileMap, nil
}

func readDistributionZip(filename string) (map[string]bool, error) {
	fileMap := make(map[string]bool)
	// Create a reader out of the zip archive
	zipReader, err := zip.OpenReader(filename)
	if err != nil {
		return nil, err
	}
	defer zipReader.Close()

	productName := viper.GetString(constant.PRODUCT_NAME)
	// Iterate through each file/dir found in
	for _, file := range zipReader.Reader.File {
		relativePath := strings.TrimPrefix(file.Name, productName)
		if !file.FileInfo().IsDir() {
			fileMap[relativePath] = false
		}
	}
	return fileMap, nil
}<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"path/filepath"
	"strconv"

	"github.com/iobeam/iobeam/client"
)

const (
	baseApiPathApp = "/v1/apps"
	flagSetApp     = "iobeam app"
	cmdGet         = "get"
	cmdDelete      = "delete"
	cmdLaunch      = "deploy"
	cmdList        = "list"
	cmdStart       = "start"
	cmdStop        = "stop"
	cmdUpdate      = "update"

	appStatusRunning = "RUNNING"
	appStatusStopped = "STOPPED"
)

func (c *Command) newFlagSetApp(cmd string) *flag.FlagSet {
	return c.NewFlagSet(flagSetApp + " " + cmd)
}

// bundle describes an app's file bundle
type bundle struct {
	Checksum checksum `json:"checksum"`
	Type     string   `json:"type"`
	URI      string   `json:"uri"`
}

func (b *bundle) Print() {
	fmt.Printf("URI     : %s\n", b.URI)
	fmt.Printf("Type    : %s\n", b.Type)
	b.Checksum.Print()
	fmt.Println()
}

// appData defines the JSON resource of an iobeam app
type appData struct {
	AppId           uint64 `json:"app_id,omitempty"`
	AppName         string `json:"app_name"`
	ProjectId       uint64 `json:"project_id"`
	Bundle          bundle `json:"bundle"`
	Created         string `json:"created,omitempty"`
	LastMod         string `json:"last_modified,omitempty"`
	RequestedStatus string `json:"requested_status,omitempty"`
	CurrentStatus   string `json:"current_status,omitempty"`
}

func (i *appData) Print() {
	fmt.Printf("App ID  : %d\n", i.AppId)
	fmt.Printf("App Name: %s\n", i.AppName)
	fmt.Printf("Created : %s\n", i.Created)
	if len(i.RequestedStatus) > 0 {
		fmt.Printf("Requested Status: %s\n", i.RequestedStatus)
	}
	if len(i.CurrentStatus) > 0 {
		fmt.Printf("Current Status  : %s\n", i.CurrentStatus)
	}
	fmt.Println()
	fmt.Println("BUNDLE INFO")
	i.Bundle.Print()
}

// NewAppsCommand returns the base 'app' command.
func NewAppsCommand(ctx *Context) *Command {
	cmd := &Command{
		Name:  "app",
		Usage: "Commands for managing apps.",
		SubCommands: Mux{
			cmdLaunch: newLaunchAppCmd(ctx),
			cmdDelete: newDeleteAppCmd(ctx),
			cmdGet:    newGetAppCmd(ctx),
			cmdList:   newListAppsCmd(ctx),
			cmdStart:  newStartAppCmd(ctx),
			cmdStop:   newStopAppCmd(ctx),
			cmdUpdate: newUpdateAppCmd(ctx),
		},
	}
	cmd.NewFlagSet(flagSetApp)

	return cmd
}

// launchAppArgs are the arguments for the 'launch' subcommand
type launchAppArgs struct {
	uploadFileArgs
	name string
}

func (a *launchAppArgs) IsValid() bool {
	return a.uploadFileArgs.IsValid() && len(a.name) > 0
}

func newLaunchAppCmd(ctx *Context) *Command {
	args := new(launchAppArgs)

	cmd := &Command{
		Name:    cmdLaunch,
		ApiPath: baseApiPathApp,
		Usage:   "Launch a Spark app on iobeam.",
		Data:    args,
		Action:  launchApp,
	}
	flags := cmd.newFlagSetApp(cmdLaunch)
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject, "Project ID (defaults to active project).")
	flags.StringVar(&args.name, "name", "", "Name of the app. (REQUIRED)")
	flags.StringVar(&args.path, "path", "", "Path to app to upload. (REQUIRED)")

	return cmd
}

func launchApp(c *Command, ctx *Context) error {
	args := c.Data.(*launchAppArgs)
	digest, err := _uploadFile(ctx, &args.uploadFileArgs)
	if err != nil {
		return err
	}

	data := &appData{
		AppName:         args.name,
		ProjectId:       args.projectId,
		RequestedStatus: appStatusRunning,
		Bundle: bundle{
			Type: "JAR",
			URI:  "file://" + filepath.Base(args.path),
			Checksum: checksum{
				Sum:       digest,
				Algorithm: "SHA256",
			},
		},
	}

	_, err = ctx.Client.
		Post(c.ApiPath).
		Expect(201).
		ProjectToken(ctx.Profile, args.projectId).
		Body(data).
		ResponseBody(data).
		ResponseBodyHandler(func(body interface{}) error {

		app := body.(*appData)
		fmt.Println("New app created.")
		fmt.Printf("App ID: %v\n", app.AppId)
		fmt.Printf("App Name: %v\n", app.AppName)
		fmt.Println()

		return nil
	}).Execute()

	return err
}

// updateAppArgs are the arguments for the 'update' subcommand
type updateAppArgs struct {
	launchAppArgs
	id uint64
}

func (a *updateAppArgs) IsValid() bool {
	return a.id > 0 && (len(a.name) > 0 || len(a.path) > 0)
}

func newUpdateAppCmd(ctx *Context) *Command {
	args := new(updateAppArgs)

	cmd := &Command{
		Name:    cmdUpdate,
		ApiPath: baseApiPathApp,
		Usage:   "Update an app, including replacing the JAR.",
		Data:    args,
		Action:  updateApp,
	}
	flags := cmd.newFlagSetApp(cmdUpdate)
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject, "Project ID (defaults to active project).")
	flags.Uint64Var(&args.id, "id", 0, "App ID to update. (REQUIRED)")
	flags.StringVar(&args.name, "name", "", "Name of the app.")
	flags.StringVar(&args.path, "path", "", "Path to app to upload.")

	return cmd
}

func updateApp(c *Command, ctx *Context) error {
	args := c.Data.(*updateAppArgs)

	// Get app info to do PUT
	app, err := _getApp(ctx, &baseAppArgs{
		id:        args.id,
		projectId: args.projectId,
	})
	if err != nil {
		fmt.Println("errrrrr")
		return err
	}

	if len(args.name) > 0 {
		app.AppName = args.name
	}

	if len(args.path) > 0 {
		digest, err := _uploadFile(ctx, &args.uploadFileArgs)
		if err != nil {
			return err
		}
		app.Bundle = bundle{
			Type: "JAR",
			URI:  "file://" + filepath.Base(args.path),
			Checksum: checksum{
				Sum:       digest,
				Algorithm: "SHA256",
			},
		}
	}

	rsp, err := ctx.Client.
		Put(baseApiPathApp+"/"+strconv.FormatUint(args.id, 10)).
		Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		Body(app).
		Execute()

	if err == nil {
		fmt.Println("App successfully updated.")
	} else if rsp.Http().StatusCode == 204 {
		fmt.Println("App not modified.")
		return nil
	}

	return err
}

// baseAppArgs are the very basic arguments for subcommands
type baseAppArgs struct {
	projectId uint64
	name      string
	id        uint64
}

func (a *baseAppArgs) IsValid() bool {
	return a.projectId > 0 && (len(a.name) > 0 || a.id > 0)
}

// getAppArgs are the arguments for the 'get' subcommand
type getAppArgs struct {
	baseAppArgs
}

func (a *getAppArgs) IsValid() bool {
	return a.baseAppArgs.IsValid()
}

func newGetAppCmd(ctx *Context) *Command {
	args := new(getAppArgs)

	cmd := &Command{
		Name:    cmdGet,
		ApiPath: baseApiPathApp,
		Usage:   "Get app information.",
		Data:    args,
		Action:  getApp,
	}
	flags := cmd.newFlagSetApp(cmdGet)
	flags.Uint64Var(&args.id, "id", 0, "App ID to get (this or -name is required)")
	flags.StringVar(&args.name, "name", "", "App name to get (this or -id is required)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID to get from (defaults to active project)")

	return cmd
}

func getApp(c *Command, ctx *Context) error {
	args := c.Data.(*getAppArgs)
	app, err := _getApp(ctx, &args.baseAppArgs)
	if err == nil {
		app.Print()
	}
	return err
}

func _getApp(ctx *Context, args *baseAppArgs) (*appData, error) {
	var req *client.Request
	if args.id > 0 {
		req = ctx.Client.Get(baseApiPathApp + "/" + strconv.FormatUint(args.id, 10))
	} else {
		req = ctx.Client.Get(baseApiPathApp).Param("name", args.name)
	}

	app := new(appData)
	_, err := req.Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		ResponseBody(app).
		ResponseBodyHandler(func(body interface{}) error {
		return nil
	}).Execute()

	return app, err
}

// deleteAppArgs are the arguments for the 'delete' subcommand
type deleteAppArgs struct {
	baseAppArgs
}

func (a *deleteAppArgs) IsValid() bool {
	return a.baseAppArgs.IsValid()
}

func newDeleteAppCmd(ctx *Context) *Command {
	args := new(deleteAppArgs)

	cmd := &Command{
		Name:    cmdDelete,
		ApiPath: baseApiPathApp,
		Usage:   "Delete an app.",
		Data:    args,
		Action:  deleteApp,
	}
	flags := cmd.newFlagSetApp(cmdDelete)
	flags.Uint64Var(&args.id, "id", 0, "App ID to delete (REQUIRED)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID to delete from (defaults to active project)")

	return cmd
}

func deleteApp(c *Command, ctx *Context) error {
	args := c.Data.(*deleteAppArgs)
	req := ctx.Client.Delete(baseApiPathApp + "/" + strconv.FormatUint(args.id, 10))

	_, err := req.Expect(204).
		ProjectToken(ctx.Profile, args.projectId).Execute()

	if err == nil {
		fmt.Printf("App %d sucessfully deleted.\n", args.id)
	}

	return err
}

// listAppsArgs are the arguments for the 'list' sucommand
type listAppsArgs struct {
	projectId uint64
}

func (a *listAppsArgs) IsValid() bool {
	return a.projectId > 0
}

func newListAppsCmd(ctx *Context) *Command {
	args := new(listAppsArgs)

	cmd := &Command{
		Name:    cmdList,
		ApiPath: baseApiPathApp,
		Usage:   "List apps for a project.",
		Data:    args,
		Action:  listApps,
	}
	flags := cmd.newFlagSetApp(cmdList)
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject, "Project ID to list from (defaults to active project).")

	return cmd
}

func listApps(c *Command, ctx *Context) error {
	type listResult struct {
		Apps []appData `json:"apps"`
	}
	args := c.Data.(*listAppsArgs)

	_, err := ctx.Client.
		Get(c.ApiPath).
		Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		ResponseBody(new(listResult)).
		ResponseBodyHandler(func(body interface{}) error {
		list := body.(*listResult)
		if len(list.Apps) > 0 {
			spacer := ""
			for _, info := range list.Apps {
				fmt.Printf(spacer)
				info.Print()
				spacer = "----------\n"
			}
		} else {
			fmt.Printf("No apps found for project %d.\n", args.projectId)
		}

		return nil
	}).Execute()

	return err
}

func newStartAppCmd(ctx *Context) *Command {
	args := new(baseAppArgs)

	cmd := &Command{
		Name:    cmdStart,
		ApiPath: baseApiPathApp,
		Usage:   "Start an app by name/id.",
		Data:    args,
		Action:  startApp,
	}
	flags := cmd.newFlagSetApp(cmdStart)
	flags.Uint64Var(&args.id, "id", 0, "App ID to start (REQUIRED).")
	//flags.StringVar(&args.name, "name", "", "App name to start (this or -id is required)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID of app (defaults to active project)")

	return cmd
}

func startApp(c *Command, ctx *Context) error {
	args := c.Data.(*baseAppArgs)
	return _updateAppStatus(ctx, args, appStatusRunning)
}

func newStopAppCmd(ctx *Context) *Command {
	args := new(baseAppArgs)

	cmd := &Command{
		Name:    cmdStop,
		ApiPath: baseApiPathApp,
		Usage:   "Stop an app by name/id.",
		Data:    args,
		Action:  stopApp,
	}
	flags := cmd.newFlagSetApp(cmdStart)
	flags.Uint64Var(&args.id, "id", 0, "App ID to stop (REQUIRED).")
	//flags.StringVar(&args.name, "name", "", "App name to stop (this or -id is required)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID of app (defaults to active project)")

	return cmd
}

func stopApp(c *Command, ctx *Context) error {
	args := c.Data.(*baseAppArgs)
	return _updateAppStatus(ctx, args, appStatusStopped)
}

func _updateAppStatus(ctx *Context, args *baseAppArgs, status string) error {
	app, err := _getApp(ctx, args)
	if err != nil {
		return err
	}

	var req *client.Request
	if args.id > 0 {
		req = ctx.Client.Put(baseApiPathApp + "/" + strconv.FormatUint(args.id, 10))
	} else {
		req = ctx.Client.Put(baseApiPathApp).Param("name", args.name)
	}

	app.RequestedStatus = status
	rsp, err := req.Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		Body(app).
		Execute()

	if err != nil && rsp.Http().StatusCode == 204 {
		return nil
	}

	return err
}
<commit_msg>command: Change name to 'app create'<commit_after>package command

import (
	"flag"
	"fmt"
	"path/filepath"
	"strconv"

	"github.com/iobeam/iobeam/client"
)

const (
	baseApiPathApp = "/v1/apps"
	flagSetApp     = "iobeam app"
	cmdGet         = "get"
	cmdDelete      = "delete"
	cmdLaunch      = "create"
	cmdList        = "list"
	cmdStart       = "start"
	cmdStop        = "stop"
	cmdUpdate      = "update"

	appStatusRunning = "RUNNING"
	appStatusStopped = "STOPPED"
)

func (c *Command) newFlagSetApp(cmd string) *flag.FlagSet {
	return c.NewFlagSet(flagSetApp + " " + cmd)
}

// bundle describes an app's file bundle
type bundle struct {
	Checksum checksum `json:"checksum"`
	Type     string   `json:"type"`
	URI      string   `json:"uri"`
}

func (b *bundle) Print() {
	fmt.Printf("URI     : %s\n", b.URI)
	fmt.Printf("Type    : %s\n", b.Type)
	b.Checksum.Print()
	fmt.Println()
}

// appData defines the JSON resource of an iobeam app
type appData struct {
	AppId           uint64 `json:"app_id,omitempty"`
	AppName         string `json:"app_name"`
	ProjectId       uint64 `json:"project_id"`
	Bundle          bundle `json:"bundle"`
	Created         string `json:"created,omitempty"`
	LastMod         string `json:"last_modified,omitempty"`
	RequestedStatus string `json:"requested_status,omitempty"`
	CurrentStatus   string `json:"current_status,omitempty"`
}

func (i *appData) Print() {
	fmt.Printf("App ID  : %d\n", i.AppId)
	fmt.Printf("App Name: %s\n", i.AppName)
	fmt.Printf("Created : %s\n", i.Created)
	if len(i.RequestedStatus) > 0 {
		fmt.Printf("Requested Status: %s\n", i.RequestedStatus)
	}
	if len(i.CurrentStatus) > 0 {
		fmt.Printf("Current Status  : %s\n", i.CurrentStatus)
	}
	fmt.Println()
	fmt.Println("BUNDLE INFO")
	i.Bundle.Print()
}

// NewAppsCommand returns the base 'app' command.
func NewAppsCommand(ctx *Context) *Command {
	cmd := &Command{
		Name:  "app",
		Usage: "Commands for managing apps.",
		SubCommands: Mux{
			cmdLaunch: newLaunchAppCmd(ctx),
			cmdDelete: newDeleteAppCmd(ctx),
			cmdGet:    newGetAppCmd(ctx),
			cmdList:   newListAppsCmd(ctx),
			cmdStart:  newStartAppCmd(ctx),
			cmdStop:   newStopAppCmd(ctx),
			cmdUpdate: newUpdateAppCmd(ctx),
		},
	}
	cmd.NewFlagSet(flagSetApp)

	return cmd
}

// launchAppArgs are the arguments for the 'launch' subcommand
type launchAppArgs struct {
	uploadFileArgs
	name string
}

func (a *launchAppArgs) IsValid() bool {
	return a.uploadFileArgs.IsValid() && len(a.name) > 0
}

func newLaunchAppCmd(ctx *Context) *Command {
	args := new(launchAppArgs)

	cmd := &Command{
		Name:    cmdLaunch,
		ApiPath: baseApiPathApp,
		Usage:   "Create (and launch) a Spark app.",
		Data:    args,
		Action:  launchApp,
	}
	flags := cmd.newFlagSetApp(cmdLaunch)
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject, "Project ID (defaults to active project).")
	flags.StringVar(&args.name, "name", "", "Name of the app. (REQUIRED)")
	flags.StringVar(&args.path, "path", "", "Path to app to upload. (REQUIRED)")

	return cmd
}

func launchApp(c *Command, ctx *Context) error {
	args := c.Data.(*launchAppArgs)
	digest, err := _uploadFile(ctx, &args.uploadFileArgs)
	if err != nil {
		return err
	}

	data := &appData{
		AppName:         args.name,
		ProjectId:       args.projectId,
		RequestedStatus: appStatusRunning,
		Bundle: bundle{
			Type: "JAR",
			URI:  "file://" + filepath.Base(args.path),
			Checksum: checksum{
				Sum:       digest,
				Algorithm: "SHA256",
			},
		},
	}

	_, err = ctx.Client.
		Post(c.ApiPath).
		Expect(201).
		ProjectToken(ctx.Profile, args.projectId).
		Body(data).
		ResponseBody(data).
		ResponseBodyHandler(func(body interface{}) error {

		app := body.(*appData)
		fmt.Println("New app created.")
		fmt.Printf("App ID: %v\n", app.AppId)
		fmt.Printf("App Name: %v\n", app.AppName)
		fmt.Println()

		return nil
	}).Execute()

	return err
}

// updateAppArgs are the arguments for the 'update' subcommand
type updateAppArgs struct {
	launchAppArgs
	id uint64
}

func (a *updateAppArgs) IsValid() bool {
	return a.id > 0 && (len(a.name) > 0 || len(a.path) > 0)
}

func newUpdateAppCmd(ctx *Context) *Command {
	args := new(updateAppArgs)

	cmd := &Command{
		Name:    cmdUpdate,
		ApiPath: baseApiPathApp,
		Usage:   "Update an app, including replacing the JAR.",
		Data:    args,
		Action:  updateApp,
	}
	flags := cmd.newFlagSetApp(cmdUpdate)
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject, "Project ID (defaults to active project).")
	flags.Uint64Var(&args.id, "id", 0, "App ID to update. (REQUIRED)")
	flags.StringVar(&args.name, "name", "", "Name of the app.")
	flags.StringVar(&args.path, "path", "", "Path to app to upload.")

	return cmd
}

func updateApp(c *Command, ctx *Context) error {
	args := c.Data.(*updateAppArgs)

	// Get app info to do PUT
	app, err := _getApp(ctx, &baseAppArgs{
		id:        args.id,
		projectId: args.projectId,
	})
	if err != nil {
		fmt.Println("errrrrr")
		return err
	}

	if len(args.name) > 0 {
		app.AppName = args.name
	}

	if len(args.path) > 0 {
		digest, err := _uploadFile(ctx, &args.uploadFileArgs)
		if err != nil {
			return err
		}
		app.Bundle = bundle{
			Type: "JAR",
			URI:  "file://" + filepath.Base(args.path),
			Checksum: checksum{
				Sum:       digest,
				Algorithm: "SHA256",
			},
		}
	}

	rsp, err := ctx.Client.
		Put(baseApiPathApp+"/"+strconv.FormatUint(args.id, 10)).
		Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		Body(app).
		Execute()

	if err == nil {
		fmt.Println("App successfully updated.")
	} else if rsp.Http().StatusCode == 204 {
		fmt.Println("App not modified.")
		return nil
	}

	return err
}

// baseAppArgs are the very basic arguments for subcommands
type baseAppArgs struct {
	projectId uint64
	name      string
	id        uint64
}

func (a *baseAppArgs) IsValid() bool {
	return a.projectId > 0 && (len(a.name) > 0 || a.id > 0)
}

// getAppArgs are the arguments for the 'get' subcommand
type getAppArgs struct {
	baseAppArgs
}

func (a *getAppArgs) IsValid() bool {
	return a.baseAppArgs.IsValid()
}

func newGetAppCmd(ctx *Context) *Command {
	args := new(getAppArgs)

	cmd := &Command{
		Name:    cmdGet,
		ApiPath: baseApiPathApp,
		Usage:   "Get app information.",
		Data:    args,
		Action:  getApp,
	}
	flags := cmd.newFlagSetApp(cmdGet)
	flags.Uint64Var(&args.id, "id", 0, "App ID to get (this or -name is required)")
	flags.StringVar(&args.name, "name", "", "App name to get (this or -id is required)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID to get from (defaults to active project)")

	return cmd
}

func getApp(c *Command, ctx *Context) error {
	args := c.Data.(*getAppArgs)
	app, err := _getApp(ctx, &args.baseAppArgs)
	if err == nil {
		app.Print()
	}
	return err
}

func _getApp(ctx *Context, args *baseAppArgs) (*appData, error) {
	var req *client.Request
	if args.id > 0 {
		req = ctx.Client.Get(baseApiPathApp + "/" + strconv.FormatUint(args.id, 10))
	} else {
		req = ctx.Client.Get(baseApiPathApp).Param("name", args.name)
	}

	app := new(appData)
	_, err := req.Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		ResponseBody(app).
		ResponseBodyHandler(func(body interface{}) error {
		return nil
	}).Execute()

	return app, err
}

// deleteAppArgs are the arguments for the 'delete' subcommand
type deleteAppArgs struct {
	baseAppArgs
}

func (a *deleteAppArgs) IsValid() bool {
	return a.baseAppArgs.IsValid()
}

func newDeleteAppCmd(ctx *Context) *Command {
	args := new(deleteAppArgs)

	cmd := &Command{
		Name:    cmdDelete,
		ApiPath: baseApiPathApp,
		Usage:   "Delete an app.",
		Data:    args,
		Action:  deleteApp,
	}
	flags := cmd.newFlagSetApp(cmdDelete)
	flags.Uint64Var(&args.id, "id", 0, "App ID to delete (REQUIRED)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID to delete from (defaults to active project)")

	return cmd
}

func deleteApp(c *Command, ctx *Context) error {
	args := c.Data.(*deleteAppArgs)
	req := ctx.Client.Delete(baseApiPathApp + "/" + strconv.FormatUint(args.id, 10))

	_, err := req.Expect(204).
		ProjectToken(ctx.Profile, args.projectId).Execute()

	if err == nil {
		fmt.Printf("App %d sucessfully deleted.\n", args.id)
	}

	return err
}

// listAppsArgs are the arguments for the 'list' sucommand
type listAppsArgs struct {
	projectId uint64
}

func (a *listAppsArgs) IsValid() bool {
	return a.projectId > 0
}

func newListAppsCmd(ctx *Context) *Command {
	args := new(listAppsArgs)

	cmd := &Command{
		Name:    cmdList,
		ApiPath: baseApiPathApp,
		Usage:   "List apps for a project.",
		Data:    args,
		Action:  listApps,
	}
	flags := cmd.newFlagSetApp(cmdList)
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject, "Project ID to list from (defaults to active project).")

	return cmd
}

func listApps(c *Command, ctx *Context) error {
	type listResult struct {
		Apps []appData `json:"apps"`
	}
	args := c.Data.(*listAppsArgs)

	_, err := ctx.Client.
		Get(c.ApiPath).
		Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		ResponseBody(new(listResult)).
		ResponseBodyHandler(func(body interface{}) error {
		list := body.(*listResult)
		if len(list.Apps) > 0 {
			spacer := ""
			for _, info := range list.Apps {
				fmt.Printf(spacer)
				info.Print()
				spacer = "----------\n"
			}
		} else {
			fmt.Printf("No apps found for project %d.\n", args.projectId)
		}

		return nil
	}).Execute()

	return err
}

func newStartAppCmd(ctx *Context) *Command {
	args := new(baseAppArgs)

	cmd := &Command{
		Name:    cmdStart,
		ApiPath: baseApiPathApp,
		Usage:   "Start an app by name/id.",
		Data:    args,
		Action:  startApp,
	}
	flags := cmd.newFlagSetApp(cmdStart)
	flags.Uint64Var(&args.id, "id", 0, "App ID to start (REQUIRED).")
	//flags.StringVar(&args.name, "name", "", "App name to start (this or -id is required)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID of app (defaults to active project)")

	return cmd
}

func startApp(c *Command, ctx *Context) error {
	args := c.Data.(*baseAppArgs)
	return _updateAppStatus(ctx, args, appStatusRunning)
}

func newStopAppCmd(ctx *Context) *Command {
	args := new(baseAppArgs)

	cmd := &Command{
		Name:    cmdStop,
		ApiPath: baseApiPathApp,
		Usage:   "Stop an app by name/id.",
		Data:    args,
		Action:  stopApp,
	}
	flags := cmd.newFlagSetApp(cmdStart)
	flags.Uint64Var(&args.id, "id", 0, "App ID to stop (REQUIRED).")
	//flags.StringVar(&args.name, "name", "", "App name to stop (this or -id is required)")
	flags.Uint64Var(&args.projectId, "projectId", ctx.Profile.ActiveProject,
		"Project ID of app (defaults to active project)")

	return cmd
}

func stopApp(c *Command, ctx *Context) error {
	args := c.Data.(*baseAppArgs)
	return _updateAppStatus(ctx, args, appStatusStopped)
}

func _updateAppStatus(ctx *Context, args *baseAppArgs, status string) error {
	app, err := _getApp(ctx, args)
	if err != nil {
		return err
	}

	var req *client.Request
	if args.id > 0 {
		req = ctx.Client.Put(baseApiPathApp + "/" + strconv.FormatUint(args.id, 10))
	} else {
		req = ctx.Client.Put(baseApiPathApp).Param("name", args.name)
	}

	app.RequestedStatus = status
	rsp, err := req.Expect(200).
		ProjectToken(ctx.Profile, args.projectId).
		Body(app).
		Execute()

	if err != nil && rsp.Http().StatusCode == 204 {
		return nil
	}

	return err
}
<|endoftext|>


<commit_before>package command

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

// InitCommand generates a new job template that you can customize to your
// liking, like vagrant init
type InitCommand struct {
	Meta
}

func (c *InitCommand) Help() string {
	return initUsage
}

func (c *InitCommand) Run(args []string) int {
	dir, err := os.Getwd()
	if err != nil {
		c.Ui.Error("Unable to determine pwd; aborting")
		return 1
	}

	// Derive the job name from the pwd folder name, which is our best guess at
	// the project's name
	jobname := filepath.Base(dir)
	jobfile := fmt.Sprintf("%s.nomad", jobname)
	jobpath := filepath.Join(dir, jobfile)
	if _, err := os.Stat(jobpath); err == nil {
		c.Ui.Error(fmt.Sprintf("%s file already exists", jobfile))
		return 1
	}

	file, err := os.Create(jobfile)
	defer file.Close()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Unable to create file %s: %s", jobfile, err))
		return 1
	}

	_, err = file.WriteString(defaultJob)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Failed to write job template to %s", jobfile))
		return 1
	}

	c.Ui.Output(fmt.Sprintf("Initialized nomad job template in %s", jobfile))

	return 0
}

func (c *InitCommand) Synopsis() string {
	return "Create a new job template"
}

const initUsage = ``

const defaultJob = `
job "my-app" {
    region = "global"
    type = "service"
    priority = 50

    // Each task in the group will be scheduled on the same machine(s).
    group "app-group" {
        // How many copies of this group should we run?
        count = 5

        task "python-webapp" {
            driver = "docker"
            config {
                image = "org/container"
            }
            resources {
                // For CPU 1024 = 1ghz
                cpu = 500
                // Memory in megabytes
                memory = 128

                network {
                    dynamic_ports = 2
                }
            }
        }

        task "logshipper" {
            driver = "exec"
        }

        constraint {
            attribute = "kernel.os"
            value = "linux"
        }
    }
}
`
<commit_msg>Switch to named port config syntax<commit_after>package command

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

// InitCommand generates a new job template that you can customize to your
// liking, like vagrant init
type InitCommand struct {
	Meta
}

func (c *InitCommand) Help() string {
	return initUsage
}

func (c *InitCommand) Run(args []string) int {
	dir, err := os.Getwd()
	if err != nil {
		c.Ui.Error("Unable to determine pwd; aborting")
		return 1
	}

	// Derive the job name from the pwd folder name, which is our best guess at
	// the project's name
	jobname := filepath.Base(dir)
	jobfile := fmt.Sprintf("%s.nomad", jobname)
	jobpath := filepath.Join(dir, jobfile)
	if _, err := os.Stat(jobpath); err == nil {
		c.Ui.Error(fmt.Sprintf("%s file already exists", jobfile))
		return 1
	}

	file, err := os.Create(jobfile)
	defer file.Close()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Unable to create file %s: %s", jobfile, err))
		return 1
	}

	_, err = file.WriteString(defaultJob)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Failed to write job template to %s", jobfile))
		return 1
	}

	c.Ui.Output(fmt.Sprintf("Initialized nomad job template in %s", jobfile))

	return 0
}

func (c *InitCommand) Synopsis() string {
	return "Create a new job template"
}

const initUsage = ``

const defaultJob = `
job "my-app" {
    region = "global"
    type = "service"
    priority = 50

    // Each task in the group will be scheduled on the same machine(s).
    group "app-group" {
        // How many copies of this group should we run?
        count = 5

        task "python-webapp" {
            driver = "docker"
            config {
                image = "org/container"
            }
            resources {
                // For CPU 1024 = 1ghz
                cpu = 500
                // Memory in megabytes
                memory = 128

                network {
                    dynamic_ports = [
                        "http",
                        "https",
                    ]
                }
            }
        }

        task "logshipper" {
            driver = "exec"
        }

        constraint {
            attribute = "kernel.os"
            value = "linux"
        }
    }
}
`
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"os"
	"path"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/command/agent"
	"github.com/mitchellh/cli"
)

const (
	// lockKillGracePeriod is how long we allow a child between
	// a SIGTERM and a SIGKILL. This is to let the child cleanup
	// any necessary state. We have to balance this with the risk
	// of a split-brain where multiple children may be acting as if
	// they hold a lock. This value is currently based on the default
	// lock-delay value of 15 seconds. This only affects locks and not
	// semaphores.
	lockKillGracePeriod = 5 * time.Second
)

// LockCommand is a Command implementation that is used to setup
// a "lock" which manages lock acquisition and invokes a sub-process
type LockCommand struct {
	ShutdownCh <-chan struct{}
	Ui         cli.Ui

	child     *os.Process
	childLock sync.Mutex
	verbose   bool
}

func (c *LockCommand) Help() string {
	helpText := `
Usage: consul lock [options] prefix child...

  Acquires a lock or semaphore at a given path, and invokes a child
  process when successful. The child process can assume the lock is
  held while it executes. If the lock is lost or communication is
  disrupted the child process will be sent a SIGTERM signal and given
  time to gracefully exit. After the grace period expires the process
  will be hard terminated.
  For Consul agents on Windows, the child process is always hard
  terminated with a SIGKILL, since Windows has no POSIX compatible
  notion for SIGTERM.

  When -n=1, only a single lock holder or leader exists providing
  mutual exclusion. Setting a higher value switches to a semaphore
  allowing multiple holders to coordinate.

  The prefix provided must have write privileges.

Options:

  -http-addr=127.0.0.1:8500  HTTP address of the Consul agent.
  -n=1                       Maximum number of allowed lock holders. If this
                             value is one, it operates as a lock, otherwise
                             a semaphore is used.
  -name=""                   Optional name to associate with lock session.
  -token=""                  ACL token to use. Defaults to that of agent.
  -pass-stdin                Pass stdin to child process.
  -verbose                   Enables verbose output
`
	return strings.TrimSpace(helpText)
}

func (c *LockCommand) Run(args []string) int {
	var childDone chan struct{}
	var name, token string
	var limit int
	var passStdin bool
	cmdFlags := flag.NewFlagSet("watch", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.IntVar(&limit, "n", 1, "")
	cmdFlags.StringVar(&name, "name", "", "")
	cmdFlags.StringVar(&token, "token", "", "")
	cmdFlags.BoolVar(&passStdin, "pass-stdin", false, "")
	cmdFlags.BoolVar(&c.verbose, "verbose", false, "")
	httpAddr := HTTPAddrFlag(cmdFlags)
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	// Check the limit
	if limit <= 0 {
		c.Ui.Error(fmt.Sprintf("Lock holder limit must be positive"))
		return 1
	}

	// Verify the prefix and child are provided
	extra := cmdFlags.Args()
	if len(extra) < 2 {
		c.Ui.Error("Key prefix and child command must be specified")
		c.Ui.Error("")
		c.Ui.Error(c.Help())
		return 1
	}
	prefix := extra[0]
	prefix = strings.TrimPrefix(prefix, "/")
	script := strings.Join(extra[1:], " ")

	// Calculate a session name if none provided
	if name == "" {
		name = fmt.Sprintf("Consul lock for '%s' at '%s'", script, prefix)
	}

	// Create and test the HTTP client
	conf := api.DefaultConfig()
	conf.Address = *httpAddr
	conf.Token = token
	client, err := api.NewClient(conf)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
		return 1
	}
	_, err = client.Agent().NodeName()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
		return 1
	}

	// Setup the lock or semaphore
	var lu *LockUnlock
	if limit == 1 {
		lu, err = c.setupLock(client, prefix, name)
	} else {
		lu, err = c.setupSemaphore(client, limit, prefix, name)
	}
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Lock setup failed: %s", err))
		return 1
	}

	// Attempt the acquisition
	if c.verbose {
		c.Ui.Info("Attempting lock acquisition")
	}
	lockCh, err := lu.lockFn(c.ShutdownCh)
	if lockCh == nil {
		if err == nil {
			c.Ui.Error("Shutdown triggered during lock acquisition")
		} else {
			c.Ui.Error(fmt.Sprintf("Lock acquisition failed: %s", err))
		}
		return 1
	}

	// Check if we were shutdown but managed to still acquire the lock
	select {
	case <-c.ShutdownCh:
		c.Ui.Error("Shutdown triggered during lock acquisition")
		goto RELEASE
	default:
	}

	// Start the child process
	childDone = make(chan struct{})
	go func() {
		if err := c.startChild(script, childDone, passStdin); err != nil {
			c.Ui.Error(fmt.Sprintf("%s", err))
		}
	}()

	// Monitor for shutdown, child termination, or lock loss
	select {
	case <-c.ShutdownCh:
		if c.verbose {
			c.Ui.Info("Shutdown triggered, killing child")
		}
	case <-lockCh:
		if c.verbose {
			c.Ui.Info("Lock lost, killing child")
		}
	case <-childDone:
		if c.verbose {
			c.Ui.Info("Child terminated, releasing lock")
		}
		goto RELEASE
	}

	// Kill the child
	if err := c.killChild(childDone); err != nil {
		c.Ui.Error(fmt.Sprintf("%s", err))
	}

RELEASE:
	// Release the lock before termination
	if err := lu.unlockFn(); err != nil {
		c.Ui.Error(fmt.Sprintf("Lock release failed: %s", err))
		return 1
	}

	// Cleanup the lock if no longer in use
	if err := lu.cleanupFn(); err != nil {
		if err != lu.inUseErr {
			c.Ui.Error(fmt.Sprintf("Lock cleanup failed: %s", err))
			return 1
		} else if c.verbose {
			c.Ui.Info("Cleanup aborted, lock in use")
		}
	} else if c.verbose {
		c.Ui.Info("Cleanup succeeded")
	}
	return 0
}

// setupLock is used to setup a new Lock given the API client,
// the key prefix to operate on, and an optional session name.
func (c *LockCommand) setupLock(client *api.Client, prefix, name string) (*LockUnlock, error) {
	// Use the DefaultSemaphoreKey extention, this way if a lock and
	// semaphore are both used at the same prefix, we will get a conflict
	// which we can report to the user.
	key := path.Join(prefix, api.DefaultSemaphoreKey)
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Setting up lock at path: %s", key))
	}
	opts := api.LockOptions{
		Key:         key,
		SessionName: name,
	}
	l, err := client.LockOpts(&opts)
	if err != nil {
		return nil, err
	}
	lu := &LockUnlock{
		lockFn:    l.Lock,
		unlockFn:  l.Unlock,
		cleanupFn: l.Destroy,
		inUseErr:  api.ErrLockInUse,
	}
	return lu, nil
}

// setupSemaphore is used to setup a new Semaphore given the
// API client, key prefix, session name, and slot holder limit.
func (c *LockCommand) setupSemaphore(client *api.Client, limit int, prefix, name string) (*LockUnlock, error) {
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Setting up semaphore (limit %d) at prefix: %s", limit, prefix))
	}
	opts := api.SemaphoreOptions{
		Prefix:      prefix,
		Limit:       limit,
		SessionName: name,
	}
	s, err := client.SemaphoreOpts(&opts)
	if err != nil {
		return nil, err
	}
	lu := &LockUnlock{
		lockFn:    s.Acquire,
		unlockFn:  s.Release,
		cleanupFn: s.Destroy,
		inUseErr:  api.ErrSemaphoreInUse,
	}
	return lu, nil
}

// startChild is a long running routine used to start and
// wait for the child process to exit.
func (c *LockCommand) startChild(script string, doneCh chan struct{}, passStdin bool) error {
	defer close(doneCh)
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Starting handler '%s'", script))
	}
	// Create the command
	cmd, err := agent.ExecScript(script)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error executing handler: %s", err))
		return err
	}

	// Setup the command streams
	cmd.Env = append(os.Environ(),
		"CONSUL_LOCK_HELD=true",
	)
	if passStdin {
		if c.verbose {
			c.Ui.Info("Stdin passed to handler process")
		}
		cmd.Stdin = os.Stdin
	} else {
		cmd.Stdin = nil
	}
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// Start the child process
	c.childLock.Lock()
	if err := cmd.Start(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error starting handler: %s", err))
		c.childLock.Unlock()
		return err
	}

	// Setup the child info
	c.child = cmd.Process
	c.childLock.Unlock()

	// Wait for the child process
	if err := cmd.Wait(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error running handler: %s", err))
		return err
	}
	return nil
}

// killChild is used to forcefully kill the child, first using SIGTERM
// to allow for a graceful cleanup and then using SIGKILL for a hard
// termination.
// On Windows, the child is always hard terminated with a SIGKILL, even
// on the first attempt.
func (c *LockCommand) killChild(childDone chan struct{}) error {
	// Get the child process
	c.childLock.Lock()
	child := c.child
	c.childLock.Unlock()

	// If there is no child process (failed to start), we can quit early
	if child == nil {
		if c.verbose {
			c.Ui.Info("No child process to kill")
		}
		return nil
	}

	// Attempt termination first
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Terminating child pid %d", child.Pid))
	}
	if err := signalPid(child.Pid, syscall.SIGTERM); err != nil {
		return fmt.Errorf("Failed to terminate %d: %v", child.Pid, err)
	}

	// Wait for termination, or until a timeout
	select {
	case <-childDone:
		if c.verbose {
			c.Ui.Info("Child terminated")
		}
		return nil
	case <-time.After(lockKillGracePeriod):
		if c.verbose {
			c.Ui.Info(fmt.Sprintf("Child did not exit after grace period of %v",
				lockKillGracePeriod))
		}
	}

	// Send a final SIGKILL
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Killing child pid %d", child.Pid))
	}
	if err := signalPid(child.Pid, syscall.SIGKILL); err != nil {
		return fmt.Errorf("Failed to kill %d: %v", child.Pid, err)
	}
	return nil
}

func (c *LockCommand) Synopsis() string {
	return "Execute a command holding a lock"
}

// LockUnlock is used to abstract over the differences between
// a lock and a semaphore.
type LockUnlock struct {
	lockFn    func(<-chan struct{}) (<-chan struct{}, error)
	unlockFn  func() error
	cleanupFn func() error
	inUseErr  error
}
<commit_msg>lock.go: fix another race condition<commit_after>package command

import (
	"flag"
	"fmt"
	"os"
	"path"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/command/agent"
	"github.com/mitchellh/cli"
)

const (
	// lockKillGracePeriod is how long we allow a child between
	// a SIGTERM and a SIGKILL. This is to let the child cleanup
	// any necessary state. We have to balance this with the risk
	// of a split-brain where multiple children may be acting as if
	// they hold a lock. This value is currently based on the default
	// lock-delay value of 15 seconds. This only affects locks and not
	// semaphores.
	lockKillGracePeriod = 5 * time.Second
)

// LockCommand is a Command implementation that is used to setup
// a "lock" which manages lock acquisition and invokes a sub-process
type LockCommand struct {
	ShutdownCh <-chan struct{}
	Ui         cli.Ui

	child     *os.Process
	childLock sync.Mutex
	verbose   bool
}

func (c *LockCommand) Help() string {
	helpText := `
Usage: consul lock [options] prefix child...

  Acquires a lock or semaphore at a given path, and invokes a child
  process when successful. The child process can assume the lock is
  held while it executes. If the lock is lost or communication is
  disrupted the child process will be sent a SIGTERM signal and given
  time to gracefully exit. After the grace period expires the process
  will be hard terminated.
  For Consul agents on Windows, the child process is always hard
  terminated with a SIGKILL, since Windows has no POSIX compatible
  notion for SIGTERM.

  When -n=1, only a single lock holder or leader exists providing
  mutual exclusion. Setting a higher value switches to a semaphore
  allowing multiple holders to coordinate.

  The prefix provided must have write privileges.

Options:

  -http-addr=127.0.0.1:8500  HTTP address of the Consul agent.
  -n=1                       Maximum number of allowed lock holders. If this
                             value is one, it operates as a lock, otherwise
                             a semaphore is used.
  -name=""                   Optional name to associate with lock session.
  -token=""                  ACL token to use. Defaults to that of agent.
  -pass-stdin                Pass stdin to child process.
  -verbose                   Enables verbose output
`
	return strings.TrimSpace(helpText)
}

func (c *LockCommand) Run(args []string) int {
	var childDone chan struct{}
	var name, token string
	var limit int
	var passStdin bool
	cmdFlags := flag.NewFlagSet("watch", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.IntVar(&limit, "n", 1, "")
	cmdFlags.StringVar(&name, "name", "", "")
	cmdFlags.StringVar(&token, "token", "", "")
	cmdFlags.BoolVar(&passStdin, "pass-stdin", false, "")
	cmdFlags.BoolVar(&c.verbose, "verbose", false, "")
	httpAddr := HTTPAddrFlag(cmdFlags)
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	// Check the limit
	if limit <= 0 {
		c.Ui.Error(fmt.Sprintf("Lock holder limit must be positive"))
		return 1
	}

	// Verify the prefix and child are provided
	extra := cmdFlags.Args()
	if len(extra) < 2 {
		c.Ui.Error("Key prefix and child command must be specified")
		c.Ui.Error("")
		c.Ui.Error(c.Help())
		return 1
	}
	prefix := extra[0]
	prefix = strings.TrimPrefix(prefix, "/")
	script := strings.Join(extra[1:], " ")

	// Calculate a session name if none provided
	if name == "" {
		name = fmt.Sprintf("Consul lock for '%s' at '%s'", script, prefix)
	}

	// Create and test the HTTP client
	conf := api.DefaultConfig()
	conf.Address = *httpAddr
	conf.Token = token
	client, err := api.NewClient(conf)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
		return 1
	}
	_, err = client.Agent().NodeName()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
		return 1
	}

	// Setup the lock or semaphore
	var lu *LockUnlock
	if limit == 1 {
		lu, err = c.setupLock(client, prefix, name)
	} else {
		lu, err = c.setupSemaphore(client, limit, prefix, name)
	}
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Lock setup failed: %s", err))
		return 1
	}

	// Attempt the acquisition
	if c.verbose {
		c.Ui.Info("Attempting lock acquisition")
	}
	lockCh, err := lu.lockFn(c.ShutdownCh)
	if lockCh == nil {
		if err == nil {
			c.Ui.Error("Shutdown triggered during lock acquisition")
		} else {
			c.Ui.Error(fmt.Sprintf("Lock acquisition failed: %s", err))
		}
		return 1
	}

	// Check if we were shutdown but managed to still acquire the lock
	select {
	case <-c.ShutdownCh:
		c.Ui.Error("Shutdown triggered during lock acquisition")
		goto RELEASE
	default:
	}

	// Start the child process
	childDone = make(chan struct{})
	go func() {
		if err := c.startChild(script, childDone, passStdin); err != nil {
			c.Ui.Error(fmt.Sprintf("%s", err))
		}
	}()

	// Monitor for shutdown, child termination, or lock loss
	select {
	case <-c.ShutdownCh:
		if c.verbose {
			c.Ui.Info("Shutdown triggered, killing child")
		}
	case <-lockCh:
		if c.verbose {
			c.Ui.Info("Lock lost, killing child")
		}
	case <-childDone:
		if c.verbose {
			c.Ui.Info("Child terminated, releasing lock")
		}
		goto RELEASE
	}

	// Prevent starting a new child.  The lock is never released
	// after this point.
	c.childLock.Lock()
	// Kill any existing child
	if err := c.killChild(childDone); err != nil {
		c.Ui.Error(fmt.Sprintf("%s", err))
	}

RELEASE:
	// Release the lock before termination
	if err := lu.unlockFn(); err != nil {
		c.Ui.Error(fmt.Sprintf("Lock release failed: %s", err))
		return 1
	}

	// Cleanup the lock if no longer in use
	if err := lu.cleanupFn(); err != nil {
		if err != lu.inUseErr {
			c.Ui.Error(fmt.Sprintf("Lock cleanup failed: %s", err))
			return 1
		} else if c.verbose {
			c.Ui.Info("Cleanup aborted, lock in use")
		}
	} else if c.verbose {
		c.Ui.Info("Cleanup succeeded")
	}
	return 0
}

// setupLock is used to setup a new Lock given the API client,
// the key prefix to operate on, and an optional session name.
func (c *LockCommand) setupLock(client *api.Client, prefix, name string) (*LockUnlock, error) {
	// Use the DefaultSemaphoreKey extention, this way if a lock and
	// semaphore are both used at the same prefix, we will get a conflict
	// which we can report to the user.
	key := path.Join(prefix, api.DefaultSemaphoreKey)
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Setting up lock at path: %s", key))
	}
	opts := api.LockOptions{
		Key:         key,
		SessionName: name,
	}
	l, err := client.LockOpts(&opts)
	if err != nil {
		return nil, err
	}
	lu := &LockUnlock{
		lockFn:    l.Lock,
		unlockFn:  l.Unlock,
		cleanupFn: l.Destroy,
		inUseErr:  api.ErrLockInUse,
	}
	return lu, nil
}

// setupSemaphore is used to setup a new Semaphore given the
// API client, key prefix, session name, and slot holder limit.
func (c *LockCommand) setupSemaphore(client *api.Client, limit int, prefix, name string) (*LockUnlock, error) {
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Setting up semaphore (limit %d) at prefix: %s", limit, prefix))
	}
	opts := api.SemaphoreOptions{
		Prefix:      prefix,
		Limit:       limit,
		SessionName: name,
	}
	s, err := client.SemaphoreOpts(&opts)
	if err != nil {
		return nil, err
	}
	lu := &LockUnlock{
		lockFn:    s.Acquire,
		unlockFn:  s.Release,
		cleanupFn: s.Destroy,
		inUseErr:  api.ErrSemaphoreInUse,
	}
	return lu, nil
}

// startChild is a long running routine used to start and
// wait for the child process to exit.
func (c *LockCommand) startChild(script string, doneCh chan struct{}, passStdin bool) error {
	defer close(doneCh)
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Starting handler '%s'", script))
	}
	// Create the command
	cmd, err := agent.ExecScript(script)
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error executing handler: %s", err))
		return err
	}

	// Setup the command streams
	cmd.Env = append(os.Environ(),
		"CONSUL_LOCK_HELD=true",
	)
	if passStdin {
		if c.verbose {
			c.Ui.Info("Stdin passed to handler process")
		}
		cmd.Stdin = os.Stdin
	} else {
		cmd.Stdin = nil
	}
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// Start the child process
	c.childLock.Lock()
	if err := cmd.Start(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error starting handler: %s", err))
		c.childLock.Unlock()
		return err
	}

	// Setup the child info
	c.child = cmd.Process
	c.childLock.Unlock()

	// Wait for the child process
	if err := cmd.Wait(); err != nil {
		c.Ui.Error(fmt.Sprintf("Error running handler: %s", err))
		return err
	}
	return nil
}

// killChild is used to forcefully kill the child, first using SIGTERM
// to allow for a graceful cleanup and then using SIGKILL for a hard
// termination.
// On Windows, the child is always hard terminated with a SIGKILL, even
// on the first attempt.
func (c *LockCommand) killChild(childDone chan struct{}) error {
	// Get the child process
	child := c.child

	// If there is no child process (failed to start), we can quit early
	if child == nil {
		if c.verbose {
			c.Ui.Info("No child process to kill")
		}
		return nil
	}

	// Attempt termination first
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Terminating child pid %d", child.Pid))
	}
	if err := signalPid(child.Pid, syscall.SIGTERM); err != nil {
		return fmt.Errorf("Failed to terminate %d: %v", child.Pid, err)
	}

	// Wait for termination, or until a timeout
	select {
	case <-childDone:
		if c.verbose {
			c.Ui.Info("Child terminated")
		}
		return nil
	case <-time.After(lockKillGracePeriod):
		if c.verbose {
			c.Ui.Info(fmt.Sprintf("Child did not exit after grace period of %v",
				lockKillGracePeriod))
		}
	}

	// Send a final SIGKILL
	if c.verbose {
		c.Ui.Info(fmt.Sprintf("Killing child pid %d", child.Pid))
	}
	if err := signalPid(child.Pid, syscall.SIGKILL); err != nil {
		return fmt.Errorf("Failed to kill %d: %v", child.Pid, err)
	}
	return nil
}

func (c *LockCommand) Synopsis() string {
	return "Execute a command holding a lock"
}

// LockUnlock is used to abstract over the differences between
// a lock and a semaphore.
type LockUnlock struct {
	lockFn    func(<-chan struct{}) (<-chan struct{}, error)
	unlockFn  func() error
	cleanupFn func() error
	inUseErr  error
}
<|endoftext|>