<commit_before>// Package dlog implements a generic logger facade.
//
// There are two first-class things of value in this package:
//
// First: The Logger interface.  This is a simple structured logging
// interface that is mostly trivial to implement on top of most
// logging backends, and allows library code to not need to care about
// what specific logging system the calling program uses.
//
// Second: The WithLogger, GetLogger, and WithLoggerField functions
// for tracking logger context.  These allow you to painlessly
// associate a logger with a context.
//
// If you are writing library code and want a logger, then you should
// take a context.Context as an argument, and then call GetLogger on
// that Context argument.
package dlog

import (
	"context"
	"log"
)

// Logger is a generic logging interface that most loggers implement,
// so that consumers don't need to care about the actual log
// implementation.
//
// Note that unlike logrus.FieldLogger, it does not include Fatal or
// Panic logging options.  Do proper error handling!  Return those
// errors!
type Logger interface {
	WithField(key string, value interface{}) Logger
	StdLogger(LogLevel) *log.Logger

	Tracef(format string, args ...interface{})
	Debugf(format string, args ...interface{})
	Infof(format string, args ...interface{})
	Printf(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Warningf(format string, args ...interface{})
	Errorf(format string, args ...interface{})

	Trace(args ...interface{})
	Debug(args ...interface{})
	Info(args ...interface{})
	Print(args ...interface{})
	Warn(args ...interface{})
	Warning(args ...interface{})
	Error(args ...interface{})

	Traceln(args ...interface{})
	Debugln(args ...interface{})
	Infoln(args ...interface{})
	Println(args ...interface{})
	Warnln(args ...interface{})
	Warningln(args ...interface{})
	Errorln(args ...interface{})
}

// LogLevel is an abstracted common log-level type for for
// Logger.StdLogger().
type LogLevel uint32

const (
	// LogLevelError is for errors that should definitely be noted.
	LogLevelError LogLevel = iota
	// LogLevelWarn is for non-critical entries that deserve eyes.
	LogLevelWarn
	// LogLevelInfo is for general operational entries about what's
	// going on inside the application.
	LogLevelInfo
	// LogLevelDebug is for debugging.  Very verbose logging.
	LogLevelDebug
	// LogLevelTrace is for extreme debugging.  Even finer-grained
	// informational events than the Debug.
	LogLevelTrace
)

// WithLogger returns a copy of ctx with logger associated with it,
// for future calls to GetLogger.
//
// You should only really ever call WithLogger from the initial
// process set up (i.e. directly inside your 'main()' function).
func WithLogger(ctx context.Context, logger Logger) context.Context {
	return context.WithValue(ctx, loggerContextKey{}, logger)
}

// WithLoggerField is a convenience wrapper for
//
//     WithLogger(ctx, GetLogger(ctx).WithField(key, value))
func WithLoggerField(ctx context.Context, key string, value interface{}) context.Context {
	return WithLogger(ctx, GetLogger(ctx).WithField(key, value))
}

// GetLogger returns the Logger associated with ctx.  If ctx has no
// Logger associated with it, a "fallback" logger (see
// SetFallbackLogger) is returned.  This function always returns a
// usable logger, unless you have specifically told it not to by
// calling SetFallbackLogger(nil).
func GetLogger(ctx context.Context) Logger {
	logger := ctx.Value(loggerContextKey{})
	if logger == nil {
		return getFallbackLogger()
	}
	return logger.(Logger)
}

type loggerContextKey struct{}
<commit_msg>(from AES) dlog: Rename WithLoggerField to just WithField<commit_after>// Package dlog implements a generic logger facade.
//
// There are two first-class things of value in this package:
//
// First: The Logger interface.  This is a simple structured logging
// interface that is mostly trivial to implement on top of most
// logging backends, and allows library code to not need to care about
// what specific logging system the calling program uses.
//
// Second: The WithLogger, GetLogger, and WithField functions for
// tracking logger context.  These allow you to painlessly associate a
// logger with a context.
//
// If you are writing library code and want a logger, then you should
// take a context.Context as an argument, and then call GetLogger on
// that Context argument.
package dlog

import (
	"context"
	"log"
)

// Logger is a generic logging interface that most loggers implement,
// so that consumers don't need to care about the actual log
// implementation.
//
// Note that unlike logrus.FieldLogger, it does not include Fatal or
// Panic logging options.  Do proper error handling!  Return those
// errors!
type Logger interface {
	WithField(key string, value interface{}) Logger
	StdLogger(LogLevel) *log.Logger

	Tracef(format string, args ...interface{})
	Debugf(format string, args ...interface{})
	Infof(format string, args ...interface{})
	Printf(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Warningf(format string, args ...interface{})
	Errorf(format string, args ...interface{})

	Trace(args ...interface{})
	Debug(args ...interface{})
	Info(args ...interface{})
	Print(args ...interface{})
	Warn(args ...interface{})
	Warning(args ...interface{})
	Error(args ...interface{})

	Traceln(args ...interface{})
	Debugln(args ...interface{})
	Infoln(args ...interface{})
	Println(args ...interface{})
	Warnln(args ...interface{})
	Warningln(args ...interface{})
	Errorln(args ...interface{})
}

// LogLevel is an abstracted common log-level type for for
// Logger.StdLogger().
type LogLevel uint32

const (
	// LogLevelError is for errors that should definitely be noted.
	LogLevelError LogLevel = iota
	// LogLevelWarn is for non-critical entries that deserve eyes.
	LogLevelWarn
	// LogLevelInfo is for general operational entries about what's
	// going on inside the application.
	LogLevelInfo
	// LogLevelDebug is for debugging.  Very verbose logging.
	LogLevelDebug
	// LogLevelTrace is for extreme debugging.  Even finer-grained
	// informational events than the Debug.
	LogLevelTrace
)

// WithLogger returns a copy of ctx with logger associated with it,
// for future calls to GetLogger.
//
// You should only really ever call WithLogger from the initial
// process set up (i.e. directly inside your 'main()' function).
func WithLogger(ctx context.Context, logger Logger) context.Context {
	return context.WithValue(ctx, loggerContextKey{}, logger)
}

// WithField is a convenience wrapper for
//
//     WithLogger(ctx, GetLogger(ctx).WithField(key, value))
func WithField(ctx context.Context, key string, value interface{}) context.Context {
	return WithLogger(ctx, GetLogger(ctx).WithField(key, value))
}

// GetLogger returns the Logger associated with ctx.  If ctx has no
// Logger associated with it, a "fallback" logger (see
// SetFallbackLogger) is returned.  This function always returns a
// usable logger, unless you have specifically told it not to by
// calling SetFallbackLogger(nil).
func GetLogger(ctx context.Context) Logger {
	logger := ctx.Value(loggerContextKey{})
	if logger == nil {
		return getFallbackLogger()
	}
	return logger.(Logger)
}

type loggerContextKey struct{}
<|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 gcs

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

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

	"k8s.io/release/pkg/gcp"
	"k8s.io/utils/pointer"
)

var (
	// GcsPrefix url prefix for google cloud storage buckets
	GcsPrefix      = "gs://"
	concurrentFlag = "-m"
	recursiveFlag  = "-r"
	noClobberFlag  = "-n"
)

type Options struct {
	// gsutil options
	Concurrent *bool
	Recursive  *bool
	NoClobber  *bool

	// local options
	// AllowMissing allows a copy operation to be skipped if the source or
	// destination does not exist. This is useful for scenarios where copy
	// operations happen in a loop/channel, so a single "failure" does not block
	// the entire operation.
	AllowMissing *bool
}

// DefaultGCSCopyOptions have the default options for the GCS copy action
var DefaultGCSCopyOptions = &Options{
	Concurrent:   pointer.BoolPtr(true),
	Recursive:    pointer.BoolPtr(true),
	NoClobber:    pointer.BoolPtr(true),
	AllowMissing: pointer.BoolPtr(true),
}

// CopyToGCS copies a local directory to the specified GCS path
// TODO: Consider using IsPathNormalized here
func CopyToGCS(src, gcsPath string, opts *Options) error {
	logrus.Infof("Copying %s to GCS (%s)", src, gcsPath)
	gcsPath, gcsPathErr := NormalizeGCSPath(gcsPath)
	if gcsPathErr != nil {
		return errors.Wrap(gcsPathErr, "normalize GCS path")
	}

	_, err := os.Stat(src)
	if err != nil {
		logrus.Info("Unable to get local source directory info")

		if *opts.AllowMissing {
			logrus.Infof("Source directory (%s) does not exist. Skipping GCS upload.", src)
			return nil
		}

		return errors.New("source directory does not exist")
	}

	return bucketCopy(src, gcsPath, opts)
}

// CopyToLocal copies a GCS path to the specified local directory
// TODO: Consider using IsPathNormalized here
func CopyToLocal(gcsPath, dst string, opts *Options) error {
	logrus.Infof("Copying GCS (%s) to %s", gcsPath, dst)
	gcsPath, gcsPathErr := NormalizeGCSPath(gcsPath)
	if gcsPathErr != nil {
		return errors.Wrap(gcsPathErr, "normalize GCS path")
	}

	return bucketCopy(gcsPath, dst, opts)
}

// CopyBucketToBucket copies between two GCS paths.
// TODO: Consider using IsPathNormalized here
func CopyBucketToBucket(src, dst string, opts *Options) error {
	logrus.Infof("Copying %s to %s", src, dst)

	src, srcErr := NormalizeGCSPath(src)
	if srcErr != nil {
		return errors.Wrap(srcErr, "normalize GCS path")
	}

	dst, dstErr := NormalizeGCSPath(dst)
	if dstErr != nil {
		return errors.Wrap(dstErr, "normalize GCS path")
	}

	return bucketCopy(src, dst, opts)
}

func bucketCopy(src, dst string, opts *Options) error {
	args := []string{}

	if *opts.Concurrent {
		logrus.Debug("Setting GCS copy to run concurrently")
		args = append(args, concurrentFlag)
	}

	args = append(args, "cp")
	if *opts.Recursive {
		logrus.Debug("Setting GCS copy to run recursively")
		args = append(args, recursiveFlag)
	}
	if *opts.NoClobber {
		logrus.Debug("Setting GCS copy to not clobber existing files")
		args = append(args, noClobberFlag)
	}

	args = append(args, src, dst)

	if err := gcp.GSUtil(args...); err != nil {
		return errors.Wrap(err, "gcs copy")
	}

	return nil
}

// GetReleasePath returns a GCS path to retrieve builds from or push builds to
//
// Expected destination format:
//   gs://<bucket>/<gcsRoot>[/fast][/<version>]
func GetReleasePath(
	bucket, gcsRoot, version string,
	fast bool) (string, error) {
	gcsPath, err := getPath(
		bucket,
		gcsRoot,
		version,
		"release",
		fast,
	)
	if err != nil {
		return "", errors.Wrap(err, "normalize GCS path")
	}

	logrus.Infof("Release path is %s", gcsPath)
	return gcsPath, nil
}

// GetMarkerPath returns a GCS path where version markers should be stored
//
// Expected destination format:
//   gs://<bucket>/<gcsRoot>
func GetMarkerPath(
	bucket, gcsRoot string) (string, error) {
	gcsPath, err := getPath(
		bucket,
		gcsRoot,
		"",
		"marker",
		false,
	)
	if err != nil {
		return "", errors.Wrap(err, "normalize GCS path")
	}

	logrus.Infof("Version marker path is %s", gcsPath)
	return gcsPath, nil
}

// GetReleasePath returns a GCS path to retrieve builds from or push builds to
//
// Expected destination format:
//   gs://<bucket>/<gcsRoot>[/fast][/<version>]
// TODO: Support "release" buildType
func getPath(
	bucket, gcsRoot, version, pathType string,
	fast bool) (string, error) {
	if gcsRoot == "" {
		return "", errors.New("GCS root must be specified")
	}

	gcsPathParts := []string{}

	gcsPathParts = append(gcsPathParts, bucket, gcsRoot)

	if pathType == "release" {
		if fast {
			gcsPathParts = append(gcsPathParts, "fast")
		}

		if version != "" {
			gcsPathParts = append(gcsPathParts, version)
		}
	} else if pathType == "marker" {
	} else {
		return "", errors.New("a GCS path type must be specified")
	}

	// Ensure any constructed GCS path is prefixed with `gs://`
	return NormalizeGCSPath(gcsPathParts...)
}

// NormalizeGCSPath takes a GCS path and ensures that the `GcsPrefix` is
// prepended to it.
// TODO: Should there be an append function for paths to prevent multiple calls
//       like in build.checkBuildExists()?
func NormalizeGCSPath(gcsPathParts ...string) (string, error) {
	gcsPath := ""

	// Ensure there is at least one element in the gcsPathParts slice before
	// trying to construct a path
	if len(gcsPathParts) == 0 {
		return "", errors.New("must contain at least one path part")
	} else if len(gcsPathParts) == 1 {
		if gcsPathParts[0] == "" {
			return "", errors.New("path should not be an empty string")
		}

		gcsPath = gcsPathParts[0]
	} else {
		var emptyParts int

		for i, part := range gcsPathParts {
			if part == "" {
				emptyParts++
			}

			if i == 0 {
				continue
			}

			if strings.Contains(part, "gs:/") {
				return "", errors.New("one of the GCS path parts contained a `gs:/`, which may suggest a filepath.Join() error in the caller")
			}

			if i == len(gcsPathParts)-1 && emptyParts == len(gcsPathParts) {
				return "", errors.New("all paths provided were empty")
			}
		}

		gcsPath = filepath.Join(gcsPathParts...)
	}

	// Strip `gs://` if it was included in gcsPathParts
	gcsPath = strings.TrimPrefix(gcsPath, GcsPrefix)

	// Strip `gs:/` if:
	// - `gs://` was included in gcsPathParts
	// - gcsPathParts had more than element
	// - filepath.Join() was called somewhere in a caller's logic
	gcsPath = strings.TrimPrefix(gcsPath, "gs:/")

	// Strip `/`
	// This scenario may never happen, but let's catch it, just in case
	gcsPath = strings.TrimPrefix(gcsPath, "/")

	gcsPath = GcsPrefix + gcsPath

	isNormalized := IsPathNormalized(gcsPath)
	if !isNormalized {
		return gcsPath, errors.New("unknown error while trying to normalize GCS path")
	}

	return gcsPath, nil
}

// IsPathNormalized determines if a GCS path is prefixed with `gs://`.
// Use this function as pre-check for any gsutil/GCS functions that manipulate
// GCS bucket contents.
func IsPathNormalized(gcsPath string) bool {
	var errCount int

	if !strings.HasPrefix(gcsPath, GcsPrefix) {
		logrus.Errorf("GCS path (%s) should be prefixed with `gs://`", gcsPath)
		errCount++
	}

	strippedPath := strings.TrimPrefix(gcsPath, GcsPrefix)
	if strings.Contains(strippedPath, "gs:/") {
		logrus.Errorf("GCS path (%s) should be prefixed with `gs:/`", gcsPath)
		errCount++
	}

	// TODO: Add logic to handle invalid path characters

	if errCount > 0 {
		return false
	}

	return true
}

// RsyncRecursive runs `gsutil rsync` in recursive mode. The caller of this
// function has to ensure that the provided paths are prefixed with gs:// if
// necessary (see `NormalizeGCSPath()`).
// TODO: Implementation of `gsutil rsync` should support local directory copies
//       with `-d`
func RsyncRecursive(src, dst string) error {
	if !IsPathNormalized(src) || !IsPathNormalized(dst) {
		return errors.New("cannot run `gsutil rsync` as one or more paths does not begin with `gs://`")
	}

	return errors.Wrap(
		gcp.GSUtil(concurrentFlag, "rsync", recursiveFlag, src, dst),
		"running gsutil rsync",
	)
}

// PathExists returns true if the specified GCS path exists.
func PathExists(gcsPath string) (bool, error) {
	if !IsPathNormalized(gcsPath) {
		return false, errors.New("cannot run `gsutil ls` GCS path does not begin with `gs://`")
	}

	err := gcp.GSUtil(
		"ls",
		gcsPath,
	)
	if err != nil {
		return false, err
	}

	logrus.Infof("Found %s", gcsPath)
	return true, nil
}
<commit_msg>pkg/gcp/gcs: RsyncRecursive doesn't need to run against normalized paths<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 gcs

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

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

	"k8s.io/release/pkg/gcp"
	"k8s.io/utils/pointer"
)

