<commit_before>package watch

import (
	"context"
	"fmt"
	"io"
	"sync"
	"time"

	consulapi "github.com/hashicorp/consul/api"
	"github.com/mitchellh/mapstructure"
)

const DefaultTimeout = 10 * time.Second

// Plan is the parsed version of a watch specification. A watch provides
// the details of a query, which generates a view into the Consul data store.
// This view is watched for changes and a handler is invoked to take any
// appropriate actions.
type Plan struct {
	Datacenter  string
	Token       string
	Type        string
	HandlerType string
	Exempt      map[string]interface{}

	Watcher WatcherFunc
	// Handler is kept for backward compatibility but only supports watches based
	// on index param. To support hash based watches, set HybridHandler instead.
	Handler       HandlerFunc
	HybridHandler HybridHandlerFunc
	LogOutput     io.Writer

	address      string
	client       *consulapi.Client
	lastParamVal BlockingParamVal
	lastResult   interface{}

	stop       bool
	stopCh     chan struct{}
	stopLock   sync.Mutex
	cancelFunc context.CancelFunc
}

type HttpHandlerConfig struct {
	Path          string              `mapstructure:"path"`
	Method        string              `mapstructure:"method"`
	Timeout       time.Duration       `mapstructure:"-"`
	TimeoutRaw    string              `mapstructure:"timeout"`
	Header        map[string][]string `mapstructure:"header"`
	TLSSkipVerify bool                `mapstructure:"tls_skip_verify"`
}

// BlockingParamVal is an interface representing the common operations needed for
// different styles of blocking. It's used to abstract the core watch plan from
// whether we are performing index-based or hash-based blocking.
type BlockingParamVal interface {
	// Equal returns whether the other param value should be considered equal
	// (i.e. representing no change in the watched resource). Equal must not panic
	// if other is nil.
	Equal(other BlockingParamVal) bool

	// Next is called when deciding which value to use on the next blocking call.
	// It assumes the BlockingParamVal value it is called on is the most recent one
	// returned and passes the previous one which may be nil as context. This
	// allows types to customize logic around ordering without assuming there is
	// an order. For example WaitIndexVal can check that the index didn't go
	// backwards and if it did then reset to 0. Most other cases should just
	// return themselves (the most recent value) to be used in the next request.
	Next(previous BlockingParamVal) BlockingParamVal
}

// WaitIndexVal is a type representing a Consul index that implements
// BlockingParamVal.
type WaitIndexVal uint64

// Equal implements BlockingParamVal
func (idx WaitIndexVal) Equal(other BlockingParamVal) bool {
	if otherIdx, ok := other.(WaitIndexVal); ok {
		return idx == otherIdx
	}
	return false
}

// Next implements BlockingParamVal
func (idx WaitIndexVal) Next(previous BlockingParamVal) BlockingParamVal {
	if previous == nil {
		return idx
	}
	prevIdx, ok := previous.(WaitIndexVal)
	if ok && prevIdx > idx {
		// This value is smaller than the previous index, reset.
		return WaitIndexVal(0)
	}
	return idx
}

// WaitHashVal is a type representing a Consul content hash that implements
// BlockingParamVal.
type WaitHashVal string

// Equal implements BlockingParamVal
func (h WaitHashVal) Equal(other BlockingParamVal) bool {
	if otherHash, ok := other.(WaitHashVal); ok {
		return h == otherHash
	}
	return false
}

// Next implements BlockingParamVal
func (h WaitHashVal) Next(previous BlockingParamVal) BlockingParamVal {
	return h
}

// WatcherFunc is used to watch for a diff.
type WatcherFunc func(*Plan) (BlockingParamVal, interface{}, error)

// HandlerFunc is used to handle new data. It only works for index-based watches
// (which is almost all end points currently) and is kept for backwards
// compatibility until more places can make use of hash-based watches too.
type HandlerFunc func(uint64, interface{})

// HybridHandlerFunc is used to handle new data. It can support either
// index-based or hash-based watches via the BlockingParamVal.
type HybridHandlerFunc func(BlockingParamVal, interface{})

// Parse takes a watch query and compiles it into a WatchPlan or an error
func Parse(params map[string]interface{}) (*Plan, error) {
	return ParseExempt(params, nil)
}

// ParseExempt takes a watch query and compiles it into a WatchPlan or an error
// Any exempt parameters are stored in the Exempt map
func ParseExempt(params map[string]interface{}, exempt []string) (*Plan, error) {
	plan := &Plan{
		stopCh: make(chan struct{}),
		Exempt: make(map[string]interface{}),
	}

	// Parse the generic parameters
	if err := assignValue(params, "datacenter", &plan.Datacenter); err != nil {
		return nil, err
	}
	if err := assignValue(params, "token", &plan.Token); err != nil {
		return nil, err
	}
	if err := assignValue(params, "type", &plan.Type); err != nil {
		return nil, err
	}
	// Ensure there is a watch type
	if plan.Type == "" {
		return nil, fmt.Errorf("Watch type must be specified")
	}

	// Get the specific handler
	if err := assignValue(params, "handler_type", &plan.HandlerType); err != nil {
		return nil, err
	}
	switch plan.HandlerType {
	case "http":
		if _, ok := params["http_handler_config"]; !ok {
			return nil, fmt.Errorf("Handler type 'http' requires 'http_handler_config' to be set")
		}
		config, err := parseHttpHandlerConfig(params["http_handler_config"])
		if err != nil {
			return nil, fmt.Errorf(fmt.Sprintf("Failed to parse 'http_handler_config': %v", err))
		}
		plan.Exempt["http_handler_config"] = config
		delete(params, "http_handler_config")

	case "script":
		// Let the caller check for configuration in exempt parameters
	}

	// Look for a factory function
	factory := watchFuncFactory[plan.Type]
	if factory == nil {
		return nil, fmt.Errorf("Unsupported watch type: %s", plan.Type)
	}

	// Get the watch func
	fn, err := factory(params)
	if err != nil {
		return nil, err
	}
	plan.Watcher = fn

	// Remove the exempt parameters
	if len(exempt) > 0 {
		for _, ex := range exempt {
			val, ok := params[ex]
			if ok {
				plan.Exempt[ex] = val
				delete(params, ex)
			}
		}
	}

	// Ensure all parameters are consumed
	if len(params) != 0 {
		var bad []string
		for key := range params {
			bad = append(bad, key)
		}
		return nil, fmt.Errorf("Invalid parameters: %v", bad)
	}
	return plan, nil
}

// assignValue is used to extract a value ensuring it is a string
func assignValue(params map[string]interface{}, name string, out *string) error {
	if raw, ok := params[name]; ok {
		val, ok := raw.(string)
		if !ok {
			return fmt.Errorf("Expecting %s to be a string", name)
		}
		*out = val
		delete(params, name)
	}
	return nil
}

// assignValueBool is used to extract a value ensuring it is a bool
func assignValueBool(params map[string]interface{}, name string, out *bool) error {
	if raw, ok := params[name]; ok {
		val, ok := raw.(bool)
		if !ok {
			return fmt.Errorf("Expecting %s to be a boolean", name)
		}
		*out = val
		delete(params, name)
	}
	return nil
}

// assignValueStringSlice is used to extract a value ensuring it is either a string or a slice of strings
func assignValueStringSlice(params map[string]interface{}, name string, out *[]string) error {
	if raw, ok := params[name]; ok {
		var tmp []string
		switch raw.(type) {
		case string:
			tmp = make([]string, 1, 1)
			tmp[0] = raw.(string)
		case []string:
			l := len(raw.([]string))
			tmp = make([]string, l, l)
			copy(tmp, raw.([]string))
		case []interface{}:
			l := len(raw.([]interface{}))
			tmp = make([]string, l, l)
			for i, v := range raw.([]interface{}) {
				if s, ok := v.(string); ok {
					tmp[i] = s
				} else {
					return fmt.Errorf("Index %d of %s expected to be string", i, name)
				}
			}
		default:
			return fmt.Errorf("Expecting %s to be a string or []string", name)
		}
		*out = tmp
		delete(params, name)
	}
	return nil
}

