<commit_before>/*
   Copyright 2020 Docker Compose CLI 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 compose

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/compose-spec/compose-go/cli"
	"github.com/compose-spec/compose-go/types"
	dockercli "github.com/docker/cli/cli"
	"github.com/docker/cli/cli-plugins/manager"
	"github.com/docker/cli/cli/command"
	"github.com/morikuni/aec"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/docker/compose/v2/cmd/formatter"
	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/compose"
	"github.com/docker/compose/v2/pkg/progress"
	"github.com/docker/compose/v2/pkg/utils"
)

// Command defines a compose CLI command as a func with args
type Command func(context.Context, []string) error

// CobraCommand defines a cobra command function
type CobraCommand func(context.Context, *cobra.Command, []string) error

// AdaptCmd adapt a CobraCommand func to cobra library
func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
	return func(cmd *cobra.Command, args []string) error {
		ctx := cmd.Context()
		contextString := fmt.Sprintf("%s", ctx)
		if !strings.HasSuffix(contextString, ".WithCancel") { // need to handle cancel
			cancellableCtx, cancel := context.WithCancel(cmd.Context())
			ctx = cancellableCtx
			s := make(chan os.Signal, 1)
			signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
			go func() {
				<-s
				cancel()
			}()
		}
		err := fn(ctx, cmd, args)
		var composeErr compose.Error
		if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
			err = dockercli.StatusError{
				StatusCode: 130,
				Status:     compose.CanceledStatus,
			}
		}
		if errors.As(err, &composeErr) {
			err = dockercli.StatusError{
				StatusCode: composeErr.GetMetricsFailureCategory().ExitCode,
				Status:     err.Error(),
			}
		}
		return err
	}
}

// Adapt a Command func to cobra library
func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
	return AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
		return fn(ctx, args)
	})
}

// Warning is a global warning to be displayed to user on command failure
var Warning string

type projectOptions struct {
	ProjectName   string
	Profiles      []string
	ConfigPaths   []string
	WorkDir       string
	ProjectDir    string
	EnvFile       string
	Compatibility bool
}

// ProjectFunc does stuff within a types.Project
type ProjectFunc func(ctx context.Context, project *types.Project) error

// ProjectServicesFunc does stuff within a types.Project and a selection of services
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error

// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
	return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
		return fn(ctx, project)
	})
}

// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
	return Adapt(func(ctx context.Context, args []string) error {
		project, err := o.toProject(args, cli.WithResolvedPaths(true))
		if err != nil {
			return err
		}

		return fn(ctx, project, args)
	})
}

func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
	f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
	f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
	f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
	f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
	f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the Compose file)")
	f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the Compose file)")
	f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
	_ = f.MarkHidden("workdir")
}

func (o *projectOptions) toProjectName() (string, error) {
	if o.ProjectName != "" {
		return o.ProjectName, nil
	}

	project, err := o.toProject(nil)
	if err != nil {
		return "", err
	}
	return project.Name, nil
}

func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
	options, err := o.toProjectOptions(po...)
	if err != nil {
		return nil, compose.WrapComposeError(err)
	}

	project, err := cli.ProjectFromOptions(options)
	if err != nil {
		return nil, compose.WrapComposeError(err)
	}

	if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
		compose.Separator = "_"
	}

	ef := o.EnvFile
	if ef != "" && !filepath.IsAbs(ef) {
		ef = filepath.Join(project.WorkingDir, o.EnvFile)
	}
	for i, s := range project.Services {
		s.CustomLabels = map[string]string{
			api.ProjectLabel:     project.Name,
			api.ServiceLabel:     s.Name,
			api.VersionLabel:     api.ComposeVersion,
			api.WorkingDirLabel:  project.WorkingDir,
			api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
			api.OneoffLabel:      "False", // default, will be overridden by `run` command
		}
		if ef != "" {
			s.CustomLabels[api.EnvironmentFileLabel] = ef
		}
		project.Services[i] = s
	}

	if len(services) > 0 {
		s, err := project.GetServices(services...)
		if err != nil {
			return nil, err
		}
		o.Profiles = append(o.Profiles, s.GetProfiles()...)
	}

	if profiles, ok := options.Environment["COMPOSE_PROFILES"]; ok {
		o.Profiles = append(o.Profiles, strings.Split(profiles, ",")...)
	}

	project.ApplyProfiles(o.Profiles)

	project.WithoutUnnecessaryResources()

	err = project.ForServices(services)
	return project, err
}

func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
	return cli.NewProjectOptions(o.ConfigPaths,
		append(po,
			cli.WithWorkingDirectory(o.ProjectDir),
			cli.WithEnvFile(o.EnvFile),
			cli.WithDotEnv,
			cli.WithOsEnv,
			cli.WithConfigFileEnv,
			cli.WithDefaultConfigPath,
			cli.WithName(o.ProjectName))...)
}

// PluginName is the name of the plugin
const PluginName = "compose"

// RunningAsStandalone detects when running as a standalone program
func RunningAsStandalone() bool {
	return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
}

// RootCommand returns the compose command with its child commands
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
	opts := projectOptions{}
	var (
		ansi    string
		noAnsi  bool
		verbose bool
		version bool
	)
	command := &cobra.Command{
		Short:            "Docker Compose",
		Use:              PluginName,
		TraverseChildren: true,
		// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return cmd.Help()
			}
			if version {
				return versionCommand().Execute()
			}
			_ = cmd.Help()
			return dockercli.StatusError{
				StatusCode: compose.CommandSyntaxFailure.ExitCode,
				Status:     fmt.Sprintf("unknown docker command: %q", "compose "+args[0]),
			}
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			parent := cmd.Root()
			if parent != nil {
				parentPrerun := parent.PersistentPreRunE
				if parentPrerun != nil {
					err := parentPrerun(cmd, args)
					if err != nil {
						return err
					}
				}
			}
			if noAnsi {
				if ansi != "auto" {
					return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
				}
				ansi = "never"
				fmt.Fprint(os.Stderr, aec.Apply("option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n", aec.RedF))
			}
			if verbose {
				logrus.SetLevel(logrus.TraceLevel)
			}
			formatter.SetANSIMode(ansi)
			switch ansi {
			case "never":
				progress.Mode = progress.ModePlain
			case "tty":
				progress.Mode = progress.ModeTTY
			}
			if opts.WorkDir != "" {
				if opts.ProjectDir != "" {
					return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
				}
				opts.ProjectDir = opts.WorkDir
				fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
			}
			return nil
		},
	}

	command.AddCommand(
		upCommand(&opts, backend),
		downCommand(&opts, backend),
		startCommand(&opts, backend),
		restartCommand(&opts, backend),
		stopCommand(&opts, backend),
		psCommand(&opts, backend),
		listCommand(backend),
		logsCommand(&opts, backend),
		convertCommand(&opts, backend),
		killCommand(&opts, backend),
		runCommand(&opts, dockerCli, backend),
		removeCommand(&opts, backend),
		execCommand(&opts, dockerCli, backend),
		pauseCommand(&opts, backend),
		unpauseCommand(&opts, backend),
		topCommand(&opts, backend),
		eventsCommand(&opts, backend),
		portCommand(&opts, backend),
		imagesCommand(&opts, backend),
		versionCommand(),
		buildCommand(&opts, backend),
		pushCommand(&opts, backend),
		pullCommand(&opts, backend),
		createCommand(&opts, backend),
		copyCommand(&opts, backend),
	)
	command.Flags().SetInterspersed(false)
	opts.addProjectFlags(command.Flags())
	command.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
	command.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
	command.Flags().MarkHidden("version") //nolint:errcheck
	command.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
	command.Flags().MarkHidden("no-ansi") //nolint:errcheck
	command.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
	command.Flags().MarkHidden("verbose") //nolint:errcheck
	return command
}
<commit_msg>Takes COMPOSE_PROJECT_NAME into consideration on commands<commit_after>/*
   Copyright 2020 Docker Compose CLI 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 compose

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/compose-spec/compose-go/cli"
	"github.com/compose-spec/compose-go/types"
	dockercli "github.com/docker/cli/cli"
	"github.com/docker/cli/cli-plugins/manager"
	"github.com/docker/cli/cli/command"
	"github.com/morikuni/aec"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/docker/compose/v2/cmd/formatter"
	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/compose"
	"github.com/docker/compose/v2/pkg/progress"
	"github.com/docker/compose/v2/pkg/utils"
)

// Command defines a compose CLI command as a func with args
type Command func(context.Context, []string) error

// CobraCommand defines a cobra command function
type CobraCommand func(context.Context, *cobra.Command, []string) error

// AdaptCmd adapt a CobraCommand func to cobra library
func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
	return func(cmd *cobra.Command, args []string) error {
		ctx := cmd.Context()
		contextString := fmt.Sprintf("%s", ctx)
		if !strings.HasSuffix(contextString, ".WithCancel") { // need to handle cancel
			cancellableCtx, cancel := context.WithCancel(cmd.Context())
			ctx = cancellableCtx
			s := make(chan os.Signal, 1)
			signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
			go func() {
				<-s
				cancel()
			}()
		}
		err := fn(ctx, cmd, args)
		var composeErr compose.Error
		if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
			err = dockercli.StatusError{
				StatusCode: 130,
				Status:     compose.CanceledStatus,
			}
		}
		if errors.As(err, &composeErr) {
			err = dockercli.StatusError{
				StatusCode: composeErr.GetMetricsFailureCategory().ExitCode,
				Status:     err.Error(),
			}
		}
		return err
	}
}

// Adapt a Command func to cobra library
func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
	return AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
		return fn(ctx, args)
	})
}

// Warning is a global warning to be displayed to user on command failure
var Warning string

type projectOptions struct {
	ProjectName   string
	Profiles      []string
	ConfigPaths   []string
	WorkDir       string
	ProjectDir    string
	EnvFile       string
	Compatibility bool
}

// ProjectFunc does stuff within a types.Project
type ProjectFunc func(ctx context.Context, project *types.Project) error

// ProjectServicesFunc does stuff within a types.Project and a selection of services
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error

// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
	return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
		return fn(ctx, project)
	})
}

// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
	return Adapt(func(ctx context.Context, args []string) error {
		project, err := o.toProject(args, cli.WithResolvedPaths(true))
		if err != nil {
			return err
		}

		return fn(ctx, project, args)
	})
}

func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
	f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
	f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
	f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
	f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
	f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the Compose file)")
	f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the Compose file)")
	f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
	_ = f.MarkHidden("workdir")
}

func (o *projectOptions) toProjectName() (string, error) {
	if o.ProjectName != "" {
		return o.ProjectName, nil
	}

	envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
	if envProjectName != "" {
		return envProjectName, nil
	}

	project, err := o.toProject(nil)
	if err != nil {
		return "", err
	}
	return project.Name, nil
}

func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
	options, err := o.toProjectOptions(po...)
	if err != nil {
		return nil, compose.WrapComposeError(err)
	}

	project, err := cli.ProjectFromOptions(options)
	if err != nil {
		return nil, compose.WrapComposeError(err)
	}

	if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
		compose.Separator = "_"
	}

	ef := o.EnvFile
	if ef != "" && !filepath.IsAbs(ef) {
		ef = filepath.Join(project.WorkingDir, o.EnvFile)
	}
	for i, s := range project.Services {
		s.CustomLabels = map[string]string{
			api.ProjectLabel:     project.Name,
			api.ServiceLabel:     s.Name,
			api.VersionLabel:     api.ComposeVersion,
			api.WorkingDirLabel:  project.WorkingDir,
			api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
			api.OneoffLabel:      "False", // default, will be overridden by `run` command
		}
		if ef != "" {
			s.CustomLabels[api.EnvironmentFileLabel] = ef
		}
		project.Services[i] = s
	}

	if len(services) > 0 {
		s, err := project.GetServices(services...)
		if err != nil {
			return nil, err
		}
		o.Profiles = append(o.Profiles, s.GetProfiles()...)
	}

	if profiles, ok := options.Environment["COMPOSE_PROFILES"]; ok {
		o.Profiles = append(o.Profiles, strings.Split(profiles, ",")...)
	}

	project.ApplyProfiles(o.Profiles)

	project.WithoutUnnecessaryResources()

	err = project.ForServices(services)
	return project, err
}

func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
	return cli.NewProjectOptions(o.ConfigPaths,
		append(po,
			cli.WithWorkingDirectory(o.ProjectDir),
			cli.WithEnvFile(o.EnvFile),
			cli.WithDotEnv,
			cli.WithOsEnv,
			cli.WithConfigFileEnv,
			cli.WithDefaultConfigPath,
			cli.WithName(o.ProjectName))...)
}

// PluginName is the name of the plugin
const PluginName = "compose"

// RunningAsStandalone detects when running as a standalone program
func RunningAsStandalone() bool {
	return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
}

// RootCommand returns the compose command with its child commands
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
	opts := projectOptions{}
	var (
		ansi    string
		noAnsi  bool
		verbose bool
		version bool
	)
	command := &cobra.Command{
		Short:            "Docker Compose",
		Use:              PluginName,
		TraverseChildren: true,
		// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) == 0 {
				return cmd.Help()
			}
			if version {
				return versionCommand().Execute()
			}
			_ = cmd.Help()
			return dockercli.StatusError{
				StatusCode: compose.CommandSyntaxFailure.ExitCode,
				Status:     fmt.Sprintf("unknown docker command: %q", "compose "+args[0]),
			}
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			parent := cmd.Root()
			if parent != nil {
				parentPrerun := parent.PersistentPreRunE
				if parentPrerun != nil {
					err := parentPrerun(cmd, args)
					if err != nil {
						return err
					}
				}
			}
			if noAnsi {
				if ansi != "auto" {
					return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
				}
				ansi = "never"
				fmt.Fprint(os.Stderr, aec.Apply("option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n", aec.RedF))
			}
			if verbose {
				logrus.SetLevel(logrus.TraceLevel)
			}
			formatter.SetANSIMode(ansi)
			switch ansi {
			case "never":
				progress.Mode = progress.ModePlain
			case "tty":
				progress.Mode = progress.ModeTTY
			}
			if opts.WorkDir != "" {
				if opts.ProjectDir != "" {
					return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
				}
				opts.ProjectDir = opts.WorkDir
				fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
			}
			return nil
		},
	}

	command.AddCommand(
		upCommand(&opts, backend),
		downCommand(&opts, backend),
		startCommand(&opts, backend),
		restartCommand(&opts, backend),
		stopCommand(&opts, backend),
		psCommand(&opts, backend),
		listCommand(backend),
		logsCommand(&opts, backend),
		convertCommand(&opts, backend),
		killCommand(&opts, backend),
		runCommand(&opts, dockerCli, backend),
		removeCommand(&opts, backend),
		execCommand(&opts, dockerCli, backend),
		pauseCommand(&opts, backend),
		unpauseCommand(&opts, backend),
		topCommand(&opts, backend),
		eventsCommand(&opts, backend),
		portCommand(&opts, backend),
		imagesCommand(&opts, backend),
		versionCommand(),
		buildCommand(&opts, backend),
		pushCommand(&opts, backend),
		pullCommand(&opts, backend),
		createCommand(&opts, backend),
		copyCommand(&opts, backend),
	)
	command.Flags().SetInterspersed(false)
	opts.addProjectFlags(command.Flags())
	command.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
	command.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
	command.Flags().MarkHidden("version") //nolint:errcheck
	command.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
	command.Flags().MarkHidden("no-ansi") //nolint:errcheck
	command.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
	command.Flags().MarkHidden("verbose") //nolint:errcheck
	return command
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/ethereum/go-ethereum/accounts/keystore"
	ethcommon "github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/console"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/rpc"
	"github.com/livepeer/go-livepeer/eth"

	"github.com/golang/glog"
)

const (
	clientIdentifier = "geth" // Client identifier to advertise over the network
	passphrase       = ""
	serviceURI       = "https://127.0.0.1:8936"
)

var (
	ethTxTimeout              = 600 * time.Second
	endpoint                  = "ws://localhost:8546/"
	gethMiningAccount         = "87da6a8c6e9eff15d703fc2773e32f6af8dbe301"
	gethMiningAccountOverride = false
	controllerAddr            = "0x04B9De88c81cda06165CF65a908e5f1EFBB9493B"
	controllerAddrOverride    = false
)

func main() {
	flag.Set("logtostderr", "true")
	baseDataDir := flag.String("datadir", ".lpdev2", "default data directory")
	endpointAddr := flag.String("endpoint", "", "Geth endpoint to connect to")
	miningAccountFlag := flag.String("miningaccount", "", "Override geth mining account (usually not needed)")
	controllerAddrFlag := flag.String("controller", "", "Override controller address (usually not needed)")

	flag.Parse()
	if *endpointAddr != "" {
		endpoint = *endpointAddr
	}
	if *miningAccountFlag != "" {
		gethMiningAccount = *miningAccountFlag
		gethMiningAccountOverride = true
	}
	if *controllerAddrFlag != "" {
		controllerAddr = *controllerAddrFlag
		controllerAddrOverride = true
	}
	args := flag.Args()
	goodToGo := false
	isBroadcaster := true
	if len(args) > 1 && args[0] == "setup" {
		switch args[1] {
		case "broadcaster":
			goodToGo = true
		case "transcoder":
			isBroadcaster = false
			goodToGo = true
		}
	}
	if !goodToGo {
		fmt.Println(`
    Usage: go run cmd/devtool/devtool.go setup broadcaster|transcoder
        It will create initilize eth account (on private testnet) to be used for broadcaster or transcoder
        and will create shell script (run_broadcaster_ETHACC.sh or run_transcoder_ETHACC.sh) to run it.`)
		return
	}

	t := getNodeType(isBroadcaster)

	tmp, err := ioutil.TempDir("", "livepeer")
	if err != nil {
		glog.Fatalf("Can't create temporary directory: %v", err)
	}
	defer os.RemoveAll(tmp)

	tempKeystoreDir := filepath.Join(tmp, "keystore")
	acc := createKey(tempKeystoreDir)
	glog.Infof("Using account %s", acc)
	dataDir := filepath.Join(*baseDataDir, t+"_"+acc)
	dataDirToCreate := filepath.Join(dataDir, "devenv")
	err = os.MkdirAll(dataDirToCreate, 0755)
	if err != nil {
		glog.Fatalf("Can't create directory %v", err)
	}

	keystoreDir := filepath.Join(dataDirToCreate, "keystore")
	err = os.Rename(tempKeystoreDir, keystoreDir)
	if err != nil {
		glog.Fatal(err)
	}
	remoteConsole(acc)
	ethSetup(acc, keystoreDir, isBroadcaster)
	createRunScript(acc, dataDir, isBroadcaster)
	glog.Info("Finished")
}

func getNodeType(isBroadcaster bool) string {
	t := "broadcaster"
	if !isBroadcaster {
		t = "transcoder"
	}
	return t
}

func ethSetup(ethAcctAddr, keystoreDir string, isBroadcaster bool) {
	time.Sleep(3 * time.Second)
	//Set up eth client
	backend, err := ethclient.Dial(endpoint)
	if err != nil {
		glog.Errorf("Failed to connect to Ethereum client: %v", err)
		return
	}
	glog.Infof("Using controller address %s", controllerAddr)

	client, err := eth.NewClient(ethcommon.HexToAddress(ethAcctAddr), keystoreDir, backend,
		ethcommon.HexToAddress(controllerAddr), ethTxTimeout)
	if err != nil {
		glog.Errorf("Failed to create client: %v", err)
		return
	}

	var bigGasPrice *big.Int = big.NewInt(int64(200))
	// var bigGasPrice *big.Int = big.NewInt(int64(00000))

	err = client.Setup(passphrase, 1000000, bigGasPrice)
	if err != nil {
		glog.Fatalf("Failed to setup client: %v", err)
		return
	}
	glog.Infof("Requesting tokens from faucet")

	tx, err := client.Request()
	if err != nil {
		glog.Errorf("Error requesting tokens from faucet: %v", err)
		return
	}

	err = client.CheckTx(tx)
	if err != nil {
		glog.Errorf("Error requesting tokens from faucet: %v", err)
		return
	}
	glog.Info("Done requesting tokens.")
	time.Sleep(4 * time.Second)

	if !isBroadcaster {
		// XXX TODO curl -X "POST" http://localhost:$transcoderCliPort/initializeRound
		time.Sleep(3 * time.Second)
		for {
			currentRound, err := client.CurrentRound()
			if err != nil {
				glog.Errorf("Error getting current round: %v", err)
				return
			}
			if currentRound.Int64() > 1 {
				break
			}
			// first round is initialized and locked, need to wait
			glog.Info("Waiting will first round ended.")
			time.Sleep(4 * time.Second)
		}
		tx, err := client.InitializeRound()
		// ErrRoundInitialized
		if err != nil {
			if err.Error() != "ErrRoundInitialized" {
				glog.Errorf("Error initializing round: %v", err)
				return
			}
		} else {
			err = client.CheckTx(tx)
			if err != nil {
				glog.Errorf("Error initializng round: %v", err)
				return
			}
		}
		glog.Info("Done initializing round.")
		glog.Info("Activating transcoder")
		// curl -d "blockRewardCut=10&feeShare=5&pricePerSegment=1&amount=500" --data-urlencode "serviceURI=https://$transcoderServiceAddr" \
		//   -H "Content-Type: application/x-www-form-urlencoded" \
		//   -X "POST" http://localhost:$transcoderCliPort/activateTranscoder\
		var amount *big.Int = big.NewInt(int64(500))
		glog.Infof("Bonding %v to %s", amount, ethAcctAddr)

		tx, err = client.Bond(amount, ethcommon.HexToAddress(ethAcctAddr))
		if err != nil {
			glog.Error(err)
			return
		}

		err = client.CheckTx(tx)
		if err != nil {
			glog.Error(err)
			return
		}
		glog.Infof("Registering transcoder %v", ethAcctAddr)
		price := big.NewInt(1)

		tx, err = client.Transcoder(eth.FromPerc(10), eth.FromPerc(5), price)
		if err == eth.ErrCurrentRoundLocked {
			// wait for next round and retry
		}
		if err != nil {
			glog.Error(err)
			return
		}

		err = client.CheckTx(tx)
		if err != nil {
			glog.Error(err)
			return
		}

		glog.Infof("Storing service URI %v in service registry...", serviceURI)

		tx, err = client.SetServiceURI(serviceURI)
		if err != nil {
			glog.Error(err)
			return
		}

		err = client.CheckTx(tx)
		if err != nil {
			glog.Error(err)
		}
	}
}

func createRunScript(ethAcctAddr, dataDir string, isBroadcaster bool) {
	script := "#!/bin/bash\n"
	script += fmt.Sprintf(`./livepeer -v 99 -controllerAddr %s -datadir ./%s \
    -ethAcctAddr %s \
    -ethUrl %s \
    -ethPassword "" \
    -gasPrice 200 -gasLimit 2000000 -devenv=true \
    -monitor=false -currentManifest=true `,
		controllerAddr, dataDir, ethAcctAddr, endpoint)

	if !isBroadcaster {
		script += fmt.Sprintf(` -initializeRound=true \
    -serviceAddr 127.0.0.1:8936 -httpAddr 127.0.0.1:8936  -transcoder \
    -cliAddr 127.0.0.1:7936 -ipfsPath ./%s/trans
    `, dataDir)
	}

	glog.Info(script)
	fName := fmt.Sprintf("run_%s_%s.sh", getNodeType(isBroadcaster), ethAcctAddr)
	err := ioutil.WriteFile(fName, []byte(script), 0755)
	if err != nil {
		glog.Warningf("Error writing run script: %v", err)
	}
}

func createKey(keystoreDir string) string {
	keyStore := keystore.NewKeyStore(keystoreDir, keystore.StandardScryptN, keystore.StandardScryptP)

	account, err := keyStore.NewAccount(passphrase)
	if err != nil {
		glog.Fatal(err)
	}
	glog.Infof("Using ETH account: %v", account.Address.Hex())
	return account.Address.Hex()
}

func remoteConsole(destAccountAddr string) error {
	broadcasterGeth := "0161e041aad467a890839d5b08b138c1e6373072"
	if destAccountAddr != "" {
		broadcasterGeth = destAccountAddr
	}

	client, err := rpc.Dial(endpoint)
	if err != nil {
		glog.Fatalf("Unable to attach to remote geth: %v", err)
	}
	// get mining account
	if !gethMiningAccountOverride {
		var accounts []string
		err = client.Call(&accounts, "eth_accounts")
		if err != nil {
			glog.Fatalf("Error finding mining account: %v", err)
		}
		if len(accounts) == 0 {
			glog.Fatal("Can't find mining account")
		}
		gethMiningAccount = accounts[0]
		glog.Infof("Found mining account: %s", gethMiningAccount)
	}

	tmp, err := ioutil.TempDir("", "console")
	if err != nil {
		glog.Fatalf("Can't create temporary directory: %v", err)
	}
	defer os.RemoveAll(tmp)

	printer := new(bytes.Buffer)

	config := console.Config{
		DataDir: tmp,
		Client:  client,
		Printer: printer,
	}

	console, err := console.New(config)
	if err != nil {
		glog.Fatalf("Failed to start the JavaScript console: %v", err)
	}
	defer console.Stop(false)

	if !controllerAddrOverride {
		// f9a6cf519167d81bc5cb3d26c60c0c9a19704aa908c148e82a861b570f4cd2d7 - SetContractInfo event
		getControllerAddressScript := `
		var logs = [];
		var filter = web3.eth.filter({ fromBlock: 0, toBlock: "latest",
			topics: ["0xf9a6cf519167d81bc5cb3d26c60c0c9a19704aa908c148e82a861b570f4cd2d7"]});
		filter.get(function(error, result){
			logs.push(result);
		});
		console.log(logs[0][0].address);''
	`
		glog.Infof("Running eth script: %s", getControllerAddressScript)
		err = console.Evaluate(getControllerAddressScript)
		if err != nil {
			glog.Error(err)
		}
		if printer.Len() == 0 {
			glog.Fatal("Can't find deployed controller")
		}
		controllerAddr = strings.Split(printer.String(), "\n")[0]

		glog.Infof("Found controller address: %s", controllerAddr)
	}

	script := fmt.Sprintf("eth.sendTransaction({from: \"%s\", to: \"%s\", value: web3.toWei(834, \"ether\")})",
		gethMiningAccount, broadcasterGeth)
	glog.Infof("Running eth script: %s", script)

	err = console.Evaluate(script)
	if err != nil {
		glog.Error(err)
	}

	time.Sleep(3 * time.Second)

	return err
}
<commit_msg>devtool script: adding deposit<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/ethereum/go-ethereum/accounts/keystore"
	ethcommon "github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/console"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/rpc"
	"github.com/livepeer/go-livepeer/eth"

	"github.com/golang/glog"
)

const (
	clientIdentifier = "geth" // Client identifier to advertise over the network
	passphrase       = ""
	serviceURI       = "https://127.0.0.1:8936"
)

var (
	ethTxTimeout              = 600 * time.Second
	endpoint                  = "ws://localhost:8546/"
	gethMiningAccount         = "87da6a8c6e9eff15d703fc2773e32f6af8dbe301"
	gethMiningAccountOverride = false
	controllerAddr            = "0x04B9De88c81cda06165CF65a908e5f1EFBB9493B"
	controllerAddrOverride    = false
)

func main() {
	flag.Set("logtostderr", "true")
	baseDataDir := flag.String("datadir", ".lpdev2", "default data directory")
	endpointAddr := flag.String("endpoint", "", "Geth endpoint to connect to")
	miningAccountFlag := flag.String("miningaccount", "", "Override geth mining account (usually not needed)")
	controllerAddrFlag := flag.String("controller", "", "Override controller address (usually not needed)")

	flag.Parse()
	if *endpointAddr != "" {
		endpoint = *endpointAddr
	}
	if *miningAccountFlag != "" {
		gethMiningAccount = *miningAccountFlag
		gethMiningAccountOverride = true
	}
	if *controllerAddrFlag != "" {
		controllerAddr = *controllerAddrFlag
		controllerAddrOverride = true
	}
	args := flag.Args()
	goodToGo := false
	isBroadcaster := true
	if len(args) > 1 && args[0] == "setup" {
		switch args[1] {
		case "broadcaster":
			goodToGo = true
		case "transcoder":
			isBroadcaster = false
			goodToGo = true
		}
	}
	if !goodToGo {
		fmt.Println(`
    Usage: go run cmd/devtool/devtool.go setup broadcaster|transcoder
        It will create initilize eth account (on private testnet) to be used for broadcaster or transcoder
        and will create shell script (run_broadcaster_ETHACC.sh or run_transcoder_ETHACC.sh) to run it.`)
		return
	}

	t := getNodeType(isBroadcaster)

	tmp, err := ioutil.TempDir("", "livepeer")
	if err != nil {
		glog.Fatalf("Can't create temporary directory: %v", err)
	}
	defer os.RemoveAll(tmp)

	tempKeystoreDir := filepath.Join(tmp, "keystore")
	acc := createKey(tempKeystoreDir)
	glog.Infof("Using account %s", acc)
	dataDir := filepath.Join(*baseDataDir, t+"_"+acc)
	dataDirToCreate := filepath.Join(dataDir, "devenv")
	err = os.MkdirAll(dataDirToCreate, 0755)
	if err != nil {
		glog.Fatalf("Can't create directory %v", err)
	}

	keystoreDir := filepath.Join(dataDirToCreate, "keystore")
	err = os.Rename(tempKeystoreDir, keystoreDir)
	if err != nil {
		glog.Fatal(err)
	}
	remoteConsole(acc)
	ethSetup(acc, keystoreDir, isBroadcaster)
	createRunScript(acc, dataDir, isBroadcaster)
	glog.Info("Finished")
}

func getNodeType(isBroadcaster bool) string {
	t := "broadcaster"
	if !isBroadcaster {
		t = "transcoder"
	}
	return t
}

func ethSetup(ethAcctAddr, keystoreDir string, isBroadcaster bool) {
	time.Sleep(3 * time.Second)
	//Set up eth client
	backend, err := ethclient.Dial(endpoint)
	if err != nil {
		glog.Errorf("Failed to connect to Ethereum client: %v", err)
		return
	}
	glog.Infof("Using controller address %s", controllerAddr)

	client, err := eth.NewClient(ethcommon.HexToAddress(ethAcctAddr), keystoreDir, backend,
		ethcommon.HexToAddress(controllerAddr), ethTxTimeout)
	if err != nil {
		glog.Errorf("Failed to create client: %v", err)
		return
	}

	var bigGasPrice *big.Int = big.NewInt(int64(200))
	// var bigGasPrice *big.Int = big.NewInt(int64(00000))

	err = client.Setup(passphrase, 1000000, bigGasPrice)
	if err != nil {
		glog.Fatalf("Failed to setup client: %v", err)
		return
	}
	glog.Infof("Requesting tokens from faucet")

	tx, err := client.Request()
	if err != nil {
		glog.Errorf("Error requesting tokens from faucet: %v", err)
		return
	}

	err = client.CheckTx(tx)
	if err != nil {
		glog.Errorf("Error requesting tokens from faucet: %v", err)
		return
	}
	glog.Info("Done requesting tokens.")
	time.Sleep(4 * time.Second)

	var depositAmount *big.Int = big.NewInt(int64(5000))

	glog.Infof("Depositing: %v", depositAmount)

	tx, err = client.FundDeposit(depositAmount)
	if err != nil {
		glog.Error(err)
		return
	}
	err = client.CheckTx(tx)
	if err != nil {
		glog.Error(err)
		return
	}
	glog.Info("Done depositing")

	if !isBroadcaster {
		// XXX TODO curl -X "POST" http://localhost:$transcoderCliPort/initializeRound
		time.Sleep(3 * time.Second)
		for {
			currentRound, err := client.CurrentRound()
			if err != nil {
				glog.Errorf("Error getting current round: %v", err)
				return
			}
			if currentRound.Int64() > 1 {
				break
			}
			// first round is initialized and locked, need to wait
			glog.Info("Waiting will first round ended.")
			time.Sleep(4 * time.Second)
		}
		tx, err := client.InitializeRound()
		// ErrRoundInitialized
		if err != nil {
			if err.Error() != "ErrRoundInitialized" {
				glog.Errorf("Error initializing round: %v", err)
				return
			}
		} else {
			err = client.CheckTx(tx)
			if err != nil {
				glog.Errorf("Error initializng round: %v", err)
				return
			}
		}
		glog.Info("Done initializing round.")
		glog.Info("Activating transcoder")
		// curl -d "blockRewardCut=10&feeShare=5&pricePerSegment=1&amount=500" --data-urlencode "serviceURI=https://$transcoderServiceAddr" \
		//   -H "Content-Type: application/x-www-form-urlencoded" \
		//   -X "POST" http://localhost:$transcoderCliPort/activateTranscoder\
		var amount *big.Int = big.NewInt(int64(500))
		glog.Infof("Bonding %v to %s", amount, ethAcctAddr)

		tx, err = client.Bond(amount, ethcommon.HexToAddress(ethAcctAddr))
		if err != nil {
			glog.Error(err)
			return
		}

		err = client.CheckTx(tx)
		if err != nil {
			glog.Error(err)
			return
		}
		glog.Infof("Registering transcoder %v", ethAcctAddr)
		price := big.NewInt(1)

		tx, err = client.Transcoder(eth.FromPerc(10), eth.FromPerc(5), price)
		if err == eth.ErrCurrentRoundLocked {
			// wait for next round and retry
		}
		if err != nil {
			glog.Error(err)
			return
		}

		err = client.CheckTx(tx)
		if err != nil {
			glog.Error(err)
			return
		}

		glog.Infof("Storing service URI %v in service registry...", serviceURI)

		tx, err = client.SetServiceURI(serviceURI)
		if err != nil {
			glog.Error(err)
			return
		}

		err = client.CheckTx(tx)
		if err != nil {
			glog.Error(err)
		}
	}
}

func createRunScript(ethAcctAddr, dataDir string, isBroadcaster bool) {
	script := "#!/bin/bash\n"
	script += fmt.Sprintf(`./livepeer -v 99 -controllerAddr %s -datadir ./%s \
    -ethAcctAddr %s \
    -ethUrl %s \
    -ethPassword "" \
    -gasPrice 200 -gasLimit 2000000 -devenv=true \
    -monitor=false -currentManifest=true `,
		controllerAddr, dataDir, ethAcctAddr, endpoint)

	if !isBroadcaster {
		script += fmt.Sprintf(` -initializeRound=true \
    -serviceAddr 127.0.0.1:8936 -httpAddr 127.0.0.1:8936  -transcoder \
    -cliAddr 127.0.0.1:7936 -ipfsPath ./%s/trans
    `, dataDir)
	}

	glog.Info(script)
	fName := fmt.Sprintf("run_%s_%s.sh", getNodeType(isBroadcaster), ethAcctAddr)
	err := ioutil.WriteFile(fName, []byte(script), 0755)
	if err != nil {
		glog.Warningf("Error writing run script: %v", err)
	}
}

func createKey(keystoreDir string) string {
	keyStore := keystore.NewKeyStore(keystoreDir, keystore.StandardScryptN, keystore.StandardScryptP)

	account, err := keyStore.NewAccount(passphrase)
	if err != nil {
		glog.Fatal(err)
	}
	glog.Infof("Using ETH account: %v", account.Address.Hex())
	return account.Address.Hex()
}

func remoteConsole(destAccountAddr string) error {
	broadcasterGeth := "0161e041aad467a890839d5b08b138c1e6373072"
	if destAccountAddr != "" {
		broadcasterGeth = destAccountAddr
	}

	client, err := rpc.Dial(endpoint)
	if err != nil {
		glog.Fatalf("Unable to attach to remote geth: %v", err)
	}
	// get mining account
	if !gethMiningAccountOverride {
		var accounts []string
		err = client.Call(&accounts, "eth_accounts")
		if err != nil {
			glog.Fatalf("Error finding mining account: %v", err)
		}
		if len(accounts) == 0 {
			glog.Fatal("Can't find mining account")
		}
		gethMiningAccount = accounts[0]
		glog.Infof("Found mining account: %s", gethMiningAccount)
	}

	tmp, err := ioutil.TempDir("", "console")
	if err != nil {
		glog.Fatalf("Can't create temporary directory: %v", err)
	}
	defer os.RemoveAll(tmp)

	printer := new(bytes.Buffer)

	config := console.Config{
		DataDir: tmp,
		Client:  client,
		Printer: printer,
	}

	console, err := console.New(config)
	if err != nil {
		glog.Fatalf("Failed to start the JavaScript console: %v", err)
	}
	defer console.Stop(false)

	if !controllerAddrOverride {
		// f9a6cf519167d81bc5cb3d26c60c0c9a19704aa908c148e82a861b570f4cd2d7 - SetContractInfo event
		getControllerAddressScript := `
		var logs = [];
		var filter = web3.eth.filter({ fromBlock: 0, toBlock: "latest",
			topics: ["0xf9a6cf519167d81bc5cb3d26c60c0c9a19704aa908c148e82a861b570f4cd2d7"]});
		filter.get(function(error, result){
			logs.push(result);
		});
		console.log(logs[0][0].address);''
	`
		glog.Infof("Running eth script: %s", getControllerAddressScript)
		err = console.Evaluate(getControllerAddressScript)
		if err != nil {
			glog.Error(err)
		}
		if printer.Len() == 0 {
			glog.Fatal("Can't find deployed controller")
		}
		controllerAddr = strings.Split(printer.String(), "\n")[0]

		glog.Infof("Found controller address: %s", controllerAddr)
	}

	script := fmt.Sprintf("eth.sendTransaction({from: \"%s\", to: \"%s\", value: web3.toWei(834, \"ether\")})",
		gethMiningAccount, broadcasterGeth)
	glog.Infof("Running eth script: %s", script)

	err = console.Evaluate(script)
	if err != nil {
		glog.Error(err)
	}

	time.Sleep(3 * time.Second)

	return err
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gobs/args"
	"github.com/gobs/cmd"
	"github.com/gobs/cmd/plugins/controlflow"
	"github.com/gobs/cmd/plugins/json"
	"github.com/gobs/httpclient"
	"github.com/gobs/simplejson"

	"fmt"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var (
	reFieldValue = regexp.MustCompile(`(\w[\d\w-]*)(=(.*))?`) // field-name=value
)

func request(cmd *cmd.Cmd, client *httpclient.HttpClient, method, params string, print bool) *httpclient.HttpResponse {
	cmd.SetVar("error", "", true)
	cmd.SetVar("body", "", true)

	options := []httpclient.RequestOption{client.Method(method)}
	args := args.ParseArgs(params)

	if len(args.Arguments) > 0 {
		options = append(options, client.Path(args.Arguments[0]))
	}

	if len(args.Arguments) > 1 {
		data := strings.Join(args.Arguments[1:], " ")
		options = append(options, client.Body(strings.NewReader(data)))
	}

	if len(args.Options) > 0 {
		options = append(options, client.StringParams(args.Options))
	}

	res, err := client.SendRequest(options...)
	if err == nil {
		err = res.ResponseError()
	}
	if err != nil {
		fmt.Println("ERROR:", err)
		cmd.SetVar("error", err, true)
	}

	body := res.Content()
	if len(body) > 0 && print {
		if strings.Contains(res.Header.Get("Content-Type"), "json") {
			jbody, err := simplejson.LoadBytes(body)
			if err != nil {
				fmt.Println(err)
			} else {
				json.PrintJson(jbody.Data())
			}
		} else {
			fmt.Println(string(body))
		}
	}

	cmd.SetVar("body", string(body), true)
	return res
}

func headerName(s string) string {
	s = strings.ToLower(s)
	parts := strings.Split(s, "-")
	for i, p := range parts {
		if len(p) > 0 {
			parts[i] = strings.ToUpper(p[0:1]) + p[1:]
		}
	}
	return strings.Join(parts, "-")
}

func unquote(s string) string {
	if res, err := strconv.Unquote(strings.TrimSpace(s)); err == nil {
		return res
	}

	return s
}

func parseValue(v string) (interface{}, error) {
	switch {
	case strings.HasPrefix(v, "{") || strings.HasPrefix(v, "["):
		j, err := simplejson.LoadString(v)
		if err != nil {
			return nil, fmt.Errorf("error parsing %q", v)
		} else {
			return j.Data(), nil
		}

	case strings.HasPrefix(v, `"`):
		return strings.Trim(v, `"`), nil

	case strings.HasPrefix(v, `'`):
		return strings.Trim(v, `'`), nil

	case v == "":
		return v, nil

	case v == "true":
		return true, nil

	case v == "false":
		return false, nil

	case v == "null":
		return nil, nil

	default:
		if i, err := strconv.ParseInt(v, 10, 64); err == nil {
			return i, nil
		}
		if f, err := strconv.ParseFloat(v, 64); err == nil {
			return f, nil
		}

		return v, nil
	}
}

func main() {
	var interrupted bool
	var client = httpclient.NewHttpClient("")

	client.UserAgent = "httpclient/0.1"

	commander := &cmd.Cmd{
		HistoryFile: ".httpclient_history",
		EnableShell: true,
		Interrupt:   func(sig os.Signal) bool { interrupted = true; return false },
	}

	commander.Init(controlflow.Plugin, json.Plugin)

	commander.SetVar("print", true, false)

	commander.Add(cmd.Command{
		"base",
		`base [url]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := url.Parse(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.BaseURL = val
				commander.SetPrompt(fmt.Sprintf("%v> ", client.BaseURL), 40)
				if !commander.GetBoolVar("print") {
					return
				}
			}

			fmt.Println("base", client.BaseURL)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"insecure",
		`insecure [true|false]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := strconv.ParseBool(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.AllowInsecure(val)
			}

			// assume if there is a transport, it's because we set AllowInsecure
			fmt.Println("insecure", client.GetTransport() != nil)

			return
		},
		nil})

	commander.Add(cmd.Command{
		"timeout",
		`timeout [duration]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := time.ParseDuration(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.SetTimeout(val)
			}

			fmt.Println("timeout", client.GetTimeout())
			return
		},
		nil})

	commander.Add(cmd.Command{
		"verbose",
		`verbose [true|false]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := strconv.ParseBool(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.Verbose = val
			}

			fmt.Println("Verbose", client.Verbose)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"timing",
		`timing [true|false]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := strconv.ParseBool(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				commander.Timing = val
			}

			fmt.Println("Timing", commander.Timing)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"agent",
		`agent user-agent-string`,
		func(line string) (stop bool) {
			if line != "" {
				client.UserAgent = line
			}

			fmt.Println("User-Agent:", client.UserAgent)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"header",
		`header [name [value]]`,
		func(line string) (stop bool) {
			if line == "" {
				if len(client.Headers) == 0 {
					fmt.Println("No headers")
				} else {
					fmt.Println("Headers:")
					for k, v := range client.Headers {
						fmt.Printf("  %v: %v\n", k, v)
					}
				}

				return
			}

			parts := args.GetArgsN(line, 2)
			name := headerName(parts[0])

			if len(parts) == 2 {
				client.Headers[name] = unquote(parts[1])
				if !commander.GetBoolVar("print") {
					return
				}
			}

			fmt.Printf("%v: %v\n", name, client.Headers[name])
			return
		},
		nil})

	commander.Add(cmd.Command{"head",
		`
                head [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			res := request(commander, client, "head", line, false)
			if res != nil {
				json.PrintJson(res.Header)
			}
			return
		},
		nil})

	commander.Add(cmd.Command{"get",
		`
                get [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "get", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Add(cmd.Command{"post",
		`
                post [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "post", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Add(cmd.Command{"put",
		`
                put [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "put", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Add(cmd.Command{"delete",
		`
                delete [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "delete", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Commands["set"] = commander.Commands["var"]

	switch len(os.Args) {
	case 1: // program name only
		break

	case 2: // one arg - expect URL or @filename
		cmd := os.Args[1]
		if !strings.HasPrefix(cmd, "@") {
			cmd = "base " + cmd
		}

		commander.OneCmd(cmd)

	default:
		fmt.Println("usage:", os.Args[0], "[base-url]")
		return
	}

	commander.CmdLoop()
}
<commit_msg>Added "verbose body" to log request/response bodies<commit_after>package main

import (
	"github.com/gobs/args"
	"github.com/gobs/cmd"
	"github.com/gobs/cmd/plugins/controlflow"
	"github.com/gobs/cmd/plugins/json"
	"github.com/gobs/httpclient"
	"github.com/gobs/simplejson"

	"fmt"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var (
	reFieldValue = regexp.MustCompile(`(\w[\d\w-]*)(=(.*))?`) // field-name=value
)

func request(cmd *cmd.Cmd, client *httpclient.HttpClient, method, params string, print bool) *httpclient.HttpResponse {
	cmd.SetVar("error", "", true)
	cmd.SetVar("body", "", true)

	// [-options...] "path" {body}

	options := []httpclient.RequestOption{client.Method(method)}
	args := args.ParseArgs(params)

	if len(args.Arguments) > 0 {
		options = append(options, client.Path(args.Arguments[0]))
	}

	if len(args.Arguments) > 1 {
		data := strings.Join(args.Arguments[1:], " ")
		options = append(options, client.Body(strings.NewReader(data)))
	}

	if len(args.Options) > 0 {
		options = append(options, client.StringParams(args.Options))
	}

	res, err := client.SendRequest(options...)
	if err == nil {
		err = res.ResponseError()
	}
	if err != nil {
		fmt.Println("ERROR:", err)
		cmd.SetVar("error", err, true)
	}

	body := res.Content()
	if len(body) > 0 && print {
		if strings.Contains(res.Header.Get("Content-Type"), "json") {
			jbody, err := simplejson.LoadBytes(body)
			if err != nil {
				fmt.Println(err)
			} else {
				json.PrintJson(jbody.Data())
			}
		} else {
			fmt.Println(string(body))
		}
	}

	cmd.SetVar("body", string(body), true)
	return res
}

func headerName(s string) string {
	s = strings.ToLower(s)
	parts := strings.Split(s, "-")
	for i, p := range parts {
		if len(p) > 0 {
			parts[i] = strings.ToUpper(p[0:1]) + p[1:]
		}
	}
	return strings.Join(parts, "-")
}

func unquote(s string) string {
	if res, err := strconv.Unquote(strings.TrimSpace(s)); err == nil {
		return res
	}

	return s
}

func parseValue(v string) (interface{}, error) {
	switch {
	case strings.HasPrefix(v, "{") || strings.HasPrefix(v, "["):
		j, err := simplejson.LoadString(v)
		if err != nil {
			return nil, fmt.Errorf("error parsing %q", v)
		} else {
			return j.Data(), nil
		}

	case strings.HasPrefix(v, `"`):
		return strings.Trim(v, `"`), nil

	case strings.HasPrefix(v, `'`):
		return strings.Trim(v, `'`), nil

	case v == "":
		return v, nil

	case v == "true":
		return true, nil

	case v == "false":
		return false, nil

	case v == "null":
		return nil, nil

	default:
		if i, err := strconv.ParseInt(v, 10, 64); err == nil {
			return i, nil
		}
		if f, err := strconv.ParseFloat(v, 64); err == nil {
			return f, nil
		}

		return v, nil
	}
}

func main() {
	var interrupted bool
	var logBody bool
	var client = httpclient.NewHttpClient("")

	client.UserAgent = "httpclient/0.1"

	commander := &cmd.Cmd{
		HistoryFile: ".httpclient_history",
		EnableShell: true,
		Interrupt:   func(sig os.Signal) bool { interrupted = true; return false },
	}

	commander.Init(controlflow.Plugin, json.Plugin)

	commander.SetVar("print", true, false)

	commander.Add(cmd.Command{
		"base",
		`base [url]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := url.Parse(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.BaseURL = val
				commander.SetPrompt(fmt.Sprintf("%v> ", client.BaseURL), 40)
				if !commander.GetBoolVar("print") {
					return
				}
			}

			fmt.Println("base", client.BaseURL)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"insecure",
		`insecure [true|false]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := strconv.ParseBool(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.AllowInsecure(val)
			}

			// assume if there is a transport, it's because we set AllowInsecure
			fmt.Println("insecure", client.GetTransport() != nil)

			return
		},
		nil})

	commander.Add(cmd.Command{
		"timeout",
		`timeout [duration]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := time.ParseDuration(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.SetTimeout(val)
			}

			fmt.Println("timeout", client.GetTimeout())
			return
		},
		nil})

	commander.Add(cmd.Command{
		"verbose",
		`verbose [true|false|body]`,
		func(line string) (stop bool) {
			if line == "body" {
				if !logBody {
					httpclient.StartLogging(true, true)
					logBody = true
				}
			} else if line != "" {
				val, err := strconv.ParseBool(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				client.Verbose = val

				if !val && logBody {
					httpclient.StopLogging()
					logBody = false
				}
			}

			fmt.Println("Verbose", client.Verbose)
			if logBody {
				fmt.Println("Logging Request/Response body")
			}
			return
		},
		nil})

	commander.Add(cmd.Command{
		"timing",
		`timing [true|false]`,
		func(line string) (stop bool) {
			if line != "" {
				val, err := strconv.ParseBool(line)
				if err != nil {
					fmt.Println(err)
					return
				}

				commander.Timing = val
			}

			fmt.Println("Timing", commander.Timing)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"agent",
		`agent user-agent-string`,
		func(line string) (stop bool) {
			if line != "" {
				client.UserAgent = line
			}

			fmt.Println("User-Agent:", client.UserAgent)
			return
		},
		nil})

	commander.Add(cmd.Command{
		"header",
		`header [name [value]]`,
		func(line string) (stop bool) {
			if line == "" {
				if len(client.Headers) == 0 {
					fmt.Println("No headers")
				} else {
					fmt.Println("Headers:")
					for k, v := range client.Headers {
						fmt.Printf("  %v: %v\n", k, v)
					}
				}

				return
			}

			parts := args.GetArgsN(line, 2)
			name := headerName(parts[0])

			if len(parts) == 2 {
				client.Headers[name] = unquote(parts[1])
				if !commander.GetBoolVar("print") {
					return
				}
			}

			fmt.Printf("%v: %v\n", name, client.Headers[name])
			return
		},
		nil})

	commander.Add(cmd.Command{"head",
		`
                head [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			res := request(commander, client, "head", line, false)
			if res != nil {
				json.PrintJson(res.Header)
			}
			return
		},
		nil})

	commander.Add(cmd.Command{"get",
		`
                get [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "get", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Add(cmd.Command{"post",
		`
                post [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "post", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Add(cmd.Command{"put",
		`
                put [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "put", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Add(cmd.Command{"delete",
		`
                delete [url-path] [short-data]
                `,
		func(line string) (stop bool) {
			request(commander, client, "delete", line, commander.GetBoolVar("print"))
			return
		},
		nil})

	commander.Commands["set"] = commander.Commands["var"]

	switch len(os.Args) {
	case 1: // program name only
		break

	case 2: // one arg - expect URL or @filename
		cmd := os.Args[1]
		if !strings.HasPrefix(cmd, "@") {
			cmd = "base " + cmd
		}

		commander.OneCmd(cmd)

	default:
		fmt.Println("usage:", os.Args[0], "[base-url]")
		return
	}

	commander.CmdLoop()
}
<|endoftext|>


<commit_before>package main

import (
	goflag "flag"
	"fmt"
	"math/rand"
	"os"
	"runtime"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	genericapiserver "k8s.io/apiserver/pkg/server"
	utilflag "k8s.io/component-base/cli/flag"
	"k8s.io/component-base/logs"

	"github.com/openshift/library-go/pkg/serviceability"
	openshift_integrated_oauth_server "github.com/openshift/oauth-server/pkg/cmd/oauth-server"
	"github.com/openshift/openshift-apiserver/pkg/cmd/openshift-apiserver"
	"github.com/openshift/openshift-controller-manager/pkg/cmd/openshift-controller-manager"
	"github.com/openshift/sdn/pkg/openshift-network-controller"

	"github.com/openshift/origin/pkg/version"
)

func main() {
	stopCh := genericapiserver.SetupSignalHandler()

	rand.Seed(time.Now().UTC().UnixNano())

	pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
	pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)

	logs.InitLogs()
	defer logs.FlushLogs()
	defer serviceability.BehaviorOnPanic(os.Getenv("OPENSHIFT_ON_PANIC"), version.Get())()
	defer serviceability.Profile(os.Getenv("OPENSHIFT_PROFILE")).Stop()

	if len(os.Getenv("GOMAXPROCS")) == 0 {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	command := NewHyperShiftCommand(stopCh)
	if err := command.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

func NewHyperShiftCommand(stopCh <-chan struct{}) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "hypershift",
		Short: "Combined server command for OpenShift",
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Help()
			os.Exit(1)
		},
	}

	startOpenShiftAPIServer := openshift_apiserver.NewOpenShiftAPIServerCommand(openshift_apiserver.RecommendedStartAPIServerName, os.Stdout, os.Stderr, stopCh)
	startOpenShiftAPIServer.Deprecated = "will be removed in 4.2"
	startOpenShiftAPIServer.Hidden = true
	cmd.AddCommand(startOpenShiftAPIServer)

	startOpenShiftControllerManager := openshift_controller_manager.NewOpenShiftControllerManagerCommand(openshift_controller_manager.RecommendedStartControllerManagerName, os.Stdout, os.Stderr)
	startOpenShiftControllerManager.Deprecated = "will be removed in 4.2"
	startOpenShiftControllerManager.Hidden = true
	cmd.AddCommand(startOpenShiftControllerManager)

	startOpenShiftNetworkController := openshift_network_controller.NewOpenShiftNetworkControllerCommand(openshift_network_controller.RecommendedStartNetworkControllerName, "hypershift", os.Stdout, os.Stderr)
	cmd.AddCommand(startOpenShiftNetworkController)

	startOsin := openshift_integrated_oauth_server.NewOsinServer(os.Stdout, os.Stderr, stopCh)
	startOsin.Use = "openshift-osinserver"
	startOsin.Deprecated = "will be removed in 4.0"
	startOsin.Hidden = true
	cmd.AddCommand(startOsin)

	return cmd
}
<commit_msg>Remove osinserver from hypershift command<commit_after>package main

import (
	goflag "flag"
	"fmt"
	"math/rand"
	"os"
	"runtime"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	genericapiserver "k8s.io/apiserver/pkg/server"
	utilflag "k8s.io/component-base/cli/flag"
	"k8s.io/component-base/logs"

	"github.com/openshift/library-go/pkg/serviceability"
	"github.com/openshift/openshift-apiserver/pkg/cmd/openshift-apiserver"
	"github.com/openshift/openshift-controller-manager/pkg/cmd/openshift-controller-manager"
	"github.com/openshift/sdn/pkg/openshift-network-controller"

	"github.com/openshift/origin/pkg/version"
)

func main() {
	stopCh := genericapiserver.SetupSignalHandler()

	rand.Seed(time.Now().UTC().UnixNano())

	pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
	pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)

	logs.InitLogs()
	defer logs.FlushLogs()
	defer serviceability.BehaviorOnPanic(os.Getenv("OPENSHIFT_ON_PANIC"), version.Get())()
	defer serviceability.Profile(os.Getenv("OPENSHIFT_PROFILE")).Stop()

	if len(os.Getenv("GOMAXPROCS")) == 0 {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	command := NewHyperShiftCommand(stopCh)
	if err := command.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

func NewHyperShiftCommand(stopCh <-chan struct{}) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "hypershift",
		Short: "Combined server command for OpenShift",
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Help()
			os.Exit(1)
		},
	}

	startOpenShiftAPIServer := openshift_apiserver.NewOpenShiftAPIServerCommand(openshift_apiserver.RecommendedStartAPIServerName, os.Stdout, os.Stderr, stopCh)
	startOpenShiftAPIServer.Deprecated = "will be removed in 4.2"
	startOpenShiftAPIServer.Hidden = true
	cmd.AddCommand(startOpenShiftAPIServer)

	startOpenShiftControllerManager := openshift_controller_manager.NewOpenShiftControllerManagerCommand(openshift_controller_manager.RecommendedStartControllerManagerName, os.Stdout, os.Stderr)
	startOpenShiftControllerManager.Deprecated = "will be removed in 4.2"
	startOpenShiftControllerManager.Hidden = true
	cmd.AddCommand(startOpenShiftControllerManager)

	startOpenShiftNetworkController := openshift_network_controller.NewOpenShiftNetworkControllerCommand(openshift_network_controller.RecommendedStartNetworkControllerName, "hypershift", os.Stdout, os.Stderr)
	cmd.AddCommand(startOpenShiftNetworkController)

	return cmd
}
<|endoftext|>


<commit_before>package main

import (
	"errors"

	"launchpad.net/gnuflag"
	"launchpad.net/juju-core/cmd"
	"launchpad.net/juju-core/juju"
	"launchpad.net/juju-core/state"
)

// RemoveUnitCommand is responsible adding additional units to a service.
type RemoveUnitCommand struct {
	EnvName     string
	ServiceName string
	UnitNames	[]string
}

func (c *RemoveUnitCommand) Info() *cmd.Info {
	return &cmd.Info{"remove-unit", "", "remvoes service units", ""}
}

func (c *RemoveUnitCommand) Init(f *gnuflag.FlagSet, args []string) error {
	addEnvironFlags(&c.EnvName, f)
	if err := f.Parse(true, args); err != nil {
		return err
	}
	args = f.Args()
	if len(args) == 0 {
		return errors.New("no service units specified")
	}
	c.UnitNames = f.Args()
	return nil
}

// Run connects to the environment specified on the command line
// and calls conn.RemoveUnits.
func (c *RemoveUnitCommand) Run(_ *cmd.Context) error {
	conn, err := juju.NewConnFromName(c.EnvName)
	if err != nil {
		return err
	}
	defer conn.Close()
	var units []*state.Unit
	for _, name := range c.UnitNames {
		unit, err := conn.State.Unit(name)
		if err != nil { return err }
		units = append(units, unit)
	}
	return conn.RemoveUnits(units...)
}
<commit_msg>added test<commit_after>package main

import (
	"errors"

	"launchpad.net/gnuflag"
	"launchpad.net/juju-core/cmd"
	"launchpad.net/juju-core/juju"
	"launchpad.net/juju-core/state"
)

// RemoveUnitCommand is responsible adding additional units to a service.
type RemoveUnitCommand struct {
	EnvName     string
	ServiceName string
	UnitNames   []string
}

func (c *RemoveUnitCommand) Info() *cmd.Info {
	return &cmd.Info{"remove-unit", "", "remvoes service units", ""}
}

func (c *RemoveUnitCommand) Init(f *gnuflag.FlagSet, args []string) error {
	addEnvironFlags(&c.EnvName, f)
	if err := f.Parse(true, args); err != nil {
		return err
	}
	args = f.Args()
	if len(args) == 0 {
		return errors.New("no service units specified")
	}
	c.UnitNames = f.Args()
	return nil
}

// Run connects to the environment specified on the command line
// and calls conn.RemoveUnits.
func (c *RemoveUnitCommand) Run(_ *cmd.Context) error {
	conn, err := juju.NewConnFromName(c.EnvName)
	if err != nil {
		return err
	}
	defer conn.Close()
	var units []*state.Unit
	for _, name := range c.UnitNames {
		unit, err := conn.State.Unit(name)
		if err != nil {
			return err
		}
		units = append(units, unit)
	}
	return conn.RemoveUnits(units...)
}
<|endoftext|>


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

package space

import (
	"fmt"
	"io"
	"net"
	"strings"

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/gnuflag"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/cmd/modelcmd"
	"github.com/juju/juju/cmd/output"
)

// NewListCommand returns a command used to list spaces.
func NewListCommand() cmd.Command {
	return modelcmd.Wrap(&listCommand{})
}

// listCommand displays a list of all spaces known to Juju.
type listCommand struct {
	SpaceCommandBase
	Short bool
	out   cmd.Output
}

const listCommandDoc = `
Displays all defined spaces. If --short is not given both spaces and
their subnets are displayed, otherwise just a list of spaces. The
--format argument has the same semantics as in other CLI commands -
"yaml" is the default. The --output argument allows the command
output to be redirected to a file. `

// Info is defined on the cmd.Command interface.
func (c *listCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:    "spaces",
		Args:    "[--short] [--format yaml|json] [--output <path>]",
		Purpose: "List known spaces, including associated subnets",
		Doc:     strings.TrimSpace(listCommandDoc),
		Aliases: []string{"list-spaces"},
	}
}

// SetFlags is defined on the cmd.Command interface.
func (c *listCommand) SetFlags(f *gnuflag.FlagSet) {
	c.SpaceCommandBase.SetFlags(f)
	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
		"yaml":    cmd.FormatYaml,
		"json":    cmd.FormatJson,
		"tabular": c.printTabular,
	})
	f.BoolVar(&c.Short, "short", false, "only display spaces.")
}

// Init is defined on the cmd.Command interface. It checks the
// arguments for sanity and sets up the command to run.
func (c *listCommand) Init(args []string) error {
	// No arguments are accepted, just flags.
	if err := cmd.CheckEmpty(args); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// Run implements Command.Run.
func (c *listCommand) Run(ctx *cmd.Context) error {
	return c.RunWithAPI(ctx, func(api SpaceAPI, ctx *cmd.Context) error {
		spaces, err := api.ListSpaces()
		if err != nil {
			if errors.IsNotSupported(err) {
				ctx.Infof("cannot list spaces: %v", err)
			}
			return errors.Annotate(err, "cannot list spaces")
		}
		if len(spaces) == 0 {
			ctx.Infof("no spaces to display")
			return c.out.Write(ctx, nil)
		}

		if c.Short {
			result := formattedShortList{}
			for _, space := range spaces {
				result.Spaces = append(result.Spaces, space.Name)
			}
			return c.out.Write(ctx, result)
		}
		// Construct the output list for displaying with the chosen
		// format.
		result := formattedList{
			Spaces: make(map[string]map[string]formattedSubnet),
		}

		for _, space := range spaces {
			result.Spaces[space.Name] = make(map[string]formattedSubnet)
			for _, subnet := range space.Subnets {
				subResult := formattedSubnet{
					Type:       typeUnknown,
					ProviderId: subnet.ProviderId,
					Zones:      subnet.Zones,
				}
				// Display correct status according to the life cycle value.
				//
				// TODO(dimitern): Do this on the apiserver side, also
				// do the same for params.Space, so in case of an
				// error it can be displayed.
				switch subnet.Life {
				case params.Alive:
					subResult.Status = statusInUse
				case params.Dying, params.Dead:
					subResult.Status = statusTerminating
				}

				// Use the CIDR to determine the subnet type.
				// TODO(dimitern): Do this on the apiserver side.
				if ip, _, err := net.ParseCIDR(subnet.CIDR); err != nil {
					// This should never happen as subnets will be
					// validated before saving in state.
					msg := fmt.Sprintf("error: invalid subnet CIDR: %s", subnet.CIDR)
					subResult.Status = msg
				} else if ip.To4() != nil {
					subResult.Type = typeIPv4
				} else if ip.To16() != nil {
					subResult.Type = typeIPv6
				}
				result.Spaces[space.Name][subnet.CIDR] = subResult
			}
		}
		return c.out.Write(ctx, result)
	})
}

// printTabular prints the list of spaces in tabular format
func (c *listCommand) printTabular(writer io.Writer, value interface{}) error {
	list, ok := value.(formattedList)
	if !ok {
		return errors.New("unexpected value")
	}

	tw := output.TabWriter(writer)
	fmt.Fprintf(tw, "%s\t%s\n", "SPACE", "SUBNETS")
	for space, subnets := range list.Spaces {
		fmt.Fprintf(tw, "%s", space)
		if len(subnets) == 0 {
			fmt.Fprintf(tw, "\n")
			continue
		}
		for subnet, _ := range subnets {
			fmt.Fprintf(tw, "\t%v\n", subnet)
		}

	}
	tw.Flush()
	return nil
}

const (
	typeUnknown = "unknown"
	typeIPv4    = "ipv4"
	typeIPv6    = "ipv6"

	statusInUse       = "in-use"
	statusTerminating = "terminating"
)

// TODO(dimitern): Display space attributes along with subnets (state
// or error,public,?)

type formattedList struct {
	Spaces map[string]map[string]formattedSubnet `json:"spaces" yaml:"spaces"`
}

type formattedShortList struct {
	Spaces []string `json:"spaces" yaml:"spaces"`
}

type formattedSubnet struct {
	Type       string   `json:"type" yaml:"type"`
	ProviderId string   `json:"provider-id,omitempty" yaml:"provider-id,omitempty"`
	Status     string   `json:"status,omitempty" yaml:"status,omitempty"`
	Zones      []string `json:"zones" yaml:"zones"`
}
<commit_msg>Support for short format in tabular output<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package space

import (
	"fmt"
	"io"
	"net"
	"strings"

	"github.com/juju/cmd"
	"github.com/juju/errors"
	"github.com/juju/gnuflag"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/cmd/modelcmd"
	"github.com/juju/juju/cmd/output"
)

// NewListCommand returns a command used to list spaces.
func NewListCommand() cmd.Command {
	return modelcmd.Wrap(&listCommand{})
}

// listCommand displays a list of all spaces known to Juju.
type listCommand struct {
	SpaceCommandBase
	Short bool
	out   cmd.Output
}

const listCommandDoc = `
Displays all defined spaces. If --short is not given both spaces and
their subnets are displayed, otherwise just a list of spaces. The
--format argument has the same semantics as in other CLI commands -
"yaml" is the default. The --output argument allows the command
output to be redirected to a file. `

// Info is defined on the cmd.Command interface.
func (c *listCommand) Info() *cmd.Info {
	return &cmd.Info{
		Name:    "spaces",
		Args:    "[--short] [--format yaml|json] [--output <path>]",
		Purpose: "List known spaces, including associated subnets",
		Doc:     strings.TrimSpace(listCommandDoc),
		Aliases: []string{"list-spaces"},
	}
}

// SetFlags is defined on the cmd.Command interface.
func (c *listCommand) SetFlags(f *gnuflag.FlagSet) {
	c.SpaceCommandBase.SetFlags(f)
	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
		"yaml":    cmd.FormatYaml,
		"json":    cmd.FormatJson,
		"tabular": c.printTabular,
	})
	f.BoolVar(&c.Short, "short", false, "only display spaces.")
}

// Init is defined on the cmd.Command interface. It checks the
// arguments for sanity and sets up the command to run.
func (c *listCommand) Init(args []string) error {
	// No arguments are accepted, just flags.
	if err := cmd.CheckEmpty(args); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// Run implements Command.Run.
func (c *listCommand) Run(ctx *cmd.Context) error {
	return c.RunWithAPI(ctx, func(api SpaceAPI, ctx *cmd.Context) error {
		spaces, err := api.ListSpaces()
		if err != nil {
			if errors.IsNotSupported(err) {
				ctx.Infof("cannot list spaces: %v", err)
			}
			return errors.Annotate(err, "cannot list spaces")
		}
		if len(spaces) == 0 {
			ctx.Infof("no spaces to display")
			return c.out.Write(ctx, nil)
		}

		if c.Short {
			result := formattedShortList{}
			for _, space := range spaces {
				result.Spaces = append(result.Spaces, space.Name)
			}
			return c.out.Write(ctx, result)
		}
		// Construct the output list for displaying with the chosen
		// format.
		result := formattedList{
			Spaces: make(map[string]map[string]formattedSubnet),
		}

		for _, space := range spaces {
			result.Spaces[space.Name] = make(map[string]formattedSubnet)
			for _, subnet := range space.Subnets {
				subResult := formattedSubnet{
					Type:       typeUnknown,
					ProviderId: subnet.ProviderId,
					Zones:      subnet.Zones,
				}
				// Display correct status according to the life cycle value.
				//
				// TODO(dimitern): Do this on the apiserver side, also
				// do the same for params.Space, so in case of an
				// error it can be displayed.
				switch subnet.Life {
				case params.Alive:
					subResult.Status = statusInUse
				case params.Dying, params.Dead:
					subResult.Status = statusTerminating
				}

				// Use the CIDR to determine the subnet type.
				// TODO(dimitern): Do this on the apiserver side.
				if ip, _, err := net.ParseCIDR(subnet.CIDR); err != nil {
					// This should never happen as subnets will be
					// validated before saving in state.
					msg := fmt.Sprintf("error: invalid subnet CIDR: %s", subnet.CIDR)
					subResult.Status = msg
				} else if ip.To4() != nil {
					subResult.Type = typeIPv4
				} else if ip.To16() != nil {
					subResult.Type = typeIPv6
				}
				result.Spaces[space.Name][subnet.CIDR] = subResult
			}
		}
		return c.out.Write(ctx, result)
	})
}

// printTabular prints the list of spaces in tabular format
func (c *listCommand) printTabular(writer io.Writer, value interface{}) error {
	tw := output.TabWriter(writer)
	if c.Short {
		list, ok := value.(formattedShortList)
		if !ok {
			return errors.New("unexpected value")
		}
		fmt.Fprintf(tw, "SPACE\n")
		for _, space := range list.Spaces {
			fmt.Fprintf(tw, "%v\n", space)
		}
	} else {
		list, ok := value.(formattedList)
		if !ok {
			return errors.New("unexpected value")
		}

		fmt.Fprintf(tw, "%s\t%s\n", "SPACE", "SUBNETS")
		for space, subnets := range list.Spaces {
			fmt.Fprintf(tw, "%s", space)
			if len(subnets) == 0 {
				fmt.Fprintf(tw, "\n")
				continue
			}
			for subnet, _ := range subnets {
				fmt.Fprintf(tw, "\t%v\n", subnet)
			}
		}
	}
	tw.Flush()
	return nil
}

const (
	typeUnknown = "unknown"
	typeIPv4    = "ipv4"
	typeIPv6    = "ipv6"

	statusInUse       = "in-use"
	statusTerminating = "terminating"
)

// TODO(dimitern): Display space attributes along with subnets (state
// or error,public,?)

type formattedList struct {
	Spaces map[string]map[string]formattedSubnet `json:"spaces" yaml:"spaces"`
}

type formattedShortList struct {
	Spaces []string `json:"spaces" yaml:"spaces"`
}

type formattedSubnet struct {
	Type       string   `json:"type" yaml:"type"`
	ProviderId string   `json:"provider-id,omitempty" yaml:"provider-id,omitempty"`
	Status     string   `json:"status,omitempty" yaml:"status,omitempty"`
	Zones      []string `json:"zones" yaml:"zones"`
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes 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 cmd

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

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"k8s.io/release/pkg/gcp"
	"k8s.io/release/pkg/gcp/auth"
	"k8s.io/release/pkg/gcp/build"
	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/release"
	"k8s.io/release/pkg/util"
)

type GcbmgrOptions struct {
	Stage        bool
	Release      bool
	Stream       bool
	BuildAtHead  bool
	Branch       string
	ReleaseType  string
	BuildVersion string
	GcpUser      string
	LastJobs     int64
	Repo         Repository
	Version      Version
}

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Repository
type Repository interface {
	Open() error
	CheckState(string, string, string) error
	GetTag() (string, error)
}

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Version
type Version interface {
	GetKubeVersionForBranch(release.VersionType, string) (string, error)
}

var (
	gcbmgrOpts = &GcbmgrOptions{}
	buildOpts  = &build.Options{}
)

// gcbmgrCmd is a krel subcommand which invokes RunGcbmgr()
var gcbmgrCmd = &cobra.Command{
	Use:           "gcbmgr",
	Short:         "Run gcbmgr",
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return RunGcbmgr(gcbmgrOpts)
	},
}

func init() {
	// Submit types
	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.Stage,
		"stage",
		false,
		"submit a stage run to GCB",
	)
	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.Release,
		"release",
		false,
		"submit a release run to GCB",
	)

	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.Branch,
		"branch",
		git.DefaultBranch,
		"branch to run the specified GCB run against",
	)

	// Release types
	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.ReleaseType,
		"type",
		release.ReleaseTypeAlpha,
		fmt.Sprintf("release type, must be one of: '%s'",
			strings.Join([]string{
				release.ReleaseTypeAlpha,
				release.ReleaseTypeBeta,
				release.ReleaseTypeRC,
				release.ReleaseTypeOfficial,
			}, "', '"),
		),
	)

	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.BuildVersion,
		"build-version",
		"",
		fmt.Sprintf("the build version to be used. "+
			"Can be empty for `stage` releases, where it gets automatically "+
			"inferred by %q and the provided target branch.",
			release.VersionTypeCILatest,
		),
	)

	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.BuildAtHead,
		"build-at-head",
		false,
		"the build version to be used when was to use the lastest in the branch.",
	)

	// gcloud options
	gcbmgrCmd.PersistentFlags().StringVar(
		&buildOpts.Project,
		"project",
		release.DefaultKubernetesStagingProject,
		"GCP project to run GCB in",
	)
	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.Stream,
		"stream",
		false,
		"if specified, GCB will run synchronously, tailing its logs to stdout",
	)
	gcbmgrCmd.PersistentFlags().StringVar(
		&buildOpts.CloudbuildFile,
		"gcb-config",
		build.DefaultCloudbuildFile,
		"if specified, this will be used as the name of the Google Cloud Build config file",
	)
	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.GcpUser,
		"gcp-user",
		"",
		"if specified, this will be used as the GCP_USER_TAG",
	)

	gcbmgrCmd.PersistentFlags().Int64Var(
		&gcbmgrOpts.LastJobs,
		"list-jobs",
		5,
		"list the last N build jobs in the project",
	)

	gcbmgrOpts.Repo = release.NewRepo()
	gcbmgrOpts.Version = release.NewVersion()
	rootCmd.AddCommand(gcbmgrCmd)
}

// RunGcbmgr is the function invoked by 'krel gcbmgr', responsible for
// submitting release jobs to GCB
func RunGcbmgr(opts *GcbmgrOptions) error {
	if err := opts.Validate(); err != nil {
		return errors.Wrap(err, "validating gcbmgr options")
	}

	toolOrg := release.GetToolOrg()
	toolRepo := release.GetToolRepo()
	toolBranch := release.GetToolBranch()

	if err := gcp.PreCheck(); err != nil {
		return errors.Wrap(err, "pre-checking for GCP package usage")
	}

	if err := opts.Repo.Open(); err != nil {
		return errors.Wrap(err, "open release repo")
	}

	if err := opts.Repo.CheckState(toolOrg, toolRepo, toolBranch); err != nil {
		return errors.Wrap(err, "verifying repository state")
	}

	logrus.Infof("Running gcbmgr with the following options: %+v", opts)
	logrus.Infof("Build options: %v", *buildOpts)

	buildOpts.NoSource = true
	buildOpts.DiskSize = release.DefaultDiskSize

	buildOpts.Async = true

	if opts.Stream {
		buildOpts.Async = false
	}

	gcbSubs, gcbSubsErr := SetGCBSubstitutions(opts, toolOrg, toolRepo, toolBranch)
	if gcbSubs == nil || gcbSubsErr != nil {
		return gcbSubsErr
	}

	if rootOpts.nomock {
		// TODO: Consider a '--yes' flag so we can mock this
		_, nomockSubmit, askErr := util.Ask(
			fmt.Sprintf("Really submit a --nomock release job against the %s branch?", opts.Branch),
			"yes",
			3,
		)
		if askErr != nil {
			return askErr
		}

		if nomockSubmit {
			gcbSubs["NOMOCK_TAG"] = "nomock"
			gcbSubs["NOMOCK"] = fmt.Sprintf("--%s", gcbSubs["NOMOCK_TAG"])
		}
	} else {
		// TODO: Remove once cloudbuild.yaml doesn't strictly require vars to be set.
		gcbSubs["NOMOCK_TAG"] = ""
		gcbSubs["NOMOCK"] = ""

		bucketPrefix := release.BucketPrefix

		userBucket := fmt.Sprintf("%s%s", bucketPrefix, gcbSubs["GCP_USER_TAG"])
		userBucketSetErr := os.Setenv("USER_BUCKET", userBucket)
		if userBucketSetErr != nil {
			return userBucketSetErr
		}

		testBucket := fmt.Sprintf("%s%s", bucketPrefix, "gcb")
		testBucketSetErr := os.Setenv("BUCKET", testBucket)
		if testBucketSetErr != nil {
			return testBucketSetErr
		}
	}

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

	logrus.Info("Listing GCB substitutions prior to build submission...")
	for k, v := range gcbSubs {
		logrus.Infof("%s: %s", k, v)
	}

	var jobType string
	switch {
	// TODO: Consider a '--validate' flag to validate the GCB config without submitting
	case opts.Stage:
		jobType = "stage"
	case opts.Release:
		jobType = "release"
		buildOpts.DiskSize = "100"
	default:
		return listJobs(buildOpts.Project, opts.LastJobs)
	}

	buildOpts.ConfigDir = filepath.Join(toolRoot, "gcb", jobType)
	prepareBuildErr := build.PrepareBuilds(buildOpts)
	if prepareBuildErr != nil {
		return prepareBuildErr
	}

	// TODO: Need actual values
	var jobName, uploaded string

	version, err := opts.Repo.GetTag()
	if err != nil {
		return errors.Wrap(err, "getting current tag")
	}

	return build.RunSingleJob(buildOpts, jobName, uploaded, version, gcbSubs)
}

// SetGCBSubstitutions takes a set of GcbmgrOptions and returns a map of GCB substitutions
func SetGCBSubstitutions(o *GcbmgrOptions, toolOrg, toolRepo, toolBranch string) (map[string]string, error) {
	gcbSubs := map[string]string{}

	gcbSubs["TOOL_ORG"] = toolOrg
	gcbSubs["TOOL_REPO"] = toolRepo
	gcbSubs["TOOL_BRANCH"] = toolBranch

	gcpUser := o.GcpUser
	if gcpUser == "" {
		var gcpUserErr error
		gcpUser, gcpUserErr = auth.GetCurrentGCPUser()
		if gcpUserErr != nil {
			return gcbSubs, gcpUserErr
		}
	} else {
		// TODO: Consider removing this once the 'gcloud auth' is testable in CI
		gcpUser = auth.NormalizeGCPUser(gcpUser)
	}

	gcbSubs["GCP_USER_TAG"] = gcpUser

	gcbSubs["TYPE"] = o.ReleaseType
	gcbSubs["TYPE_TAG"] = o.ReleaseType

	gcbSubs["RELEASE_BRANCH"] = o.Branch

	buildVersion := o.BuildVersion
	if o.Release && buildVersion == "" {
		return gcbSubs, errors.New("Build version must be specified when sending a release GCB run")
	}

	if o.Stage && o.BuildAtHead {
		hash, err := git.LSRemoteExec(git.GetDefaultKubernetesRepoURL(), "rev-parse", o.Branch)
		if err != nil {
			return gcbSubs, errors.New("failed to execute the rev-parse")
		}

		fields := strings.Fields(hash)
		if len(fields) < 1 {
			return gcbSubs, errors.Errorf("unexpected output: %s", hash)
		}

		buildVersion = fields[0]
		gcbSubs["BUILD_AT_HEAD"] = buildVersion
	}

	if buildVersion == "" {
		var versionErr error
		buildVersion, versionErr = o.Version.GetKubeVersionForBranch(
			release.VersionTypeCILatest, o.Branch,
		)
		if versionErr != nil {
			return gcbSubs, versionErr
		}

		if o.Stage {
			gcbSubs["BUILD_AT_HEAD"] = ""
		}
	}

	buildpoint := buildVersion
	buildpoint = strings.ReplaceAll(buildpoint, "+", "-")
	gcbSubs["BUILD_POINT"] = buildpoint

	buildVersion = fmt.Sprintf("--buildversion=%s", buildVersion)
	gcbSubs["BUILDVERSION"] = buildVersion

	kubecrossBranches := []string{
		o.Branch,
		git.DefaultBranch,
	}

	kubecrossVersion, kubecrossVersionErr := release.GetKubecrossVersion(kubecrossBranches...)
	if kubecrossVersionErr != nil {
		return gcbSubs, kubecrossVersionErr
	}
	gcbSubs["KUBE_CROSS_VERSION"] = kubecrossVersion

	v, err := util.TagStringToSemver(buildpoint)
	if err != nil {
		return gcbSubs, errors.Errorf("Failed to parse the buildVersion %s", buildpoint)
	}

	gcbSubs["MAJOR_VERSION_TAG"] = strconv.FormatUint(v.Major, 10)
	gcbSubs["MINOR_VERSION_TAG"] = strconv.FormatUint(v.Minor, 10)

	patch := fmt.Sprintf("%d", v.Patch)
	if o.ReleaseType != release.ReleaseTypeOfficial && len(v.Pre) > 0 {
		// if the release we will build is the same in the current build point then we increment
		// otherwise we are building the next type so set to 0
		if v.Pre[0].String() == o.ReleaseType {
			patch = fmt.Sprintf("%d-%s.%d", v.Patch, o.ReleaseType, v.Pre[1].VersionNum+1)
		} else if o.ReleaseType == release.ReleaseTypeRC && v.Pre[0].String() != release.ReleaseTypeRC {
			// Now if is RC we are building and is the first time we set to 1 since the 0 is bypassed
			patch = fmt.Sprintf("%d-%s.1", v.Patch, o.ReleaseType)
		} else {
			patch = fmt.Sprintf("%d-%s.0", v.Patch, o.ReleaseType)
		}
	}
	gcbSubs["PATCH_VERSION_TAG"] = patch
	gcbSubs["KUBERNETES_VERSION_TAG"] = fmt.Sprintf("%d.%d.%s", v.Major, v.Minor, patch)

	return gcbSubs, nil
}

var BuildListJobs = build.ListJobs

// listJobs lists recent GCB jobs run in the specified project
func listJobs(project string, lastJobs int64) error {
	if lastJobs < 0 {
		logrus.Infof("--list-jobs was set to a negative number, defaulting to 5")
		lastJobs = 5
	}

	logrus.Infof("Listing last %d GCB jobs:", lastJobs)
	return BuildListJobs(project, lastJobs)
}

func (o *GcbmgrOptions) Validate() error {
	if o.Stage && o.Release {
		return errors.New("cannot specify both the 'stage' and 'release' flag; resubmit with only one build type selected")
	}

	if o.Branch == git.DefaultBranch {
		if o.ReleaseType == release.ReleaseTypeRC || o.ReleaseType == release.ReleaseTypeOfficial {
			return errors.Errorf("cannot cut a release candidate or an official release from %s", git.DefaultBranch)
		}
	} else {
		if o.ReleaseType == release.ReleaseTypeAlpha || o.ReleaseType == release.ReleaseTypeBeta {
			return errors.New("cannot cut an alpha or beta release from a release branch")
		}
	}

	if o.BuildVersion != "" && o.BuildAtHead {
		return errors.New("cannot specify both the 'build-version' and 'build-at-head' flag; resubmit with only one build option selected")
	}

	if o.BuildAtHead && o.Release {
		return errors.New("cannot specify both the 'build-at-head' flag together with the 'release' flag; resubmit with a 'build-version' flag set")
	}

	return nil
}
<commit_msg>Update release confirmation message to show expected yes/no input<commit_after>/*
Copyright 2020 The Kubernetes 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 cmd

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

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"k8s.io/release/pkg/gcp"
	"k8s.io/release/pkg/gcp/auth"
	"k8s.io/release/pkg/gcp/build"
	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/release"
	"k8s.io/release/pkg/util"
)

type GcbmgrOptions struct {
	Stage        bool
	Release      bool
	Stream       bool
	BuildAtHead  bool
	Branch       string
	ReleaseType  string
	BuildVersion string
	GcpUser      string
	LastJobs     int64
	Repo         Repository
	Version      Version
}

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Repository
type Repository interface {
	Open() error
	CheckState(string, string, string) error
	GetTag() (string, error)
}

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Version
type Version interface {
	GetKubeVersionForBranch(release.VersionType, string) (string, error)
}

var (
	gcbmgrOpts = &GcbmgrOptions{}
	buildOpts  = &build.Options{}
)

// gcbmgrCmd is a krel subcommand which invokes RunGcbmgr()
var gcbmgrCmd = &cobra.Command{
	Use:           "gcbmgr",
	Short:         "Run gcbmgr",
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return RunGcbmgr(gcbmgrOpts)
	},
}

func init() {
	// Submit types
	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.Stage,
		"stage",
		false,
		"submit a stage run to GCB",
	)
	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.Release,
		"release",
		false,
		"submit a release run to GCB",
	)

	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.Branch,
		"branch",
		git.DefaultBranch,
		"branch to run the specified GCB run against",
	)

	// Release types
	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.ReleaseType,
		"type",
		release.ReleaseTypeAlpha,
		fmt.Sprintf("release type, must be one of: '%s'",
			strings.Join([]string{
				release.ReleaseTypeAlpha,
				release.ReleaseTypeBeta,
				release.ReleaseTypeRC,
				release.ReleaseTypeOfficial,
			}, "', '"),
		),
	)

	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.BuildVersion,
		"build-version",
		"",
		fmt.Sprintf("the build version to be used. "+
			"Can be empty for `stage` releases, where it gets automatically "+
			"inferred by %q and the provided target branch.",
			release.VersionTypeCILatest,
		),
	)

	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.BuildAtHead,
		"build-at-head",
		false,
		"the build version to be used when was to use the lastest in the branch.",
	)

	// gcloud options
	gcbmgrCmd.PersistentFlags().StringVar(
		&buildOpts.Project,
		"project",
		release.DefaultKubernetesStagingProject,
		"GCP project to run GCB in",
	)
	gcbmgrCmd.PersistentFlags().BoolVar(
		&gcbmgrOpts.Stream,
		"stream",
		false,
		"if specified, GCB will run synchronously, tailing its logs to stdout",
	)
	gcbmgrCmd.PersistentFlags().StringVar(
		&buildOpts.CloudbuildFile,
		"gcb-config",
		build.DefaultCloudbuildFile,
		"if specified, this will be used as the name of the Google Cloud Build config file",
	)
	gcbmgrCmd.PersistentFlags().StringVar(
		&gcbmgrOpts.GcpUser,
		"gcp-user",
		"",
		"if specified, this will be used as the GCP_USER_TAG",
	)

	gcbmgrCmd.PersistentFlags().Int64Var(
		&gcbmgrOpts.LastJobs,
		"list-jobs",
		5,
		"list the last N build jobs in the project",
	)

	gcbmgrOpts.Repo = release.NewRepo()
	gcbmgrOpts.Version = release.NewVersion()
	rootCmd.AddCommand(gcbmgrCmd)
}

// RunGcbmgr is the function invoked by 'krel gcbmgr', responsible for
// submitting release jobs to GCB
func RunGcbmgr(opts *GcbmgrOptions) error {
	if err := opts.Validate(); err != nil {
		return errors.Wrap(err, "validating gcbmgr options")
	}

	toolOrg := release.GetToolOrg()
	toolRepo := release.GetToolRepo()
	toolBranch := release.GetToolBranch()

	if err := gcp.PreCheck(); err != nil {
		return errors.Wrap(err, "pre-checking for GCP package usage")
	}

	if err := opts.Repo.Open(); err != nil {
		return errors.Wrap(err, "open release repo")
	}

	if err := opts.Repo.CheckState(toolOrg, toolRepo, toolBranch); err != nil {
		return errors.Wrap(err, "verifying repository state")
	}

	logrus.Infof("Running gcbmgr with the following options: %+v", opts)
	logrus.Infof("Build options: %v", *buildOpts)

	buildOpts.NoSource = true
	buildOpts.DiskSize = release.DefaultDiskSize

	buildOpts.Async = true

	if opts.Stream {
		buildOpts.Async = false
	}

	gcbSubs, gcbSubsErr := SetGCBSubstitutions(opts, toolOrg, toolRepo, toolBranch)
	if gcbSubs == nil || gcbSubsErr != nil {
		return gcbSubsErr
	}

	if rootOpts.nomock {
		// TODO: Consider a '--yes' flag so we can mock this
		_, nomockSubmit, askErr := util.Ask(
			fmt.Sprintf("Really submit a --nomock release job against the %s branch? (yes/no)", opts.Branch),
			"yes",
			3,
		)
		if askErr != nil {
			return askErr
		}

		if nomockSubmit {
			gcbSubs["NOMOCK_TAG"] = "nomock"
			gcbSubs["NOMOCK"] = fmt.Sprintf("--%s", gcbSubs["NOMOCK_TAG"])
		}
	} else {
		// TODO: Remove once cloudbuild.yaml doesn't strictly require vars to be set.
		gcbSubs["NOMOCK_TAG"] = ""
		gcbSubs["NOMOCK"] = ""

		bucketPrefix := release.BucketPrefix

		userBucket := fmt.Sprintf("%s%s", bucketPrefix, gcbSubs["GCP_USER_TAG"])
		userBucketSetErr := os.Setenv("USER_BUCKET", userBucket)
		if userBucketSetErr != nil {
			return userBucketSetErr
		}

		testBucket := fmt.Sprintf("%s%s", bucketPrefix, "gcb")
		testBucketSetErr := os.Setenv("BUCKET", testBucket)
		if testBucketSetErr != nil {
			return testBucketSetErr
		}
	}

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

	logrus.Info("Listing GCB substitutions prior to build submission...")
	for k, v := range gcbSubs {
		logrus.Infof("%s: %s", k, v)
	}

	var jobType string
	switch {
	// TODO: Consider a '--validate' flag to validate the GCB config without submitting
	case opts.Stage:
		jobType = "stage"
	case opts.Release:
		jobType = "release"
		buildOpts.DiskSize = "100"
	default:
		return listJobs(buildOpts.Project, opts.LastJobs)
	}

	buildOpts.ConfigDir = filepath.Join(toolRoot, "gcb", jobType)
	prepareBuildErr := build.PrepareBuilds(buildOpts)
	if prepareBuildErr != nil {
		return prepareBuildErr
	}

	// TODO: Need actual values
	var jobName, uploaded string

	version, err := opts.Repo.GetTag()
	if err != nil {
		return errors.Wrap(err, "getting current tag")
	}

	return build.RunSingleJob(buildOpts, jobName, uploaded, version, gcbSubs)
}

// SetGCBSubstitutions takes a set of GcbmgrOptions and returns a map of GCB substitutions
func SetGCBSubstitutions(o *GcbmgrOptions, toolOrg, toolRepo, toolBranch string) (map[string]string, error) {
	gcbSubs := map[string]string{}

	gcbSubs["TOOL_ORG"] = toolOrg
	gcbSubs["TOOL_REPO"] = toolRepo
	gcbSubs["TOOL_BRANCH"] = toolBranch

	gcpUser := o.GcpUser
	if gcpUser == "" {
		var gcpUserErr error
		gcpUser, gcpUserErr = auth.GetCurrentGCPUser()
		if gcpUserErr != nil {
			return gcbSubs, gcpUserErr
		}
	} else {
		// TODO: Consider removing this once the 'gcloud auth' is testable in CI
		gcpUser = auth.NormalizeGCPUser(gcpUser)
	}

	gcbSubs["GCP_USER_TAG"] = gcpUser

	gcbSubs["TYPE"] = o.ReleaseType
	gcbSubs["TYPE_TAG"] = o.ReleaseType

	gcbSubs["RELEASE_BRANCH"] = o.Branch

	buildVersion := o.BuildVersion
	if o.Release && buildVersion == "" {
		return gcbSubs, errors.New("Build version must be specified when sending a release GCB run")
	}

	if o.Stage && o.BuildAtHead {
		hash, err := git.LSRemoteExec(git.GetDefaultKubernetesRepoURL(), "rev-parse", o.Branch)
		if err != nil {
			return gcbSubs, errors.New("failed to execute the rev-parse")
		}

		fields := strings.Fields(hash)
		if len(fields) < 1 {
			return gcbSubs, errors.Errorf("unexpected output: %s", hash)
		}

		buildVersion = fields[0]
		gcbSubs["BUILD_AT_HEAD"] = buildVersion
	}

	if buildVersion == "" {
		var versionErr error
		buildVersion, versionErr = o.Version.GetKubeVersionForBranch(
			release.VersionTypeCILatest, o.Branch,
		)
		if versionErr != nil {
			return gcbSubs, versionErr
		}

		if o.Stage {
			gcbSubs["BUILD_AT_HEAD"] = ""
		}
	}

	buildpoint := buildVersion
	buildpoint = strings.ReplaceAll(buildpoint, "+", "-")
	gcbSubs["BUILD_POINT"] = buildpoint

	buildVersion = fmt.Sprintf("--buildversion=%s", buildVersion)
	gcbSubs["BUILDVERSION"] = buildVersion

	kubecrossBranches := []string{
		o.Branch,
		git.DefaultBranch,
	}

	kubecrossVersion, kubecrossVersionErr := release.GetKubecrossVersion(kubecrossBranches...)
	if kubecrossVersionErr != nil {
		return gcbSubs, kubecrossVersionErr
	}
	gcbSubs["KUBE_CROSS_VERSION"] = kubecrossVersion

	v, err := util.TagStringToSemver(buildpoint)
	if err != nil {
		return gcbSubs, errors.Errorf("Failed to parse the buildVersion %s", buildpoint)
	}

	gcbSubs["MAJOR_VERSION_TAG"] = strconv.FormatUint(v.Major, 10)
	gcbSubs["MINOR_VERSION_TAG"] = strconv.FormatUint(v.Minor, 10)

	patch := fmt.Sprintf("%d", v.Patch)
	if o.ReleaseType != release.ReleaseTypeOfficial && len(v.Pre) > 0 {
		// if the release we will build is the same in the current build point then we increment
		// otherwise we are building the next type so set to 0
		if v.Pre[0].String() == o.ReleaseType {
			patch = fmt.Sprintf("%d-%s.%d", v.Patch, o.ReleaseType, v.Pre[1].VersionNum+1)
		} else if o.ReleaseType == release.ReleaseTypeRC && v.Pre[0].String() != release.ReleaseTypeRC {
			// Now if is RC we are building and is the first time we set to 1 since the 0 is bypassed
			patch = fmt.Sprintf("%d-%s.1", v.Patch, o.ReleaseType)
		} else {
			patch = fmt.Sprintf("%d-%s.0", v.Patch, o.ReleaseType)
		}
	}
	gcbSubs["PATCH_VERSION_TAG"] = patch
	gcbSubs["KUBERNETES_VERSION_TAG"] = fmt.Sprintf("%d.%d.%s", v.Major, v.Minor, patch)

	return gcbSubs, nil
}

var BuildListJobs = build.ListJobs

// listJobs lists recent GCB jobs run in the specified project
func listJobs(project string, lastJobs int64) error {
	if lastJobs < 0 {
		logrus.Infof("--list-jobs was set to a negative number, defaulting to 5")
		lastJobs = 5
	}

	logrus.Infof("Listing last %d GCB jobs:", lastJobs)
	return BuildListJobs(project, lastJobs)
}

func (o *GcbmgrOptions) Validate() error {
	if o.Stage && o.Release {
		return errors.New("cannot specify both the 'stage' and 'release' flag; resubmit with only one build type selected")
	}

	if o.Branch == git.DefaultBranch {
		if o.ReleaseType == release.ReleaseTypeRC || o.ReleaseType == release.ReleaseTypeOfficial {
			return errors.Errorf("cannot cut a release candidate or an official release from %s", git.DefaultBranch)
		}
	} else {
		if o.ReleaseType == release.ReleaseTypeAlpha || o.ReleaseType == release.ReleaseTypeBeta {
			return errors.New("cannot cut an alpha or beta release from a release branch")
		}
	}

	if o.BuildVersion != "" && o.BuildAtHead {
		return errors.New("cannot specify both the 'build-version' and 'build-at-head' flag; resubmit with only one build option selected")
	}

	if o.BuildAtHead && o.Release {
		return errors.New("cannot specify both the 'build-at-head' flag together with the 'release' flag; resubmit with a 'build-version' flag set")
	}

	return nil
}
<|endoftext|>