var (
	// GcsPrefix url prefix for google cloud storage buckets
	GcsPrefix      = "gs://"
	concurrentFlag = "-m"
	recursiveFlag  = "-r"
	noClobberFlag  = "-n"
)

type Options struct {
	// gsutil options
	Concurrent *bool
	Recursive  *bool
	NoClobber  *bool

	// local options
	// AllowMissing allows a copy operation to be skipped if the source or
	// destination does not exist. This is useful for scenarios where copy
	// operations happen in a loop/channel, so a single "failure" does not block
	// the entire operation.
	AllowMissing *bool
}

// DefaultGCSCopyOptions have the default options for the GCS copy action
var DefaultGCSCopyOptions = &Options{
	Concurrent:   pointer.BoolPtr(true),
	Recursive:    pointer.BoolPtr(true),
	NoClobber:    pointer.BoolPtr(true),
	AllowMissing: pointer.BoolPtr(true),
}

// CopyToGCS copies a local directory to the specified GCS path
// TODO: Consider using IsPathNormalized here
func CopyToGCS(src, gcsPath string, opts *Options) error {
	logrus.Infof("Copying %s to GCS (%s)", src, gcsPath)
	gcsPath, gcsPathErr := NormalizeGCSPath(gcsPath)
	if gcsPathErr != nil {
		return errors.Wrap(gcsPathErr, "normalize GCS path")
	}

	_, err := os.Stat(src)
	if err != nil {
		logrus.Info("Unable to get local source directory info")

		if *opts.AllowMissing {
			logrus.Infof("Source directory (%s) does not exist. Skipping GCS upload.", src)
			return nil
		}

		return errors.New("source directory does not exist")
	}

	return bucketCopy(src, gcsPath, opts)
}

// CopyToLocal copies a GCS path to the specified local directory
// TODO: Consider using IsPathNormalized here
func CopyToLocal(gcsPath, dst string, opts *Options) error {
	logrus.Infof("Copying GCS (%s) to %s", gcsPath, dst)
	gcsPath, gcsPathErr := NormalizeGCSPath(gcsPath)
	if gcsPathErr != nil {
		return errors.Wrap(gcsPathErr, "normalize GCS path")
	}

	return bucketCopy(gcsPath, dst, opts)
}

// CopyBucketToBucket copies between two GCS paths.
// TODO: Consider using IsPathNormalized here
func CopyBucketToBucket(src, dst string, opts *Options) error {
	logrus.Infof("Copying %s to %s", src, dst)

	src, srcErr := NormalizeGCSPath(src)
	if srcErr != nil {
		return errors.Wrap(srcErr, "normalize GCS path")
	}

	dst, dstErr := NormalizeGCSPath(dst)
	if dstErr != nil {
		return errors.Wrap(dstErr, "normalize GCS path")
	}

	return bucketCopy(src, dst, opts)
}

func bucketCopy(src, dst string, opts *Options) error {
	args := []string{}

	if *opts.Concurrent {
		logrus.Debug("Setting GCS copy to run concurrently")
		args = append(args, concurrentFlag)
	}

	args = append(args, "cp")
	if *opts.Recursive {
		logrus.Debug("Setting GCS copy to run recursively")
		args = append(args, recursiveFlag)
	}
	if *opts.NoClobber {
		logrus.Debug("Setting GCS copy to not clobber existing files")
		args = append(args, noClobberFlag)
	}

	args = append(args, src, dst)

	if err := gcp.GSUtil(args...); err != nil {
		return errors.Wrap(err, "gcs copy")
	}

	return nil
}

// GetReleasePath returns a GCS path to retrieve builds from or push builds to
//
// Expected destination format:
//   gs://<bucket>/<gcsRoot>[/fast][/<version>]
func GetReleasePath(
	bucket, gcsRoot, version string,
	fast bool) (string, error) {
	gcsPath, err := getPath(
		bucket,
		gcsRoot,
		version,
		"release",
		fast,
	)
	if err != nil {
		return "", errors.Wrap(err, "normalize GCS path")
	}

	logrus.Infof("Release path is %s", gcsPath)
	return gcsPath, nil
}

// GetMarkerPath returns a GCS path where version markers should be stored
//
// Expected destination format:
//   gs://<bucket>/<gcsRoot>
func GetMarkerPath(
	bucket, gcsRoot string) (string, error) {
	gcsPath, err := getPath(
		bucket,
		gcsRoot,
		"",
		"marker",
		false,
	)
	if err != nil {
		return "", errors.Wrap(err, "normalize GCS path")
	}

	logrus.Infof("Version marker path is %s", gcsPath)
	return gcsPath, nil
}

// GetReleasePath returns a GCS path to retrieve builds from or push builds to
//
// Expected destination format:
//   gs://<bucket>/<gcsRoot>[/fast][/<version>]
// TODO: Support "release" buildType
func getPath(
	bucket, gcsRoot, version, pathType string,
	fast bool) (string, error) {
	if gcsRoot == "" {
		return "", errors.New("GCS root must be specified")
	}

	gcsPathParts := []string{}

	gcsPathParts = append(gcsPathParts, bucket, gcsRoot)

	if pathType == "release" {
		if fast {
			gcsPathParts = append(gcsPathParts, "fast")
		}

		if version != "" {
			gcsPathParts = append(gcsPathParts, version)
		}
	} else if pathType == "marker" {
	} else {
		return "", errors.New("a GCS path type must be specified")
	}

	// Ensure any constructed GCS path is prefixed with `gs://`
	return NormalizeGCSPath(gcsPathParts...)
}