// Parse the 'http_handler_config' parameters
func parseHttpHandlerConfig(configParams interface{}) (*HttpHandlerConfig, error) {
	var config HttpHandlerConfig
	if err := mapstructure.Decode(configParams, &config); err != nil {
		return nil, err
	}

	if config.Path == "" {
		return nil, fmt.Errorf("Requires 'path' to be set")
	}
	if config.Method == "" {
		config.Method = "POST"
	}
	if config.TimeoutRaw == "" {
		config.Timeout = DefaultTimeout
	} else if timeout, err := time.ParseDuration(config.TimeoutRaw); err != nil {
		return nil, fmt.Errorf(fmt.Sprintf("Failed to parse timeout: %v", err))
	} else {
		config.Timeout = timeout
	}

	return &config, nil
}
<commit_msg>agent: fix watch event behavior (#5265)<commit_after>package watch

import (
	"context"
	"fmt"
	"io"
	"sync"
	"time"

	consulapi "github.com/hashicorp/consul/api"
	"github.com/mitchellh/mapstructure"
)

const DefaultTimeout = 10 * time.Second

// Plan is the parsed version of a watch specification. A watch provides
// the details of a query, which generates a view into the Consul data store.
// This view is watched for changes and a handler is invoked to take any
// appropriate actions.
type Plan struct {
	Datacenter  string
	Token       string
	Type        string
	HandlerType string
	Exempt      map[string]interface{}

	Watcher WatcherFunc
	// Handler is kept for backward compatibility but only supports watches based
	// on index param. To support hash based watches, set HybridHandler instead.
	Handler       HandlerFunc
	HybridHandler HybridHandlerFunc
	LogOutput     io.Writer

	address      string
	client       *consulapi.Client
	lastParamVal BlockingParamVal
	lastResult   interface{}

	stop       bool
	stopCh     chan struct{}
	stopLock   sync.Mutex
	cancelFunc context.CancelFunc
}

type HttpHandlerConfig struct {
	Path          string              `mapstructure:"path"`
	Method        string              `mapstructure:"method"`
	Timeout       time.Duration       `mapstructure:"-"`
	TimeoutRaw    string              `mapstructure:"timeout"`
	Header        map[string][]string `mapstructure:"header"`
	TLSSkipVerify bool                `mapstructure:"tls_skip_verify"`
}

// BlockingParamVal is an interface representing the common operations needed for
// different styles of blocking. It's used to abstract the core watch plan from
// whether we are performing index-based or hash-based blocking.
type BlockingParamVal interface {
	// Equal returns whether the other param value should be considered equal
	// (i.e. representing no change in the watched resource). Equal must not panic
	// if other is nil.
	Equal(other BlockingParamVal) bool

	// Next is called when deciding which value to use on the next blocking call.
	// It assumes the BlockingParamVal value it is called on is the most recent one
	// returned and passes the previous one which may be nil as context. This
	// allows types to customize logic around ordering without assuming there is
	// an order. For example WaitIndexVal can check that the index didn't go
	// backwards and if it did then reset to 0. Most other cases should just
	// return themselves (the most recent value) to be used in the next request.
	Next(previous BlockingParamVal) BlockingParamVal
}

// WaitIndexVal is a type representing a Consul index that implements
// BlockingParamVal.
type WaitIndexVal uint64

// Equal implements BlockingParamVal
func (idx WaitIndexVal) Equal(other BlockingParamVal) bool {
	if otherIdx, ok := other.(WaitIndexVal); ok {
		return idx == otherIdx
	}
	return false
}

// Next implements BlockingParamVal
func (idx WaitIndexVal) Next(previous BlockingParamVal) BlockingParamVal {
	if previous == nil {
		return idx
	}
	prevIdx, ok := previous.(WaitIndexVal)
	if ok && prevIdx == idx {
		// This value is the same as the previous index, reset
		return WaitIndexVal(0)
	}
	return idx
}

// WaitHashVal is a type representing a Consul content hash that implements
// BlockingParamVal.
type WaitHashVal string

// Equal implements BlockingParamVal
func (h WaitHashVal) Equal(other BlockingParamVal) bool {
	if otherHash, ok := other.(WaitHashVal); ok {
		return h == otherHash
	}
	return false
}

// Next implements BlockingParamVal
func (h WaitHashVal) Next(previous BlockingParamVal) BlockingParamVal {
	return h
}

// WatcherFunc is used to watch for a diff.
type WatcherFunc func(*Plan) (BlockingParamVal, interface{}, error)

// HandlerFunc is used to handle new data. It only works for index-based watches
// (which is almost all end points currently) and is kept for backwards
// compatibility until more places can make use of hash-based watches too.
type HandlerFunc func(uint64, interface{})

// HybridHandlerFunc is used to handle new data. It can support either
// index-based or hash-based watches via the BlockingParamVal.
type HybridHandlerFunc func(BlockingParamVal, interface{})

// Parse takes a watch query and compiles it into a WatchPlan or an error
func Parse(params map[string]interface{}) (*Plan, error) {
	return ParseExempt(params, nil)
}

// ParseExempt takes a watch query and compiles it into a WatchPlan or an error
// Any exempt parameters are stored in the Exempt map
func ParseExempt(params map[string]interface{}, exempt []string) (*Plan, error) {
	plan := &Plan{
		stopCh: make(chan struct{}),
		Exempt: make(map[string]interface{}),
	}

	// Parse the generic parameters
	if err := assignValue(params, "datacenter", &plan.Datacenter); err != nil {
		return nil, err
	}
	if err := assignValue(params, "token", &plan.Token); err != nil {
		return nil, err
	}
	if err := assignValue(params, "type", &plan.Type); err != nil {
		return nil, err
	}
	// Ensure there is a watch type
	if plan.Type == "" {
		return nil, fmt.Errorf("Watch type must be specified")
	}

	// Get the specific handler
	if err := assignValue(params, "handler_type", &plan.HandlerType); err != nil {
		return nil, err
	}
	switch plan.HandlerType {
	case "http":
		if _, ok := params["http_handler_config"]; !ok {
			return nil, fmt.Errorf("Handler type 'http' requires 'http_handler_config' to be set")
		}
		config, err := parseHttpHandlerConfig(params["http_handler_config"])
		if err != nil {
			return nil, fmt.Errorf(fmt.Sprintf("Failed to parse 'http_handler_config': %v", err))
		}
		plan.Exempt["http_handler_config"] = config
		delete(params, "http_handler_config")

	case "script":
		// Let the caller check for configuration in exempt parameters
	}

	// Look for a factory function
	factory := watchFuncFactory[plan.Type]
	if factory == nil {
		return nil, fmt.Errorf("Unsupported watch type: %s", plan.Type)
	}

	// Get the watch func
	fn, err := factory(params)
	if err != nil {
		return nil, err
	}
	plan.Watcher = fn

	// Remove the exempt parameters
	if len(exempt) > 0 {
		for _, ex := range exempt {
			val, ok := params[ex]
			if ok {
				plan.Exempt[ex] = val
				delete(params, ex)
			}
		}
	}

	// Ensure all parameters are consumed
	if len(params) != 0 {
		var bad []string
		for key := range params {
			bad = append(bad, key)
		}
		return nil, fmt.Errorf("Invalid parameters: %v", bad)
	}
	return plan, nil
}

// assignValue is used to extract a value ensuring it is a string
func assignValue(params map[string]interface{}, name string, out *string) error {
	if raw, ok := params[name]; ok {
		val, ok := raw.(string)
		if !ok {
			return fmt.Errorf("Expecting %s to be a string", name)
		}
		*out = val
		delete(params, name)
	}
	return nil
}

// assignValueBool is used to extract a value ensuring it is a bool
func assignValueBool(params map[string]interface{}, name string, out *bool) error {
	if raw, ok := params[name]; ok {
		val, ok := raw.(bool)
		if !ok {
			return fmt.Errorf("Expecting %s to be a boolean", name)
		}
		*out = val
		delete(params, name)
	}
	return nil
}

// assignValueStringSlice is used to extract a value ensuring it is either a string or a slice of strings
func assignValueStringSlice(params map[string]interface{}, name string, out *[]string) error {
	if raw, ok := params[name]; ok {
		var tmp []string
		switch raw.(type) {
		case string:
			tmp = make([]string, 1, 1)
			tmp[0] = raw.(string)
		case []string:
			l := len(raw.([]string))
			tmp = make([]string, l, l)
			copy(tmp, raw.([]string))
		case []interface{}:
			l := len(raw.([]interface{}))
			tmp = make([]string, l, l)
			for i, v := range raw.([]interface{}) {
				if s, ok := v.(string); ok {
					tmp[i] = s
				} else {
					return fmt.Errorf("Index %d of %s expected to be string", i, name)
				}
			}
		default:
			return fmt.Errorf("Expecting %s to be a string or []string", name)
		}
		*out = tmp
		delete(params, name)
	}
	return nil
}

// Parse the 'http_handler_config' parameters
func parseHttpHandlerConfig(configParams interface{}) (*HttpHandlerConfig, error) {
	var config HttpHandlerConfig
	if err := mapstructure.Decode(configParams, &config); err != nil {
		return nil, err
	}

	if config.Path == "" {
		return nil, fmt.Errorf("Requires 'path' to be set")
	}
	if config.Method == "" {
		config.Method = "POST"
	}
	if config.TimeoutRaw == "" {
		config.Timeout = DefaultTimeout
	} else if timeout, err := time.ParseDuration(config.TimeoutRaw); err != nil {
		return nil, fmt.Errorf(fmt.Sprintf("Failed to parse timeout: %v", err))
	} else {
		config.Timeout = timeout
	}

	return &config, nil
}
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"text/template"
	"time"

	"github.com/dnaeon/go-vcr/cassette"
	"github.com/dnaeon/go-vcr/recorder"
	"github.com/scaleway/scaleway-cli/internal/human"
	"github.com/scaleway/scaleway-sdk-go/api/test/v1"
	"github.com/scaleway/scaleway-sdk-go/logger"
	"github.com/scaleway/scaleway-sdk-go/scw"
	"github.com/scaleway/scaleway-sdk-go/strcase"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	// Environment variable are prefixed by "CLI_" in order to avoid magic behavior with SDK variables.
	// E.g.: SDK_UPDATE_CASSETTES=false will disable retry on WaitFor* methods.
	UpdateGoldens   = os.Getenv("CLI_UPDATE_GOLDENS") == "true"
	UpdateCassettes = os.Getenv("CLI_UPDATE_CASSETTES") == "true"
)

// CheckFuncCtx contain the result of a command execution
type CheckFuncCtx struct {
	// Exit code return by the CLI
	ExitCode int

	// Content print on stdout
	Stdout []byte

	// Content print on stderr
	Stderr []byte

	// Error returned by the command
	Err error

	// Command result
	Result interface{}

	// Meta bag
	Meta map[string]interface{}
}

// TestCheck is a function that perform assertion on a CheckFuncCtx
type TestCheck func(t *testing.T, ctx *CheckFuncCtx)

type BeforeFunc func(ctx *BeforeFuncCtx) error

type AfterFunc func(ctx *AfterFuncCtx) error

type BeforeFuncCtx struct {
	Client     *scw.Client
	ExecuteCmd func(cmd string) interface{}
	Meta       map[string]interface{}
}

type AfterFuncCtx struct {
	Client     *scw.Client
	ExecuteCmd func(cmd string) interface{}
	Meta       map[string]interface{}
	CmdResult  interface{}
}

// TestConfig contain configuration that can be used with the Test function
type TestConfig struct {

	// Array of command to load (see main.go)
	Commands *Commands

	// If set to true the client will be initialize to use a e2e token.
	UseE2EClient bool

	// BeforeFunc is a hook that will be called before test is run. You can use this function to bootstrap resources.
	BeforeFunc BeforeFunc

	// The command line you want to test
	Cmd string

	// A list of check function that will be run on result.
	Check TestCheck

	// AfterFunc is a hook that will be called after test is run. You can use this function to teardown resources.
	AfterFunc AfterFunc

	// Run tests in parallel.
	DisableParallel bool

	// Fake build info for this test.
	BuildInfo BuildInfo
}

// getTestFilePath returns a valid filename path based on the go test name and suffix. (Take care of non fs friendly char)
func getTestFilePath(t *testing.T, suffix string) string {
	fileName := t.Name()
	fileName = strings.Replace(fileName, "/", "-", -1)
	fileName = strcase.ToBashArg(fileName)
	return filepath.Join(".", "testdata", fileName+suffix)
}

func getTestClient(t *testing.T, e2eClient bool) (client *scw.Client, cleanup func()) {
	clientOpts := []scw.ClientOption{
		scw.WithDefaultRegion(scw.RegionFrPar),
		scw.WithDefaultZone(scw.ZoneFrPar1),
		scw.WithEnv(),
		scw.WithUserAgent("cli-e2e-test"),
		scw.WithDefaultOrganizationID("11111111-1111-1111-1111-111111111111"),
	}

	if !e2eClient {
		httpClient, cleanup, err := getHTTPRecoder(t, UpdateCassettes)
		require.NoError(t, err)
		clientOpts = append(clientOpts, scw.WithHTTPClient(httpClient))
		config, err := scw.LoadConfig()
		if err == nil {
			p, err := config.GetActiveProfile()
			require.NoError(t, err)
			clientOpts = append(clientOpts, scw.WithProfile(p))
		}

		client, err := scw.NewClient(clientOpts...)
		require.NoError(t, err)
		return client, cleanup
	}

	client, err := scw.NewClient(clientOpts...)
	require.NoError(t, err)
	res, err := test.NewAPI(client).Register(&test.RegisterRequest{Username: "sidi"})
	require.NoError(t, err)

	client, err = scw.NewClient(append(clientOpts, scw.WithAuth(res.AccessKey, res.SecretKey))...)
	require.NoError(t, err)

	return client, func() {}
}

