<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"image"
	_ "image/jpeg"
	_ "image/png"
	"io/ioutil"
	"log"
	"os"

	timg "github.com/moshen/gotermimg"
	"github.com/moshen/gotermimg/terminal"
	"github.com/moshen/gotermimg/vendor/gif"
	"github.com/moshen/gotermimg/vendor/termutil"
)

func main() {
	isUTF8 := flag.Bool("u", false, "Enable UTF8 output")
	width := flag.Uint("x", 0, `Scale to n*2 columns wide in ANSI mode, n columns wide in UTF8 mode.
        When -x=0 (the default), aspect ratio is maintained.
        For example if -y is provided without -x, width is scaled to
        maintain aspect ratio`)
	height := flag.Uint("y", 0, `Scale to n rows high in ANSI mode, n/2 rows high in UTF8 mode.
        When -y=0 (the default), aspect ratio is maintained.
        For example if -x is provided without -y, height is scaled to
        maintain aspect ratio`)
	loopTimes := flag.Uint("l", 0, `Loop animation n times
        When -l=0 (the default), animation is looped indefinitely. Supersedes -s
        Only applies to multi-frame gifs`)
	loopSeconds := flag.Uint("s", 0, `Loop animation n seconds
        When -s=0 (the default), this option is ignored.
        Only applies to multi-frame gifs`)

	flag.Usage = func() {
		fmt.Fprint(os.Stderr, `Usage: gotermimg [-u] [-x=n] [-y=n] [-l=n|-s=n] [IMAGEFILE]
  IMAGEFILE - png, gif or jpg.  gif will auto-play.
  Image data can be piped to stdin instead of providing IMAGEFILE.

  If neither -x or -y are provided, and the image is larger than your current
  terminal, it will be automatically scaled to fit.

`)
		flag.PrintDefaults()
	}

	flag.Parse()

	var buf *bytes.Reader
	switch {
	case !termutil.Isatty(os.Stdin.Fd()):
		bufData, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			log.Fatal(err)
		}
		buf = bytes.NewReader(bufData)
	case len(flag.Args()) < 1:
		flag.Usage()
		os.Exit(1)
	default:
		file, err := os.Open(flag.Arg(0))
		if err != nil {
			log.Fatal(err)
		}
		bufData, err := ioutil.ReadAll(file)
		if err != nil {
			log.Fatal(err)
		}
		file.Close()
		buf = bytes.NewReader(bufData)
	}

	conf, imgformat, err := image.DecodeConfig(buf)
	if err != nil {
		log.Fatal(err)
	}
	buf.Seek(0, 0)

	var conv timg.Converter
	if *isUTF8 {
		conv = timg.UTF8
	} else {
		conv = timg.ANSI
	}

	var trans timg.Transformer
	if *width != 0 || *height != 0 {
		trans = timg.Resize(*width, *height)
	} else if termutil.Isatty(os.Stdout.Fd()) {
		x, y, err := terminal.Size(os.Stdout.Fd())
		if err != nil {
			log.Fatal(err)
		}

		// Convert the actual terminal dimensions into effective dimensions
		switch {
		case *isUTF8:
			y = y * 2
		case x%2 == 0:
			x = x / 2
		default:
			x = (x - 1) / 2
		}

		switch {
		case uint(conf.Width) > x && uint(conf.Height) > y:
			if conf.Width > conf.Height {
				trans = timg.Resize(0, y)
			} else {
				trans = timg.Resize(x, 0)
			}
		case uint(conf.Width) > x:
			trans = timg.Resize(x, 0)
		case uint(conf.Height) > y:
			trans = timg.Resize(0, y)
		}
	}

	if imgformat == "gif" {
		gifimg, err := gif.DecodeAll(buf)
		if err != nil {
			log.Fatal(err)
		}

		if len(gifimg.Image) > 1 {
			var loop timg.KeepLooping
			switch {
			// Don't bother looping if we're not outputting to a tty
			case !termutil.Isatty(os.Stdout.Fd()):
				loop = timg.LoopTimes(1)
			case *loopTimes > 0:
				loop = timg.LoopTimes(*loopTimes)
			case *loopSeconds > 0:
				loop = timg.LoopSeconds(*loopSeconds)
			}

			timg.PrintAnimation(timg.Gif(gifimg, conv, trans), loop)
		} else {
			timg.PrintImage(gifimg.Image[0], conv, trans)
		}
	} else {
		img, _, err := image.Decode(buf)
		if err != nil {
			log.Fatal(err)
		}
		timg.PrintImage(img, conv, trans)
	}
}
<commit_msg>Fix auto-resize to fit current term<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"image"
	_ "image/jpeg"
	_ "image/png"
	"io/ioutil"
	"log"
	"os"

	timg "github.com/moshen/gotermimg"
	"github.com/moshen/gotermimg/terminal"
	"github.com/moshen/gotermimg/vendor/gif"
	"github.com/moshen/gotermimg/vendor/termutil"
)

func main() {
	isUTF8 := flag.Bool("u", false, "Enable UTF8 output")
	width := flag.Uint("x", 0, `Scale to n*2 columns wide in ANSI mode, n columns wide in UTF8 mode.
        When -x=0 (the default), aspect ratio is maintained.
        For example if -y is provided without -x, width is scaled to
        maintain aspect ratio`)
	height := flag.Uint("y", 0, `Scale to n rows high in ANSI mode, n/2 rows high in UTF8 mode.
        When -y=0 (the default), aspect ratio is maintained.
        For example if -x is provided without -y, height is scaled to
        maintain aspect ratio`)
	loopTimes := flag.Uint("l", 0, `Loop animation n times
        When -l=0 (the default), animation is looped indefinitely. Supersedes -s
        Only applies to multi-frame gifs`)
	loopSeconds := flag.Uint("s", 0, `Loop animation n seconds
        When -s=0 (the default), this option is ignored.
        Only applies to multi-frame gifs`)

	flag.Usage = func() {
		fmt.Fprint(os.Stderr, `Usage: gotermimg [-u] [-x=n] [-y=n] [-l=n|-s=n] [IMAGEFILE]
  IMAGEFILE - png, gif or jpg.  gif will auto-play.
  Image data can be piped to stdin instead of providing IMAGEFILE.

  If neither -x or -y are provided, and the image is larger than your current
  terminal, it will be automatically scaled to fit.

`)
		flag.PrintDefaults()
	}

	flag.Parse()

	var buf *bytes.Reader
	switch {
	case !termutil.Isatty(os.Stdin.Fd()):
		bufData, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			log.Fatal(err)
		}
		buf = bytes.NewReader(bufData)
	case len(flag.Args()) < 1:
		flag.Usage()
		os.Exit(1)
	default:
		file, err := os.Open(flag.Arg(0))
		if err != nil {
			log.Fatal(err)
		}
		bufData, err := ioutil.ReadAll(file)
		if err != nil {
			log.Fatal(err)
		}
		file.Close()
		buf = bytes.NewReader(bufData)
	}

	conf, imgformat, err := image.DecodeConfig(buf)
	if err != nil {
		log.Fatal(err)
	}
	buf.Seek(0, 0)

	var conv timg.Converter
	if *isUTF8 {
		conv = timg.UTF8
	} else {
		conv = timg.ANSI
	}

	var trans timg.Transformer
	if *width != 0 || *height != 0 {
		trans = timg.Resize(*width, *height)
	} else if termutil.Isatty(os.Stdout.Fd()) {
		x, y, err := terminal.Size(os.Stdout.Fd())
		if err != nil {
			log.Fatal(err)
		}

		y = y - 1

		// Convert the actual terminal dimensions into effective dimensions
		switch {
		case *isUTF8:
			y = y * 2
		case x%2 == 0:
			x = x / 2
		default:
			x = (x - 1) / 2
		}

		if uint(conf.Width) > x || uint(conf.Height) > y {
			aspectTerm := float32(x) / float32(y)
			aspectImg := float32(conf.Width) / float32(conf.Height)

			if aspectImg > aspectTerm {
				trans = timg.Resize(x, 0)
			} else {
				trans = timg.Resize(0, y)
			}
		}
	}

	if imgformat == "gif" {
		gifimg, err := gif.DecodeAll(buf)
		if err != nil {
			log.Fatal(err)
		}

		if len(gifimg.Image) > 1 {
			var loop timg.KeepLooping
			switch {
			// Don't bother looping if we're not outputting to a tty
			case !termutil.Isatty(os.Stdout.Fd()):
				loop = timg.LoopTimes(1)
			case *loopTimes > 0:
				loop = timg.LoopTimes(*loopTimes)
			case *loopSeconds > 0:
				loop = timg.LoopSeconds(*loopSeconds)
			}

			timg.PrintAnimation(timg.Gif(gifimg, conv, trans), loop)
		} else {
			timg.PrintImage(gifimg.Image[0], conv, trans)
		}
	} else {
		img, _, err := image.Decode(buf)
		if err != nil {
			log.Fatal(err)
		}
		timg.PrintImage(img, conv, trans)
	}
}
<|endoftext|>