// NormalizeGCSPath takes a GCS path and ensures that the `GcsPrefix` is
// prepended to it.
// TODO: Should there be an append function for paths to prevent multiple calls
//       like in build.checkBuildExists()?
func NormalizeGCSPath(gcsPathParts ...string) (string, error) {
	gcsPath := ""

	// Ensure there is at least one element in the gcsPathParts slice before
	// trying to construct a path
	if len(gcsPathParts) == 0 {
		return "", errors.New("must contain at least one path part")
	} else if len(gcsPathParts) == 1 {
		if gcsPathParts[0] == "" {
			return "", errors.New("path should not be an empty string")
		}

		gcsPath = gcsPathParts[0]
	} else {
		var emptyParts int

		for i, part := range gcsPathParts {
			if part == "" {
				emptyParts++
			}

			if i == 0 {
				continue
			}

			if strings.Contains(part, "gs:/") {
				return "", errors.New("one of the GCS path parts contained a `gs:/`, which may suggest a filepath.Join() error in the caller")
			}

			if i == len(gcsPathParts)-1 && emptyParts == len(gcsPathParts) {
				return "", errors.New("all paths provided were empty")
			}
		}

		gcsPath = filepath.Join(gcsPathParts...)
	}

	// Strip `gs://` if it was included in gcsPathParts
	gcsPath = strings.TrimPrefix(gcsPath, GcsPrefix)

	// Strip `gs:/` if:
	// - `gs://` was included in gcsPathParts
	// - gcsPathParts had more than element
	// - filepath.Join() was called somewhere in a caller's logic
	gcsPath = strings.TrimPrefix(gcsPath, "gs:/")

	// Strip `/`
	// This scenario may never happen, but let's catch it, just in case
	gcsPath = strings.TrimPrefix(gcsPath, "/")

	gcsPath = GcsPrefix + gcsPath

	isNormalized := IsPathNormalized(gcsPath)
	if !isNormalized {
		return gcsPath, errors.New("unknown error while trying to normalize GCS path")
	}

	return gcsPath, nil
}

// IsPathNormalized determines if a GCS path is prefixed with `gs://`.
// Use this function as pre-check for any gsutil/GCS functions that manipulate
// GCS bucket contents.
func IsPathNormalized(gcsPath string) bool {
	var errCount int

	if !strings.HasPrefix(gcsPath, GcsPrefix) {
		logrus.Errorf("GCS path (%s) should be prefixed with `gs://`", gcsPath)
		errCount++
	}

	strippedPath := strings.TrimPrefix(gcsPath, GcsPrefix)
	if strings.Contains(strippedPath, "gs:/") {
		logrus.Errorf("GCS path (%s) should be prefixed with `gs:/`", gcsPath)
		errCount++
	}

	// TODO: Add logic to handle invalid path characters

	if errCount > 0 {
		return false
	}

	return true
}

// RsyncRecursive runs `gsutil rsync` in recursive mode. The caller of this
// function has to ensure that the provided paths are prefixed with gs:// if
// necessary (see `NormalizeGCSPath()`).
func RsyncRecursive(src, dst string) error {
	return errors.Wrap(
		gcp.GSUtil(concurrentFlag, "rsync", recursiveFlag, src, dst),
		"running gsutil rsync",
	)
}

// PathExists returns true if the specified GCS path exists.
func PathExists(gcsPath string) (bool, error) {
	if !IsPathNormalized(gcsPath) {
		return false, errors.New("cannot run `gsutil ls` GCS path does not begin with `gs://`")
	}

	err := gcp.GSUtil(
		"ls",
		gcsPath,
	)
	if err != nil {
		return false, err
	}

	logrus.Infof("Found %s", gcsPath)
	return true, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package helm // import "k8s.io/helm/pkg/helm"

import (
	"io"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"

	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/proto/hapi/chart"
	rls "k8s.io/helm/pkg/proto/hapi/services"
)

// Client manages client side of the Helm-Tiller protocol.
type Client struct {
	opts options
}

// NewClient creates a new client.
func NewClient(opts ...Option) *Client {
	var c Client
	return c.Option(opts...)
}

// Option configures the Helm client with the provided options.
func (h *Client) Option(opts ...Option) *Client {
	for _, opt := range opts {
		opt(&h.opts)
	}
	return h
}

// ListReleases lists the current releases.
func (h *Client) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.listReq
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.list(ctx, req)
}

// InstallRelease loads a chart from chstr, installs it, and returns the release response.
func (h *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
	// load the chart to install
	chart, err := chartutil.Load(chstr)
	if err != nil {
		return nil, err
	}

	return h.InstallReleaseFromChart(chart, ns, opts...)
}

// InstallReleaseFromChart installs a new chart and returns the release response.
func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
	// apply the install options
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.instReq
	req.Chart = chart
	req.Namespace = ns
	req.DryRun = h.opts.dryRun
	req.DisableHooks = h.opts.disableHooks
	req.ReuseName = h.opts.reuseName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values)
	if err != nil {
		return nil, err
	}
	err = chartutil.ProcessRequirementsImportValues(req.Chart)
	if err != nil {
		return nil, err
	}

	return h.install(ctx, req)
}

// DeleteRelease uninstalls a named release and returns the response.
func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
	// apply the uninstall options
	for _, opt := range opts {
		opt(&h.opts)
	}

	if h.opts.dryRun {
		// In the dry run case, just see if the release exists
		r, err := h.ReleaseContent(rlsName)
		if err != nil {
			return &rls.UninstallReleaseResponse{}, err
		}
		return &rls.UninstallReleaseResponse{Release: r.Release}, nil
	}

	req := &h.opts.uninstallReq
	req.Name = rlsName
	req.DisableHooks = h.opts.disableHooks
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.delete(ctx, req)
}

// UpdateRelease loads a chart from chstr and updates a release to a new/different chart.
func (h *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
	// load the chart to update
	chart, err := chartutil.Load(chstr)
	if err != nil {
		return nil, err
	}

	return h.UpdateReleaseFromChart(rlsName, chart, opts...)
}

// UpdateReleaseFromChart updates a release to a new/different chart.
func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {

	// apply the update options
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.updateReq
	req.Chart = chart
	req.DryRun = h.opts.dryRun
	req.Name = rlsName
	req.DisableHooks = h.opts.disableHooks
	req.Recreate = h.opts.recreate
	req.Force = h.opts.force
	req.ResetValues = h.opts.resetValues
	req.ReuseValues = h.opts.reuseValues
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values)
	if err != nil {
		return nil, err
	}
	err = chartutil.ProcessRequirementsImportValues(req.Chart)
	if err != nil {
		return nil, err
	}

	return h.update(ctx, req)
}

// GetVersion returns the server version.
func (h *Client) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &rls.GetVersionRequest{}
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.version(ctx, req)
}

// RollbackRelease rolls back a release to the previous version.
func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.rollbackReq
	req.Recreate = h.opts.recreate
	req.Force = h.opts.force
	req.DisableHooks = h.opts.disableHooks
	req.DryRun = h.opts.dryRun
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.rollback(ctx, req)
}

// ReleaseStatus returns the given release's status.
func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.statusReq
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.status(ctx, req)
}

// ReleaseContent returns the configuration for a given release.
func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.contentReq
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.content(ctx, req)
}

// ReleaseHistory returns a release's revision history.
func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}

	req := &h.opts.histReq
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.history(ctx, req)
}

// RunReleaseTest executes a pre-defined test on a release.
func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
	for _, opt := range opts {
		opt(&h.opts)
	}

	req := &h.opts.testReq
	req.Name = rlsName
	ctx := NewContext()

	return h.test(ctx, req)
}

// connect returns a gRPC connection to Tiller or error. The gRPC dial options
// are constructed here.
func (h *Client) connect(ctx context.Context) (conn *grpc.ClientConn, err error) {
	opts := []grpc.DialOption{
		grpc.WithTimeout(5 * time.Second),
		grpc.WithBlock(),
	}
	switch {
	case h.opts.useTLS:
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(h.opts.tlsConfig)))
	default:
		opts = append(opts, grpc.WithInsecure())
	}
	if conn, err = grpc.Dial(h.opts.host, opts...); err != nil {
		return nil, err
	}
	return conn, nil
}

// Executes tiller.ListReleases RPC.
func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	s, err := rlc.ListReleases(ctx, req)
	if err != nil {
		return nil, err
	}

	return s.Recv()
}

// Executes tiller.InstallRelease RPC.
func (h *Client) install(ctx context.Context, req *rls.InstallReleaseRequest) (*rls.InstallReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.InstallRelease(ctx, req)
}

// Executes tiller.UninstallRelease RPC.
func (h *Client) delete(ctx context.Context, req *rls.UninstallReleaseRequest) (*rls.UninstallReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.UninstallRelease(ctx, req)
}

// Executes tiller.UpdateRelease RPC.
func (h *Client) update(ctx context.Context, req *rls.UpdateReleaseRequest) (*rls.UpdateReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.UpdateRelease(ctx, req)
}

// Executes tiller.RollbackRelease RPC.
func (h *Client) rollback(ctx context.Context, req *rls.RollbackReleaseRequest) (*rls.RollbackReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.RollbackRelease(ctx, req)
}

// Executes tiller.GetReleaseStatus RPC.
func (h *Client) status(ctx context.Context, req *rls.GetReleaseStatusRequest) (*rls.GetReleaseStatusResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetReleaseStatus(ctx, req)
}

// Executes tiller.GetReleaseContent RPC.
func (h *Client) content(ctx context.Context, req *rls.GetReleaseContentRequest) (*rls.GetReleaseContentResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetReleaseContent(ctx, req)
}

// Executes tiller.GetVersion RPC.
func (h *Client) version(ctx context.Context, req *rls.GetVersionRequest) (*rls.GetVersionResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetVersion(ctx, req)
}

// Executes tiller.GetHistory RPC.
func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.GetHistoryResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetHistory(ctx, req)
}

// Executes tiller.TestRelease RPC.
func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) {
	errc := make(chan error, 1)
	c, err := h.connect(ctx)
	if err != nil {
		errc <- err
		return nil, errc
	}

	ch := make(chan *rls.TestReleaseResponse, 1)
	go func() {
		defer close(errc)
		defer close(ch)
		defer c.Close()

		rlc := rls.NewReleaseServiceClient(c)
		s, err := rlc.RunReleaseTest(ctx, req)
		if err != nil {
			errc <- err
			return
		}

		for {
			msg, err := s.Recv()
			if err == io.EOF {
				return
			}
			if err != nil {
				errc <- err
				return
			}
			ch <- msg
		}
	}()

	return ch, errc
}
<commit_msg>add a keepalive of 30s to the client (#3183)<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package helm // import "k8s.io/helm/pkg/helm"

import (
	"io"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/keepalive"

	"k8s.io/helm/pkg/chartutil"
	"k8s.io/helm/pkg/proto/hapi/chart"
	rls "k8s.io/helm/pkg/proto/hapi/services"
)

// Client manages client side of the Helm-Tiller protocol.
type Client struct {
	opts options
}

// NewClient creates a new client.
func NewClient(opts ...Option) *Client {
	var c Client
	return c.Option(opts...)
}

// Option configures the Helm client with the provided options.
func (h *Client) Option(opts ...Option) *Client {
	for _, opt := range opts {
		opt(&h.opts)
	}
	return h
}

// ListReleases lists the current releases.
func (h *Client) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.listReq
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.list(ctx, req)
}