// Run a CLI integration test. See TestConfig for configuration option
func Test(config *TestConfig) func(t *testing.T) {
	return func(t *testing.T) {
		if config.DisableParallel {
			t.Parallel()
		}

		// Because human marshal of date is relative (e.g 3 minutes ago) we must make sure it stay consistent for golden to works.
		// Here we return a constant string. We may need to find a better place to put this.
		human.RegisterMarshalerFunc(time.Time{}, func(i interface{}, opt *human.MarshalOpt) (string, error) {
			return "few seconds ago", nil
		})

		client, cleanup := getTestClient(t, config.UseE2EClient)
		defer cleanup()

		meta := map[string]interface{}{}

		cmdTemplate := func(cmd string) string {
			cmdBuf := &bytes.Buffer{}
			require.NoError(t, template.Must(template.New("cmd").Parse(cmd)).Execute(cmdBuf, meta))
			return cmdBuf.String()
		}

		executeCmd := func(cmd string) interface{} {
			stdoutBuffer := &bytes.Buffer{}
			stderrBuffer := &bytes.Buffer{}
			logger.Debugf("command: %s", cmdTemplate(cmd))
			_, result, err := Bootstrap(&BootstrapConfig{
				Args:             strings.Split(cmdTemplate(cmd), " "),
				Commands:         config.Commands,
				BuildInfo:        &config.BuildInfo,
				Stdout:           stdoutBuffer,
				Stderr:           stderrBuffer,
				Client:           client,
				DisableTelemetry: true,
			})
			require.NoError(t, err, "stdout: %s\nstderr: %s", stdoutBuffer.String(), stderrBuffer.String())

			return result
		}

		// Run config.BeforeFunc
		if config.BeforeFunc != nil {
			require.NoError(t, config.BeforeFunc(&BeforeFuncCtx{
				Client:     client,
				ExecuteCmd: executeCmd,
				Meta:       meta,
			}))
		}

		// Run config.Cmd
		var result interface{}
		var exitCode int
		var err error

		if config.Cmd != "" {
			stdout := &bytes.Buffer{}
			stderr := &bytes.Buffer{}
			logger.Debugf("command: %s", cmdTemplate(config.Cmd))
			exitCode, result, err = Bootstrap(&BootstrapConfig{
				Args:             strings.Split(cmdTemplate(config.Cmd), " "),
				Commands:         config.Commands,
				BuildInfo:        &config.BuildInfo,
				Stdout:           stdout,
				Stderr:           stderr,
				Client:           client,
				DisableTelemetry: true,
			})

			config.Check(t, &CheckFuncCtx{
				ExitCode: exitCode,
				Stdout:   stdout.Bytes(),
				Stderr:   stderr.Bytes(),
				Meta:     meta,
				Result:   result,
				Err:      err,
			})
		}

		// Run config.AfterFunc
		if config.AfterFunc != nil {
			require.NoError(t, config.AfterFunc(&AfterFuncCtx{
				Client:     client,
				ExecuteCmd: executeCmd,
				Meta:       meta,
				CmdResult:  result,
			}))
		}
	}
}

// BeforeFuncCombine combines multiple before functions into one.
func BeforeFuncCombine(beforeFuncs ...BeforeFunc) BeforeFunc {
	return func(ctx *BeforeFuncCtx) error {
		if len(beforeFuncs) < 2 {
			panic(fmt.Errorf("BeforeFuncCombine must be used to combine more than one BeforeFunc"))
		}
		for _, beforeFunc := range beforeFuncs {
			err := beforeFunc(ctx)
			if err != nil {
				return err
			}
		}
		return nil
	}
}

// AfterFuncCombine combines multiple after functions into one.
func AfterFuncCombine(afterFuncs ...AfterFunc) AfterFunc {
	return func(ctx *AfterFuncCtx) error {
		if len(afterFuncs) < 2 {
			panic(fmt.Errorf("AfterFuncCombine must be used to combine more than one AfterFunc"))
		}
		for _, afterFunc := range afterFuncs {
			err := afterFunc(ctx)
			if err != nil {
				return err
			}
		}
		return nil
	}
}

// ExecStoreBeforeCmd executes the given before command and register the result
// in the context Meta at metaKey.
func ExecStoreBeforeCmd(metaKey, cmd string) BeforeFunc {
	return func(ctx *BeforeFuncCtx) error {
		ctx.Meta[metaKey] = ctx.ExecuteCmd(cmd)
		return nil
	}
}

// ExecBeforeCmd executes the given before command.
func ExecBeforeCmd(cmd string) BeforeFunc {
	return func(ctx *BeforeFuncCtx) error {
		ctx.ExecuteCmd(cmd)
		return nil
	}
}

// ExecAfterCmd executes the given before command.
func ExecAfterCmd(cmd string) AfterFunc {
	return func(ctx *AfterFuncCtx) error {
		ctx.ExecuteCmd(cmd)
		return nil
	}
}

// TestCheckCombine combines multiple check functions into one.
func TestCheckCombine(checks ...TestCheck) TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		assert.Equal(t, true, len(checks) > 1)
		for _, check := range checks {
			check(t, ctx)
		}
	}
}

// TestCheckExitCode assert exitCode
func TestCheckExitCode(expectedCode int) TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		assert.Equal(t, expectedCode, ctx.ExitCode, "Invalid exit code")
	}
}

// TestCheckStderrGolden assert stderr using golden
func TestCheckStderrGolden() TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		testGolden(t, getTestFilePath(t, ".stderr.golden"), ctx.Stderr)
	}
}

// TestCheckStdoutGolden assert stdout using golden
func TestCheckStdoutGolden() TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		testGolden(t, getTestFilePath(t, ".stdout.golden"), ctx.Stdout)
	}
}

// TestCheckGolden assert stderr and stdout using golden
func TestCheckGolden() TestCheck {
	return TestCheckCombine(
		TestCheckStdoutGolden(),
		TestCheckStderrGolden(),
	)
}

// TestCheckError asserts error
func TestCheckError(err error) TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		assert.Equal(t, err, ctx.Err, "Invalid error")
	}
}

func testGolden(t *testing.T, goldenPath string, actual []byte) {
	actualIsEmpty := len(actual) == 0

	// In order to avoid diff in goldens we set all timestamp to the same date
	actual = uniformLogTimestamps(actual)
	if UpdateGoldens {
		if actualIsEmpty {
			_ = os.Remove(goldenPath)
		} else {
			require.NoError(t, os.MkdirAll(path.Dir(goldenPath), 0755))
			require.NoError(t, ioutil.WriteFile(goldenPath, actual, 0644))
		}
	}

	expected, err := ioutil.ReadFile(goldenPath)
	if actualIsEmpty {
		assert.NotNil(t, err)
	} else {
		require.NoError(t, err)
		assert.Equal(t, string(expected), string(actual))
	}
}

var regLogTimestamp = regexp.MustCompile(`((\d)+\/(\d)+\/(\d)+ (\d)+\:(\d)+\:(\d)+)`)

// uniformLogTimestamps replace all log timestamp to the date "2019/12/09 16:04:07"
func uniformLogTimestamps(input []byte) []byte {
	return regLogTimestamp.ReplaceAll(input, []byte("2019/12/09 16:04:07"))
}

// getHTTPRecoder creates a new httpClient that records all HTTP requests in a cassette.
// This cassette is then replayed whenever tests are executed again. This means that once the
// requests are recorded in the cassette, no more real HTTP request must be made to run the tests.
//
// It is important to call add a `defer cleanup()` so the given cassette files are correctly
// closed and saved after the requests.
func getHTTPRecoder(t *testing.T, update bool) (client *http.Client, cleanup func(), err error) {
	recorderMode := recorder.ModeReplaying
	if update {
		recorderMode = recorder.ModeRecording
	}

	// Setup recorder and scw client
	r, err := recorder.NewAsMode(getTestFilePath(t, ".cassette"), recorderMode, nil)
	if err != nil {
		return nil, nil, err
	}

	// Add a filter which removes Authorization headers from all requests:
	r.AddFilter(func(i *cassette.Interaction) error {
		delete(i.Request.Headers, "x-auth-token")
		delete(i.Request.Headers, "X-Auth-Token")
		return nil
	})

	return &http.Client{Transport: r}, func() {
		assert.NoError(t, r.Stop()) // Make sure recorder is stopped once done with it
	}, nil
}
<commit_msg>test: add support for default zone and region in test config (#741)<commit_after>package core

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"text/template"
	"time"

	"github.com/dnaeon/go-vcr/cassette"
	"github.com/dnaeon/go-vcr/recorder"
	"github.com/scaleway/scaleway-cli/internal/human"
	"github.com/scaleway/scaleway-sdk-go/api/test/v1"
	"github.com/scaleway/scaleway-sdk-go/logger"
	"github.com/scaleway/scaleway-sdk-go/scw"
	"github.com/scaleway/scaleway-sdk-go/strcase"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	// Environment variable are prefixed by "CLI_" in order to avoid magic behavior with SDK variables.
	// E.g.: SDK_UPDATE_CASSETTES=false will disable retry on WaitFor* methods.
	UpdateGoldens   = os.Getenv("CLI_UPDATE_GOLDENS") == "true"
	UpdateCassettes = os.Getenv("CLI_UPDATE_CASSETTES") == "true"
)

// CheckFuncCtx contain the result of a command execution
type CheckFuncCtx struct {
	// Exit code return by the CLI
	ExitCode int

	// Content print on stdout
	Stdout []byte

	// Content print on stderr
	Stderr []byte

	// Error returned by the command
	Err error

	// Command result
	Result interface{}

	// Meta bag
	Meta map[string]interface{}
}

// TestCheck is a function that perform assertion on a CheckFuncCtx
type TestCheck func(t *testing.T, ctx *CheckFuncCtx)

type BeforeFunc func(ctx *BeforeFuncCtx) error

type AfterFunc func(ctx *AfterFuncCtx) error

type BeforeFuncCtx struct {
	Client     *scw.Client
	ExecuteCmd func(cmd string) interface{}
	Meta       map[string]interface{}
}

type AfterFuncCtx struct {
	Client     *scw.Client
	ExecuteCmd func(cmd string) interface{}
	Meta       map[string]interface{}
	CmdResult  interface{}
}

// TestConfig contain configuration that can be used with the Test function
type TestConfig struct {

	// Array of command to load (see main.go)
	Commands *Commands

	// If set to true the client will be initialize to use a e2e token.
	UseE2EClient bool

	// DefaultRegion to use with scw client (default: scw.RegionFrPar)
	DefaultRegion scw.Region

	// DefaultZone to use with scw client (default: scw.ZoneFrPar1)
	DefaultZone scw.Zone

	// BeforeFunc is a hook that will be called before test is run. You can use this function to bootstrap resources.
	BeforeFunc BeforeFunc

	// The command line you want to test
	Cmd string

	// A list of check function that will be run on result.
	Check TestCheck

	// AfterFunc is a hook that will be called after test is run. You can use this function to teardown resources.
	AfterFunc AfterFunc

	// Run tests in parallel.
	DisableParallel bool

	// Fake build info for this test.
	BuildInfo BuildInfo
}

// getTestFilePath returns a valid filename path based on the go test name and suffix. (Take care of non fs friendly char)
func getTestFilePath(t *testing.T, suffix string) string {
	fileName := t.Name()
	fileName = strings.Replace(fileName, "/", "-", -1)
	fileName = strcase.ToBashArg(fileName)
	return filepath.Join(".", "testdata", fileName+suffix)
}