<commit_before>/*
Copyright The Helm Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bytes"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"testing"

	shellwords "github.com/mattn/go-shellwords"
	"github.com/spf13/cobra"

	"helm.sh/helm/v3/internal/test"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/chartutil"
	kubefake "helm.sh/helm/v3/pkg/kube/fake"
	"helm.sh/helm/v3/pkg/release"
	"helm.sh/helm/v3/pkg/storage"
	"helm.sh/helm/v3/pkg/storage/driver"
	"helm.sh/helm/v3/pkg/time"
)

func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }

func init() {
	action.Timestamper = testTimestamper
}

func runTestCmd(t *testing.T, tests []cmdTestCase) {
	t.Helper()
	for _, tt := range tests {
		for i := 0; i <= tt.repeat; i++ {
			t.Run(tt.name, func(t *testing.T) {
				defer resetEnv()()

				storage := storageFixture()
				for _, rel := range tt.rels {
					if err := storage.Create(rel); err != nil {
						t.Fatal(err)
					}
				}
				t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd)
				_, out, err := executeActionCommandC(storage, tt.cmd)
				if (err != nil) != tt.wantError {
					t.Errorf("expected error, got '%v'", err)
				}
				if tt.golden != "" {
					test.AssertGoldenString(t, out, tt.golden)
				}
			})
		}
	}
}

func runTestActionCmd(t *testing.T, tests []cmdTestCase) {
	t.Helper()
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer resetEnv()()

			store := storageFixture()
			for _, rel := range tt.rels {
				store.Create(rel)
			}
			_, out, err := executeActionCommandC(store, tt.cmd)
			if (err != nil) != tt.wantError {
				t.Errorf("expected error, got '%v'", err)
			}
			if tt.golden != "" {
				test.AssertGoldenString(t, out, tt.golden)
			}
		})
	}
}

func storageFixture() *storage.Storage {
	return storage.Init(driver.NewMemory())
}

func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) {
	args, err := shellwords.Parse(cmd)
	if err != nil {
		return nil, "", err
	}
	buf := new(bytes.Buffer)

	actionConfig := &action.Configuration{
		Releases:     store,
		KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
		Capabilities: chartutil.DefaultCapabilities,
		Log:          func(format string, v ...interface{}) {},
	}

	root := newRootCmd(actionConfig, buf, args)
	root.SetOutput(buf)
	root.SetArgs(args)

	c, err := root.ExecuteC()

	return c, buf.String(), err
}

// cmdTestCase describes a test case that works with releases.
type cmdTestCase struct {
	name      string
	cmd       string
	golden    string
	wantError bool
	// Rels are the available releases at the start of the test.
	rels []*release.Release
	// Number of repeats (in case a feature was previously flaky and the test checks
	// it's now stably producing identical results). 0 means test is run exactly once.
	repeat int
}

func executeActionCommand(cmd string) (*cobra.Command, string, error) {
	return executeActionCommandC(storageFixture(), cmd)
}

func resetEnv() func() {
	origSettings, origEnv := settings, os.Environ()
	return func() {
		os.Clearenv()
		settings = origSettings
		for _, pair := range origEnv {
			kv := strings.SplitN(pair, "=", 2)
			os.Setenv(kv[0], kv[1])
		}
	}
}

func testChdir(t *testing.T, dir string) func() {
	t.Helper()
	old, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(dir); err != nil {
		t.Fatal(err)
	}
	return func() { os.Chdir(old) }
}

func TestPluginExitCode(t *testing.T) {
	if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" {
		os.Args = []string{"helm", "exitwith", "2"}

		// We DO call helm's main() here. So this looks like a normal `helm` process.
		main()

		// As main calls os.Exit, we never reach this line.
		// But the test called this block of code catches and verifies the exit code.
		return
	}

	// Currently, plugins assume a Linux subsystem. Skip the execution
	// tests until this is fixed
	if runtime.GOOS != "windows" {
		// Do a second run of this specific test(TestPluginExitCode) with RUN_MAIN_FOR_TESTING=1 set,
		// So that the second run is able to run main() and this first run can verify the exit status returned by that.
		//
		// This technique originates from https://talks.golang.org/2014/testing.slide#23.
		cmd := exec.Command(os.Args[0], "-test.run=TestPluginExitCode")
		cmd.Env = append(
			os.Environ(),
			"RUN_MAIN_FOR_TESTING=1",
			// See pkg/cli/environment.go for which envvars can be used for configuring these passes
			// and also see plugin_test.go for how a plugin env can be set up.
			// We just does the same setup as plugin_test.go via envvars
			"HELM_PLUGINS=testdata/helmhome/helm/plugins",
			"HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml",
			"HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository",
		)
		stdout := &bytes.Buffer{}
		stderr := &bytes.Buffer{}
		cmd.Stdout = stdout
		cmd.Stderr = stderr
		err := cmd.Run()
		exiterr, ok := err.(*exec.ExitError)

		if !ok {
			t.Fatalf("Unexpected error returned by os.Exit: %T", err)
		}

		if stdout.String() != "" {
			t.Errorf("Expected no write to stdout: Got %q", stdout.String())
		}

		expectedStderr := "Error: plugin \"exitwith\" exited with error\n"
		if stderr.String() != expectedStderr {
			t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String())
		}

		if exiterr.ExitCode() != 2 {
			t.Errorf("Expected exit code 2: Got %d", exiterr.ExitCode())
		}
	}
}
<commit_msg>fix(test): Make resetEnv() properly reset settings<commit_after>/*
Copyright The Helm Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bytes"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"testing"

	shellwords "github.com/mattn/go-shellwords"
	"github.com/spf13/cobra"

	"helm.sh/helm/v3/internal/test"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/cli"
	kubefake "helm.sh/helm/v3/pkg/kube/fake"
	"helm.sh/helm/v3/pkg/release"
	"helm.sh/helm/v3/pkg/storage"
	"helm.sh/helm/v3/pkg/storage/driver"
	"helm.sh/helm/v3/pkg/time"
)

func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }

func init() {
	action.Timestamper = testTimestamper
}

func runTestCmd(t *testing.T, tests []cmdTestCase) {
	t.Helper()
	for _, tt := range tests {
		for i := 0; i <= tt.repeat; i++ {
			t.Run(tt.name, func(t *testing.T) {
				defer resetEnv()()

				storage := storageFixture()
				for _, rel := range tt.rels {
					if err := storage.Create(rel); err != nil {
						t.Fatal(err)
					}
				}
				t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd)
				_, out, err := executeActionCommandC(storage, tt.cmd)
				if (err != nil) != tt.wantError {
					t.Errorf("expected error, got '%v'", err)
				}
				if tt.golden != "" {
					test.AssertGoldenString(t, out, tt.golden)
				}
			})
		}
	}
}

func runTestActionCmd(t *testing.T, tests []cmdTestCase) {
	t.Helper()
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer resetEnv()()

			store := storageFixture()
			for _, rel := range tt.rels {
				store.Create(rel)
			}
			_, out, err := executeActionCommandC(store, tt.cmd)
			if (err != nil) != tt.wantError {
				t.Errorf("expected error, got '%v'", err)
			}
			if tt.golden != "" {
				test.AssertGoldenString(t, out, tt.golden)
			}
		})
	}
}

func storageFixture() *storage.Storage {
	return storage.Init(driver.NewMemory())
}

func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command, string, error) {
	args, err := shellwords.Parse(cmd)
	if err != nil {
		return nil, "", err
	}
	buf := new(bytes.Buffer)

	actionConfig := &action.Configuration{
		Releases:     store,
		KubeClient:   &kubefake.PrintingKubeClient{Out: ioutil.Discard},
		Capabilities: chartutil.DefaultCapabilities,
		Log:          func(format string, v ...interface{}) {},
	}

	root := newRootCmd(actionConfig, buf, args)
	root.SetOutput(buf)
	root.SetArgs(args)

	c, err := root.ExecuteC()

	return c, buf.String(), err
}

// cmdTestCase describes a test case that works with releases.
type cmdTestCase struct {
	name      string
	cmd       string
	golden    string
	wantError bool
	// Rels are the available releases at the start of the test.
	rels []*release.Release
	// Number of repeats (in case a feature was previously flaky and the test checks
	// it's now stably producing identical results). 0 means test is run exactly once.
	repeat int
}

func executeActionCommand(cmd string) (*cobra.Command, string, error) {
	return executeActionCommandC(storageFixture(), cmd)
}

func resetEnv() func() {
	origEnv := os.Environ()
	return func() {
		os.Clearenv()
		for _, pair := range origEnv {
			kv := strings.SplitN(pair, "=", 2)
			os.Setenv(kv[0], kv[1])
		}
		settings = cli.New()
	}
}

func testChdir(t *testing.T, dir string) func() {
	t.Helper()
	old, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Chdir(dir); err != nil {
		t.Fatal(err)
	}
	return func() { os.Chdir(old) }
}

func TestPluginExitCode(t *testing.T) {
	if os.Getenv("RUN_MAIN_FOR_TESTING") == "1" {
		os.Args = []string{"helm", "exitwith", "2"}

		// We DO call helm's main() here. So this looks like a normal `helm` process.
		main()

		// As main calls os.Exit, we never reach this line.
		// But the test called this block of code catches and verifies the exit code.
		return
	}

	// Currently, plugins assume a Linux subsystem. Skip the execution
	// tests until this is fixed
	if runtime.GOOS != "windows" {
		// Do a second run of this specific test(TestPluginExitCode) with RUN_MAIN_FOR_TESTING=1 set,
		// So that the second run is able to run main() and this first run can verify the exit status returned by that.
		//
		// This technique originates from https://talks.golang.org/2014/testing.slide#23.
		cmd := exec.Command(os.Args[0], "-test.run=TestPluginExitCode")
		cmd.Env = append(
			os.Environ(),
			"RUN_MAIN_FOR_TESTING=1",
			// See pkg/cli/environment.go for which envvars can be used for configuring these passes
			// and also see plugin_test.go for how a plugin env can be set up.
			// We just does the same setup as plugin_test.go via envvars
			"HELM_PLUGINS=testdata/helmhome/helm/plugins",
			"HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml",
			"HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository",
		)
		stdout := &bytes.Buffer{}
		stderr := &bytes.Buffer{}
		cmd.Stdout = stdout
		cmd.Stderr = stderr
		err := cmd.Run()
		exiterr, ok := err.(*exec.ExitError)

		if !ok {
			t.Fatalf("Unexpected error returned by os.Exit: %T", err)
		}

		if stdout.String() != "" {
			t.Errorf("Expected no write to stdout: Got %q", stdout.String())
		}

		expectedStderr := "Error: plugin \"exitwith\" exited with error\n"
		if stderr.String() != expectedStderr {
			t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String())
		}

		if exiterr.ExitCode() != 2 {
			t.Errorf("Expected exit code 2: Got %d", exiterr.ExitCode())
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"sync"
	"time"
)

var mtx sync.Mutex = sync.Mutex{}
var runner *exec.Cmd

// runBuild will run a build command and restart the package
func runBuild(config Config) {

	// do only allow one build command at the time
	mtx.Lock()
	defer mtx.Unlock()

	// kill any current runner
	if runner != nil {
		runner.Process.Kill() // nolint:errcheck
	}

	log.Println("BUILDING\n----------------------------")

	builder := exec.Command("go", "build", "-o", "/tmp/app")
	builder.Dir = config.Directory
	builder.Stdout = os.Stdout
	builder.Stderr = os.Stderr
	err := builder.Run()
	if err != nil {
		log.Printf("[ERROR] %+v", err)
		return
	}

	log.Println("RUNNING\n----------------------------")

	// executing the binary
	runner = exec.Command("/tmp/app", config.Arguments...)
	runner.Dir = config.Directory
	runner.Stdout = os.Stdout
	runner.Stderr = os.Stderr
	err = runner.Start()
	if err != nil {
		log.Printf("[ERROR] %+v", err)
	}

	<-time.After(time.Millisecond * 300)

}

// runTest will run go test on the package
func runTest(config Config) {

	// do only allow one build command at the time
	mtx.Lock()
	defer mtx.Unlock()

	// kill any current runner
	if runner != nil {
		runner.Process.Kill() // nolint:errcheck
	}

	// clear the screen before each new run
	log.Println("TESTING\n----------------------------")

	// testing
	arguments := append([]string{"test"}, config.Arguments...)
	runner = exec.Command("go", arguments...)
	runner.Dir = config.Directory
	runner.Stdout = os.Stdout
	runner.Stderr = os.Stderr

	// run the program
	err := runner.Run()
	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR: could not run go test", err)
	}

	return

}
<commit_msg>abort build process if new build should occur<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"sync"
	"time"
)

var mtx sync.Mutex = sync.Mutex{}
var runner *exec.Cmd
var builder *exec.Cmd

// runBuild will run a build command and restart the package
func runBuild(config Config) {

	// kill any current builder
	if builder != nil {
		builder.Process.Kill() // nolint:errcheck
		builder = nil
	}

	mtx.Lock()
	defer mtx.Unlock()

	fmt.Printf("\n%s RELOAD\n----------------------------\n", time.Now().Format("2006/01/02 15:04:05"))

	builder = exec.Command("go", "build", "-o", "/tmp/app")
	builder.Dir = config.Directory
	builder.Stdout = os.Stdout
	builder.Stderr = os.Stderr
	err := builder.Run()
	if err != nil {
		log.Printf("[BUILD ERROR] %+v", err)
		return
	}

	// kill any current runner
	if runner != nil {
		runner.Process.Kill() // nolint:errcheck
		runner = nil
	}

	// executing the binary
	runner = exec.Command("/tmp/app", config.Arguments...)
	runner.Dir = config.Directory
	runner.Stdout = os.Stdout
	runner.Stderr = os.Stderr
	err = runner.Start()
	if err != nil {
		log.Printf("[RUN ERROR] %+v", err)
	}

}

// runTest will run go test on the package
func runTest(config Config) {

	// kill any current runner
	if runner != nil {
		runner.Process.Kill() // nolint:errcheck
	}

	// clear the screen before each new run
	log.Println("TESTING\n----------------------------")

	// testing
	arguments := append([]string{"test"}, config.Arguments...)
	runner = exec.Command("go", arguments...)
	runner.Dir = config.Directory
	runner.Stdout = os.Stdout
	runner.Stderr = os.Stderr

	// run the program
	err := runner.Run()
	if err != nil {
		log.Printf("[TEST ERROR] %+v", err)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"runtime"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	raven "github.com/getsentry/raven-go"
	"github.com/honeycombio/beeline-go"
	libhoney "github.com/honeycombio/libhoney-go"
	librato "github.com/mihasya/go-metrics-librato"
	metrics "github.com/rcrowley/go-metrics"
	travismetrics "github.com/travis-ci/jupiter-brain/metrics"
	"github.com/travis-ci/jupiter-brain/server"
)

func main() {
	app := cli.NewApp()
	app.Usage = "Jupiter Brain API server"
	app.Author = "Travis CI"
	app.Email = "contact+jupiter-brain@travis-ci.org"
	app.Version = VersionString
	app.Compiled = GeneratedTime()
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "addr",
			Usage: "host:port to listen to",
			Value: func() string {
				v := ":" + os.Getenv("PORT")
				if v == ":" {
					v = ":42161"
				}
				return v
			}(),
			EnvVar: "JUPITER_BRAIN_ADDR",
		},
		cli.StringFlag{
			Name:   "auth-token",
			Usage:  "authentication token for the api server",
			EnvVar: "JUPITER_BRAIN_AUTH_TOKEN",
		},
		cli.StringFlag{
			Name:   "vsphere-api-url",
			Usage:  "URL to vSphere API",
			EnvVar: "JUPITER_BRAIN_VSPHERE_API_URL,VSPHERE_API_URL",
		},
		cli.StringFlag{
			Name:   "vsphere-base-path",
			Usage:  "path to folder of base VMs in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_BASE_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-vm-path",
			Usage:  "path to folder where VMs will be put in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_VM_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-cluster-path",
			Usage:  "path to compute cluster that VMs will be booted in",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CLUSTER_PATH",
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-read-operations",
			Usage:  "number of concurrent fetch and list operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_READ_OPERATIONS",
			Value:  4,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-create-operations",
			Usage:  "number of concurrent start operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_CREATE_OPERATIONS",
			Value:  48,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-delete-operations",
			Usage:  "number of concurrent terminate operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_DELETE_OPERATIONS",
			Value:  48,
		},
		cli.StringFlag{
			Name:   "database-url",
			Usage:  "URL to the PostgreSQL database",
			EnvVar: "JUPITER_BRAIN_DATABASE_URL,DATABASE_URL",
		},
		cli.IntFlag{
			Name:   "database-pool-size",
			Usage:  "pool size",
			EnvVar: "JUPITER_BRAIN_DATABASE_POOL_SIZE,DATABASE_POOL_SIZE",
			Value:  2,
		},
		cli.BoolFlag{
			Name:   "debug",
			Usage:  "enable debug logging",
			EnvVar: "JUPITER_BRAIN_DEBUG,DEBUG",
		},
		cli.StringFlag{
			Name:   "sentry-dsn",
			Usage:  "Sentry DSN to send errors to",
			EnvVar: "JUPITER_BRAIN_SENTRY_DSN,SENTRY_DSN",
		},
		cli.StringFlag{
			Name:   "sentry-environment",
			Usage:  "Environment name to pass to Sentry",
			EnvVar: "JUPITER_BRAIN_SENTRY_ENVIRONMENT,SENTRY_ENVIRONMENT",
		},
		cli.StringFlag{
			Name:   "librato-email",
			Usage:  "Email for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_EMAIL,LIBRATO_EMAIL",
		},
		cli.StringFlag{
			Name:   "librato-token",
			Usage:  "Token for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_TOKEN,LIBRATO_TOKEN",
		},
		cli.StringFlag{
			Name:   "librato-source",
			Usage:  "The source to use when sending metrics to Librato",
			EnvVar: "JUPITER_BRAIN_LIBRATO_SOURCE,LIBRATO_SOURCE",
		},
		cli.StringFlag{
			Name:   "pprof-addr",
			Usage:  "Whether to enable pprof endpoints over HTTP",
			EnvVar: "JUPITER_BRAIN_PPROF_ADDR",
		},
		cli.DurationFlag{
			Name:   "request-timeout",
			Usage:  "The max time an incoming HTTP request can take before timing out",
			EnvVar: "JUPITER_BRAIN_REQUEST_TIMEOUT",
			Value:  3 * time.Minute,
		},
		cli.StringFlag{
			Name:   "honeycomb-write-key",
			Usage:  "The write key for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_WRITE_KEY",
		},
		cli.StringFlag{
			Name:   "honeycomb-dataset",
			Usage:  "The dataset name for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_DATASET",
		},
		cli.StringFlag{
			Name:   "honeycomb-request-dataset",
			Usage:  "The dataset name for Honeycomb to track HTTP requests",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_REQUEST_DATASET",
		},
	}
	app.Action = runServer

	app.RunAndExitOnError()
}

func runServer(c *cli.Context) {
	logrus.SetFormatter(&logrus.TextFormatter{DisableColors: true})

	if c.String("librato-email") != "" && c.String("librato-token") != "" && c.String("librato-source") != "" {
		logrus.Info("starting librato metrics reporter")

		go librato.Librato(
			metrics.DefaultRegistry,
			time.Minute,
			c.String("librato-email"),
			c.String("librato-token"),
			c.String("librato-source"),
			[]float64{0.50, 0.75, 0.90, 0.95, 0.99, 0.999, 1.0},
			time.Millisecond,
		)
	}
	go travismetrics.ReportMemstatsMetrics()

	if c.String("honeycomb-write-key") != "" && c.String("honeycomb-request-dataset") != "" {
		beeline.Init(beeline.Config{
			WriteKey:    c.String("honeycomb-write-key"),
			Dataset:     c.String("honeycomb-request-dataset"),
			ServiceName: c.String("librato-source"),
		})

		libhoney.AddDynamicField("meta.goroutines", func() interface{} { return runtime.NumGoroutine() })
		libhoney.AddField("app.version", c.App.Version)
	}

	raven.SetDSN(c.String("sentry-dsn"))
	raven.SetRelease(VersionString)
	if c.String("sentry-environment") != "" {
		raven.SetEnvironment(c.String("sentry-environment"))
	}

	server.Main(&server.Config{
		Addr:              c.String("addr"),
		AuthToken:         c.String("auth-token"),
		Debug:             c.Bool("debug"),
		SentryDSN:         c.String("sentry-dsn"),
		SentryEnvironment: c.String("sentry-environment"),

		VSphereURL:                        c.String("vsphere-api-url"),
		VSphereBasePath:                   c.String("vsphere-base-path"),
		VSphereVMPath:                     c.String("vsphere-vm-path"),
		VSphereClusterPath:                c.String("vsphere-cluster-path"),
		VSphereConcurrentReadOperations:   c.Int("vsphere-concurrent-read-operations"),
		VSphereConcurrentCreateOperations: c.Int("vsphere-concurrent-create-operations"),
		VSphereConcurrentDeleteOperations: c.Int("vsphere-concurrent-delete-operations"),

		DatabaseURL:      c.String("database-url"),
		DatabasePoolSize: c.Int("database-pool-size"),

		PprofAddr:      c.String("pprof-addr"),
		RequestTimeout: c.Duration("request-timeout"),
	})
}
<commit_msg>Add environment variable for Honeycomb sample rate<commit_after>package main

import (
	"os"
	"runtime"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	raven "github.com/getsentry/raven-go"
	"github.com/honeycombio/beeline-go"
	libhoney "github.com/honeycombio/libhoney-go"
	librato "github.com/mihasya/go-metrics-librato"
	metrics "github.com/rcrowley/go-metrics"
	travismetrics "github.com/travis-ci/jupiter-brain/metrics"
	"github.com/travis-ci/jupiter-brain/server"
)

func main() {
	app := cli.NewApp()
	app.Usage = "Jupiter Brain API server"
	app.Author = "Travis CI"
	app.Email = "contact+jupiter-brain@travis-ci.org"
	app.Version = VersionString
	app.Compiled = GeneratedTime()
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "addr",
			Usage: "host:port to listen to",
			Value: func() string {
				v := ":" + os.Getenv("PORT")
				if v == ":" {
					v = ":42161"
				}
				return v
			}(),
			EnvVar: "JUPITER_BRAIN_ADDR",
		},
		cli.StringFlag{
			Name:   "auth-token",
			Usage:  "authentication token for the api server",
			EnvVar: "JUPITER_BRAIN_AUTH_TOKEN",
		},
		cli.StringFlag{
			Name:   "vsphere-api-url",
			Usage:  "URL to vSphere API",
			EnvVar: "JUPITER_BRAIN_VSPHERE_API_URL,VSPHERE_API_URL",
		},
		cli.StringFlag{
			Name:   "vsphere-base-path",
			Usage:  "path to folder of base VMs in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_BASE_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-vm-path",
			Usage:  "path to folder where VMs will be put in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_VM_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-cluster-path",
			Usage:  "path to compute cluster that VMs will be booted in",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CLUSTER_PATH",
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-read-operations",
			Usage:  "number of concurrent fetch and list operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_READ_OPERATIONS",
			Value:  4,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-create-operations",
			Usage:  "number of concurrent start operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_CREATE_OPERATIONS",
			Value:  48,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-delete-operations",
			Usage:  "number of concurrent terminate operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_DELETE_OPERATIONS",
			Value:  48,
		},
		cli.StringFlag{
			Name:   "database-url",
			Usage:  "URL to the PostgreSQL database",
			EnvVar: "JUPITER_BRAIN_DATABASE_URL,DATABASE_URL",
		},
		cli.IntFlag{
			Name:   "database-pool-size",
			Usage:  "pool size",
			EnvVar: "JUPITER_BRAIN_DATABASE_POOL_SIZE,DATABASE_POOL_SIZE",
			Value:  2,
		},
		cli.BoolFlag{
			Name:   "debug",
			Usage:  "enable debug logging",
			EnvVar: "JUPITER_BRAIN_DEBUG,DEBUG",
		},
		cli.StringFlag{
			Name:   "sentry-dsn",
			Usage:  "Sentry DSN to send errors to",
			EnvVar: "JUPITER_BRAIN_SENTRY_DSN,SENTRY_DSN",
		},
		cli.StringFlag{
			Name:   "sentry-environment",
			Usage:  "Environment name to pass to Sentry",
			EnvVar: "JUPITER_BRAIN_SENTRY_ENVIRONMENT,SENTRY_ENVIRONMENT",
		},
		cli.StringFlag{
			Name:   "librato-email",
			Usage:  "Email for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_EMAIL,LIBRATO_EMAIL",
		},
		cli.StringFlag{
			Name:   "librato-token",
			Usage:  "Token for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_TOKEN,LIBRATO_TOKEN",
		},
		cli.StringFlag{
			Name:   "librato-source",
			Usage:  "The source to use when sending metrics to Librato",
			EnvVar: "JUPITER_BRAIN_LIBRATO_SOURCE,LIBRATO_SOURCE",
		},
		cli.StringFlag{
			Name:   "pprof-addr",
			Usage:  "Whether to enable pprof endpoints over HTTP",
			EnvVar: "JUPITER_BRAIN_PPROF_ADDR",
		},
		cli.DurationFlag{
			Name:   "request-timeout",
			Usage:  "The max time an incoming HTTP request can take before timing out",
			EnvVar: "JUPITER_BRAIN_REQUEST_TIMEOUT",
			Value:  3 * time.Minute,
		},
		cli.StringFlag{
			Name:   "honeycomb-write-key",
			Usage:  "The write key for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_WRITE_KEY",
		},
		cli.StringFlag{
			Name:   "honeycomb-dataset",
			Usage:  "The dataset name for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_DATASET",
		},
		cli.StringFlag{
			Name:   "honeycomb-request-dataset",
			Usage:  "The dataset name for Honeycomb to track HTTP requests",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_REQUEST_DATASET",
		},
		cli.IntFlag{
			Name:   "honeycomb-sample-rate",
			Usage:  "The rate at which to sample Honeycomb events",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_SAMPLE_RATE",
			Value:  1,
		},
	}
	app.Action = runServer

	app.RunAndExitOnError()
}

func runServer(c *cli.Context) {
	logrus.SetFormatter(&logrus.TextFormatter{DisableColors: true})

	if c.String("librato-email") != "" && c.String("librato-token") != "" && c.String("librato-source") != "" {
		logrus.Info("starting librato metrics reporter")

		go librato.Librato(
			metrics.DefaultRegistry,
			time.Minute,
			c.String("librato-email"),
			c.String("librato-token"),
			c.String("librato-source"),
			[]float64{0.50, 0.75, 0.90, 0.95, 0.99, 0.999, 1.0},
			time.Millisecond,
		)
	}
	go travismetrics.ReportMemstatsMetrics()

	if c.String("honeycomb-write-key") != "" && c.String("honeycomb-request-dataset") != "" {
		beeline.Init(beeline.Config{
			WriteKey:    c.String("honeycomb-write-key"),
			Dataset:     c.String("honeycomb-request-dataset"),
			ServiceName: c.String("librato-source"),
			SampleRate:  uint(c.Int("honeycomb-sample-rate")),
		})

		libhoney.AddDynamicField("meta.goroutines", func() interface{} { return runtime.NumGoroutine() })
		libhoney.AddField("app.version", c.App.Version)
	}

	raven.SetDSN(c.String("sentry-dsn"))
	raven.SetRelease(VersionString)
	if c.String("sentry-environment") != "" {
		raven.SetEnvironment(c.String("sentry-environment"))
	}

	server.Main(&server.Config{
		Addr:              c.String("addr"),
		AuthToken:         c.String("auth-token"),
		Debug:             c.Bool("debug"),
		SentryDSN:         c.String("sentry-dsn"),
		SentryEnvironment: c.String("sentry-environment"),

		VSphereURL:                        c.String("vsphere-api-url"),
		VSphereBasePath:                   c.String("vsphere-base-path"),
		VSphereVMPath:                     c.String("vsphere-vm-path"),
		VSphereClusterPath:                c.String("vsphere-cluster-path"),
		VSphereConcurrentReadOperations:   c.Int("vsphere-concurrent-read-operations"),
		VSphereConcurrentCreateOperations: c.Int("vsphere-concurrent-create-operations"),
		VSphereConcurrentDeleteOperations: c.Int("vsphere-concurrent-delete-operations"),

		DatabaseURL:      c.String("database-url"),
		DatabasePoolSize: c.Int("database-pool-size"),

		PprofAddr:      c.String("pprof-addr"),
		RequestTimeout: c.Duration("request-timeout"),
	})
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"launchpad.net/gnuflag"
	. "launchpad.net/gocheck"
	_ "launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/testing"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	stdtesting "testing"
)

func TestPackage(t *stdtesting.T) {
	testing.MgoTestPackage(t)
}

type MainSuite struct{}

var _ = Suite(&MainSuite{})

var flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing")

// Reentrancy point for testing (something as close as possible to) the juju
// tool itself.
func TestRunMain(t *stdtesting.T) {
	if *flagRunMain {
		Main(flag.Args())
	}
}

func badrun(c *C, exit int, cmd ...string) string {
	args := append([]string{"-test.run", "TestRunMain", "-run-main", "--", "juju"}, cmd...)
	ps := exec.Command(os.Args[0], args...)
	output, err := ps.CombinedOutput()
	if exit != 0 {
		c.Assert(err, ErrorMatches, fmt.Sprintf("exit status %d", exit))
	}
	return string(output)
}

func deployHelpText() string {
	buff := &bytes.Buffer{}
	dc := &DeployCommand{}
	info := dc.Info()
	info.Name = "juju deploy"
	f := gnuflag.NewFlagSet(info.Name, gnuflag.ContinueOnError)
	dc.SetFlags(f)
	buff.Write(info.Help(f))
	return buff.String()
}

var runMainTests = []struct {
	summary string
	args    []string
	code    int
	out     string
}{
	{
		summary: "no params shows help",
		args:    []string{},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju help is the same as juju",
		args:    []string{"help"},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju --help works too",
		args:    []string{"--help"},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju help basics is the same as juju",
		args:    []string{"help", "basics"},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju help foo doesn't exist",
		args:    []string{"help", "foo"},
		code:    1,
		out:     "error: unknown command or topic for foo\n",
	}, {
		summary: "juju help deploy shows the default help without global options",
		args:    []string{"help", "deploy"},
		code:    0,
		out:     deployHelpText(),
	}, {
		summary: "juju --help deploy shows the same help as 'help deploy'",
		args:    []string{"--help", "deploy"},
		code:    0,
		out:     deployHelpText(),
	}, {
		summary: "juju deploy --help shows the same help as 'help deploy'",
		args:    []string{"deploy", "--help"},
		code:    0,
		out:     deployHelpText(),
	}, {
		summary: "unknown command",
		args:    []string{"discombobulate"},
		code:    2,
		out:     "error: unrecognized command: juju discombobulate\n",
	}, {
		summary: "unknown option before command",
		args:    []string{"--cheese", "bootstrap"},
		code:    2,
		out:     "error: flag provided but not defined: --cheese\n",
	}, {
		summary: "unknown option after command",
		args:    []string{"bootstrap", "--cheese"},
		code:    2,
		out:     "error: flag provided but not defined: --cheese\n",
	}, {
		summary: "known option, but specified before command",
		args:    []string{"--environment", "blah", "bootstrap"},
		code:    2,
		out:     "error: flag provided but not defined: --environment\n",
	},
}

func (s *MainSuite) TestRunMain(c *C) {
	for i, t := range runMainTests {
		c.Logf("test %d: %s", i, t.summary)
		out := badrun(c, t.code, t.args...)
		c.Assert(out, Equals, t.out)
	}
}

var brokenConfig = `
environments:
    one:
        type: dummy
        state-server: false
        authorized-keys: i-am-a-key
        broken: %s
`

// breakJuju forces the dummy environment to return an error
// when environMethod is called.
func breakJuju(c *C, environMethod string) (msg string) {
	yaml := fmt.Sprintf(brokenConfig, environMethod)
	err := ioutil.WriteFile(homePath(".juju", "environments.yaml"), []byte(yaml), 0666)
	c.Assert(err, IsNil)

	return fmt.Sprintf("dummy.%s is broken", environMethod)
}

func (s *MainSuite) TestActualRunJujuArgsBeforeCommand(c *C) {
	defer makeFakeHome(c, "one").restore()

	// Check global args work when specified before command
	msg := breakJuju(c, "Bootstrap")
	logpath := filepath.Join(c.MkDir(), "log")
	out := badrun(c, 1, "--log-file", logpath, "--verbose", "--debug", "bootstrap")
	c.Assert(out, Equals, "error: "+msg+"\n")
	content, err := ioutil.ReadFile(logpath)
	c.Assert(err, IsNil)
	fullmsg := fmt.Sprintf(`(.|\n)*JUJU juju bootstrap command failed: %s\n`, msg)
	c.Assert(string(content), Matches, fullmsg)
}

func (s *MainSuite) TestActualRunJujuArgsAfterCommand(c *C) {
	defer makeFakeHome(c, "one").restore()

	// Check global args work when specified after command
	msg := breakJuju(c, "Bootstrap")
	logpath := filepath.Join(c.MkDir(), "log")
	out := badrun(c, 1, "bootstrap", "--log-file", logpath, "--verbose", "--debug")
	c.Assert(out, Equals, "error: "+msg+"\n")
	content, err := ioutil.ReadFile(logpath)
	c.Assert(err, IsNil)
	fullmsg := fmt.Sprintf(`(.|\n)*JUJU juju bootstrap command failed: %s\n`, msg)
	c.Assert(string(content), Matches, fullmsg)
}

var commandNames = []string{
	"add-relation",
	"add-unit",
	"bootstrap",
	"deploy",
	"destroy-environment",
	"destroy-machine",
	"destroy-relation",
	"destroy-service",
	"destroy-unit",
	"expose",
	"generate-config", // alias for init
	"get",
	"get-constraints",
	"help",
	"init",
	"remove-relation", // alias for destroy-relation
	"remove-unit",     // alias for destroy-unit
	"resolved",
	"scp",
	"set",
	"set-constraints",
	"ssh",
	"stat", // alias for status
	"status",
	"terminate-machine", // alias for destroy-machine
	"unexpose",
	"upgrade-juju",
}

func (s *MainSuite) TestHelpCommands(c *C) {
	// Check that we have correctly registered all the commands
	// by checking the help output.
	out := badrun(c, 0, "help", "commands")
	lines := strings.Split(out, "\n")
	var names []string
	for _, line := range lines {
		f := strings.Fields(line)
		if len(f) == 0 {
			continue
		}
		names = append(names, f[0])
	}
	// The names should be output in alphabetical order, so don't sort.
	c.Assert(names, DeepEquals, commandNames)
}

var topicNames = []string{
	"basics",
	"commands",
	"global-options",
	"topics",
}

func (s *MainSuite) TestHelpTopics(c *C) {
	// Check that we have correctly registered all the topics
	// by checking the help output.
	out := badrun(c, 0, "help", "topics")
	lines := strings.Split(out, "\n")
	var names []string
	for _, line := range lines {
		f := strings.Fields(line)
		if len(f) == 0 {
			continue
		}
		names = append(names, f[0])
	}
	// The names should be output in alphabetical order, so don't sort.
	c.Assert(names, DeepEquals, topicNames)
}

type fakeHome string

func makeFakeHome(c *C, certNames ...string) fakeHome {
	oldHome := os.Getenv("HOME")
	os.Setenv("HOME", c.MkDir())

	err := os.Mkdir(homePath(".juju"), 0777)
	c.Assert(err, IsNil)
	for _, name := range certNames {
		err := ioutil.WriteFile(homePath(".juju", name+"-cert.pem"), []byte(testing.CACert), 0666)
		c.Assert(err, IsNil)

		err = ioutil.WriteFile(homePath(".juju", name+"-private-key.pem"), []byte(testing.CAKey), 0666)
		c.Assert(err, IsNil)
	}

	err = os.Mkdir(homePath(".ssh"), 0777)
	c.Assert(err, IsNil)
	err = ioutil.WriteFile(homePath(".ssh", "id_rsa.pub"), []byte("auth key\n"), 0666)
	c.Assert(err, IsNil)

	return fakeHome(oldHome)
}

func homePath(names ...string) string {
	all := append([]string{os.Getenv("HOME")}, names...)
	return filepath.Join(all...)
}

func (h fakeHome) restore() {
	os.Setenv("HOME", string(h))
}
<commit_msg>Add a test for global options.<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"launchpad.net/gnuflag"
	. "launchpad.net/gocheck"
	_ "launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/testing"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	stdtesting "testing"
)

func TestPackage(t *stdtesting.T) {
	testing.MgoTestPackage(t)
}

type MainSuite struct{}

var _ = Suite(&MainSuite{})

var flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing")

// Reentrancy point for testing (something as close as possible to) the juju
// tool itself.
func TestRunMain(t *stdtesting.T) {
	if *flagRunMain {
		Main(flag.Args())
	}
}

func badrun(c *C, exit int, cmd ...string) string {
	args := append([]string{"-test.run", "TestRunMain", "-run-main", "--", "juju"}, cmd...)
	ps := exec.Command(os.Args[0], args...)
	output, err := ps.CombinedOutput()
	if exit != 0 {
		c.Assert(err, ErrorMatches, fmt.Sprintf("exit status %d", exit))
	}
	return string(output)
}

func deployHelpText() string {
	buff := &bytes.Buffer{}
	dc := &DeployCommand{}
	info := dc.Info()
	info.Name = "juju deploy"
	f := gnuflag.NewFlagSet(info.Name, gnuflag.ContinueOnError)
	dc.SetFlags(f)
	buff.Write(info.Help(f))
	return buff.String()
}

var runMainTests = []struct {
	summary string
	args    []string
	code    int
	out     string
}{
	{
		summary: "no params shows help",
		args:    []string{},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju help is the same as juju",
		args:    []string{"help"},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju --help works too",
		args:    []string{"--help"},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju help basics is the same as juju",
		args:    []string{"help", "basics"},
		code:    0,
		out:     strings.TrimLeft(helpBasics, "\n"),
	}, {
		summary: "juju help foo doesn't exist",
		args:    []string{"help", "foo"},
		code:    1,
		out:     "error: unknown command or topic for foo\n",
	}, {
		summary: "juju help deploy shows the default help without global options",
		args:    []string{"help", "deploy"},
		code:    0,
		out:     deployHelpText(),
	}, {
		summary: "juju --help deploy shows the same help as 'help deploy'",
		args:    []string{"--help", "deploy"},
		code:    0,
		out:     deployHelpText(),
	}, {
		summary: "juju deploy --help shows the same help as 'help deploy'",
		args:    []string{"deploy", "--help"},
		code:    0,
		out:     deployHelpText(),
	}, {
		summary: "unknown command",
		args:    []string{"discombobulate"},
		code:    2,
		out:     "error: unrecognized command: juju discombobulate\n",
	}, {
		summary: "unknown option before command",
		args:    []string{"--cheese", "bootstrap"},
		code:    2,
		out:     "error: flag provided but not defined: --cheese\n",
	}, {
		summary: "unknown option after command",
		args:    []string{"bootstrap", "--cheese"},
		code:    2,
		out:     "error: flag provided but not defined: --cheese\n",
	}, {
		summary: "known option, but specified before command",
		args:    []string{"--environment", "blah", "bootstrap"},
		code:    2,
		out:     "error: flag provided but not defined: --environment\n",
	},
}

func (s *MainSuite) TestRunMain(c *C) {
	for i, t := range runMainTests {
		c.Logf("test %d: %s", i, t.summary)
		out := badrun(c, t.code, t.args...)
		c.Assert(out, Equals, t.out)
	}
}

var brokenConfig = `
environments:
    one:
        type: dummy
        state-server: false
        authorized-keys: i-am-a-key
        broken: %s
`

// breakJuju forces the dummy environment to return an error
// when environMethod is called.
func breakJuju(c *C, environMethod string) (msg string) {
	yaml := fmt.Sprintf(brokenConfig, environMethod)
	err := ioutil.WriteFile(homePath(".juju", "environments.yaml"), []byte(yaml), 0666)
	c.Assert(err, IsNil)

	return fmt.Sprintf("dummy.%s is broken", environMethod)
}

func (s *MainSuite) TestActualRunJujuArgsBeforeCommand(c *C) {
	defer makeFakeHome(c, "one").restore()

	// Check global args work when specified before command
	msg := breakJuju(c, "Bootstrap")
	logpath := filepath.Join(c.MkDir(), "log")
	out := badrun(c, 1, "--log-file", logpath, "--verbose", "--debug", "bootstrap")
	c.Assert(out, Equals, "error: "+msg+"\n")
	content, err := ioutil.ReadFile(logpath)
	c.Assert(err, IsNil)
	fullmsg := fmt.Sprintf(`(.|\n)*JUJU juju bootstrap command failed: %s\n`, msg)
	c.Assert(string(content), Matches, fullmsg)
}

func (s *MainSuite) TestActualRunJujuArgsAfterCommand(c *C) {
	defer makeFakeHome(c, "one").restore()

	// Check global args work when specified after command
	msg := breakJuju(c, "Bootstrap")
	logpath := filepath.Join(c.MkDir(), "log")
	out := badrun(c, 1, "bootstrap", "--log-file", logpath, "--verbose", "--debug")
	c.Assert(out, Equals, "error: "+msg+"\n")
	content, err := ioutil.ReadFile(logpath)
	c.Assert(err, IsNil)
	fullmsg := fmt.Sprintf(`(.|\n)*JUJU juju bootstrap command failed: %s\n`, msg)
	c.Assert(string(content), Matches, fullmsg)
}

var commandNames = []string{
	"add-relation",
	"add-unit",
	"bootstrap",
	"deploy",
	"destroy-environment",
	"destroy-machine",
	"destroy-relation",
	"destroy-service",
	"destroy-unit",
	"expose",
	"generate-config", // alias for init
	"get",
	"get-constraints",
	"help",
	"init",
	"remove-relation", // alias for destroy-relation
	"remove-unit",     // alias for destroy-unit
	"resolved",
	"scp",
	"set",
	"set-constraints",
	"ssh",
	"stat", // alias for status
	"status",
	"terminate-machine", // alias for destroy-machine
	"unexpose",
	"upgrade-juju",
}

func (s *MainSuite) TestHelpCommands(c *C) {
	// Check that we have correctly registered all the commands
	// by checking the help output.
	out := badrun(c, 0, "help", "commands")
	lines := strings.Split(out, "\n")
	var names []string
	for _, line := range lines {
		f := strings.Fields(line)
		if len(f) == 0 {
			continue
		}
		names = append(names, f[0])
	}
	// The names should be output in alphabetical order, so don't sort.
	c.Assert(names, DeepEquals, commandNames)
}

var topicNames = []string{
	"basics",
	"commands",
	"global-options",
	"topics",
}

func (s *MainSuite) TestHelpTopics(c *C) {
	// Check that we have correctly registered all the topics
	// by checking the help output.
	out := badrun(c, 0, "help", "topics")
	lines := strings.Split(out, "\n")
	var names []string
	for _, line := range lines {
		f := strings.Fields(line)
		if len(f) == 0 {
			continue
		}
		names = append(names, f[0])
	}
	// The names should be output in alphabetical order, so don't sort.
	c.Assert(names, DeepEquals, topicNames)
}

var globalFlags = []string{
	"--debug .*",
	"-h, --help .*",
	"--log-file .*",
	"-v, --verbose .*",
}

func (s *MainSuite) TestHelpGlobalOptions(c *C) {
	// Check that we have correctly registered all the topics
	// by checking the help output.
	out := badrun(c, 0, "help", "global-options")
	c.Assert(out, Matches, `Global Options

These options may be used with any command, and may appear in front of any
command\.(.|\n)*`)
	lines := strings.Split(out, "\n")
	var flags []string
	for _, line := range lines {
		f := strings.Fields(line)
		if len(f) == 0 || line[0] != '-' {
			continue
		}
		flags = append(flags, line)
	}
	c.Assert(len(flags), Equals, len(globalFlags))
	for i, line := range flags {
		c.Assert(line, Matches, globalFlags[i])
	}
}

type fakeHome string

func makeFakeHome(c *C, certNames ...string) fakeHome {
	oldHome := os.Getenv("HOME")
	os.Setenv("HOME", c.MkDir())

	err := os.Mkdir(homePath(".juju"), 0777)
	c.Assert(err, IsNil)
	for _, name := range certNames {
		err := ioutil.WriteFile(homePath(".juju", name+"-cert.pem"), []byte(testing.CACert), 0666)
		c.Assert(err, IsNil)

		err = ioutil.WriteFile(homePath(".juju", name+"-private-key.pem"), []byte(testing.CAKey), 0666)
		c.Assert(err, IsNil)
	}

	err = os.Mkdir(homePath(".ssh"), 0777)
	c.Assert(err, IsNil)
	err = ioutil.WriteFile(homePath(".ssh", "id_rsa.pub"), []byte("auth key\n"), 0666)
	c.Assert(err, IsNil)

	return fakeHome(oldHome)
}

func homePath(names ...string) string {
	all := append([]string{os.Getenv("HOME")}, names...)
	return filepath.Join(all...)
}

func (h fakeHome) restore() {
	os.Setenv("HOME", string(h))
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	"github.com/gravitational/version"
	"github.com/gravitational/version/pkg/tool"
)

// pkg is the path to the package the tool will create linker flags for.
var pkg = flag.String("pkg", "", "root package path")

// versionPackage is the path to this version package.
// It is used to access version information attributes during link time.
// This flag is useful when the version package is custom-vendored and has a different package path.
var versionPackage = flag.String("verpkg", "github.com/gravitational/version", "path to the version package")

var compatMode = flag.Bool("compat", false, "generate linker flags using go1.4 syntax")

var tagOnly = flag.Bool("tag", false, "print tag only")

var release = flag.Bool("os-release", false, "print version with OS and architecture e.g. v0.0.1-linux-amd64")

// semverPattern defines a regexp pattern to modify the results of `git describe` to be semver-complaint.
var semverPattern = regexp.MustCompile(`(.+)-([0-9]{1,})-g([0-9a-f]{14})$`)

// goVersionPattern defines a regexp pattern to parse versions of the `go tool`.
var goVersionPattern = regexp.MustCompile(`go([1-9])\.(\d+)(?:.\d+)*`)

func main() {
	if err := run(); err != nil {
		log.Fatalln(err)
	}
}

func run() error {
	log.SetFlags(0)
	flag.Parse()
	if *pkg == "" {
		dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			return err
		}
		*pkg = dir
	}

	goVersion, err := goToolVersion()
	if err != nil {
		return fmt.Errorf("failed to determine go tool version: %v\n", err)
	}

	info, err := getVersionInfo(*pkg)
	if err != nil {
		return fmt.Errorf("failed to determine version information: %v\n", err)
	}

	// print just tag and return
	if *tagOnly {
		fmt.Printf(info.Version)
		return nil
	}

	if *release {
		fmt.Printf("%v-%v-%v", info.Version, runtime.GOOS, runtime.GOARCH)
		return nil
	}

	var linkFlags []string
	linkFlag := func(key, value string) string {
		if goVersion <= 14 || *compatMode {
			return fmt.Sprintf("-X %s.%s %s", *versionPackage, key, value)
		}
		return fmt.Sprintf("-X %s.%s=%s", *versionPackage, key, value)
	}

	// Determine the values of version-related variables as commands to the go linker.
	if info.GitCommit != "" {
		linkFlags = append(linkFlags, linkFlag("gitCommit", info.GitCommit))
		linkFlags = append(linkFlags, linkFlag("gitTreeState", info.GitTreeState))
	}
	if info.Version != "" {
		linkFlags = append(linkFlags, linkFlag("version", info.Version))
	}

	fmt.Printf("%s", strings.Join(linkFlags, " "))
	return nil
}

// getVersionInfo collects the build version information for package pkg.
func getVersionInfo(pkg string) (*version.Info, error) {
	git := newGit(pkg)
	commitID, err := git.commitID()
	if err != nil {
		return nil, fmt.Errorf("failed to obtain git commit ID: %v\n", err)
	}
	treeState, err := git.treeState()
	if err != nil {
		return nil, fmt.Errorf("failed to determine git tree state: %v\n", err)
	}
	tag, err := git.tag(commitID)
	if err != nil {
		tag = ""
	}
	if tag != "" {
		tag = semverify(tag)
		if treeState == dirty {
			tag = tag + "-" + string(treeState)
		}
	}
	return &version.Info{
		Version:      tag,
		GitCommit:    commitID,
		GitTreeState: string(treeState),
	}, nil
}

// goToolVersion determines the version of the `go tool`.
func goToolVersion() (toolVersion, error) {
	goTool := &tool.T{Cmd: "go"}
	out, err := goTool.Exec("version")
	if err != nil {
		return toolVersionUnknown, err
	}
	build := strings.Split(out, " ")
	if len(build) > 2 {
		return parseToolVersion(build[2]), nil
	}
	return toolVersionUnknown, nil
}

// parseToolVersion translates a string version of the form 'go1.4.3' to a numeric value 14.
func parseToolVersion(version string) toolVersion {
	match := goVersionPattern.FindStringSubmatch(version)
	if len(match) > 2 {
		// After a successful match, match[1] and match[2] are integers
		major := mustAtoi(match[1])
		minor := mustAtoi(match[2])
		return toolVersion(major*10 + minor)
	}
	return toolVersionUnknown
}

func newGit(pkg string) *git {
	args := []string{"--work-tree", pkg, "--git-dir", filepath.Join(pkg, ".git")}
	return &git{&tool.T{
		Cmd:  "git",
		Args: args,
	}}
}

// git represents an instance of the git tool.
type git struct {
	*tool.T
}

// treeState describes the state of the git tree.
// `git describe --dirty` only considers changes to existing files.
// We track tree state and consider untracked files as they also affect the build.
type treeState string

const (
	clean treeState = "clean"
	dirty           = "dirty"
)

// toolVersion represents a tool version as an integer.
// toolVersion only considers the first two significant version parts and is computed as follows:
// 	majorVersion*10+minorVersion
type toolVersion int

const toolVersionUnknown toolVersion = 0

func (r *git) commitID() (string, error) {
	return r.Exec("rev-parse", "HEAD^{commit}")
}

func (r *git) treeState() (treeState, error) {
	out, err := r.Exec("status", "--porcelain")
	if err != nil {
		return "", err
	}
	if len(out) == 0 {
		return clean, nil
	}
	return dirty, nil
}

func (r *git) tag(commitID string) (string, error) {
	return r.Exec("describe", "--tags", "--abbrev=14", commitID+"^{commit}")
}

// semverify transforms the output of `git describe` to be semver-complaint.
func semverify(version string) string {
	var result []byte
	match := semverPattern.FindStringSubmatchIndex(version)
	if match != nil {
		return string(semverPattern.ExpandString(result, "$1.$2+$3", string(version), match))
	}
	return version
}

// mustAtoi converts value to an integer.
// It panics if the value does not represent a valid integer.
func mustAtoi(value string) int {
	result, err := strconv.Atoi(value)
	if err != nil {
		panic(err)
	}
	return result
}
<commit_msg>add --docker-tag flag<commit_after>/*
Copyright 2015 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	"github.com/gravitational/version"
	"github.com/gravitational/version/pkg/tool"
)

// pkg is the path to the package the tool will create linker flags for.
var pkg = flag.String("pkg", "", "root package path")

// versionPackage is the path to this version package.
// It is used to access version information attributes during link time.
// This flag is useful when the version package is custom-vendored and has a different package path.
var versionPackage = flag.String("verpkg", "github.com/gravitational/version", "path to the version package")

var compatMode = flag.Bool("compat", false, "generate linker flags using go1.4 syntax")

var tagOnly = flag.Bool("tag", false, "print tag only")

var release = flag.Bool("os-release", false, "print version with OS and architecture e.g. v0.0.1-linux-amd64")

var dockerTag = flag.Bool("docker-tag", false, "print version compatible with docker tag requirements")

// semverPattern defines a regexp pattern to modify the results of `git describe` to be semver-complaint.
var semverPattern = regexp.MustCompile(`(.+)-([0-9]{1,})-g([0-9a-f]{14})$`)

// goVersionPattern defines a regexp pattern to parse versions of the `go tool`.
var goVersionPattern = regexp.MustCompile(`go([1-9])\.(\d+)(?:.\d+)*`)

// dockerTagAntiPatern matches all chars not accepted by docker tag requirements
var dockerTagAntiPattern = regexp.MustCompile(`[^\w-_.]`)

func main() {
	if err := run(); err != nil {
		log.Fatalln(err)
	}
}

func run() error {
	log.SetFlags(0)
	flag.Parse()
	if *pkg == "" {
		dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			return err
		}
		*pkg = dir
	}

	goVersion, err := goToolVersion()
	if err != nil {
		return fmt.Errorf("failed to determine go tool version: %v\n", err)
	}

	info, err := getVersionInfo(*pkg)
	if err != nil {
		return fmt.Errorf("failed to determine version information: %v\n", err)
	}

	// print just tag and return
	if *tagOnly {
		fmt.Printf(info.Version)
		return nil
	}

	if *release {
		fmt.Printf("%v-%v-%v", info.Version, runtime.GOOS, runtime.GOARCH)
		return nil
	}

	if *dockerTag {
		fmt.Printf("%v", dockerTagAntiPattern.ReplaceAllString(info.Version, "-"))
		return nil
	}

	var linkFlags []string
	linkFlag := func(key, value string) string {
		if goVersion <= 14 || *compatMode {
			return fmt.Sprintf("-X %s.%s %s", *versionPackage, key, value)
		}
		return fmt.Sprintf("-X %s.%s=%s", *versionPackage, key, value)
	}

	// Determine the values of version-related variables as commands to the go linker.
	if info.GitCommit != "" {
		linkFlags = append(linkFlags, linkFlag("gitCommit", info.GitCommit))
		linkFlags = append(linkFlags, linkFlag("gitTreeState", info.GitTreeState))
	}
	if info.Version != "" {
		linkFlags = append(linkFlags, linkFlag("version", info.Version))
	}

	fmt.Printf("%s", strings.Join(linkFlags, " "))
	return nil
}

// getVersionInfo collects the build version information for package pkg.
func getVersionInfo(pkg string) (*version.Info, error) {
	git := newGit(pkg)
	commitID, err := git.commitID()
	if err != nil {
		return nil, fmt.Errorf("failed to obtain git commit ID: %v\n", err)
	}
	treeState, err := git.treeState()
	if err != nil {
		return nil, fmt.Errorf("failed to determine git tree state: %v\n", err)
	}
	tag, err := git.tag(commitID)
	if err != nil {
		tag = ""
	}
	if tag != "" {
		tag = semverify(tag)
		if treeState == dirty {
			tag = tag + "-" + string(treeState)
		}
	}
	return &version.Info{
		Version:      tag,
		GitCommit:    commitID,
		GitTreeState: string(treeState),
	}, nil
}

// goToolVersion determines the version of the `go tool`.
func goToolVersion() (toolVersion, error) {
	goTool := &tool.T{Cmd: "go"}
	out, err := goTool.Exec("version")
	if err != nil {
		return toolVersionUnknown, err
	}
	build := strings.Split(out, " ")
	if len(build) > 2 {
		return parseToolVersion(build[2]), nil
	}
	return toolVersionUnknown, nil
}

// parseToolVersion translates a string version of the form 'go1.4.3' to a numeric value 14.
func parseToolVersion(version string) toolVersion {
	match := goVersionPattern.FindStringSubmatch(version)
	if len(match) > 2 {
		// After a successful match, match[1] and match[2] are integers
		major := mustAtoi(match[1])
		minor := mustAtoi(match[2])
		return toolVersion(major*10 + minor)
	}
	return toolVersionUnknown
}

func newGit(pkg string) *git {
	args := []string{"--work-tree", pkg, "--git-dir", filepath.Join(pkg, ".git")}
	return &git{&tool.T{
		Cmd:  "git",
		Args: args,
	}}
}

// git represents an instance of the git tool.
type git struct {
	*tool.T
}

// treeState describes the state of the git tree.
// `git describe --dirty` only considers changes to existing files.
// We track tree state and consider untracked files as they also affect the build.
type treeState string

const (
	clean treeState = "clean"
	dirty           = "dirty"
)

// toolVersion represents a tool version as an integer.
// toolVersion only considers the first two significant version parts and is computed as follows:
// 	majorVersion*10+minorVersion
type toolVersion int

const toolVersionUnknown toolVersion = 0

func (r *git) commitID() (string, error) {
	return r.Exec("rev-parse", "HEAD^{commit}")
}

func (r *git) treeState() (treeState, error) {
	out, err := r.Exec("status", "--porcelain")
	if err != nil {
		return "", err
	}
	if len(out) == 0 {
		return clean, nil
	}
	return dirty, nil
}

func (r *git) tag(commitID string) (string, error) {
	return r.Exec("describe", "--tags", "--abbrev=14", commitID+"^{commit}")
}

// semverify transforms the output of `git describe` to be semver-complaint.
func semverify(version string) string {
	var result []byte
	match := semverPattern.FindStringSubmatchIndex(version)
	if match != nil {
		return string(semverPattern.ExpandString(result, "$1.$2+$3", string(version), match))
	}
	return version
}

// mustAtoi converts value to an integer.
// It panics if the value does not represent a valid integer.
func mustAtoi(value string) int {
	result, err := strconv.Atoi(value)
	if err != nil {
		panic(err)
	}
	return result
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/grafana/metrictank/cmd/mt-fakemetrics/out"
	"github.com/grafana/metrictank/cmd/mt-fakemetrics/out/gnet"
	"github.com/grafana/metrictank/logger"
	"github.com/grafana/metrictank/schema"
	"github.com/grafana/metrictank/stats"
	"github.com/raintank/met/statsd"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"os"
	"strings"
	"time"
)

var (
	gatewayAddress        string
	gatewayKey            string
	orgId                 int
	partitionCount        int32
	partitionMethodString string
	testMetricsInterval   time.Duration
	queryInterval         time.Duration
	lookbackPeriod        time.Duration
	logLevel              string
	lastPublish           int64

	statsGraphite   *stats.Graphite
	statsPrefix     string
	statsAddr       string
	statsBufferSize int
	statsTimeout    time.Duration

	partitionMethod schema.PartitionByMethod
	publisher       out.Out
)

func init() {
	parrotCmd.Flags().StringVar(&gatewayAddress, "gateway-address", "http://localhost:6059", "the url of the metrics gateway")
	parrotCmd.Flags().StringVar(&gatewayKey, "gateway-key", "", "the bearer token to include with gateway requests")
	parrotCmd.Flags().IntVar(&orgId, "org-id", 1, "org id to publish parrot metrics to")
	parrotCmd.Flags().Int32Var(&partitionCount, "partition-count", 8, "number of kafka partitions in use")
	parrotCmd.Flags().StringVar(&partitionMethodString, "partition-method", "bySeries", "the partition method in use on the gateway, must be one of bySeries|bySeriesWithTags|bySeriesWithTagsFnv")
	parrotCmd.Flags().DurationVar(&testMetricsInterval, "test-metrics-interval", 10*time.Second, "interval to send test metrics")
	parrotCmd.Flags().DurationVar(&queryInterval, "query-interval", 10*time.Second, "interval to query to validate metrics")
	parrotCmd.Flags().DurationVar(&lookbackPeriod, "lookback-period", 5*time.Minute, "how far to look back when validating metrics")
	parrotCmd.Flags().StringVar(&statsPrefix, "stats-prefix", "", "stats prefix (will add trailing dot automatically if needed)")
	parrotCmd.Flags().StringVar(&statsAddr, "stats-address", "localhost:2003", "address to send monitoring statistics to")
	parrotCmd.Flags().IntVar(&statsBufferSize, "stats-buffer-size", 20000, "how many messages (holding all measurements from one interval) to buffer up in case graphite endpoint is unavailable.")
	parrotCmd.Flags().DurationVar(&statsTimeout, "stats-timeout", time.Second*10, "timeout after which a write is considered not successful")

	parrotCmd.Flags().StringVar(&logLevel, "log-level", "info", "log level. panic|fatal|error|warning|info|debug")

	formatter := &logger.TextFormatter{}
	formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	log.SetFormatter(formatter)
}

func main() {
	err := parrotCmd.Execute()
	if err != nil {
		log.Fatal(err)
	}
}

var parrotCmd = &cobra.Command{
	Use:   "mt-parrot",
	Short: "generate deterministic metrics for each metrictank partition",
	Run: func(cmd *cobra.Command, args []string) {
		lvl, err := log.ParseLevel(logLevel)
		if err != nil {
			log.Fatalf("failed to parse log-level, %s", err.Error())
		}
		if int(lookbackPeriod.Seconds())%int(testMetricsInterval.Seconds()) != 0 {
			log.Fatal("lookback period must be evenly divisible by test metrics interval")
		}

		log.SetLevel(lvl)
		parsePartitionMethod()
		initGateway()
		initStats()

		metrics := generateMetrics(partitionCount)
		go produceTestMetrics(metrics)

		monitor()
	},
}

//parsePartitionMethod parses the partitionScheme cli flag,
//exiting if an invalid partition schema is entered or if org based partitioning is used (not currently allowed by parrot).
func parsePartitionMethod() {
	var err error
	partitionMethod, err = schema.PartitonMethodFromString(partitionMethodString)
	if err != nil {
		log.Fatal(err)
	}
	if partitionMethod == schema.PartitionByOrg {
		log.Fatal("byOrg not supported")
	}
}

func initGateway() {
	var err error
	backend, _ := statsd.New(false, "", "")
	publisher, err = gnet.New(gatewayAddress+"/metrics", gatewayKey, backend)
	if err != nil {
		log.Fatal(err)
	}
	log.Info("gateway initialized")
}

func initStats() {
	hostname, _ := os.Hostname()
	prefix := strings.Replace(statsPrefix, "$hostname", strings.Replace(hostname, ".", "_", -1), -1)
	//need to use a negative interval so we can manually set the report timestamps
	statsGraphite = stats.NewGraphite(prefix, statsAddr, -1, statsBufferSize, statsTimeout)
}
<commit_msg>ensure that durations are multiples of 1s<commit_after>package main

import (
	"github.com/grafana/metrictank/cmd/mt-fakemetrics/out"
	"github.com/grafana/metrictank/cmd/mt-fakemetrics/out/gnet"
	"github.com/grafana/metrictank/logger"
	"github.com/grafana/metrictank/schema"
	"github.com/grafana/metrictank/stats"
	"github.com/raintank/met/statsd"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"os"
	"strings"
	"time"
)

var (
	gatewayAddress        string
	gatewayKey            string
	orgId                 int
	partitionCount        int32
	partitionMethodString string
	testMetricsInterval   time.Duration
	queryInterval         time.Duration
	lookbackPeriod        time.Duration
	logLevel              string
	lastPublish           int64

	statsGraphite   *stats.Graphite
	statsPrefix     string
	statsAddr       string
	statsBufferSize int
	statsTimeout    time.Duration

	partitionMethod schema.PartitionByMethod
	publisher       out.Out
)

func init() {
	parrotCmd.Flags().StringVar(&gatewayAddress, "gateway-address", "http://localhost:6059", "the url of the metrics gateway")
	parrotCmd.Flags().StringVar(&gatewayKey, "gateway-key", "", "the bearer token to include with gateway requests")
	parrotCmd.Flags().IntVar(&orgId, "org-id", 1, "org id to publish parrot metrics to")
	parrotCmd.Flags().Int32Var(&partitionCount, "partition-count", 8, "number of kafka partitions in use")
	parrotCmd.Flags().StringVar(&partitionMethodString, "partition-method", "bySeries", "the partition method in use on the gateway, must be one of bySeries|bySeriesWithTags|bySeriesWithTagsFnv")
	parrotCmd.Flags().DurationVar(&testMetricsInterval, "test-metrics-interval", 10*time.Second, "interval to send test metrics")
	parrotCmd.Flags().DurationVar(&queryInterval, "query-interval", 10*time.Second, "interval to query to validate metrics")
	parrotCmd.Flags().DurationVar(&lookbackPeriod, "lookback-period", 5*time.Minute, "how far to look back when validating metrics")
	parrotCmd.Flags().StringVar(&statsPrefix, "stats-prefix", "", "stats prefix (will add trailing dot automatically if needed)")
	parrotCmd.Flags().StringVar(&statsAddr, "stats-address", "localhost:2003", "address to send monitoring statistics to")
	parrotCmd.Flags().IntVar(&statsBufferSize, "stats-buffer-size", 20000, "how many messages (holding all measurements from one interval) to buffer up in case graphite endpoint is unavailable.")
	parrotCmd.Flags().DurationVar(&statsTimeout, "stats-timeout", time.Second*10, "timeout after which a write is considered not successful")

	parrotCmd.Flags().StringVar(&logLevel, "log-level", "info", "log level. panic|fatal|error|warning|info|debug")

	formatter := &logger.TextFormatter{}
	formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	log.SetFormatter(formatter)
}

func main() {
	err := parrotCmd.Execute()
	if err != nil {
		log.Fatal(err)
	}
}

var parrotCmd = &cobra.Command{
	Use:   "mt-parrot",
	Short: "generate deterministic metrics for each metrictank partition",
	Run: func(cmd *cobra.Command, args []string) {
		lvl, err := log.ParseLevel(logLevel)
		if err != nil {
			log.Fatalf("failed to parse log-level, %s", err.Error())
		}

		validateDurationsInSeconds()
		if int(lookbackPeriod.Seconds())%int(testMetricsInterval.Seconds()) != 0 {
			log.Fatal("lookback period must be evenly divisible by test metrics interval")
		}

		log.SetLevel(lvl)
		parsePartitionMethod()
		initGateway()
		initStats()

		metrics := generateMetrics(partitionCount)
		go produceTestMetrics(metrics)

		monitor()
	},
}

//parsePartitionMethod parses the partitionScheme cli flag,
//exiting if an invalid partition schema is entered or if org based partitioning is used (not currently allowed by parrot).
func parsePartitionMethod() {
	var err error
	partitionMethod, err = schema.PartitonMethodFromString(partitionMethodString)
	if err != nil {
		log.Fatal(err)
	}
	if partitionMethod == schema.PartitionByOrg {
		log.Fatal("byOrg not supported")
	}
}

func initGateway() {
	var err error
	backend, _ := statsd.New(false, "", "")
	publisher, err = gnet.New(gatewayAddress+"/metrics", gatewayKey, backend)
	if err != nil {
		log.Fatal(err)
	}
	log.Info("gateway initialized")
}

func initStats() {
	hostname, _ := os.Hostname()
	prefix := strings.Replace(statsPrefix, "$hostname", strings.Replace(hostname, ".", "_", -1), -1)
	//need to use a negative interval so we can manually set the report timestamps
	statsGraphite = stats.NewGraphite(prefix, statsAddr, -1, statsBufferSize, statsTimeout)
}

func validateDurationsInSeconds() {
	if testMetricsInterval%time.Second != 0 {
		log.Fatal("test-metrics-interval must be in seconds")
	}
	if queryInterval%time.Second != 0 {
		log.Fatal("query-interval must be in seconds")
	}
	if lookbackPeriod%time.Second != 0 {
		log.Fatal("lookback-period must be in seconds")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"go/format"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

type generateType struct {
	Name    string
	Initial string
	Fields  []generateField
}

type generateField struct {
	Name     string
	Initial  string
	TypeName string
	JSONName string
	View     string
}

// following the type, an optional third param could designate the editor.Input-like
// func to call which would output different text based on the element returned
// blog title:string Author:string PostCategory:string content:string:richtext some_thing:int

// blog title:string Author:string PostCategory:string content:string some_thing:int
func parseType(args []string) (generateType, error) {
	t := generateType{
		Name: fieldName(args[0]),
	}
	t.Initial = strings.ToLower(string(t.Name[0]))

	fields := args[1:]
	for _, field := range fields {
		f, err := parseField(field)
		if err != nil {
			return generateType{}, err
		}
		// NEW
		// set initial (1st character of the type's name) on field so we don't need
		// to set the template variable like was done in prior version
		f.Initial = t.Initial

		t.Fields = append(t.Fields, f)
	}

	return t, nil
}

func parseField(raw string) (generateField, error) {
	// contents:string or // contents:string:richtext
	if !strings.Contains(raw, ":") {
		return generateField{}, fmt.Errorf("Invalid generate argument. [%s]", raw)
	}

	data := strings.Split(raw, ":")
	name := fieldName(data[0])

	field := generateField{
		Name:     name,
		Initial:  string(name[0]),
		TypeName: strings.ToLower(data[1]),
		JSONName: fieldJSONName(data[0]),
	}

	fieldType := "input"
	if len(data) == 3 {
		fieldType = data[2]
	}

	err := setFieldView(&field, fieldType)
	if err != nil {
		return generateField{}, err
	}

	return field, nil
}

// get the initial field name passed and check it for all possible cases
// MyTitle:string myTitle:string my_title:string -> MyTitle
// error-message:string -> ErrorMessage
func fieldName(name string) string {
	// remove _ or - if first character
	if name[0] == '-' || name[0] == '_' {
		name = name[1:]
	}

	// remove _ or - if last character
	if name[len(name)-1] == '-' || name[len(name)-1] == '_' {
		name = name[:len(name)-1]
	}

	// upcase the first character
	name = strings.ToUpper(string(name[0])) + name[1:]

	// remove _ or - character, and upcase the character immediately following
	for i := 0; i < len(name); i++ {
		r := rune(name[i])
		if isUnderscore(r) || isHyphen(r) {
			up := strings.ToUpper(string(name[i+1]))
			name = name[:i] + up + name[i+2:]
		}
	}

	return name
}

// get the initial field name passed and convert to json-like name
// MyTitle:string myTitle:string my_title:string -> my_title
// error-message:string -> error-message
func fieldJSONName(name string) string {
	// remove _ or - if first character
	if name[0] == '-' || name[0] == '_' {
		name = name[1:]
	}

	// downcase the first character
	name = strings.ToLower(string(name[0])) + name[1:]

	// check for uppercase character, downcase and insert _ before it if i-1
	// isn't already _ or -
	for i := 0; i < len(name); i++ {
		r := rune(name[i])
		if isUpper(r) {
			low := strings.ToLower(string(r))
			if name[i-1] == '_' || name[i-1] == '-' {
				name = name[:i] + low + name[i+1:]
			} else {
				name = name[:i] + "_" + low + name[i+1:]
			}
		}
	}

	return name
}

// set the specified view inside the editor field for a generated field for a type
func setFieldView(field *generateField, viewType string) error {
	var err error
	var tmpl *template.Template
	buf := &bytes.Buffer{}

	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	tmplDir := filepath.Join(pwd, "cmd", "ponzu", "templates")
	tmplFrom := func(filename string) (*template.Template, error) {
		return template.ParseFiles(filepath.Join(tmplDir, filename))
	}

	switch strings.ToLower(viewType) {
	case "hidden":
	case "textarea":
	case "richtext":
	case "select":
	case "input":
		tmpl, err = tmplFrom("gen-input.tmpl")
	case "checkbox":
	case "file":
	case "tags":
	case "custom":
	default:
		tmpl, err = tmplFrom("gen-input.tmpl")
	}

	if err != nil {
		return err
	}

	err = tmpl.Execute(buf, field)
	if err != nil {
		return err
	}

	field.View = buf.String()

	return nil
}

func isUpper(char rune) bool {
	if char >= 'A' && char <= 'Z' {
		return true
	}

	return false
}

func isUnderscore(char rune) bool {
	return char == '_'
}

func isHyphen(char rune) bool {
	return char == '-'
}

func generateContentType(args []string) error {
	name := args[0]
	fileName := strings.ToLower(name) + ".go"

	// open file in ./content/ dir
	// if exists, alert user of conflict
	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	contentDir := filepath.Join(pwd, "content")
	filePath := filepath.Join(contentDir, fileName)

	if _, err := os.Stat(filePath); !os.IsNotExist(err) {
		return fmt.Errorf("Please remove '%s' before executing this command.", fileName)
	}

	// no file exists.. ok to write new one
	file, err := os.Create(filePath)
	defer file.Close()
	if err != nil {
		return err
	}

	// parse type info from args
	gt, err := parseType(args)
	if err != nil {
		return fmt.Errorf("Failed to parse type args: %s", err.Error())
	}

	tmplPath := filepath.Join(pwd, "cmd", "ponzu", "templates", "gen-content.tmpl")
	tmpl, err := template.ParseFiles(tmplPath)
	if err != nil {
		return fmt.Errorf("Failed to parse template: %s", err.Error())
	}

	buf := &bytes.Buffer{}
	err = tmpl.Execute(buf, gt)
	if err != nil {
		return fmt.Errorf("Failed to execute template: %s", err.Error())
	}

	fmtBuf, err := format.Source(buf.Bytes())
	if err != nil {
		return fmt.Errorf("Failed to format template: %s", err.Error())
	}

	_, err = file.Write(fmtBuf)
	if err != nil {
		return fmt.Errorf("Failed to write generated file buffer: %s", err.Error())
	}

	return nil
}
<commit_msg>downcase initial char from type name<commit_after>package main

import (
	"bytes"
	"fmt"
	"go/format"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

type generateType struct {
	Name    string
	Initial string
	Fields  []generateField
}

type generateField struct {
	Name     string
	Initial  string
	TypeName string
	JSONName string
	View     string
}

// following the type, an optional third param could designate the editor.Input-like
// func to call which would output different text based on the element returned
// blog title:string Author:string PostCategory:string content:string:richtext some_thing:int

// blog title:string Author:string PostCategory:string content:string some_thing:int
func parseType(args []string) (generateType, error) {
	t := generateType{
		Name: fieldName(args[0]),
	}
	t.Initial = strings.ToLower(string(t.Name[0]))

	fields := args[1:]
	for _, field := range fields {
		f, err := parseField(field)
		if err != nil {
			return generateType{}, err
		}
		// NEW
		// set initial (1st character of the type's name) on field so we don't need
		// to set the template variable like was done in prior version
		f.Initial = t.Initial

		t.Fields = append(t.Fields, f)
	}

	return t, nil
}

func parseField(raw string) (generateField, error) {
	// contents:string or // contents:string:richtext
	if !strings.Contains(raw, ":") {
		return generateField{}, fmt.Errorf("Invalid generate argument. [%s]", raw)
	}

	data := strings.Split(raw, ":")
	name := fieldName(data[0])

	field := generateField{
		Name:     name,
		Initial:  strings.ToLower(string(name[0])),
		TypeName: strings.ToLower(data[1]),
		JSONName: fieldJSONName(data[0]),
	}

	fieldType := "input"
	if len(data) == 3 {
		fieldType = data[2]
	}

	err := setFieldView(&field, fieldType)
	if err != nil {
		return generateField{}, err
	}

	return field, nil
}

// get the initial field name passed and check it for all possible cases
// MyTitle:string myTitle:string my_title:string -> MyTitle
// error-message:string -> ErrorMessage
func fieldName(name string) string {
	// remove _ or - if first character
	if name[0] == '-' || name[0] == '_' {
		name = name[1:]
	}

	// remove _ or - if last character
	if name[len(name)-1] == '-' || name[len(name)-1] == '_' {
		name = name[:len(name)-1]
	}

	// upcase the first character
	name = strings.ToUpper(string(name[0])) + name[1:]

	// remove _ or - character, and upcase the character immediately following
	for i := 0; i < len(name); i++ {
		r := rune(name[i])
		if isUnderscore(r) || isHyphen(r) {
			up := strings.ToUpper(string(name[i+1]))
			name = name[:i] + up + name[i+2:]
		}
	}

	return name
}

// get the initial field name passed and convert to json-like name
// MyTitle:string myTitle:string my_title:string -> my_title
// error-message:string -> error-message
func fieldJSONName(name string) string {
	// remove _ or - if first character
	if name[0] == '-' || name[0] == '_' {
		name = name[1:]
	}

	// downcase the first character
	name = strings.ToLower(string(name[0])) + name[1:]

	// check for uppercase character, downcase and insert _ before it if i-1
	// isn't already _ or -
	for i := 0; i < len(name); i++ {
		r := rune(name[i])
		if isUpper(r) {
			low := strings.ToLower(string(r))
			if name[i-1] == '_' || name[i-1] == '-' {
				name = name[:i] + low + name[i+1:]
			} else {
				name = name[:i] + "_" + low + name[i+1:]
			}
		}
	}

	return name
}

// set the specified view inside the editor field for a generated field for a type
func setFieldView(field *generateField, viewType string) error {
	var err error
	var tmpl *template.Template
	buf := &bytes.Buffer{}

	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	tmplDir := filepath.Join(pwd, "cmd", "ponzu", "templates")
	tmplFrom := func(filename string) (*template.Template, error) {
		return template.ParseFiles(filepath.Join(tmplDir, filename))
	}

	switch strings.ToLower(viewType) {
	case "hidden":
	case "textarea":
	case "richtext":
	case "select":
	case "input":
		tmpl, err = tmplFrom("gen-input.tmpl")
	case "checkbox":
	case "file":
	case "tags":
	case "custom":
	default:
		tmpl, err = tmplFrom("gen-input.tmpl")
	}

	if err != nil {
		return err
	}

	err = tmpl.Execute(buf, field)
	if err != nil {
		return err
	}

	field.View = buf.String()

	return nil
}

func isUpper(char rune) bool {
	if char >= 'A' && char <= 'Z' {
		return true
	}

	return false
}

func isUnderscore(char rune) bool {
	return char == '_'
}

func isHyphen(char rune) bool {
	return char == '-'
}

func generateContentType(args []string) error {
	name := args[0]
	fileName := strings.ToLower(name) + ".go"

	// open file in ./content/ dir
	// if exists, alert user of conflict
	pwd, err := os.Getwd()
	if err != nil {
		return err
	}

	contentDir := filepath.Join(pwd, "content")
	filePath := filepath.Join(contentDir, fileName)

	if _, err := os.Stat(filePath); !os.IsNotExist(err) {
		return fmt.Errorf("Please remove '%s' before executing this command.", fileName)
	}

	// no file exists.. ok to write new one
	file, err := os.Create(filePath)
	defer file.Close()
	if err != nil {
		return err
	}

	// parse type info from args
	gt, err := parseType(args)
	if err != nil {
		return fmt.Errorf("Failed to parse type args: %s", err.Error())
	}

	tmplPath := filepath.Join(pwd, "cmd", "ponzu", "templates", "gen-content.tmpl")
	tmpl, err := template.ParseFiles(tmplPath)
	if err != nil {
		return fmt.Errorf("Failed to parse template: %s", err.Error())
	}

	buf := &bytes.Buffer{}
	err = tmpl.Execute(buf, gt)
	if err != nil {
		return fmt.Errorf("Failed to execute template: %s", err.Error())
	}

	fmtBuf, err := format.Source(buf.Bytes())
	if err != nil {
		return fmt.Errorf("Failed to format template: %s", err.Error())
	}

	_, err = file.Write(fmtBuf)
	if err != nil {
		return fmt.Errorf("Failed to write generated file buffer: %s", err.Error())
	}

	return nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/containers/image/docker"
	"github.com/containers/image/manifest"
	"github.com/containers/image/transports"
	"github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// inspectOutput is the output format of (skopeo inspect), primarily so that we can format it with a simple json.MarshalIndent.
type inspectOutput struct {
	Name          string `json:",omitempty"`
	Tag           string `json:",omitempty"`
	Digest        digest.Digest
	RepoTags      []string
	Created       *time.Time
	DockerVersion string
	Labels        map[string]string
	Architecture  string
	Os            string
	Layers        []string
}

type inspectOptions struct {
	global *globalOptions
	image  *imageOptions
	raw    bool // Output the raw manifest instead of parsing information about the image
}

func inspectCmd(global *globalOptions) cli.Command {
	sharedFlags, sharedOpts := sharedImageFlags()
	imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
	opts := inspectOptions{
		global: global,
		image:  imageOpts,
	}
	return cli.Command{
		Name:  "inspect",
		Usage: "Inspect image IMAGE-NAME",
		Description: fmt.Sprintf(`
	Return low-level information about "IMAGE-NAME" in a registry/transport

	Supported transports:
	%s

	See skopeo(1) section "IMAGE NAMES" for the expected format
	`, strings.Join(transports.ListNames(), ", ")),
		ArgsUsage: "IMAGE-NAME",
		Flags: append(append([]cli.Flag{
			cli.BoolFlag{
				Name:        "raw",
				Usage:       "output raw manifest",
				Destination: &opts.raw,
			},
		}, sharedFlags...), imageFlags...),
		Action: commandAction(opts.run),
	}
}

func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) {
	ctx, cancel := opts.global.commandTimeoutContext()
	defer cancel()

	if len(args) != 1 {
		return errors.New("Exactly one argument expected")
	}
	img, err := parseImage(ctx, opts.image, args[0])
	if err != nil {
		return err
	}

	defer func() {
		if err := img.Close(); err != nil {
			retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err))
		}
	}()

	rawManifest, _, err := img.Manifest(ctx)
	if err != nil {
		return err
	}
	if opts.raw {
		_, err := stdout.Write(rawManifest)
		if err != nil {
			return fmt.Errorf("Error writing manifest to standard output: %v", err)
		}
		return nil
	}
	imgInspect, err := img.Inspect(ctx)
	if err != nil {
		return err
	}
	outputData := inspectOutput{
		Name: "", // Set below if DockerReference() is known
		Tag:  imgInspect.Tag,
		// Digest is set below.
		RepoTags:      []string{}, // Possibly overriden for docker.Transport.
		Created:       imgInspect.Created,
		DockerVersion: imgInspect.DockerVersion,
		Labels:        imgInspect.Labels,
		Architecture:  imgInspect.Architecture,
		Os:            imgInspect.Os,
		Layers:        imgInspect.Layers,
	}
	outputData.Digest, err = manifest.Digest(rawManifest)
	if err != nil {
		return fmt.Errorf("Error computing manifest digest: %v", err)
	}
	if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
		outputData.Name = dockerRef.Name()
	}
	if img.Reference().Transport() == docker.Transport {
		sys, err := opts.image.newSystemContext()
		if err != nil {
			return err
		}
		outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference())
		if err != nil {
			// some registries may decide to block the "list all tags" endpoint
			// gracefully allow the inspect to continue in this case. Currently
			// the IBM Bluemix container registry has this restriction.
			if !strings.Contains(err.Error(), "401") {
				return fmt.Errorf("Error determining repository tags: %v", err)
			}
			logrus.Warnf("Registry disallows tag list retrieval; skipping")
		}
	}
	out, err := json.MarshalIndent(outputData, "", "    ")
	if err != nil {
		return err
	}
	fmt.Fprintln(stdout, string(out))
	return nil
}
<commit_msg>Do not interpret % metacharacters in (skopeo inspect) output<commit_after>package main

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

	"github.com/containers/image/docker"
	"github.com/containers/image/manifest"
	"github.com/containers/image/transports"
	"github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// inspectOutput is the output format of (skopeo inspect), primarily so that we can format it with a simple json.MarshalIndent.
type inspectOutput struct {
	Name          string `json:",omitempty"`
	Tag           string `json:",omitempty"`
	Digest        digest.Digest
	RepoTags      []string
	Created       *time.Time
	DockerVersion string
	Labels        map[string]string
	Architecture  string
	Os            string
	Layers        []string
}

type inspectOptions struct {
	global *globalOptions
	image  *imageOptions
	raw    bool // Output the raw manifest instead of parsing information about the image
}

func inspectCmd(global *globalOptions) cli.Command {
	sharedFlags, sharedOpts := sharedImageFlags()
	imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "")
	opts := inspectOptions{
		global: global,
		image:  imageOpts,
	}
	return cli.Command{
		Name:  "inspect",
		Usage: "Inspect image IMAGE-NAME",
		Description: fmt.Sprintf(`
	Return low-level information about "IMAGE-NAME" in a registry/transport

	Supported transports:
	%s

	See skopeo(1) section "IMAGE NAMES" for the expected format
	`, strings.Join(transports.ListNames(), ", ")),
		ArgsUsage: "IMAGE-NAME",
		Flags: append(append([]cli.Flag{
			cli.BoolFlag{
				Name:        "raw",
				Usage:       "output raw manifest",
				Destination: &opts.raw,
			},
		}, sharedFlags...), imageFlags...),
		Action: commandAction(opts.run),
	}
}

func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) {
	ctx, cancel := opts.global.commandTimeoutContext()
	defer cancel()

	if len(args) != 1 {
		return errors.New("Exactly one argument expected")
	}
	img, err := parseImage(ctx, opts.image, args[0])
	if err != nil {
		return err
	}

	defer func() {
		if err := img.Close(); err != nil {
			retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err))
		}
	}()

	rawManifest, _, err := img.Manifest(ctx)
	if err != nil {
		return err
	}
	if opts.raw {
		_, err := stdout.Write(rawManifest)
		if err != nil {
			return fmt.Errorf("Error writing manifest to standard output: %v", err)
		}
		return nil
	}
	imgInspect, err := img.Inspect(ctx)
	if err != nil {
		return err
	}
	outputData := inspectOutput{
		Name: "", // Set below if DockerReference() is known
		Tag:  imgInspect.Tag,
		// Digest is set below.
		RepoTags:      []string{}, // Possibly overriden for docker.Transport.
		Created:       imgInspect.Created,
		DockerVersion: imgInspect.DockerVersion,
		Labels:        imgInspect.Labels,
		Architecture:  imgInspect.Architecture,
		Os:            imgInspect.Os,
		Layers:        imgInspect.Layers,
	}
	outputData.Digest, err = manifest.Digest(rawManifest)
	if err != nil {
		return fmt.Errorf("Error computing manifest digest: %v", err)
	}
	if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
		outputData.Name = dockerRef.Name()
	}
	if img.Reference().Transport() == docker.Transport {
		sys, err := opts.image.newSystemContext()
		if err != nil {
			return err
		}
		outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference())
		if err != nil {
			// some registries may decide to block the "list all tags" endpoint
			// gracefully allow the inspect to continue in this case. Currently
			// the IBM Bluemix container registry has this restriction.
			if !strings.Contains(err.Error(), "401") {
				return fmt.Errorf("Error determining repository tags: %v", err)
			}
			logrus.Warnf("Registry disallows tag list retrieval; skipping")
		}
	}
	out, err := json.MarshalIndent(outputData, "", "    ")
	if err != nil {
		return err
	}
	fmt.Fprintf(stdout, "%s\n", string(out))
	return nil
}
<|endoftext|>