// InstallRelease loads a chart from chstr, installs it, and returns the release response.
func (h *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
	// load the chart to install
	chart, err := chartutil.Load(chstr)
	if err != nil {
		return nil, err
	}

	return h.InstallReleaseFromChart(chart, ns, opts...)
}

// InstallReleaseFromChart installs a new chart and returns the release response.
func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) {
	// apply the install options
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.instReq
	req.Chart = chart
	req.Namespace = ns
	req.DryRun = h.opts.dryRun
	req.DisableHooks = h.opts.disableHooks
	req.ReuseName = h.opts.reuseName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values)
	if err != nil {
		return nil, err
	}
	err = chartutil.ProcessRequirementsImportValues(req.Chart)
	if err != nil {
		return nil, err
	}

	return h.install(ctx, req)
}

// DeleteRelease uninstalls a named release and returns the response.
func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) {
	// apply the uninstall options
	for _, opt := range opts {
		opt(&h.opts)
	}

	if h.opts.dryRun {
		// In the dry run case, just see if the release exists
		r, err := h.ReleaseContent(rlsName)
		if err != nil {
			return &rls.UninstallReleaseResponse{}, err
		}
		return &rls.UninstallReleaseResponse{Release: r.Release}, nil
	}

	req := &h.opts.uninstallReq
	req.Name = rlsName
	req.DisableHooks = h.opts.disableHooks
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.delete(ctx, req)
}

// UpdateRelease loads a chart from chstr and updates a release to a new/different chart.
func (h *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {
	// load the chart to update
	chart, err := chartutil.Load(chstr)
	if err != nil {
		return nil, err
	}

	return h.UpdateReleaseFromChart(rlsName, chart, opts...)
}

// UpdateReleaseFromChart updates a release to a new/different chart.
func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) {

	// apply the update options
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.updateReq
	req.Chart = chart
	req.DryRun = h.opts.dryRun
	req.Name = rlsName
	req.DisableHooks = h.opts.disableHooks
	req.Recreate = h.opts.recreate
	req.Force = h.opts.force
	req.ResetValues = h.opts.resetValues
	req.ReuseValues = h.opts.reuseValues
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values)
	if err != nil {
		return nil, err
	}
	err = chartutil.ProcessRequirementsImportValues(req.Chart)
	if err != nil {
		return nil, err
	}

	return h.update(ctx, req)
}

// GetVersion returns the server version.
func (h *Client) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &rls.GetVersionRequest{}
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.version(ctx, req)
}

// RollbackRelease rolls back a release to the previous version.
func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.rollbackReq
	req.Recreate = h.opts.recreate
	req.Force = h.opts.force
	req.DisableHooks = h.opts.disableHooks
	req.DryRun = h.opts.dryRun
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.rollback(ctx, req)
}

// ReleaseStatus returns the given release's status.
func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.statusReq
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.status(ctx, req)
}

// ReleaseContent returns the configuration for a given release.
func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}
	req := &h.opts.contentReq
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.content(ctx, req)
}

// ReleaseHistory returns a release's revision history.
func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) {
	for _, opt := range opts {
		opt(&h.opts)
	}

	req := &h.opts.histReq
	req.Name = rlsName
	ctx := NewContext()

	if h.opts.before != nil {
		if err := h.opts.before(ctx, req); err != nil {
			return nil, err
		}
	}
	return h.history(ctx, req)
}

// RunReleaseTest executes a pre-defined test on a release.
func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
	for _, opt := range opts {
		opt(&h.opts)
	}

	req := &h.opts.testReq
	req.Name = rlsName
	ctx := NewContext()

	return h.test(ctx, req)
}

// connect returns a gRPC connection to Tiller or error. The gRPC dial options
// are constructed here.
func (h *Client) connect(ctx context.Context) (conn *grpc.ClientConn, err error) {
	opts := []grpc.DialOption{
		grpc.WithTimeout(5 * time.Second),
		grpc.WithBlock(),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			// Send keepalive every 30 seconds to prevent the connection from
			// getting closed by upstreams
			Time: time.Duration(30) * time.Second,
		}),
	}
	switch {
	case h.opts.useTLS:
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(h.opts.tlsConfig)))
	default:
		opts = append(opts, grpc.WithInsecure())
	}
	if conn, err = grpc.Dial(h.opts.host, opts...); err != nil {
		return nil, err
	}
	return conn, nil
}

// Executes tiller.ListReleases RPC.
func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	s, err := rlc.ListReleases(ctx, req)
	if err != nil {
		return nil, err
	}

	return s.Recv()
}

// Executes tiller.InstallRelease RPC.
func (h *Client) install(ctx context.Context, req *rls.InstallReleaseRequest) (*rls.InstallReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.InstallRelease(ctx, req)
}

// Executes tiller.UninstallRelease RPC.
func (h *Client) delete(ctx context.Context, req *rls.UninstallReleaseRequest) (*rls.UninstallReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.UninstallRelease(ctx, req)
}

// Executes tiller.UpdateRelease RPC.
func (h *Client) update(ctx context.Context, req *rls.UpdateReleaseRequest) (*rls.UpdateReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.UpdateRelease(ctx, req)
}

// Executes tiller.RollbackRelease RPC.
func (h *Client) rollback(ctx context.Context, req *rls.RollbackReleaseRequest) (*rls.RollbackReleaseResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.RollbackRelease(ctx, req)
}

// Executes tiller.GetReleaseStatus RPC.
func (h *Client) status(ctx context.Context, req *rls.GetReleaseStatusRequest) (*rls.GetReleaseStatusResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetReleaseStatus(ctx, req)
}

// Executes tiller.GetReleaseContent RPC.
func (h *Client) content(ctx context.Context, req *rls.GetReleaseContentRequest) (*rls.GetReleaseContentResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetReleaseContent(ctx, req)
}

// Executes tiller.GetVersion RPC.
func (h *Client) version(ctx context.Context, req *rls.GetVersionRequest) (*rls.GetVersionResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetVersion(ctx, req)
}

// Executes tiller.GetHistory RPC.
func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.GetHistoryResponse, error) {
	c, err := h.connect(ctx)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	rlc := rls.NewReleaseServiceClient(c)
	return rlc.GetHistory(ctx, req)
}

// Executes tiller.TestRelease RPC.
func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) {
	errc := make(chan error, 1)
	c, err := h.connect(ctx)
	if err != nil {
		errc <- err
		return nil, errc
	}

	ch := make(chan *rls.TestReleaseResponse, 1)
	go func() {
		defer close(errc)
		defer close(ch)
		defer c.Close()

		rlc := rls.NewReleaseServiceClient(c)
		s, err := rlc.RunReleaseTest(ctx, req)
		if err != nil {
			errc <- err
			return
		}

		for {
			msg, err := s.Recv()
			if err == io.EOF {
				return
			}
			if err != nil {
				errc <- err
				return
			}
			ch <- msg
		}
	}()

	return ch, errc
}
<|endoftext|>


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

package pkg

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	neturl "net/url"
	"strings"
	"time"
)

const (
	HTTP_2xx = 2
	HTTP_4xx = 4
)

type Err error

type ErrTimeout struct {
	Err
}

type ErrNotFound struct {
	Err
}

type ErrInvalid struct {
	Err
}

type ErrServer struct {
	Err
}

type ErrNetwork struct {
	Err
}

type HttpClient struct {
	// Initial backoff duration. Defaults to 50 milliseconds
	InitialBackoff time.Duration

	// Maximum exp backoff duration. Defaults to 5 seconds
	MaxBackoff time.Duration

	// Maximum number of connection retries. Defaults to 15
	MaxRetries int

	// Whether or not to skip TLS verification. Defaults to false
	SkipTLS bool

	client *http.Client
}

type Getter interface {
	Get(string) ([]byte, error)
	GetRetry(string) ([]byte, error)
}

func NewHttpClient() *HttpClient {
	hc := &HttpClient{
		InitialBackoff: 50 * time.Millisecond,
		MaxBackoff:     time.Second * 5,
		MaxRetries:     15,
		SkipTLS:        false,
		client: &http.Client{
			Timeout: time.Duration(2) * time.Second,
		},
	}

	return hc
}

func ExpBackoff(interval, max time.Duration) time.Duration {
	interval = interval * 2
	if interval > max {
		interval = max
	}
	return interval
}

// GetRetry fetches a given URL with support for exponential backoff and maximum retries
func (h *HttpClient) GetRetry(rawurl string) ([]byte, error) {
	if rawurl == "" {
		return nil, ErrInvalid{errors.New("URL is empty. Skipping.")}
	}

	url, err := neturl.Parse(rawurl)
	if err != nil {
		return nil, ErrInvalid{err}
	}

	// Unfortunately, url.Parse is too generic to throw errors if a URL does not
	// have a valid HTTP scheme. So, we have to do this extra validation
	if !strings.HasPrefix(url.Scheme, "http") {
		return nil, ErrInvalid{fmt.Errorf("URL %s does not have a valid HTTP scheme. Skipping.", rawurl)}
	}

	dataURL := url.String()

	duration := h.InitialBackoff
	for retry := 1; retry <= h.MaxRetries; retry++ {
		log.Printf("Fetching data from %s. Attempt #%d", dataURL, retry)

		data, err := h.Get(dataURL)
		switch err.(type) {
		case ErrNetwork:
			log.Printf(err.Error())
		case ErrServer:
			log.Printf(err.Error())
		case ErrNotFound:
			return data, err
		default:
			return data, err
		}

		duration = ExpBackoff(duration, h.MaxBackoff)
		log.Printf("Sleeping for %v...", duration)
		time.Sleep(duration)
	}

	return nil, ErrTimeout{fmt.Errorf("Unable to fetch data. Maximum retries reached: %d", h.MaxRetries)}
}

func (h *HttpClient) Get(dataURL string) ([]byte, error) {
	if resp, err := h.client.Get(dataURL); err == nil {
		defer resp.Body.Close()
		switch resp.StatusCode / 100 {
		case HTTP_2xx:
			return ioutil.ReadAll(resp.Body)
		case HTTP_4xx:
			return nil, ErrNotFound{fmt.Errorf("Not found. HTTP status code: %d", resp.StatusCode)}
		default:
			return nil, ErrServer{fmt.Errorf("Server error. HTTP status code: %d", resp.StatusCode)}
		}
	} else {
		return nil, ErrNetwork{fmt.Errorf("Unable to fetch data: %s", err.Error())}
	}
}
<commit_msg>pkg/http: up the timeout to 10 seconds<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pkg

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	neturl "net/url"
	"strings"
	"time"
)