func getTestClient(t *testing.T, testConfig *TestConfig) (client *scw.Client, cleanup func()) {
	clientOpts := []scw.ClientOption{
		scw.WithDefaultRegion(scw.RegionFrPar),
		scw.WithDefaultZone(scw.ZoneFrPar1),
		scw.WithEnv(),
		scw.WithUserAgent("cli-e2e-test"),
		scw.WithDefaultOrganizationID("11111111-1111-1111-1111-111111111111"),
	}

	if testConfig.DefaultRegion != "" {
		clientOpts = append(clientOpts, scw.WithDefaultRegion(testConfig.DefaultRegion))
	}

	if testConfig.DefaultZone != "" {
		clientOpts = append(clientOpts, scw.WithDefaultZone(testConfig.DefaultZone))
	}

	if !testConfig.UseE2EClient {
		httpClient, cleanup, err := getHTTPRecoder(t, UpdateCassettes)
		require.NoError(t, err)
		clientOpts = append(clientOpts, scw.WithHTTPClient(httpClient))
		config, err := scw.LoadConfig()
		if err == nil {
			p, err := config.GetActiveProfile()
			require.NoError(t, err)
			clientOpts = append(clientOpts, scw.WithProfile(p))
		}

		client, err := scw.NewClient(clientOpts...)
		require.NoError(t, err)
		return client, cleanup
	}

	client, err := scw.NewClient(clientOpts...)
	require.NoError(t, err)
	res, err := test.NewAPI(client).Register(&test.RegisterRequest{Username: "sidi"})
	require.NoError(t, err)

	client, err = scw.NewClient(append(clientOpts, scw.WithAuth(res.AccessKey, res.SecretKey))...)
	require.NoError(t, err)

	return client, func() {}
}

// Run a CLI integration test. See TestConfig for configuration option
func Test(config *TestConfig) func(t *testing.T) {
	return func(t *testing.T) {
		if config.DisableParallel {
			t.Parallel()
		}

		// Because human marshal of date is relative (e.g 3 minutes ago) we must make sure it stay consistent for golden to works.
		// Here we return a constant string. We may need to find a better place to put this.
		human.RegisterMarshalerFunc(time.Time{}, func(i interface{}, opt *human.MarshalOpt) (string, error) {
			return "few seconds ago", nil
		})

		client, cleanup := getTestClient(t, config)
		defer cleanup()

		meta := map[string]interface{}{}

		cmdTemplate := func(cmd string) string {
			cmdBuf := &bytes.Buffer{}
			require.NoError(t, template.Must(template.New("cmd").Parse(cmd)).Execute(cmdBuf, meta))
			return cmdBuf.String()
		}

		executeCmd := func(cmd string) interface{} {
			stdoutBuffer := &bytes.Buffer{}
			stderrBuffer := &bytes.Buffer{}
			logger.Debugf("command: %s", cmdTemplate(cmd))
			_, result, err := Bootstrap(&BootstrapConfig{
				Args:             strings.Split(cmdTemplate(cmd), " "),
				Commands:         config.Commands,
				BuildInfo:        &config.BuildInfo,
				Stdout:           stdoutBuffer,
				Stderr:           stderrBuffer,
				Client:           client,
				DisableTelemetry: true,
			})
			require.NoError(t, err, "stdout: %s\nstderr: %s", stdoutBuffer.String(), stderrBuffer.String())

			return result
		}

		// Run config.BeforeFunc
		if config.BeforeFunc != nil {
			require.NoError(t, config.BeforeFunc(&BeforeFuncCtx{
				Client:     client,
				ExecuteCmd: executeCmd,
				Meta:       meta,
			}))
		}

		// Run config.Cmd
		var result interface{}
		var exitCode int
		var err error

		if config.Cmd != "" {
			stdout := &bytes.Buffer{}
			stderr := &bytes.Buffer{}
			logger.Debugf("command: %s", cmdTemplate(config.Cmd))
			exitCode, result, err = Bootstrap(&BootstrapConfig{
				Args:             strings.Split(cmdTemplate(config.Cmd), " "),
				Commands:         config.Commands,
				BuildInfo:        &config.BuildInfo,
				Stdout:           stdout,
				Stderr:           stderr,
				Client:           client,
				DisableTelemetry: true,
			})

			config.Check(t, &CheckFuncCtx{
				ExitCode: exitCode,
				Stdout:   stdout.Bytes(),
				Stderr:   stderr.Bytes(),
				Meta:     meta,
				Result:   result,
				Err:      err,
			})
		}

		// Run config.AfterFunc
		if config.AfterFunc != nil {
			require.NoError(t, config.AfterFunc(&AfterFuncCtx{
				Client:     client,
				ExecuteCmd: executeCmd,
				Meta:       meta,
				CmdResult:  result,
			}))
		}
	}
}

// BeforeFuncCombine combines multiple before functions into one.
func BeforeFuncCombine(beforeFuncs ...BeforeFunc) BeforeFunc {
	return func(ctx *BeforeFuncCtx) error {
		if len(beforeFuncs) < 2 {
			panic(fmt.Errorf("BeforeFuncCombine must be used to combine more than one BeforeFunc"))
		}
		for _, beforeFunc := range beforeFuncs {
			err := beforeFunc(ctx)
			if err != nil {
				return err
			}
		}
		return nil
	}
}

// AfterFuncCombine combines multiple after functions into one.
func AfterFuncCombine(afterFuncs ...AfterFunc) AfterFunc {
	return func(ctx *AfterFuncCtx) error {
		if len(afterFuncs) < 2 {
			panic(fmt.Errorf("AfterFuncCombine must be used to combine more than one AfterFunc"))
		}
		for _, afterFunc := range afterFuncs {
			err := afterFunc(ctx)
			if err != nil {
				return err
			}
		}
		return nil
	}
}

// ExecStoreBeforeCmd executes the given before command and register the result
// in the context Meta at metaKey.
func ExecStoreBeforeCmd(metaKey, cmd string) BeforeFunc {
	return func(ctx *BeforeFuncCtx) error {
		ctx.Meta[metaKey] = ctx.ExecuteCmd(cmd)
		return nil
	}
}

// ExecBeforeCmd executes the given before command.
func ExecBeforeCmd(cmd string) BeforeFunc {
	return func(ctx *BeforeFuncCtx) error {
		ctx.ExecuteCmd(cmd)
		return nil
	}
}

// ExecAfterCmd executes the given before command.
func ExecAfterCmd(cmd string) AfterFunc {
	return func(ctx *AfterFuncCtx) error {
		ctx.ExecuteCmd(cmd)
		return nil
	}
}

// TestCheckCombine combines multiple check functions into one.
func TestCheckCombine(checks ...TestCheck) TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		assert.Equal(t, true, len(checks) > 1)
		for _, check := range checks {
			check(t, ctx)
		}
	}
}

// TestCheckExitCode assert exitCode
func TestCheckExitCode(expectedCode int) TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		assert.Equal(t, expectedCode, ctx.ExitCode, "Invalid exit code")
	}
}

// TestCheckStderrGolden assert stderr using golden
func TestCheckStderrGolden() TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		testGolden(t, getTestFilePath(t, ".stderr.golden"), ctx.Stderr)
	}
}

// TestCheckStdoutGolden assert stdout using golden
func TestCheckStdoutGolden() TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		testGolden(t, getTestFilePath(t, ".stdout.golden"), ctx.Stdout)
	}
}

// TestCheckGolden assert stderr and stdout using golden
func TestCheckGolden() TestCheck {
	return TestCheckCombine(
		TestCheckStdoutGolden(),
		TestCheckStderrGolden(),
	)
}

// TestCheckError asserts error
func TestCheckError(err error) TestCheck {
	return func(t *testing.T, ctx *CheckFuncCtx) {
		assert.Equal(t, err, ctx.Err, "Invalid error")
	}
}

func testGolden(t *testing.T, goldenPath string, actual []byte) {
	actualIsEmpty := len(actual) == 0

	// In order to avoid diff in goldens we set all timestamp to the same date
	actual = uniformLogTimestamps(actual)
	if UpdateGoldens {
		if actualIsEmpty {
			_ = os.Remove(goldenPath)
		} else {
			require.NoError(t, os.MkdirAll(path.Dir(goldenPath), 0755))
			require.NoError(t, ioutil.WriteFile(goldenPath, actual, 0644))
		}
	}

	expected, err := ioutil.ReadFile(goldenPath)
	if actualIsEmpty {
		assert.NotNil(t, err)
	} else {
		require.NoError(t, err)
		assert.Equal(t, string(expected), string(actual))
	}
}

var regLogTimestamp = regexp.MustCompile(`((\d)+\/(\d)+\/(\d)+ (\d)+\:(\d)+\:(\d)+)`)

// uniformLogTimestamps replace all log timestamp to the date "2019/12/09 16:04:07"
func uniformLogTimestamps(input []byte) []byte {
	return regLogTimestamp.ReplaceAll(input, []byte("2019/12/09 16:04:07"))
}

// getHTTPRecoder creates a new httpClient that records all HTTP requests in a cassette.
// This cassette is then replayed whenever tests are executed again. This means that once the
// requests are recorded in the cassette, no more real HTTP request must be made to run the tests.
//
// It is important to call add a `defer cleanup()` so the given cassette files are correctly
// closed and saved after the requests.
func getHTTPRecoder(t *testing.T, update bool) (client *http.Client, cleanup func(), err error) {
	recorderMode := recorder.ModeReplaying
	if update {
		recorderMode = recorder.ModeRecording
	}

	// Setup recorder and scw client
	r, err := recorder.NewAsMode(getTestFilePath(t, ".cassette"), recorderMode, nil)
	if err != nil {
		return nil, nil, err
	}

	// Add a filter which removes Authorization headers from all requests:
	r.AddFilter(func(i *cassette.Interaction) error {
		delete(i.Request.Headers, "x-auth-token")
		delete(i.Request.Headers, "X-Auth-Token")
		return nil
	})

	return &http.Client{Transport: r}, func() {
		assert.NoError(t, r.Stop()) // Make sure recorder is stopped once done with it
	}, nil
}
<|endoftext|>


<commit_before>// Copyright © 2016 National Data Service

package cmd

import (
	"encoding/json"
	"fmt"
	"github.com/spf13/cobra"
	"os"
	"text/tabwriter"
)

// listCmd represents the list command
var listCmd = &cobra.Command{
	Use:   "list",
	Short: "List available services or resources",
}

var listServicesCmd = &cobra.Command{
	Use:    "services",
	Short:  "List available services",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		services, err := client.ListServices()
		if err != nil {
			fmt.Printf("Error listing services: %s\n", err)
			return
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "SERVICE\tDEPENDENCY\tREQUIRED")
		for _, service := range *services {
			if service.IsStack {
				fmt.Fprintf(w, "%s\n", service.Key)
				for _, dependency := range service.Dependencies {
					if dependency.Required {
						fmt.Fprintf(w, "\t%s\t(required)\n", dependency.DependencyKey)
					} else {
						fmt.Fprintf(w, "\t%s\t(optional)\n", dependency.DependencyKey)
					}
				}
			}
		}
		w.Flush()
	},
	PostRun: RefreshToken,
}

var listStacksCmd = &cobra.Command{
	Use:    "stacks",
	Short:  "List existing stacks",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		stacks, err := client.ListStacks(apiUser.username)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "STACK\tSERVICE\tSTATUS\tSID")
		for _, stack := range *stacks {

			fmt.Fprintf(w, "%s\t\t%s\t%s\n", stack.Name, stack.Status, stack.Id)
			for _, service := range stack.Services {
				//spec, _ := client.GetService(service.Service)
				fmt.Fprintf(w, "\t%s\t%s\t%s\n", service.Service, service.Status, service.Id)
			}
		}
		w.Flush()
		if Verbose {
			data, _ := json.MarshalIndent(stacks, "", "   ")
			fmt.Println(string(data))
		}
	},
	PostRun: RefreshToken,
}

var listVolumesCmd = &cobra.Command{
	Use:    "volumes",
	Short:  "List existing volumes",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		volumes, err := client.ListVolumes(apiUser.username)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "NAME\tATTACHED TO\tSIZE\tSTATUS")
		for _, volume := range *volumes {
			fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", volume.Name, volume.Attached, volume.Size, volume.Status)
		}
		w.Flush()
	},
	PostRun: RefreshToken,
}

var listProjectsCmd = &cobra.Command{
	Use:    "projects",
	Short:  "List existing projects (admin users only)",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		password := credentials("Admin password: ")
		token, err := client.Login("admin", password)
		if err != nil {
			fmt.Printf("Unable to list projects: %s \n", err)
			return
		}

		projects, err := client.ListProjects(token)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "ID\tNAMESPACE")
		for _, project := range *projects {
			fmt.Fprintf(w, "%s\t%s\n", project.Id, project.Namespace)
		}
		w.Flush()

	},
	PostRun: RefreshToken,
}

var listConfigsCmd = &cobra.Command{
	Use:    "configs [service ids]",
	Short:  "List service configs",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		if len(args) == 0 {
			cmd.Usage()
			os.Exit(-1)
		}
		configs, err := client.GetConfigs(args)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}

		if configs != nil {
			for sid, cfg := range *configs {
				fmt.Println(sid)
				data, _ := json.MarshalIndent(cfg, "", "   ")
				fmt.Println(string(data))
			}
		}

	},
	PostRun: RefreshToken,
}

func init() {
	RootCmd.AddCommand(listCmd)
	listCmd.AddCommand(listServicesCmd)
	listCmd.AddCommand(listStacksCmd)
	listCmd.AddCommand(listVolumesCmd)
	listCmd.AddCommand(listProjectsCmd)
	listCmd.AddCommand(listConfigsCmd)
}
<commit_msg>Changed output of list projects command<commit_after>// Copyright © 2016 National Data Service

package cmd

import (
	"encoding/json"
	"fmt"
	"github.com/spf13/cobra"
	"os"
	"text/tabwriter"
)

// listCmd represents the list command
var listCmd = &cobra.Command{
	Use:   "list",
	Short: "List available services or resources",
}

var listServicesCmd = &cobra.Command{
	Use:    "services",
	Short:  "List available services",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		services, err := client.ListServices()
		if err != nil {
			fmt.Printf("Error listing services: %s\n", err)
			return
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "SERVICE\tDEPENDENCY\tREQUIRED")
		for _, service := range *services {
			if service.IsStack {
				fmt.Fprintf(w, "%s\n", service.Key)
				for _, dependency := range service.Dependencies {
					if dependency.Required {
						fmt.Fprintf(w, "\t%s\t(required)\n", dependency.DependencyKey)
					} else {
						fmt.Fprintf(w, "\t%s\t(optional)\n", dependency.DependencyKey)
					}
				}
			}
		}
		w.Flush()
	},
	PostRun: RefreshToken,
}

var listStacksCmd = &cobra.Command{
	Use:    "stacks",
	Short:  "List existing stacks",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		stacks, err := client.ListStacks(apiUser.username)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "STACK\tSERVICE\tSTATUS\tSID")
		for _, stack := range *stacks {

			fmt.Fprintf(w, "%s\t\t%s\t%s\n", stack.Name, stack.Status, stack.Id)
			for _, service := range stack.Services {
				//spec, _ := client.GetService(service.Service)
				fmt.Fprintf(w, "\t%s\t%s\t%s\n", service.Service, service.Status, service.Id)
			}
		}
		w.Flush()
		if Verbose {
			data, _ := json.MarshalIndent(stacks, "", "   ")
			fmt.Println(string(data))
		}
	},
	PostRun: RefreshToken,
}

var listVolumesCmd = &cobra.Command{
	Use:    "volumes",
	Short:  "List existing volumes",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		volumes, err := client.ListVolumes(apiUser.username)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "NAME\tATTACHED TO\tSIZE\tSTATUS")
		for _, volume := range *volumes {
			fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", volume.Name, volume.Attached, volume.Size, volume.Status)
		}
		w.Flush()
	},
	PostRun: RefreshToken,
}

var listProjectsCmd = &cobra.Command{
	Use:    "projects",
	Short:  "List existing projects (admin users only)",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		password := credentials("Admin password: ")
		token, err := client.Login("admin", password)
		if err != nil {
			fmt.Printf("Unable to list projects: %s \n", err)
			return
		}

		projects, err := client.ListProjects(token)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 10, 4, 3, ' ', 0)
		fmt.Fprintln(w, "NAMESPACE\tQUOTA\tDESCRIPTION")
		for _, project := range *projects {
			fmt.Fprintf(w, "%s\t%d\t%s\n", project.Namespace, project.StorageQuota, project.Description)
		}
		w.Flush()

	},
	PostRun: RefreshToken,
}

var listConfigsCmd = &cobra.Command{
	Use:    "configs [service ids]",
	Short:  "List service configs",
	PreRun: Connect,
	Run: func(cmd *cobra.Command, args []string) {

		if len(args) == 0 {
			cmd.Usage()
			os.Exit(-1)
		}
		configs, err := client.GetConfigs(args)
		if err != nil {
			fmt.Printf("List failed: %s\n", err)
			os.Exit(-1)
		}

		if configs != nil {
			for sid, cfg := range *configs {
				fmt.Println(sid)
				data, _ := json.MarshalIndent(cfg, "", "   ")
				fmt.Println(string(data))
			}
		}

	},
	PostRun: RefreshToken,
}

func init() {
	RootCmd.AddCommand(listCmd)
	listCmd.AddCommand(listServicesCmd)
	listCmd.AddCommand(listStacksCmd)
	listCmd.AddCommand(listVolumesCmd)
	listCmd.AddCommand(listProjectsCmd)
	listCmd.AddCommand(listConfigsCmd)
}
<|endoftext|>


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

package fetch

import (
	"context"
	"errors"

	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/derrors"
	"golang.org/x/pkgsite/internal/log"
	"golang.org/x/pkgsite/internal/proxy"
	"golang.org/x/pkgsite/internal/version"
)

// LatestModuleVersions uses the proxy to get information about the latest
// versions of modulePath. It returns a LatestModuleVersions whose RawVersion
// and CookedVersion is obtained from the proxy @v/list and @latest endpoints.
// The cooked version is computed by choosing the latest version after removing
// versions that are retracted in the go.mod file of the raw version.
//
// The GoodVersion of LatestModuleVersions is not set. It should be determined
// when inserting into a data source, since it depends on the contents of the
// data source.
//
// The hasGoMod function that is passed in should check if version v of the
// module has a go.mod file, using a source other than the proxy (e.g. a
// database). If it doesn't have enough information to decide, it should return
// an error that wraps derrors.NotFound.
//
// If a module has no tagged versions and hasn't been accessed at a
// pseudo-version in a while, then the proxy's list endpoint will serve nothing
// and its @latest endpoint will return a 404/410. (Example:
// cloud.google.com/go/compute/metadata, which has a
// v0.0.0-20181107005212-dafb9c8d8707 that @latest does not return.) That is not
// a failure, but a valid state in which there is no version information for a
// module, even though particular pseudo-versions of the module might exist. In
// this case, LatestModuleVersions returns (nil, nil).
func LatestModuleVersions(ctx context.Context, modulePath string, prox *proxy.Client, hasGoMod func(v string) (bool, error)) (info *internal.LatestModuleVersions, err error) {
	defer derrors.WrapStack(&err, "LatestModuleVersions(%q)", modulePath)

	defer func() {
		if info != nil {
			log.Debugf(ctx, "LatestModuleVersions(%q) => (raw=%q cooked=%q, %v)", modulePath, info.RawVersion, info.CookedVersion, err)
		}
	}()

	// Remember calls to hasGoMod because they can be expensive.
	hasGoModResults := map[string]bool{}
	hasGoModFunc := func(v string) (bool, error) {
		result, ok := hasGoModResults[v]
		if ok {
			return result, nil
		}
		err := derrors.NotFound
		if hasGoMod != nil {
			result, err = hasGoMod(v)
		}
		if err != nil && !errors.Is(err, derrors.NotFound) {
			return false, err
		}
		if err != nil {
			// hasGoMod doesn't know; download the zip.
			zr, err := prox.Zip(ctx, modulePath, v)
			if err != nil {
				return false, err
			}
			result = hasGoModFile(zr, modulePath, v)
		}
		hasGoModResults[v] = result
		return result, nil
	}

	// Get the raw latest version.
	versions, err := prox.Versions(ctx, modulePath)
	if err != nil {
		return nil, err
	}
	latestInfo, err := prox.Info(ctx, modulePath, internal.LatestVersion)
	if err != nil {
		if !errors.Is(err, derrors.NotFound) {
			return nil, err
		}
	} else {
		versions = append(versions, latestInfo.Version)
	}
	if len(versions) == 0 {
		// No tagged versions, and @latest returns NotFound: no version information.
		return nil, nil
	}
	rawLatest, err := version.Latest(versions, hasGoModFunc)
	if err != nil {
		return nil, err
	}

	// Get the go.mod file at the raw latest version.
	modBytes, err := prox.Mod(ctx, modulePath, rawLatest)
	if err != nil {
		return nil, err
	}
	lmv, err := internal.NewLatestModuleVersions(modulePath, rawLatest, "", "", modBytes)
	if err != nil {
		return nil, err
	}

	// Get the cooked latest version by disallowing retracted versions.
	unretractedVersions := version.RemoveIf(versions, lmv.IsRetracted)
	lmv.CookedVersion, err = version.Latest(unretractedVersions, hasGoModFunc)
	if err != nil {
		return nil, err
	}
	return lmv, nil
}
<commit_msg>internal/fetch: don't fail if latest .mod not found<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fetch

import (
	"context"
	"errors"
	"fmt"

	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/derrors"
	"golang.org/x/pkgsite/internal/log"
	"golang.org/x/pkgsite/internal/proxy"
	"golang.org/x/pkgsite/internal/version"
)