const (
	HTTP_2xx = 2
	HTTP_4xx = 4
)

type Err error

type ErrTimeout struct {
	Err
}

type ErrNotFound struct {
	Err
}

type ErrInvalid struct {
	Err
}

type ErrServer struct {
	Err
}

type ErrNetwork struct {
	Err
}

type HttpClient struct {
	// Initial backoff duration. Defaults to 50 milliseconds
	InitialBackoff time.Duration

	// Maximum exp backoff duration. Defaults to 5 seconds
	MaxBackoff time.Duration

	// Maximum number of connection retries. Defaults to 15
	MaxRetries int

	// Whether or not to skip TLS verification. Defaults to false
	SkipTLS bool

	client *http.Client
}

type Getter interface {
	Get(string) ([]byte, error)
	GetRetry(string) ([]byte, error)
}

func NewHttpClient() *HttpClient {
	hc := &HttpClient{
		InitialBackoff: 50 * time.Millisecond,
		MaxBackoff:     time.Second * 5,
		MaxRetries:     15,
		SkipTLS:        false,
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
	}

	return hc
}

func ExpBackoff(interval, max time.Duration) time.Duration {
	interval = interval * 2
	if interval > max {
		interval = max
	}
	return interval
}

// GetRetry fetches a given URL with support for exponential backoff and maximum retries
func (h *HttpClient) GetRetry(rawurl string) ([]byte, error) {
	if rawurl == "" {
		return nil, ErrInvalid{errors.New("URL is empty. Skipping.")}
	}

	url, err := neturl.Parse(rawurl)
	if err != nil {
		return nil, ErrInvalid{err}
	}

	// Unfortunately, url.Parse is too generic to throw errors if a URL does not
	// have a valid HTTP scheme. So, we have to do this extra validation
	if !strings.HasPrefix(url.Scheme, "http") {
		return nil, ErrInvalid{fmt.Errorf("URL %s does not have a valid HTTP scheme. Skipping.", rawurl)}
	}

	dataURL := url.String()

	duration := h.InitialBackoff
	for retry := 1; retry <= h.MaxRetries; retry++ {
		log.Printf("Fetching data from %s. Attempt #%d", dataURL, retry)

		data, err := h.Get(dataURL)
		switch err.(type) {
		case ErrNetwork:
			log.Printf(err.Error())
		case ErrServer:
			log.Printf(err.Error())
		case ErrNotFound:
			return data, err
		default:
			return data, err
		}

		duration = ExpBackoff(duration, h.MaxBackoff)
		log.Printf("Sleeping for %v...", duration)
		time.Sleep(duration)
	}

	return nil, ErrTimeout{fmt.Errorf("Unable to fetch data. Maximum retries reached: %d", h.MaxRetries)}
}

func (h *HttpClient) Get(dataURL string) ([]byte, error) {
	if resp, err := h.client.Get(dataURL); err == nil {
		defer resp.Body.Close()
		switch resp.StatusCode / 100 {
		case HTTP_2xx:
			return ioutil.ReadAll(resp.Body)
		case HTTP_4xx:
			return nil, ErrNotFound{fmt.Errorf("Not found. HTTP status code: %d", resp.StatusCode)}
		default:
			return nil, ErrServer{fmt.Errorf("Server error. HTTP status code: %d", resp.StatusCode)}
		}
	} else {
		return nil, ErrNetwork{fmt.Errorf("Unable to fetch data: %s", err.Error())}
	}
}
<|endoftext|>


<commit_before>package msg

import (
	"bytes"
	"encoding/json"
	"fmt"
	"unicode"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/convert"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/balanceinfo"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/book"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/candle"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/event"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingcredit"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingloan"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingoffer"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/order"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/position"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/status"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/ticker"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/trades"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/wallet"
)

type Msg struct {
	Data     []byte
	Err      error
	CID      int
	IsPublic bool
}

func (m Msg) IsEvent() bool {
	t := bytes.TrimLeftFunc(m.Data, unicode.IsSpace)
	return bytes.HasPrefix(t, []byte("{"))
}

func (m Msg) IsRaw() bool {
	t := bytes.TrimLeftFunc(m.Data, unicode.IsSpace)
	return bytes.HasPrefix(t, []byte("["))
}

func (m Msg) ProcessRaw(chanInfo map[int64]event.Info) (interface{}, error) {
	var raw []interface{}
	if err := json.Unmarshal(m.Data, &raw); err != nil {
		return nil, fmt.Errorf("parsing msg: %s, err: %s", m.Data, err)
	}
	// payload data is always last element of the slice
	pld := raw[len(raw)-1]
	// chanID is always 1st element of the slice
	chID := convert.I64ValOrZero(raw[0])
	// allocate channel name by id to know how to transform raw data
	inf, ok := chanInfo[chID]
	if !ok {
		return nil, fmt.Errorf("unrecognized chanId:%d", chID)
	}

	switch data := pld.(type) {
	case string:
		return event.Info{
			ChanID:    chID,
			Subscribe: event.Subscribe{Event: data},
		}, nil
	case []interface{}:
		switch inf.Channel {
		case "trades":
			return trades.FromWSRaw(inf.Symbol, raw, data)
		case "ticker":
			return ticker.FromWSRaw(inf.Symbol, data)
		case "book":
			return book.FromWSRaw(inf.Symbol, inf.Precision, data)
		case "candles":
			return candle.FromWSRaw(inf.Key, data)
		case "status":
			return status.FromWSRaw(inf.Key, data)
		}
	}

	return raw, nil
}

func (m Msg) ProcessPrivateRaw() (interface{}, error) {
	var raw []interface{}
	if err := json.Unmarshal(m.Data, &raw); err != nil {
		return nil, fmt.Errorf("parsing auth msg: %s, err: %s", m.Data, err)
	}
	// payload data is always last element of the slice
	pld := raw[len(raw)-1]
	// op name is 2nd element
	op := convert.SValOrEmpty(raw[1])

	switch data := pld.(type) {
	case string:
		return event.Info{
			ChanID:    convert.I64ValOrZero(raw[0]),
			Subscribe: event.Subscribe{Event: data},
		}, nil
	case []interface{}:
		switch op {
		case "bu":
			return balanceinfo.UpdateFromRaw(data)
		case "ps":
			return position.SnapshotFromRaw(data)
		case "pn":
			return position.NewFromRaw(data)
		case "pu":
			return position.UpdateFromRaw(data)
		case "pc":
			return position.CancelFromRaw(data)
		case "ws":
			return wallet.SnapshotFromRaw(data, wallet.FromWsRaw)
		case "wu":
			return wallet.UpdateFromRaw(data)
		case "os":
			return order.SnapshotFromRaw(data)
		case "on":
			return order.NewFromRaw(data)
		case "on-req":
			// TODO
		case "ou":
			return order.UpdateFromRaw(data)
		case "oc":
			return order.CancelFromRaw(data)
		case "oc-req":
			// TODO
		case "oc_multi-req":
			// TODO
		case "te":
			// TODO
		case "tu":
			// TODO
		case "fte":
			return trades.AFTEFromRaw(data)
		case "ftu":
			return trades.AFTUFromRaw(data)
		case "mis":
			// TODO
		case "miu":
			// TODO
		case "n":
			// TODO
		case "fos":
			return fundingoffer.SnapshotFromRaw(data)
		case "fon":
			return fundingoffer.NewFromRaw(data)
		case "fou":
			return fundingoffer.UpdateFromRaw(data)
		case "foc":
			return fundingoffer.CancelFromRaw(data)
		case "fcs":
			return fundingcredit.SnapshotFromRaw(data)
		case "fcn":
			return fundingcredit.NewFromRaw(data)
		case "fcu":
			return fundingcredit.UpdateFromRaw(data)
		case "fcc":
			return fundingcredit.CancelFromRaw(data)
		case "fls":
			return fundingloan.SnapshotFromRaw(data)
		case "fln":
			return fundingloan.NewFromRaw(data)
		case "flu":
			return fundingloan.UpdateFromRaw(data)
		case "flc":
			return fundingloan.CancelFromRaw(data)
		case "hfts":
			// TODO
		case "uac":
			// TODO
		}
	}

	return raw, nil
}

func (m Msg) ProcessEvent() (i event.Info, err error) {
	if err = json.Unmarshal(m.Data, &i); err != nil {
		return i, fmt.Errorf("parsing msg: %s, err: %s", m.Data, err)
	}
	return
}
<commit_msg>adjusting mux msg for wallet implementation to work<commit_after>package msg

import (
	"bytes"
	"encoding/json"
	"fmt"
	"unicode"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/convert"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/balanceinfo"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/book"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/candle"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/event"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingcredit"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingloan"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingoffer"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/order"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/position"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/status"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/ticker"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/trades"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/wallet"
)

type Msg struct {
	Data     []byte
	Err      error
	CID      int
	IsPublic bool
}

func (m Msg) IsEvent() bool {
	t := bytes.TrimLeftFunc(m.Data, unicode.IsSpace)
	return bytes.HasPrefix(t, []byte("{"))
}

func (m Msg) IsRaw() bool {
	t := bytes.TrimLeftFunc(m.Data, unicode.IsSpace)
	return bytes.HasPrefix(t, []byte("["))
}

func (m Msg) ProcessRaw(chanInfo map[int64]event.Info) (interface{}, error) {
	var raw []interface{}
	if err := json.Unmarshal(m.Data, &raw); err != nil {
		return nil, fmt.Errorf("parsing msg: %s, err: %s", m.Data, err)
	}
	// payload data is always last element of the slice
	pld := raw[len(raw)-1]
	// chanID is always 1st element of the slice
	chID := convert.I64ValOrZero(raw[0])
	// allocate channel name by id to know how to transform raw data
	inf, ok := chanInfo[chID]
	if !ok {
		return nil, fmt.Errorf("unrecognized chanId:%d", chID)
	}

	switch data := pld.(type) {
	case string:
		return event.Info{
			ChanID:    chID,
			Subscribe: event.Subscribe{Event: data},
		}, nil
	case []interface{}:
		switch inf.Channel {
		case "trades":
			return trades.FromWSRaw(inf.Symbol, raw, data)
		case "ticker":
			return ticker.FromWSRaw(inf.Symbol, data)
		case "book":
			return book.FromWSRaw(inf.Symbol, inf.Precision, data)
		case "candles":
			return candle.FromWSRaw(inf.Key, data)
		case "status":
			return status.FromWSRaw(inf.Key, data)
		}
	}

	return raw, nil
}

func (m Msg) ProcessPrivateRaw() (interface{}, error) {
	var raw []interface{}
	if err := json.Unmarshal(m.Data, &raw); err != nil {
		return nil, fmt.Errorf("parsing auth msg: %s, err: %s", m.Data, err)
	}
	// payload data is always last element of the slice
	pld := raw[len(raw)-1]
	// op name is 2nd element
	op := convert.SValOrEmpty(raw[1])

	switch data := pld.(type) {
	case string:
		return event.Info{
			ChanID:    convert.I64ValOrZero(raw[0]),
			Subscribe: event.Subscribe{Event: data},
		}, nil
	case []interface{}:
		switch op {
		case "bu":
			return balanceinfo.UpdateFromRaw(data)
		case "ps":
			return position.SnapshotFromRaw(data)
		case "pn":
			return position.NewFromRaw(data)
		case "pu":
			return position.UpdateFromRaw(data)
		case "pc":
			return position.CancelFromRaw(data)
		case "ws":
			return wallet.SnapshotFromRaw(data)
		case "wu":
			return wallet.UpdateFromRaw(data)
		case "os":
			return order.SnapshotFromRaw(data)
		case "on":
			return order.NewFromRaw(data)
		case "on-req":
			// TODO
		case "ou":
			return order.UpdateFromRaw(data)
		case "oc":
			return order.CancelFromRaw(data)
		case "oc-req":
			// TODO
		case "oc_multi-req":
			// TODO
		case "te":
			// TODO
		case "tu":
			// TODO
		case "fte":
			return trades.AFTEFromRaw(data)
		case "ftu":
			return trades.AFTUFromRaw(data)
		case "mis":
			// TODO
		case "miu":
			// TODO
		case "n":
			// TODO
		case "fos":
			return fundingoffer.SnapshotFromRaw(data)
		case "fon":
			return fundingoffer.NewFromRaw(data)
		case "fou":
			return fundingoffer.UpdateFromRaw(data)
		case "foc":
			return fundingoffer.CancelFromRaw(data)
		case "fcs":
			return fundingcredit.SnapshotFromRaw(data)
		case "fcn":
			return fundingcredit.NewFromRaw(data)
		case "fcu":
			return fundingcredit.UpdateFromRaw(data)
		case "fcc":
			return fundingcredit.CancelFromRaw(data)
		case "fls":
			return fundingloan.SnapshotFromRaw(data)
		case "fln":
			return fundingloan.NewFromRaw(data)
		case "flu":
			return fundingloan.UpdateFromRaw(data)
		case "flc":
			return fundingloan.CancelFromRaw(data)
		case "hfts":
			// TODO
		case "uac":
			// TODO
		}
	}

	return raw, nil
}

func (m Msg) ProcessEvent() (i event.Info, err error) {
	if err = json.Unmarshal(m.Data, &i); err != nil {
		return i, fmt.Errorf("parsing msg: %s, err: %s", m.Data, err)
	}
	return
}
<|endoftext|>


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

package plans

import (
	"fmt"
	"sort"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/column"
	"github.com/pingcap/tidb/context"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/field"
	"github.com/pingcap/tidb/model"
	mysql "github.com/pingcap/tidb/mysqldef"
	"github.com/pingcap/tidb/plan"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/sessionctx/variable"
	"github.com/pingcap/tidb/stmt"
	"github.com/pingcap/tidb/util/charset"
	"github.com/pingcap/tidb/util/format"
)

var (
	_ plan.Plan = (*ShowPlan)(nil)
)

// ShowPlan is used for show statements
type ShowPlan struct {
	Target     int
	DBName     string
	TableName  string
	ColumnName string
	Flag       int
	Full       bool
	// Used by SHOW VARIABLES
	GlobalScope bool
	Pattern     *expression.PatternLike
	Where       expression.Expression
	rows        []*plan.Row
	cursor      int
}

func (s *ShowPlan) isColOK(c *column.Col) bool {
	// support `desc tableName columnName`
	// TODO: columnName can be a regular
	if s.ColumnName == "" {
		return true
	}

	if strings.EqualFold(s.ColumnName, c.Name.L) {
		return true
	}

	return false
}

// Explain implements plan.Plan Explain interface.
func (s *ShowPlan) Explain(w format.Formatter) {
	// TODO: finish this
}

// GetFields implements plan.Plan GetFields interface.
func (s *ShowPlan) GetFields() []*field.ResultField {
	var (
		names []string
		types []byte
	)

	switch s.Target {
	case stmt.ShowEngines:
		names = []string{"Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"}
	case stmt.ShowDatabases:
		names = []string{"Database"}
	case stmt.ShowTables:
		names = []string{fmt.Sprintf("Tables_in_%s", s.DBName)}
		if s.Full {
			names = append(names, "Table_type")
		}
	case stmt.ShowColumns:
		names = column.ColDescFieldNames(s.Full)
	case stmt.ShowWarnings:
		names = []string{"Level", "Code", "Message"}
		types = []byte{mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar}
	case stmt.ShowCharset:
		names = []string{"Charset", "Description", "Default collation", "Maxlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	case stmt.ShowVariables:
		names = []string{"Variable_name", "Value"}
	case stmt.ShowCollation:
		names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong,
			mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	}
	fields := make([]*field.ResultField, 0, len(names))
	for i, name := range names {
		f := &field.ResultField{Name: name}
		if types == nil || types[i] == 0 {
			// use varchar as the default return column type
			f.Col.Tp = mysql.TypeVarchar
		} else {
			f.Col.Tp = types[i]
		}

		fields = append(fields, f)
	}

	return fields
}

// Filter implements plan.Plan Filter interface.
func (s *ShowPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) {
	return s, false, nil
}

// Next implements plan.Plan Next interface.
func (s *ShowPlan) Next(ctx context.Context) (row *plan.Row, err error) {
	if s.rows == nil {
		s.fetchAll(ctx)
	}
	if s.cursor == len(s.rows) {
		return
	}
	row = s.rows[s.cursor]
	s.cursor++
	return
}

func (s *ShowPlan) fetchAll(ctx context.Context) error {
	// TODO split this function
	switch s.Target {
	case stmt.ShowEngines:
		row := &plan.Row{
			Data: []interface{}{"InnoDB", "DEFAULT", "Supports transactions, row-level locking, and foreign keys", "YES", "YES", "YES"},
		}
		s.rows = append(s.rows, row)
	case stmt.ShowDatabases:
		dbs := sessionctx.GetDomain(ctx).InfoSchema().AllSchemaNames()

		// TODO: let information_schema be the first database
		sort.Strings(dbs)

		for _, d := range dbs {
			s.rows = append(s.rows, &plan.Row{Data: []interface{}{d}})
		}
	case stmt.ShowTables:
		is := sessionctx.GetDomain(ctx).InfoSchema()
		dbName := model.NewCIStr(s.DBName)
		if !is.SchemaExists(dbName) {
			return errors.Errorf("Can not find DB: %s", dbName)
		}

		// sort for tables
		var tableNames []string
		for _, v := range is.SchemaTables(dbName) {
			tableNames = append(tableNames, v.TableName().L)
		}

		sort.Strings(tableNames)

		for _, v := range tableNames {
			data := []interface{}{v}
			if s.Full {
				// TODO: support "VIEW" later if we have supported view feature.
				// now, just use "BASE TABLE".
				data = append(data, "BASE TABLE")
			}
			s.rows = append(s.rows, &plan.Row{Data: data})
		}
	case stmt.ShowColumns:
		is := sessionctx.GetDomain(ctx).InfoSchema()
		dbName := model.NewCIStr(s.DBName)
		if !is.SchemaExists(dbName) {
			return errors.Errorf("Can not find DB: %s", dbName)
		}
		tbName := model.NewCIStr(s.TableName)
		tb, err := is.TableByName(dbName, tbName)
		if err != nil {
			return errors.Errorf("Can not find table: %s", s.TableName)
		}
		cols := tb.Cols()

		for _, col := range cols {
			if !s.isColOK(col) {
				continue
			}

			desc := column.NewColDesc(col)

			// The FULL keyword causes the output to include the column collation and comments,
			// as well as the privileges you have for each column.
			row := &plan.Row{}
			if s.Full {
				row.Data = []interface{}{
					desc.Field,
					desc.Type,
					desc.Collation,
					desc.Null,
					desc.Key,
					desc.DefaultValue,
					desc.Extra,
					desc.Privileges,
					desc.Comment,
				}
			} else {
				row.Data = []interface{}{
					desc.Field,
					desc.Type,
					desc.Null,
					desc.Key,
					desc.DefaultValue,
					desc.Extra,
				}
			}
			s.rows = append(s.rows, row)
		}
	case stmt.ShowWarnings:
	// empty result
	case stmt.ShowCharset:
		// See: http://dev.mysql.com/doc/refman/5.7/en/show-character-set.html
		descs := charset.GetAllCharsets()
		for _, desc := range descs {
			row := &plan.Row{
				Data: []interface{}{desc.Name, desc.Desc, desc.DefaultCollation, desc.Maxlen},
			}
			s.rows = append(s.rows, row)
		}
	case stmt.ShowVariables:
		sessionVars := variable.GetSessionVars(ctx)
		for _, v := range variable.SysVars {
			if s.Pattern != nil {
				s.Pattern.Expr = expression.Value{Val: v.Name}
				r, err := s.Pattern.Eval(ctx, nil)
				if err != nil {
					return errors.Trace(err)
				}
				match, ok := r.(bool)
				if !ok {
					return errors.Errorf("Eval like pattern error")
				}
				if !match {
					continue
				}
			} else if s.Where != nil {
				m := map[interface{}]interface{}{}

				m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
					if strings.EqualFold(name, "Variable_name") {
						return v.Name, nil
					}

					return nil, errors.Errorf("unknown field %s", name)
				}

				match, err := expression.EvalBoolExpr(ctx, s.Where, m)
				if err != nil {
					return errors.Trace(err)
				}
				if !match {
					continue
				}
			}
			value := v.Value
			if !s.GlobalScope {
				// Try to get Session Scope variable value
				sv, ok := sessionVars.Systems[v.Name]
				if ok {
					value = sv
				}
			}
			row := &plan.Row{Data: []interface{}{v.Name, value}}
			s.rows = append(s.rows, row)
		}
	case stmt.ShowCollation:
		collations := charset.GetCollations()
		for _, v := range collations {
			if s.Pattern != nil {
				s.Pattern.Expr = expression.Value{Val: v.Name}
				r, err := s.Pattern.Eval(ctx, nil)
				if err != nil {
					return errors.Trace(err)
				}
				match, ok := r.(bool)
				if !ok {
					return errors.Errorf("Eval like pattern error")
				}
				if !match {
					continue
				}
			} else if s.Where != nil {
				m := map[interface{}]interface{}{}

				m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
					switch {
					case strings.EqualFold(name, "Collation"):
						return v.Name, nil
					case strings.EqualFold(name, "Charset"):
						return v.CharsetName, nil
					case strings.EqualFold(name, "Id"):
						return v.ID, nil
					case strings.EqualFold(name, "Default"):
						if v.IsDefault {
							return "Yes", nil
						}
						return "", nil
					case strings.EqualFold(name, "Compiled"):
						return "Yes", nil
					case strings.EqualFold(name, "Sortlen"):
						// TODO: add sort length in Collation
						return 1, nil
					default:
						return nil, errors.Errorf("unknown field %s", name)
					}
				}

				match, err := expression.EvalBoolExpr(ctx, s.Where, m)
				if err != nil {
					return errors.Trace(err)
				}
				if !match {
					continue
				}
			}

			isDefault := ""
			if v.IsDefault {
				isDefault = "Yes"
			}
			row := &plan.Row{Data: []interface{}{v.Name, v.CharsetName, v.ID, isDefault, "Yes", 1}}
			s.rows = append(s.rows, row)
		}
	}
	return nil
}