// LatestModuleVersions uses the proxy to get information about the latest
// versions of modulePath. It returns a LatestModuleVersions whose RawVersion
// and CookedVersion is obtained from the proxy @v/list and @latest endpoints.
// The cooked version is computed by choosing the latest version after removing
// versions that are retracted in the go.mod file of the raw version.
//
// The GoodVersion of LatestModuleVersions is not set. It should be determined
// when inserting into a data source, since it depends on the contents of the
// data source.
//
// The hasGoMod function that is passed in should check if version v of the
// module has a go.mod file, using a source other than the proxy (e.g. a
// database). If it doesn't have enough information to decide, it should return
// an error that wraps derrors.NotFound.
//
// If a module has no tagged versions and hasn't been accessed at a
// pseudo-version in a while, then the proxy's list endpoint will serve nothing
// and its @latest endpoint will return a 404/410. (Example:
// cloud.google.com/go/compute/metadata, which has a
// v0.0.0-20181107005212-dafb9c8d8707 that @latest does not return.) That is not
// a failure, but a valid state in which there is no version information for a
// module, even though particular pseudo-versions of the module might exist. In
// this case, LatestModuleVersions returns (nil, nil).
func LatestModuleVersions(ctx context.Context, modulePath string, prox *proxy.Client, hasGoMod func(v string) (bool, error)) (info *internal.LatestModuleVersions, err error) {
	defer derrors.WrapStack(&err, "LatestModuleVersions(%q)", modulePath)

	defer func() {
		if info != nil {
			log.Debugf(ctx, "LatestModuleVersions(%q) => (raw=%q cooked=%q, %v)", modulePath, info.RawVersion, info.CookedVersion, err)
		}
	}()

	// Remember calls to hasGoMod because they can be expensive.
	hasGoModResults := map[string]bool{}
	hasGoModFunc := func(v string) (bool, error) {
		result, ok := hasGoModResults[v]
		if ok {
			return result, nil
		}
		err := derrors.NotFound
		if hasGoMod != nil {
			result, err = hasGoMod(v)
		}
		if err != nil && !errors.Is(err, derrors.NotFound) {
			return false, err
		}
		if err != nil {
			// hasGoMod doesn't know; download the zip.
			zr, err := prox.Zip(ctx, modulePath, v)
			if err != nil {
				return false, err
			}
			result = hasGoModFile(zr, modulePath, v)
		}
		hasGoModResults[v] = result
		return result, nil
	}

	// Get the raw latest version.
	versions, err := prox.Versions(ctx, modulePath)
	if err != nil {
		return nil, err
	}
	latestInfo, err := prox.Info(ctx, modulePath, internal.LatestVersion)
	if err != nil {
		if !errors.Is(err, derrors.NotFound) {
			return nil, err
		}
	} else {
		versions = append(versions, latestInfo.Version)
	}
	if len(versions) == 0 {
		// No tagged versions, and @latest returns NotFound: no version information.
		return nil, nil
	}
	rawLatest, err := version.Latest(versions, hasGoModFunc)
	if err != nil {
		return nil, err
	}

	// Get the go.mod file at the raw latest version.
	modBytes, err := prox.Mod(ctx, modulePath, rawLatest)
	if err != nil {
		// Something's wrong with the go.mod file, so assume a minimal one instead of failing.
		log.Warningf(ctx, "proxy.Mod(%q, %q): %v; using minimal go.mod for latest version info",
			modulePath, rawLatest)
		modBytes = []byte(fmt.Sprintf("module %s", modulePath))
	}
	lmv, err := internal.NewLatestModuleVersions(modulePath, rawLatest, "", "", modBytes)
	if err != nil {
		return nil, err
	}

	// Get the cooked latest version by disallowing retracted versions.
	unretractedVersions := version.RemoveIf(versions, lmv.IsRetracted)
	lmv.CookedVersion, err = version.Latest(unretractedVersions, hasGoModFunc)
	if err != nil {
		return nil, err
	}
	return lmv, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"reflect"
	"strconv"
	"sync"
	"time"

	"github.com/jessevdk/go-flags"

	"gopkg.in/kothar/go-backblaze.v0"
)

// Options defines command line flags used by this application
type Options struct {
	// Credentials
	AccountID      string `long:"account" env:"B2_ACCOUNT_ID" description:"The account ID to use"`
	ApplicationKey string `long:"appKey" env:"B2_APP_KEY" description:"The application key to use"`

	// Bucket
	Bucket string `short:"b" long:"bucket" description:"The bucket name to use for testing (a random bucket name will be chosen if not specified)"`
}

var opts = &Options{}

var parser = flags.NewParser(opts, flags.Default)

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {

	_, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}

	// Create client
	b2, err := backblaze.NewB2(backblaze.Credentials{
		AccountID:      opts.AccountID,
		ApplicationKey: opts.ApplicationKey,
	})
	b2.Debug = true
	check(err)

	b := testBucketCreate(b2)

	// Test basic file operations
	f, data := testFileUpload(b)
	testFileDownload(b, f, data)
	testFileRangeDownload(b, f, data)
	testFileDelete(b, f)

	// Test file listing calls
	files := uploadFiles(b)
	testListFiles(b, files)
	testListDirectories(b, files)
	deleteFiles(b, files)

	testBucketDelete(b)
}

func testBucketCreate(b2 *backblaze.B2) *backblaze.Bucket {
	// Get Test bucket
	if opts.Bucket == "" {
		opts.Bucket = "test-bucket-" + randSeq(10)
	}
	log.Printf("Testing with bucket %s", opts.Bucket)

	b, err := b2.Bucket(opts.Bucket)
	check(err)
	if b != nil {
		log.Fatal("Testing bucket already exists")
	}

	b, err = b2.CreateBucket(opts.Bucket, backblaze.AllPrivate)
	check(err)
	log.Print("Bucket created")

	return b
}

func testBucketDelete(b *backblaze.Bucket) {
	check(b.Delete())
	log.Print("Bucket deleted")
}

func testFileUpload(b *backblaze.Bucket) (*backblaze.File, []byte) {
	fileData := randBytes(1024 * 1024)

	f, err := b.UploadFile("test_file", nil, bytes.NewBuffer(fileData))
	check(err)

	log.Print("File uploaded")

	return f, fileData
}

func testFileDownload(b *backblaze.Bucket, f *backblaze.File, data []byte) {
	f, reader, err := b.DownloadFileByName(f.Name)
	check(err)

	body, err := ioutil.ReadAll(reader)
	check(err)

	if !bytes.Equal(body, data) {
		log.Fatal("Downloaded file content does not match upload")
	}

	log.Print("File downloaded")
}

func testFileRangeDownload(b *backblaze.Bucket, f *backblaze.File, data []byte) {
	f, reader, err := b.DownloadFileRangeByName(f.Name, &backblaze.FileRange{Start: 100, End: 2000})
	check(err)

	body, err := ioutil.ReadAll(reader)
	check(err)

	if !bytes.Equal(body, data[100:2000+1]) {
		log.Fatal("Downloaded file range does not match upload")
	}

	log.Print("File range downloaded")
}

func testFileDelete(b *backblaze.Bucket, f *backblaze.File) {
	_, err := b.DeleteFileVersion(f.Name, f.ID)
	check(err)

	log.Print("File deleted")
}

func uploadFiles(b *backblaze.Bucket) []*backblaze.File {
	fileData := randBytes(1024)

	files := []*backblaze.File{}

	queue := make(chan int64)
	var m sync.Mutex
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			for n := range queue {
				f, err := b.UploadFile("test/file_"+strconv.FormatInt(n, 10), nil, bytes.NewBuffer(fileData))
				check(err)

				m.Lock()
				files = append(files, f)
				m.Unlock()
			}

			wg.Done()
		}()
	}

	// Upload files
	count := 40
	for i := 1; i <= count; i++ {
		log.Printf("Uploading file %d/%d...", i, count)
		queue <- int64(i)
	}

	close(queue)
	wg.Wait()
	log.Println("Done.")

	return files
}

func testListFiles(b *backblaze.Bucket, files []*backblaze.File) {

	// List bucket content
	log.Println("Listing bucket contents")
	bulkResponse, err := b.ListFileNames("", 500)
	check(err)
	if len(bulkResponse.Files) != len(files) {
		log.Fatalf("Expected listing to return %d files but found %d", len(files), len(bulkResponse.Files))
	}

	// Test paging
	log.Println("Paging bucket contents")
	pagedFiles := []backblaze.FileStatus{}
	cursor := ""
	for {
		r, err := b.ListFileNames(cursor, 10)
		check(err)

		pagedFiles = append(pagedFiles, r.Files...)

		if r.NextFileName == "" {
			break
		}

		cursor = r.NextFileName
	}

	if !reflect.DeepEqual(bulkResponse.Files, pagedFiles) {
		log.Fatalf("Result of paged directory listing does not match bulk listing")
	}
}

func testListDirectories(b *backblaze.Bucket, files []*backblaze.File) {
	// List root directory
	log.Println("Listing root directory contents")
	bulkResponse, err := b.ListFileNamesWithPrefix("", 500, "", "/")
	check(err)
	if len(bulkResponse.Files) != 1 {
		log.Fatalf("Expected listing to return 1 directory but found %d", len(bulkResponse.Files))
	}

	// List subdirectory
	log.Println("Listing subdirectory contents")
	bulkResponse, err = b.ListFileNamesWithPrefix("", 500, "test/", "/")
	check(err)
	if len(bulkResponse.Files) != len(files) {
		log.Fatalf("Expected listing to return %d files but found %d", len(files), len(bulkResponse.Files))
	}
}

func deleteFiles(b *backblaze.Bucket, files []*backblaze.File) {
	// Delete files
	log.Printf("Deleting %d files...", len(files))
	for _, f := range files {
		_, err := b.DeleteFileVersion(f.Name, f.ID)
		check(err)
	}
	log.Println("Done.")
}

func check(err error) {
	if err == nil {
		return
	}

	log.Fatal(err)
}

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

// see http://stackoverflow.com/a/22892986/37416
func randSeq(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}

func randBytes(n int) []byte {
	b := make([]byte, n)
	for i := range b {
		b[i] = byte(rand.Int())
	}
	return b
}
<commit_msg>Add debug flag to apitest options<commit_after>package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"reflect"
	"strconv"
	"sync"
	"time"

	"github.com/jessevdk/go-flags"

	"gopkg.in/kothar/go-backblaze.v0"
)

// Options defines command line flags used by this application
type Options struct {
	// Credentials
	AccountID      string `long:"account" env:"B2_ACCOUNT_ID" description:"The account ID to use"`
	ApplicationKey string `long:"appKey" env:"B2_APP_KEY" description:"The application key to use"`

	// Bucket
	Bucket string `short:"b" long:"bucket" description:"The bucket name to use for testing (a random bucket name will be chosen if not specified)"`
	Debug  bool   `short:"d" long:"debug" description:"Show debug information during test"`
}

var opts = &Options{}

var parser = flags.NewParser(opts, flags.Default)

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {

	_, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}

	// Create client
	b2, err := backblaze.NewB2(backblaze.Credentials{
		AccountID:      opts.AccountID,
		ApplicationKey: opts.ApplicationKey,
	})
	b2.Debug = opts.Debug
	check(err)

	b := testBucketCreate(b2)

	// Test basic file operations
	f, data := testFileUpload(b)
	testFileDownload(b, f, data)
	testFileRangeDownload(b, f, data)
	testFileDelete(b, f)

	// Test file listing calls
	files := uploadFiles(b)
	testListFiles(b, files)
	testListDirectories(b, files)
	deleteFiles(b, files)

	testBucketDelete(b)
}

func testBucketCreate(b2 *backblaze.B2) *backblaze.Bucket {
	// Get Test bucket
	if opts.Bucket == "" {
		opts.Bucket = "test-bucket-" + randSeq(10)
	}
	log.Printf("Testing with bucket %s", opts.Bucket)

	b, err := b2.Bucket(opts.Bucket)
	check(err)
	if b != nil {
		log.Fatal("Testing bucket already exists")
	}

	b, err = b2.CreateBucket(opts.Bucket, backblaze.AllPrivate)
	check(err)
	log.Print("Bucket created")

	return b
}

func testBucketDelete(b *backblaze.Bucket) {
	check(b.Delete())
	log.Print("Bucket deleted")
}

func testFileUpload(b *backblaze.Bucket) (*backblaze.File, []byte) {
	fileData := randBytes(1024 * 1024)

	f, err := b.UploadFile("test_file", nil, bytes.NewBuffer(fileData))
	check(err)

	log.Print("File uploaded")

	return f, fileData
}

func testFileDownload(b *backblaze.Bucket, f *backblaze.File, data []byte) {
	f, reader, err := b.DownloadFileByName(f.Name)
	check(err)

	body, err := ioutil.ReadAll(reader)
	check(err)

	if !bytes.Equal(body, data) {
		log.Fatal("Downloaded file content does not match upload")
	}

	log.Print("File downloaded")
}