// Close implements plan.Plan Close interface.
func (s *ShowPlan) Close() error {
	s.rows = nil
	s.cursor = 0
	return nil
}
<commit_msg>plans: split show into different functions<commit_after>// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package plans

import (
	"fmt"
	"sort"
	"strings"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/column"
	"github.com/pingcap/tidb/context"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/field"
	"github.com/pingcap/tidb/model"
	mysql "github.com/pingcap/tidb/mysqldef"
	"github.com/pingcap/tidb/plan"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/sessionctx/variable"
	"github.com/pingcap/tidb/stmt"
	"github.com/pingcap/tidb/util/charset"
	"github.com/pingcap/tidb/util/format"
)

var (
	_ plan.Plan = (*ShowPlan)(nil)
)

// ShowPlan is used for show statements
type ShowPlan struct {
	Target     int
	DBName     string
	TableName  string
	ColumnName string
	Flag       int
	Full       bool
	// Used by SHOW VARIABLES
	GlobalScope bool
	Pattern     *expression.PatternLike
	Where       expression.Expression
	rows        []*plan.Row
	cursor      int
}

func (s *ShowPlan) isColOK(c *column.Col) bool {
	// support `desc tableName columnName`
	// TODO: columnName can be a regular
	if s.ColumnName == "" {
		return true
	}

	if strings.EqualFold(s.ColumnName, c.Name.L) {
		return true
	}

	return false
}

// Explain implements plan.Plan Explain interface.
func (s *ShowPlan) Explain(w format.Formatter) {
	// TODO: finish this
}