func testFileRangeDownload(b *backblaze.Bucket, f *backblaze.File, data []byte) {
	f, reader, err := b.DownloadFileRangeByName(f.Name, &backblaze.FileRange{Start: 100, End: 2000})
	check(err)

	body, err := ioutil.ReadAll(reader)
	check(err)

	if !bytes.Equal(body, data[100:2000+1]) {
		log.Fatal("Downloaded file range does not match upload")
	}

	log.Print("File range downloaded")
}

func testFileDelete(b *backblaze.Bucket, f *backblaze.File) {
	_, err := b.DeleteFileVersion(f.Name, f.ID)
	check(err)

	log.Print("File deleted")
}

func uploadFiles(b *backblaze.Bucket) []*backblaze.File {
	fileData := randBytes(1024)

	files := []*backblaze.File{}

	queue := make(chan int64)
	var m sync.Mutex
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			for n := range queue {
				f, err := b.UploadFile("test/file_"+strconv.FormatInt(n, 10), nil, bytes.NewBuffer(fileData))
				check(err)

				m.Lock()
				files = append(files, f)
				m.Unlock()
			}

			wg.Done()
		}()
	}

	// Upload files
	count := 40
	for i := 1; i <= count; i++ {
		log.Printf("Uploading file %d/%d...", i, count)
		queue <- int64(i)
	}

	close(queue)
	wg.Wait()
	log.Println("Done.")

	return files
}

func testListFiles(b *backblaze.Bucket, files []*backblaze.File) {

	// List bucket content
	log.Println("Listing bucket contents")
	bulkResponse, err := b.ListFileNames("", 500)
	check(err)
	if len(bulkResponse.Files) != len(files) {
		log.Fatalf("Expected listing to return %d files but found %d", len(files), len(bulkResponse.Files))
	}

	// Test paging
	log.Println("Paging bucket contents")
	pagedFiles := []backblaze.FileStatus{}
	cursor := ""
	for {
		r, err := b.ListFileNames(cursor, 10)
		check(err)

		pagedFiles = append(pagedFiles, r.Files...)

		if r.NextFileName == "" {
			break
		}

		cursor = r.NextFileName
	}

	if !reflect.DeepEqual(bulkResponse.Files, pagedFiles) {
		log.Fatalf("Result of paged directory listing does not match bulk listing")
	}
}

func testListDirectories(b *backblaze.Bucket, files []*backblaze.File) {
	// List root directory
	log.Println("Listing root directory contents")
	bulkResponse, err := b.ListFileNamesWithPrefix("", 500, "", "/")
	check(err)
	if len(bulkResponse.Files) != 1 {
		log.Fatalf("Expected listing to return 1 directory but found %d", len(bulkResponse.Files))
	}

	// List subdirectory
	log.Println("Listing subdirectory contents")
	bulkResponse, err = b.ListFileNamesWithPrefix("", 500, "test/", "/")
	check(err)
	if len(bulkResponse.Files) != len(files) {
		log.Fatalf("Expected listing to return %d files but found %d", len(files), len(bulkResponse.Files))
	}
}

func deleteFiles(b *backblaze.Bucket, files []*backblaze.File) {
	// Delete files
	log.Printf("Deleting %d files...", len(files))
	for _, f := range files {
		_, err := b.DeleteFileVersion(f.Name, f.ID)
		check(err)
	}
	log.Println("Done.")
}

func check(err error) {
	if err == nil {
		return
	}

	log.Fatal(err)
}

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

// see http://stackoverflow.com/a/22892986/37416
func randSeq(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}

func randBytes(n int) []byte {
	b := make([]byte, n)
	for i := range b {
		b[i] = byte(rand.Int())
	}
	return b
}
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package main

import (
	"flag"
	"io"
	"log"
	"net"
	"strconv"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	testpb "google.golang.org/grpc/interop/grpc_testing"
)

var (
	useTLS        = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
	caFile        = flag.String("tls_ca_file", "testdata/ca.pem", "The file containning the CA root cert file")
	serverHost    = flag.String("server_host", "127.0.0.1", "The server host name")
	serverPort    = flag.Int("server_port", 10000, "The server port number")
	tlsServerName = flag.String("tls_server_name", "x.test.youtube.com", "The server name use to verify the hostname returned by TLS handshake")
	testCase      = flag.String("test_case", "large_unary",
		`Configure different test cases. Valid options are:
        empty_unary : empty (zero bytes) request and response;
        large_unary : single request and (large) response;
        client_streaming : request streaming with single response;
        server_streaming : single request with response streaming;
        ping_pong : full-duplex streaming`)
)

var (
	reqSizes      = []int{27182, 8, 1828, 45904}
	respSizes     = []int{31415, 9, 2653, 58979}
	largeReqSize  = 271828
	largeRespSize = 314159
)

func newPayload(t testpb.PayloadType, size int) *testpb.Payload {
	if size < 0 {
		log.Fatalf("Requested a response with invalid length %d", size)
	}
	body := make([]byte, size)
	switch t {
	case testpb.PayloadType_COMPRESSABLE:
	case testpb.PayloadType_UNCOMPRESSABLE:
		log.Fatalf("PayloadType UNCOMPRESSABLE is not supported")
	default:
		log.Fatalf("Unsupported payload type: %d", t)
	}
	return &testpb.Payload{
		Type: t.Enum(),
		Body: body,
	}
}

func doEmptyUnaryCall(tc testpb.TestServiceClient) {
	reply, err := tc.EmptyCall(context.Background(), &testpb.Empty{})
	if err != nil {
		log.Fatal("/TestService/EmptyCall RPC failed: ", err)
	}
	if !proto.Equal(&testpb.Empty{}, reply) {
		log.Fatalf("/TestService/EmptyCall receives %v, want %v", reply, testpb.Empty{})
	}
}

func doLargeUnaryCall(tc testpb.TestServiceClient) {
	pl := newPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
	req := &testpb.SimpleRequest{
		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
		ResponseSize: proto.Int32(int32(largeRespSize)),
		Payload:      pl,
	}
	reply, err := tc.UnaryCall(context.Background(), req)
	if err != nil {
		log.Fatal("/TestService/UnaryCall RPC failed: ", err)
	}
	t := reply.GetPayload().GetType()
	s := len(reply.GetPayload().GetBody())
	if t != testpb.PayloadType_COMPRESSABLE || s != largeRespSize {
		log.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, largeRespSize)
	}
}

func doClientStreaming(tc testpb.TestServiceClient) {
	stream, err := tc.StreamingInputCall(context.Background())
	if err != nil {
		log.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err)
	}
	var sum int
	for _, s := range reqSizes {
		pl := newPayload(testpb.PayloadType_COMPRESSABLE, s)
		req := &testpb.StreamingInputCallRequest{
			Payload: pl,
		}
		if err := stream.Send(req); err != nil {
			log.Fatalf("%v.Send(%v) = %v", stream, req, err)
		}
		sum += s
		log.Printf("Sent a request of size %d, aggregated size %d", s, sum)

	}
	reply, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
	}
	if reply.GetAggregatedPayloadSize() != int32(sum) {
		log.Fatalf("%v.CloseAndRecv().GetAggregatePayloadSize() = %v; want %v", stream, reply.GetAggregatedPayloadSize(), sum)
	}
}

func doServerStreaming(tc testpb.TestServiceClient) {
	respParam := make([]*testpb.ResponseParameters, len(respSizes))
	for i, s := range respSizes {
		respParam[i] = &testpb.ResponseParameters{
			Size: proto.Int32(int32(s)),
		}
	}
	req := &testpb.StreamingOutputCallRequest{
		ResponseType:       testpb.PayloadType_COMPRESSABLE.Enum(),
		ResponseParameters: respParam,
	}
	stream, err := tc.StreamingOutputCall(context.Background(), req)
	if err != nil {
		log.Fatalf("%v.StreamingOutputCall(_) = _, %v", tc, err)
	}
	var rpcStatus error
	var respCnt int
	var index int
	for {
		reply, err := stream.Recv()
		if err != nil {
			rpcStatus = err
			break
		}
		t := reply.GetPayload().GetType()
		if t != testpb.PayloadType_COMPRESSABLE {
			log.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
		}
		size := len(reply.GetPayload().GetBody())
		if size != int(respSizes[index]) {
			log.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
		}
		index++
		respCnt++
	}
	if rpcStatus != io.EOF {
		log.Fatalf("Failed to finish the server streaming rpc: %v", err)
	}
	if respCnt != len(respSizes) {
		log.Fatalf("Got %d reply, want %d", len(respSizes), respCnt)
	}
}

func doPingPong(tc testpb.TestServiceClient) {
	stream, err := tc.FullDuplexCall(context.Background())
	if err != nil {
		log.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
	}
	var index int
	for index < len(reqSizes) {
		respParam := []*testpb.ResponseParameters{
			{
				Size: proto.Int32(int32(respSizes[index])),
			},
		}
		pl := newPayload(testpb.PayloadType_COMPRESSABLE, reqSizes[index])
		req := &testpb.StreamingOutputCallRequest{
			ResponseType:       testpb.PayloadType_COMPRESSABLE.Enum(),
			ResponseParameters: respParam,
			Payload:            pl,
		}
		if err := stream.Send(req); err != nil {
			log.Fatalf("%v.Send(%v) = %v", stream, req, err)
		}
		reply, err := stream.Recv()
		if err != nil {
			log.Fatalf("%v.Recv() = %v", stream, err)
		}
		t := reply.GetPayload().GetType()
		if t != testpb.PayloadType_COMPRESSABLE {
			log.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
		}
		size := len(reply.GetPayload().GetBody())
		if size != int(respSizes[index]) {
			log.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
		}
		index++
	}
	if err := stream.CloseSend(); err != nil {
		log.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil)
	}
	if _, err := stream.Recv(); err != io.EOF {
		log.Fatalf("%v failed to complele the ping pong test: %v", stream, err)
	}
}