// GetFields implements plan.Plan GetFields interface.
func (s *ShowPlan) GetFields() []*field.ResultField {
	var (
		names []string
		types []byte
	)

	switch s.Target {
	case stmt.ShowEngines:
		names = []string{"Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"}
	case stmt.ShowDatabases:
		names = []string{"Database"}
	case stmt.ShowTables:
		names = []string{fmt.Sprintf("Tables_in_%s", s.DBName)}
		if s.Full {
			names = append(names, "Table_type")
		}
	case stmt.ShowColumns:
		names = column.ColDescFieldNames(s.Full)
	case stmt.ShowWarnings:
		names = []string{"Level", "Code", "Message"}
		types = []byte{mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar}
	case stmt.ShowCharset:
		names = []string{"Charset", "Description", "Default collation", "Maxlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	case stmt.ShowVariables:
		names = []string{"Variable_name", "Value"}
	case stmt.ShowCollation:
		names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
		types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong,
			mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
	}
	fields := make([]*field.ResultField, 0, len(names))
	for i, name := range names {
		f := &field.ResultField{Name: name}
		if types == nil || types[i] == 0 {
			// use varchar as the default return column type
			f.Col.Tp = mysql.TypeVarchar
		} else {
			f.Col.Tp = types[i]
		}

		fields = append(fields, f)
	}

	return fields
}

// Filter implements plan.Plan Filter interface.
func (s *ShowPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) {
	return s, false, nil
}

// Next implements plan.Plan Next interface.
func (s *ShowPlan) Next(ctx context.Context) (row *plan.Row, err error) {
	if s.rows == nil {
		s.fetchAll(ctx)
	}
	if s.cursor == len(s.rows) {
		return
	}
	row = s.rows[s.cursor]
	s.cursor++
	return
}

func (s *ShowPlan) fetchAll(ctx context.Context) error {
	switch s.Target {
	case stmt.ShowEngines:
		return s.fetchShowEngines(ctx)
	case stmt.ShowDatabases:
		return s.fetchShowDatabases(ctx)
	case stmt.ShowTables:
		return s.fetchShowTables(ctx)
	case stmt.ShowColumns:
		return s.fetchShowColumns(ctx)
	case stmt.ShowWarnings:
		// empty result
	case stmt.ShowCharset:
		return s.fetchShowCharset(ctx)
	case stmt.ShowVariables:
		return s.fetchShowVariables(ctx)
	case stmt.ShowCollation:
		return s.fetchShowCollation(ctx)
	}
	return nil
}

// Close implements plan.Plan Close interface.
func (s *ShowPlan) Close() error {
	s.rows = nil
	s.cursor = 0
	return nil
}

func (s *ShowPlan) evalCondition(ctx context.Context, m map[interface{}]interface{}) (bool, error) {
	var cond expression.Expression
	if s.Pattern != nil {
		cond = s.Pattern
	} else if s.Where != nil {
		cond = s.Where
	}

	if cond == nil {
		return true, nil
	}

	return expression.EvalBoolExpr(ctx, cond, m)
}

func (s *ShowPlan) fetchShowColumns(ctx context.Context) error {
	is := sessionctx.GetDomain(ctx).InfoSchema()
	dbName := model.NewCIStr(s.DBName)
	if !is.SchemaExists(dbName) {
		return errors.Errorf("Can not find DB: %s", dbName)
	}
	tbName := model.NewCIStr(s.TableName)
	tb, err := is.TableByName(dbName, tbName)
	if err != nil {
		return errors.Errorf("Can not find table: %s", s.TableName)
	}
	cols := tb.Cols()

	for _, col := range cols {
		if !s.isColOK(col) {
			continue
		}

		desc := column.NewColDesc(col)

		// The FULL keyword causes the output to include the column collation and comments,
		// as well as the privileges you have for each column.
		row := &plan.Row{}
		if s.Full {
			row.Data = []interface{}{
				desc.Field,
				desc.Type,
				desc.Collation,
				desc.Null,
				desc.Key,
				desc.DefaultValue,
				desc.Extra,
				desc.Privileges,
				desc.Comment,
			}
		} else {
			row.Data = []interface{}{
				desc.Field,
				desc.Type,
				desc.Null,
				desc.Key,
				desc.DefaultValue,
				desc.Extra,
			}
		}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowCollation(ctx context.Context) error {
	collations := charset.GetCollations()
	m := map[interface{}]interface{}{}

	for _, v := range collations {
		if s.Pattern != nil {
			s.Pattern.Expr = expression.Value{Val: v.Name}
		} else if s.Where != nil {
			m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
				switch {
				case strings.EqualFold(name, "Collation"):
					return v.Name, nil
				case strings.EqualFold(name, "Charset"):
					return v.CharsetName, nil
				case strings.EqualFold(name, "Id"):
					return v.ID, nil
				case strings.EqualFold(name, "Default"):
					if v.IsDefault {
						return "Yes", nil
					}
					return "", nil
				case strings.EqualFold(name, "Compiled"):
					return "Yes", nil
				case strings.EqualFold(name, "Sortlen"):
					// TODO: add sort length in Collation
					return 1, nil
				default:
					return nil, errors.Errorf("unknown field %s", name)
				}
			}
		}

		match, err := s.evalCondition(ctx, m)
		if err != nil {
			return errors.Trace(err)
		}
		if !match {
			continue
		}

		isDefault := ""
		if v.IsDefault {
			isDefault = "Yes"
		}
		row := &plan.Row{Data: []interface{}{v.Name, v.CharsetName, v.ID, isDefault, "Yes", 1}}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowTables(ctx context.Context) error {
	is := sessionctx.GetDomain(ctx).InfoSchema()
	dbName := model.NewCIStr(s.DBName)
	if !is.SchemaExists(dbName) {
		return errors.Errorf("Can not find DB: %s", dbName)
	}

	// sort for tables
	var tableNames []string
	for _, v := range is.SchemaTables(dbName) {
		tableNames = append(tableNames, v.TableName().L)
	}

	sort.Strings(tableNames)

	for _, v := range tableNames {
		data := []interface{}{v}
		if s.Full {
			// TODO: support "VIEW" later if we have supported view feature.
			// now, just use "BASE TABLE".
			data = append(data, "BASE TABLE")
		}
		s.rows = append(s.rows, &plan.Row{Data: data})
	}
	return nil
}

func (s *ShowPlan) fetchShowVariables(ctx context.Context) error {
	sessionVars := variable.GetSessionVars(ctx)
	m := map[interface{}]interface{}{}

	for _, v := range variable.SysVars {
		if s.Pattern != nil {
			s.Pattern.Expr = expression.Value{Val: v.Name}
		} else if s.Where != nil {
			m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
				if strings.EqualFold(name, "Variable_name") {
					return v.Name, nil
				}

				return nil, errors.Errorf("unknown field %s", name)
			}
		}

		match, err := s.evalCondition(ctx, m)
		if err != nil {
			return errors.Trace(err)
		}
		if !match {
			continue
		}

		value := v.Value
		if !s.GlobalScope {
			// Try to get Session Scope variable value
			sv, ok := sessionVars.Systems[v.Name]
			if ok {
				value = sv
			}
		}
		row := &plan.Row{Data: []interface{}{v.Name, value}}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowCharset(ctx context.Context) error {
	// See: http://dev.mysql.com/doc/refman/5.7/en/show-character-set.html
	descs := charset.GetAllCharsets()
	for _, desc := range descs {
		row := &plan.Row{
			Data: []interface{}{desc.Name, desc.Desc, desc.DefaultCollation, desc.Maxlen},
		}
		s.rows = append(s.rows, row)
	}
	return nil
}

func (s *ShowPlan) fetchShowEngines(ctx context.Context) error {
	row := &plan.Row{
		Data: []interface{}{"InnoDB", "DEFAULT", "Supports transactions, row-level locking, and foreign keys", "YES", "YES", "YES"},
	}
	s.rows = append(s.rows, row)
	return nil
}

func (s *ShowPlan) fetchShowDatabases(ctx context.Context) error {
	dbs := sessionctx.GetDomain(ctx).InfoSchema().AllSchemaNames()

	// TODO: let information_schema be the first database
	sort.Strings(dbs)

	for _, d := range dbs {
		s.rows = append(s.rows, &plan.Row{Data: []interface{}{d}})
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"runtime"

	"github.com/tanema/amore"
	"github.com/tanema/amore/gfx"
	"github.com/tanema/amore/keyboard"
	"github.com/tanema/amore/timer"

	"github.com/tanema/amore-examples/platformer/game"
	"github.com/tanema/amore-examples/platformer/lense"
)

var (
	width   float32 = 4000
	height  float32 = 2000
	camera  *lense.Camera
	gameMap *game.Map
)

func main() {
	amore.OnLoad = onLoad
	amore.Start(update, draw)
}

func onLoad() {
	keyboard.OnKeyUp = keypress
	camera = lense.New()
	gameMap = game.NewMap(width, height, camera)
}

func update(dt float32) {
	l, t, w, h := camera.GetVisible()
	gameMap.Update(dt, l, t, w, h)
	camera.LookAt(gameMap.Player.GetCenter())
	camera.Update(dt)
}

func draw() {
	camera.Draw(gameMap.Draw)
	gfx.SetColor(255, 255, 255, 255)
	w, h := gfx.GetWidth(), gfx.GetHeight()
	stats := runtime.MemStats{}
	runtime.ReadMemStats(&stats)
	gfx.Printf(fmt.Sprintf("fps: %v, mem: %vKB", timer.GetFPS(), stats.HeapAlloc/1000), 200, gfx.ALIGN_RIGHT, w-200, h-40)
}

func keypress(key keyboard.Key) {
	switch key {
	case keyboard.KeyEscape:
		amore.Quit()
	case keyboard.KeyTab:
		gameMap.ToggleDebug()
	case keyboard.KeyReturn:
		gameMap.Reset()
	}
}
<commit_msg>Better memory tracking<commit_after>package main

import (
	"fmt"
	"runtime"

	"github.com/tanema/amore"
	"github.com/tanema/amore/gfx"
	"github.com/tanema/amore/keyboard"
	"github.com/tanema/amore/timer"

	"github.com/tanema/amore-examples/platformer/game"
	"github.com/tanema/amore-examples/platformer/lense"
)

var (
	width   float32 = 4000
	height  float32 = 2000
	camera  *lense.Camera
	gameMap *game.Map
)

func main() {
	amore.OnLoad = onLoad
	amore.Start(update, draw)
}

func onLoad() {
	keyboard.OnKeyUp = keypress
	camera = lense.New()
	gameMap = game.NewMap(width, height, camera)
}

func update(dt float32) {
	l, t, w, h := camera.GetVisible()
	gameMap.Update(dt, l, t, w, h)
	camera.LookAt(gameMap.Player.GetCenter())
	camera.Update(dt)
}

func draw() {
	camera.Draw(gameMap.Draw)
	gfx.SetColor(255, 255, 255, 255)
	w, h := gfx.GetWidth(), gfx.GetHeight()
	stats := runtime.MemStats{}
	runtime.ReadMemStats(&stats)
	gfx.Printf(fmt.Sprintf("fps: %v, mem: %vKB", timer.GetFPS(), stats.HeapAlloc/1000000), 200, gfx.ALIGN_RIGHT, w-200, h-40)
}

func keypress(key keyboard.Key) {
	switch key {
	case keyboard.KeyEscape:
		amore.Quit()
	case keyboard.KeyTab:
		gameMap.ToggleDebug()
	case keyboard.KeyReturn:
		gameMap.Reset()
	}
}
<|endoftext|>


<commit_before>package plugins

import (
	"../../seabird"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/thoj/go-ircevent"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

var country_replacements = map[string]string{
	"US": "USA",
	"United States of America": "USA",
}

type WeatherDay struct {
	Name string `json:"name"`
	Main struct {
		Temp    float32 `json:"temp"`
		TempMin float32 `json:"temp_min"`
		TempMax float32 `json:"temp_max"`
	} `json:"main"`
	Temp struct {
		Min float32 `json:"min"`
		Max float32 `json:"max"`
	} `json:"temp"`
	Sys struct {
		Country string `json:"country"`
	} `json:"sys"`
	Weather []*struct {
		Description string `json:"description"`
	} `json:"weather"`
}

type WeatherResponse struct {
	City struct {
		Name    string `json:"name"`
		Country string `json:"country"`
	} `json:"city"`
	List []*WeatherDay
}

func init() {
	seabird.RegisterPlugin("weather", NewWeatherPlugin)
}

type WeatherPlugin struct {
	Bot *seabird.Bot
}

func NewWeatherPlugin(b *seabird.Bot, c json.RawMessage) {
	p := &WeatherPlugin{b}
	b.RegisterFunction("forecast", p.Forecast)
	b.RegisterFunction("weather", p.Weather)
}

func (p *WeatherPlugin) processDay(d *WeatherDay) error {
	if len(d.Weather) < 1 {
		return errors.New("invalid api response")
	}

	if replacement, ok := country_replacements[d.Sys.Country]; ok {
		d.Sys.Country = replacement
	}

	return nil
}

func (p *WeatherPlugin) weather(loc string) (*WeatherDay, error) {
	var query string = strings.TrimSpace(loc)
	if _, err := strconv.Atoi(query); err == nil {
		// It's a number - append ,USA
		query = query + ",USA"
	}

	resp, err := http.Get(fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?units=imperial&q=%s", url.QueryEscape(query)))
	if err != nil {
		return nil, errors.New("network error")
	}

	weather := WeatherDay{}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&weather)
	if err != nil {
		return nil, errors.New("invalid api response")
	}

	if err := p.processDay(&weather); err != nil {
		return nil, err
	}

	return &weather, nil
}

func (p *WeatherPlugin) forecast(loc string, count int) (*WeatherResponse, error) {
	var query string = strings.TrimSpace(loc)
	if _, err := strconv.Atoi(query); err == nil {
		// It's a number - append ,USA
		query = query + ",USA"
	}

	resp, err := http.Get(fmt.Sprintf("http://api.openweathermap.org/data/2.5/forecast/daily?cnt=%d&units=imperial&q=%s", count, url.QueryEscape(query)))
	if err != nil {
		return nil, errors.New("network error")
	}

	weather := WeatherResponse{}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&weather)
	if err != nil || len(weather.List) < count {
		return nil, errors.New("invalid api response")
	}

	for _, v := range weather.List {
		if err := p.processDay(v); err != nil {
			return nil, err
		}
	}

	if replacement, ok := country_replacements[weather.City.Country]; ok {
		weather.City.Country = replacement
	}

	return &weather, nil
}

func (p *WeatherPlugin) Forecast(e *irc.Event) {
	weather, err := p.forecast(e.Message, 3)
	if err != nil {
		p.Bot.MentionReply(e, "%s", err.Error())
	}
	p.Bot.MentionReply(e, "3 day forecast for %s, %s.", weather.City.Name, weather.City.Country)
	for _, loc := range weather.List {
		p.Bot.MentionReply(e,
			"High %.2f, Low %.2f, %s.",
			loc.Temp.Max, loc.Temp.Min,
			loc.Weather[0].Description)
	}
}

func (p *WeatherPlugin) Weather(e *irc.Event) {
	loc, err := p.weather(e.Message)
	if err != nil {
		p.Bot.MentionReply(e, "%s", err.Error())
	}
	p.Bot.MentionReply(e,
		"%s, %s. Currently %.1f. High %.2f, Low %.2f, %s.",
		loc.Name, loc.Sys.Country,
		loc.Main.Temp, loc.Main.TempMax, loc.Main.TempMin,
		loc.Weather[0].Description)
}
<commit_msg>Fixed the issue where !weather without args causes a crash<commit_after>package plugins

import (
	"../../seabird"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/thoj/go-ircevent"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

var country_replacements = map[string]string{
	"US": "USA",
	"United States of America": "USA",
}

type WeatherDay struct {
	Name string `json:"name"`
	Main struct {
		Temp    float32 `json:"temp"`
		TempMin float32 `json:"temp_min"`
		TempMax float32 `json:"temp_max"`
	} `json:"main"`
	Temp struct {
		Min float32 `json:"min"`
		Max float32 `json:"max"`
	} `json:"temp"`
	Sys struct {
		Country string `json:"country"`
	} `json:"sys"`
	Weather []*struct {
		Description string `json:"description"`
	} `json:"weather"`
}

type WeatherResponse struct {
	City struct {
		Name    string `json:"name"`
		Country string `json:"country"`
	} `json:"city"`
	List []*WeatherDay
}

func init() {
	seabird.RegisterPlugin("weather", NewWeatherPlugin)
}

type WeatherPlugin struct {
	Bot *seabird.Bot
}

func NewWeatherPlugin(b *seabird.Bot, c json.RawMessage) {
	p := &WeatherPlugin{b}
	b.RegisterFunction("forecast", p.Forecast)
	b.RegisterFunction("weather", p.Weather)
}

func (p *WeatherPlugin) processDay(d *WeatherDay) error {
	if len(d.Weather) < 1 {
		return errors.New("invalid api response")
	}

	if replacement, ok := country_replacements[d.Sys.Country]; ok {
		d.Sys.Country = replacement
	}

	return nil
}

func (p *WeatherPlugin) weather(loc string) (*WeatherDay, error) {
	query := strings.TrimSpace(loc)
	if len(query) == 0 {
		return nil, errors.New("missing location")
	}

	if _, err := strconv.Atoi(query); err == nil {
		// It's a number - append ,USA
		query = query + ",USA"
	}

	resp, err := http.Get(fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?units=imperial&q=%s", url.QueryEscape(query)))
	if err != nil {
		return nil, errors.New("network error")
	}

	weather := WeatherDay{}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&weather)
	if err != nil {
		return nil, errors.New("invalid api response")
	}

	if err := p.processDay(&weather); err != nil {
		return nil, err
	}

	return &weather, nil
}

func (p *WeatherPlugin) forecast(loc string, count int) (*WeatherResponse, error) {
	query := strings.TrimSpace(loc)
	if len(query) == 0 {
		return nil, errors.New("missing location")
	}

	if _, err := strconv.Atoi(query); err == nil {
		// It's a number - append ,USA
		query = query + ",USA"
	}

	resp, err := http.Get(fmt.Sprintf("http://api.openweathermap.org/data/2.5/forecast/daily?cnt=%d&units=imperial&q=%s", count, url.QueryEscape(query)))
	if err != nil {
		return nil, errors.New("network error")
	}

	weather := WeatherResponse{}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&weather)
	if err != nil || len(weather.List) < count {
		return nil, errors.New("invalid api response")
	}

	for _, v := range weather.List {
		if err := p.processDay(v); err != nil {
			return nil, err
		}
	}

	if replacement, ok := country_replacements[weather.City.Country]; ok {
		weather.City.Country = replacement
	}

	return &weather, nil
}

func (p *WeatherPlugin) Forecast(e *irc.Event) {
	weather, err := p.forecast(e.Message, 3)
	if err != nil {
		p.Bot.MentionReply(e, "%s", err.Error())
		return
	}
	p.Bot.MentionReply(e, "3 day forecast for %s, %s.", weather.City.Name, weather.City.Country)
	for _, loc := range weather.List {
		p.Bot.MentionReply(e,
			"High %.2f, Low %.2f, %s.",
			loc.Temp.Max, loc.Temp.Min,
			loc.Weather[0].Description)
	}
}

func (p *WeatherPlugin) Weather(e *irc.Event) {
	loc, err := p.weather(e.Message)
	if err != nil {
		p.Bot.MentionReply(e, "%s", err.Error())
		return
	}
	p.Bot.MentionReply(e,
		"%s, %s. Currently %.1f. High %.2f, Low %.2f, %s.",
		loc.Name, loc.Sys.Country,
		loc.Main.Temp, loc.Main.TempMax, loc.Main.TempMin,
		loc.Weather[0].Description)
}
<|endoftext|>