func main() {
	flag.Parse()
	serverAddr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
	var opts []grpc.DialOption
	if *useTLS {
		var sn string
		if *tlsServerName != "" {
			sn = *tlsServerName
		}
		var creds credentials.TransportAuthenticator
		if *caFile != "" {
			var err error
			creds, err = credentials.NewClientTLSFromFile(*caFile, sn)
			if err != nil {
				log.Fatalf("Failed to create TLS credentials %v", err)
			}
		} else {
			creds = credentials.NewClientTLSFromCert(nil, sn)
		}
		opts = append(opts, grpc.WithClientTLS(creds))
	}
	conn, err := grpc.Dial(serverAddr, opts...)
	if err != nil {
		log.Fatalf("fail to dial: %v", err)
	}
	defer conn.Close()
	tc := testpb.NewTestServiceClient(conn)
	switch *testCase {
	case "empty_unary":
		doEmptyUnaryCall(tc)
	case "large_unary":
		doLargeUnaryCall(tc)
	case "client_streaming":
		doClientStreaming(tc)
	case "server_streaming":
		doServerStreaming(tc)
	case "ping_pong":
		doPingPong(tc)
	default:
		log.Fatal("Unsupported test case: ", *testCase)
	}
}
<commit_msg>Add GCE and JWT interop tests<commit_after>/*
 *
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package main

import (
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net"
	"strconv"
	"strings"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	testpb "google.golang.org/grpc/interop/grpc_testing"
)

var (
	useTLS                = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
	caFile                = flag.String("tls_ca_file", "testdata/ca.pem", "The file containning the CA root cert file")
	serviceAccountKeyFile = flag.String("service_account_key_file", "", "Path to service account json key file")
	oauthScope            = flag.String("oauth_scope", "", "The scope for OAuth2 tokens")
	defaultServiceAccount = flag.String("default_service_account", "", "Email of GCE default service account")
	serverHost            = flag.String("server_host", "127.0.0.1", "The server host name")
	serverPort            = flag.Int("server_port", 10000, "The server port number")
	tlsServerName         = flag.String("tls_server_name", "x.test.youtube.com", "The server name use to verify the hostname returned by TLS handshake")
	testCase              = flag.String("test_case", "large_unary",
		`Configure different test cases. Valid options are:
        empty_unary : empty (zero bytes) request and response;
        large_unary : single request and (large) response;
        client_streaming : request streaming with single response;
        server_streaming : single request with response streaming;
        ping_pong : full-duplex streaming;
        compute_engine_creds: large_unary with compute engine auth;
	service_account_creds: large_unary with service account auth.`)
)

var (
	reqSizes      = []int{27182, 8, 1828, 45904}
	respSizes     = []int{31415, 9, 2653, 58979}
	largeReqSize  = 271828
	largeRespSize = 314159
)

func newPayload(t testpb.PayloadType, size int) *testpb.Payload {
	if size < 0 {
		log.Fatalf("Requested a response with invalid length %d", size)
	}
	body := make([]byte, size)
	switch t {
	case testpb.PayloadType_COMPRESSABLE:
	case testpb.PayloadType_UNCOMPRESSABLE:
		log.Fatalf("PayloadType UNCOMPRESSABLE is not supported")
	default:
		log.Fatalf("Unsupported payload type: %d", t)
	}
	return &testpb.Payload{
		Type: t.Enum(),
		Body: body,
	}
}

func doEmptyUnaryCall(tc testpb.TestServiceClient) {
	reply, err := tc.EmptyCall(context.Background(), &testpb.Empty{})
	if err != nil {
		log.Fatal("/TestService/EmptyCall RPC failed: ", err)
	}
	if !proto.Equal(&testpb.Empty{}, reply) {
		log.Fatalf("/TestService/EmptyCall receives %v, want %v", reply, testpb.Empty{})
	}
	log.Println("EmptyUnaryCall done")
}

func doLargeUnaryCall(tc testpb.TestServiceClient) {
	pl := newPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
	req := &testpb.SimpleRequest{
		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
		ResponseSize: proto.Int32(int32(largeRespSize)),
		Payload:      pl,
	}
	reply, err := tc.UnaryCall(context.Background(), req)
	if err != nil {
		log.Fatal("/TestService/UnaryCall RPC failed: ", err)
	}
	t := reply.GetPayload().GetType()
	s := len(reply.GetPayload().GetBody())
	if t != testpb.PayloadType_COMPRESSABLE || s != largeRespSize {
		log.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, largeRespSize)
	}
	log.Println("LargeUnaryCall done")
}

func doClientStreaming(tc testpb.TestServiceClient) {
	stream, err := tc.StreamingInputCall(context.Background())
	if err != nil {
		log.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err)
	}
	var sum int
	for _, s := range reqSizes {
		pl := newPayload(testpb.PayloadType_COMPRESSABLE, s)
		req := &testpb.StreamingInputCallRequest{
			Payload: pl,
		}
		if err := stream.Send(req); err != nil {
			log.Fatalf("%v.Send(%v) = %v", stream, req, err)
		}
		sum += s
		log.Printf("Sent a request of size %d, aggregated size %d", s, sum)

	}
	reply, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
	}
	if reply.GetAggregatedPayloadSize() != int32(sum) {
		log.Fatalf("%v.CloseAndRecv().GetAggregatePayloadSize() = %v; want %v", stream, reply.GetAggregatedPayloadSize(), sum)
	}
	log.Println("ClientStreaming done")
}

func doServerStreaming(tc testpb.TestServiceClient) {
	respParam := make([]*testpb.ResponseParameters, len(respSizes))
	for i, s := range respSizes {
		respParam[i] = &testpb.ResponseParameters{
			Size: proto.Int32(int32(s)),
		}
	}
	req := &testpb.StreamingOutputCallRequest{
		ResponseType:       testpb.PayloadType_COMPRESSABLE.Enum(),
		ResponseParameters: respParam,
	}
	stream, err := tc.StreamingOutputCall(context.Background(), req)
	if err != nil {
		log.Fatalf("%v.StreamingOutputCall(_) = _, %v", tc, err)
	}
	var rpcStatus error
	var respCnt int
	var index int
	for {
		reply, err := stream.Recv()
		if err != nil {
			rpcStatus = err
			break
		}
		t := reply.GetPayload().GetType()
		if t != testpb.PayloadType_COMPRESSABLE {
			log.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
		}
		size := len(reply.GetPayload().GetBody())
		if size != int(respSizes[index]) {
			log.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
		}
		index++
		respCnt++
	}
	if rpcStatus != io.EOF {
		log.Fatalf("Failed to finish the server streaming rpc: %v", err)
	}
	if respCnt != len(respSizes) {
		log.Fatalf("Got %d reply, want %d", len(respSizes), respCnt)
	}
	log.Println("ServerStreaming done")
}

func doPingPong(tc testpb.TestServiceClient) {
	stream, err := tc.FullDuplexCall(context.Background())
	if err != nil {
		log.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
	}
	var index int
	for index < len(reqSizes) {
		respParam := []*testpb.ResponseParameters{
			{
				Size: proto.Int32(int32(respSizes[index])),
			},
		}
		pl := newPayload(testpb.PayloadType_COMPRESSABLE, reqSizes[index])
		req := &testpb.StreamingOutputCallRequest{
			ResponseType:       testpb.PayloadType_COMPRESSABLE.Enum(),
			ResponseParameters: respParam,
			Payload:            pl,
		}
		if err := stream.Send(req); err != nil {
			log.Fatalf("%v.Send(%v) = %v", stream, req, err)
		}
		reply, err := stream.Recv()
		if err != nil {
			log.Fatalf("%v.Recv() = %v", stream, err)
		}
		t := reply.GetPayload().GetType()
		if t != testpb.PayloadType_COMPRESSABLE {
			log.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
		}
		size := len(reply.GetPayload().GetBody())
		if size != int(respSizes[index]) {
			log.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
		}
		index++
	}
	if err := stream.CloseSend(); err != nil {
		log.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil)
	}
	if _, err := stream.Recv(); err != io.EOF {
		log.Fatalf("%v failed to complele the ping pong test: %v", stream, err)
	}
	log.Println("Pingpong done")
}

func doComputeEngineCreds(tc testpb.TestServiceClient) {
	pl := newPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
	req := &testpb.SimpleRequest{
		ResponseType:   testpb.PayloadType_COMPRESSABLE.Enum(),
		ResponseSize:   proto.Int32(int32(largeRespSize)),
		Payload:        pl,
		FillUsername:   proto.Bool(true),
		FillOauthScope: proto.Bool(true),
	}
	reply, err := tc.UnaryCall(context.Background(), req)
	if err != nil {
		log.Fatal("/TestService/UnaryCall RPC failed: ", err)
	}
	user := reply.GetUsername()
	scope := reply.GetOauthScope()
	if user != *defaultServiceAccount {
		log.Fatalf("Got user name %q, want %q.", user, *defaultServiceAccount)
	}
	if !strings.Contains(*oauthScope, scope) {
		log.Fatalf("Got OAuth scope %q which is NOT a substring of %q.", scope, *oauthScope)
	}
	log.Println("ComputeEngineCreds done")
}

func getServiceAccountJsonKey() []byte {
	jsonKey, err := ioutil.ReadFile(*serviceAccountKeyFile)
	if err != nil {
		log.Fatalf("Failed to read the service account key file: %v", err)
	}
	return jsonKey
}

func doServiceAccountCreds(tc testpb.TestServiceClient) {
	pl := newPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
	req := &testpb.SimpleRequest{
		ResponseType:   testpb.PayloadType_COMPRESSABLE.Enum(),
		ResponseSize:   proto.Int32(int32(largeRespSize)),
		Payload:        pl,
		FillUsername:   proto.Bool(true),
		FillOauthScope: proto.Bool(true),
	}
	reply, err := tc.UnaryCall(context.Background(), req)
	if err != nil {
		log.Fatal("/TestService/UnaryCall RPC failed: ", err)
	}
	jsonKey := getServiceAccountJsonKey()
	user := reply.GetUsername()
	scope := reply.GetOauthScope()
	if !strings.Contains(string(jsonKey), user) {
		log.Fatalf("Got user name %q which is NOT a substring of %q.", user, jsonKey)
	}
	if !strings.Contains(*oauthScope, scope) {
		log.Fatalf("Got OAuth scope %q which is NOT a substring of %q.", scope, *oauthScope)
	}
	log.Println("ServiceAccountCreds done")
}

func main() {
	flag.Parse()
	serverAddr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
	var opts []grpc.DialOption
	if *useTLS {
		var sn string
		if *tlsServerName != "" {
			sn = *tlsServerName
		}
		var creds credentials.TransportAuthenticator
		if *caFile != "" {
			var err error
			creds, err = credentials.NewClientTLSFromFile(*caFile, sn)
			if err != nil {
				log.Fatalf("Failed to create TLS credentials %v", err)
			}
		} else {
			creds = credentials.NewClientTLSFromCert(nil, sn)
		}
		opts = append(opts, grpc.WithClientTLS(creds))
		if *testCase == "compute_engine_creds" {
			opts = append(opts, grpc.WithPerRPCCredentials(credentials.NewComputeEngine()))
		} else if *testCase == "service_account_creds" {
			jwtCreds, err := credentials.NewServiceAccountFromFile(*serviceAccountKeyFile, *oauthScope)
			if err != nil {
				log.Fatalf("Failed to create JWT credentials: %v", err)
			}
			opts = append(opts, grpc.WithPerRPCCredentials(jwtCreds))
		}
	}
	conn, err := grpc.Dial(serverAddr, opts...)
	if err != nil {
		log.Fatalf("Fail to dial: %v", err)
	}
	defer conn.Close()
	tc := testpb.NewTestServiceClient(conn)
	switch *testCase {
	case "empty_unary":
		doEmptyUnaryCall(tc)
	case "large_unary":
		doLargeUnaryCall(tc)
	case "client_streaming":
		doClientStreaming(tc)
	case "server_streaming":
		doServerStreaming(tc)
	case "ping_pong":
		doPingPong(tc)
	case "compute_engine_creds":
		if !*useTLS {
			log.Fatalf("TLS is not enabled. TLS is required to execute compute_engine_creds test case.")
		}
		doComputeEngineCreds(tc)
	case "service_account_creds":
		if !*useTLS {
			log.Fatalf("TLS is not enabled. TLS is required to execute service_account_creds test case.")
		}
		doServiceAccountCreds(tc)
	default:
		log.Fatal("Unsupported test case: ", *testCase)
	}
}
<|endoftext|>


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

package fuse

// Attempt to unmount the file system whose mount point is the supplied
// directory.
func Unmount(dir string) error {
	return fuseshim.Unmount(dir)
}
<commit_msg>Fixed a build error.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fuse

import "github.com/jacobsa/fuse/internal/fuseshim"

// Attempt to unmount the file system whose mount point is the supplied
// directory.
func Unmount(dir string) error {
	return fuseshim.Unmount(dir)
}
<|endoftext|>