<commit_before>package builder

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/buildskia"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/go/vcsinfo"
)

const (
	GOOD_BUILDS_FILENAME = "goodbuilds.txt"
)

// errors
var (
	AlreadyExistsErr = errors.New("Checkout already exists.")
)

var (
	branchRegex = regexp.MustCompile("^refs/heads/chrome/m([0-9]+)$")
)

// Builder is for building versions of the Skia library and then compiling and
// running fiddles against those built versions.
//
//    fiddleRoot - The root directory where fiddle stores its files. See DESIGN.md.
//    depotTools - The directory where depot_tools is checked out.
type Builder struct {
	fiddleRoot string
	depotTools string

	// A cache of the hashes returned from AllAvailable.
	hashes []string

	// Mutex protects access to hashes and GOOD_BUILDS_FILENAME.
	mutex sync.Mutex
}

// New returns a new Builder instance.
func New(fiddleRoot, depotTools string) *Builder {
	return &Builder{
		fiddleRoot: fiddleRoot,
		depotTools: depotTools,
	}
}

// branch is used to sort the chrome branches in the Skia repo.
type branch struct {
	N    int
	Name string
	Hash string
}

// branchSlice is a utility class for sorting slices of branch.
type branchSlice []branch

func (p branchSlice) Len() int           { return len(p) }
func (p branchSlice) Less(i, j int) bool { return p[i].N > p[j].N }
func (p branchSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// prepDirectory adds the 'versions' directory to the fiddleRoot
// and returns the full path of that directory.
func prepDirectory(fiddleRoot string) (string, error) {
	versions := path.Join(fiddleRoot, "versions")
	if err := os.MkdirAll(versions, 0777); err != nil {
		return "", fmt.Errorf("Failed to create FIDDLE_ROOT/versions dir: %s", err)
	}
	return versions, nil
}

// buildLib, given a directory that Skia is checked out into, builds libskia.a
// and fiddle_main.o.
func buildLib(checkout, depotTools string) error {
	glog.Info("Starting CMakeBuild")
	if err := buildskia.CMakeBuild(checkout, depotTools, buildskia.RELEASE_BUILD); err != nil {
		return fmt.Errorf("Failed cmake build: %s", err)
	}

	glog.Info("Building fiddle_main.o")
	files := []string{
		filepath.Join(checkout, "experimental", "fiddle", "fiddle_main.cpp"),
	}
	if err := buildskia.CMakeCompile(checkout, path.Join(checkout, "cmakeout", "fiddle_main.o"), files, []string{}); err != nil {
		return fmt.Errorf("Failed cmake build of fiddle_main: %s", err)
	}
	return nil
}

// BuildLatestSkia builds the LKGR of Skia in the given fiddleRoot directory.
//
// The library will be checked out into fiddleRoot + "/" + githash, where githash
// is the githash of the LKGR of Skia.
//
//    force - If true then checkout and build even if the directory already exists.
//    head - If true then build Skia at HEAD, otherwise build Skia at LKGR.
//    deps - If true then install Skia dependencies.
//
// Returns the commit info for the revision of Skia checked out.
// Returns an error if any step fails, or return AlreadyExistsErr if
// the target checkout directory already exists and force is false.
func (b *Builder) BuildLatestSkia(force bool, head bool, deps bool) (*vcsinfo.LongCommit, error) {
	versions, err := prepDirectory(b.fiddleRoot)
	if err != nil {
		return nil, err
	}

	githash := ""
	if head {
		if githash, err = buildskia.GetSkiaHead(nil); err != nil {
			return nil, fmt.Errorf("Failed to retrieve Skia HEAD: %s", err)
		}
	} else {
		if githash, err = buildskia.GetSkiaHash(nil); err != nil {
			return nil, fmt.Errorf("Failed to retrieve Skia LKGR: %s", err)
		}
	}
	checkout := path.Join(versions, githash)

	fi, err := os.Stat(checkout)
	// If the file is present and a directory then only proceed if 'force' is true.
	if err == nil && fi.IsDir() == true && !force {
		return nil, AlreadyExistsErr
	}

	ret, err := buildskia.DownloadSkia("", githash, checkout, b.depotTools, false, deps)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch: %s", err)
	}

	if err := buildLib(checkout, b.depotTools); err != nil {
		return nil, err
	}
	b.mutex.Lock()
	defer b.mutex.Unlock()
	b.hashes = append(b.hashes, githash)
	fb, err := os.OpenFile(filepath.Join(b.fiddleRoot, GOOD_BUILDS_FILENAME), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, fmt.Errorf("Failed to open %s for writing: %s", GOOD_BUILDS_FILENAME, err)
	}
	defer util.Close(fb)
	_, err = fmt.Fprintf(fb, "%s\n", githash)
	if err != nil {
		return nil, fmt.Errorf("Failed to write %s: %s", GOOD_BUILDS_FILENAME, err)
	}
	return ret, nil
}

// AvailableBuilds returns a list of git hashes, all the versions
// of Skia that can be built against.
func (b *Builder) AvailableBuilds() ([]string, error) {
	b.mutex.Lock()
	defer b.mutex.Unlock()
	if len(b.hashes) > 0 {
		return b.hashes, nil
	}
	fi, err := os.Open(filepath.Join(b.fiddleRoot, GOOD_BUILDS_FILENAME))
	if err != nil {
		return nil, fmt.Errorf("Failed to open %s for reading: %s", GOOD_BUILDS_FILENAME, err)
	}
	defer util.Close(fi)
	buf, err := ioutil.ReadAll(fi)
	if err != nil {
		return nil, fmt.Errorf("Failed to read: %s", err)
	}
	hashes := strings.Split(string(buf), "\n")
	revHashes := []string{}
	for _, h := range hashes {
		if h != "" {
			revHashes = append(revHashes, h)
		}
	}
	b.hashes = revHashes
	return revHashes, nil
}

// BuildLatestSkiaChromeBranch builds the most recent branch of Skia for Chrome
// in the given fiddleRoot directory.
//
// The library will be checked out into fiddleRoot + "/" + mNN, where mNN
// is the short name of the branch for Chrome. The mNN is chosen as the largest
// NN from all the branches named refs/heads/chrome/m[0-9]+.
//
//   force - If true then checkout and build even if the directory already exists.
//
// Returns the commit info for the revision of Skia checked out.
// Returns an error if any step fails, or return AlreadyExistsErr if
// the target checkout directory already exists and force is false.
func (b *Builder) BuildLatestSkiaChromeBranch(force bool) (string, *vcsinfo.LongCommit, error) {
	versions, err := prepDirectory(b.fiddleRoot)
	if err != nil {
		return "", nil, err
	}
	branches, err := buildskia.GetSkiaBranches(nil)
	if err != nil {
		return "", nil, fmt.Errorf("Failed to retrieve branch info: %s", err)
	}
	if len(branches) == 0 {
		return "", nil, fmt.Errorf("There must be at least one branch.")
	}

	branchNums := []branch{}
	for name, br := range branches {
		if match := branchRegex.FindStringSubmatch(name); match != nil {
			n, err := strconv.Atoi(match[1])
			if err != nil {
				glog.Errorf("Failed to parse branch number: %s", err)
				continue
			}
			branchNums = append(branchNums, branch{N: n, Name: name, Hash: br.Value})
		}
	}
	sort.Sort(branchSlice(branchNums))
	if len(branchNums) == 0 {
		return "", nil, fmt.Errorf("Failed to find any appropriate branches.")
	}

	branchName := fmt.Sprintf("m%d", branchNums[0].N)
	glog.Infof("Target branch number is: %d", branchName)

	checkout := path.Join(versions, branchName)

	fi, err := os.Stat(checkout)
	// If the file is present and a directory then only proceed if 'force' is true.
	if err == nil && fi.IsDir() == true && !force {
		return "", nil, AlreadyExistsErr
	}

	res, err := buildskia.DownloadSkia(branchNums[0].Name, branchNums[0].Hash, checkout, b.depotTools, false, false)
	if err != nil {
		return "", nil, fmt.Errorf("Failed to fetch: %s", err)
	}

	if err := buildLib(checkout, b.depotTools); err != nil {
		return "", nil, err
	}
	return branchName, res, nil
}
<commit_msg>fiddle: fiddle_main has moved out of experimental into tools.<commit_after>package builder

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/buildskia"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/go/vcsinfo"
)

const (
	GOOD_BUILDS_FILENAME = "goodbuilds.txt"
)

// errors
var (
	AlreadyExistsErr = errors.New("Checkout already exists.")
)

var (
	branchRegex = regexp.MustCompile("^refs/heads/chrome/m([0-9]+)$")
)

// Builder is for building versions of the Skia library and then compiling and
// running fiddles against those built versions.
//
//    fiddleRoot - The root directory where fiddle stores its files. See DESIGN.md.
//    depotTools - The directory where depot_tools is checked out.
type Builder struct {
	fiddleRoot string
	depotTools string

	// A cache of the hashes returned from AllAvailable.
	hashes []string

	// Mutex protects access to hashes and GOOD_BUILDS_FILENAME.
	mutex sync.Mutex
}

// New returns a new Builder instance.
func New(fiddleRoot, depotTools string) *Builder {
	return &Builder{
		fiddleRoot: fiddleRoot,
		depotTools: depotTools,
	}
}

// branch is used to sort the chrome branches in the Skia repo.
type branch struct {
	N    int
	Name string
	Hash string
}

// branchSlice is a utility class for sorting slices of branch.
type branchSlice []branch

func (p branchSlice) Len() int           { return len(p) }
func (p branchSlice) Less(i, j int) bool { return p[i].N > p[j].N }
func (p branchSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// prepDirectory adds the 'versions' directory to the fiddleRoot
// and returns the full path of that directory.
func prepDirectory(fiddleRoot string) (string, error) {
	versions := path.Join(fiddleRoot, "versions")
	if err := os.MkdirAll(versions, 0777); err != nil {
		return "", fmt.Errorf("Failed to create FIDDLE_ROOT/versions dir: %s", err)
	}
	return versions, nil
}

// buildLib, given a directory that Skia is checked out into, builds libskia.a
// and fiddle_main.o.
func buildLib(checkout, depotTools string) error {
	glog.Info("Starting CMakeBuild")
	if err := buildskia.CMakeBuild(checkout, depotTools, buildskia.RELEASE_BUILD); err != nil {
		return fmt.Errorf("Failed cmake build: %s", err)
	}

	glog.Info("Building fiddle_main.o")
	files := []string{
		filepath.Join(checkout, "tools", "fiddle", "fiddle_main.cpp"),
	}
	if err := buildskia.CMakeCompile(checkout, path.Join(checkout, "cmakeout", "fiddle_main.o"), files, []string{}); err != nil {
		return fmt.Errorf("Failed cmake build of fiddle_main: %s", err)
	}
	return nil
}

// BuildLatestSkia builds the LKGR of Skia in the given fiddleRoot directory.
//
// The library will be checked out into fiddleRoot + "/" + githash, where githash
// is the githash of the LKGR of Skia.
//
//    force - If true then checkout and build even if the directory already exists.
//    head - If true then build Skia at HEAD, otherwise build Skia at LKGR.
//    deps - If true then install Skia dependencies.
//
// Returns the commit info for the revision of Skia checked out.
// Returns an error if any step fails, or return AlreadyExistsErr if
// the target checkout directory already exists and force is false.
func (b *Builder) BuildLatestSkia(force bool, head bool, deps bool) (*vcsinfo.LongCommit, error) {
	versions, err := prepDirectory(b.fiddleRoot)
	if err != nil {
		return nil, err
	}

	githash := ""
	if head {
		if githash, err = buildskia.GetSkiaHead(nil); err != nil {
			return nil, fmt.Errorf("Failed to retrieve Skia HEAD: %s", err)
		}
	} else {
		if githash, err = buildskia.GetSkiaHash(nil); err != nil {
			return nil, fmt.Errorf("Failed to retrieve Skia LKGR: %s", err)
		}
	}
	checkout := path.Join(versions, githash)

	fi, err := os.Stat(checkout)
	// If the file is present and a directory then only proceed if 'force' is true.
	if err == nil && fi.IsDir() == true && !force {
		return nil, AlreadyExistsErr
	}

	ret, err := buildskia.DownloadSkia("", githash, checkout, b.depotTools, false, deps)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch: %s", err)
	}

	if err := buildLib(checkout, b.depotTools); err != nil {
		return nil, err
	}
	b.mutex.Lock()
	defer b.mutex.Unlock()
	b.hashes = append(b.hashes, githash)
	fb, err := os.OpenFile(filepath.Join(b.fiddleRoot, GOOD_BUILDS_FILENAME), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, fmt.Errorf("Failed to open %s for writing: %s", GOOD_BUILDS_FILENAME, err)
	}
	defer util.Close(fb)
	_, err = fmt.Fprintf(fb, "%s\n", githash)
	if err != nil {
		return nil, fmt.Errorf("Failed to write %s: %s", GOOD_BUILDS_FILENAME, err)
	}
	return ret, nil
}

// AvailableBuilds returns a list of git hashes, all the versions
// of Skia that can be built against.
func (b *Builder) AvailableBuilds() ([]string, error) {
	b.mutex.Lock()
	defer b.mutex.Unlock()
	if len(b.hashes) > 0 {
		return b.hashes, nil
	}
	fi, err := os.Open(filepath.Join(b.fiddleRoot, GOOD_BUILDS_FILENAME))
	if err != nil {
		return nil, fmt.Errorf("Failed to open %s for reading: %s", GOOD_BUILDS_FILENAME, err)
	}
	defer util.Close(fi)
	buf, err := ioutil.ReadAll(fi)
	if err != nil {
		return nil, fmt.Errorf("Failed to read: %s", err)
	}
	hashes := strings.Split(string(buf), "\n")
	revHashes := []string{}
	for _, h := range hashes {
		if h != "" {
			revHashes = append(revHashes, h)
		}
	}
	b.hashes = revHashes
	return revHashes, nil
}

// BuildLatestSkiaChromeBranch builds the most recent branch of Skia for Chrome
// in the given fiddleRoot directory.
//
// The library will be checked out into fiddleRoot + "/" + mNN, where mNN
// is the short name of the branch for Chrome. The mNN is chosen as the largest
// NN from all the branches named refs/heads/chrome/m[0-9]+.
//
//   force - If true then checkout and build even if the directory already exists.
//
// Returns the commit info for the revision of Skia checked out.
// Returns an error if any step fails, or return AlreadyExistsErr if
// the target checkout directory already exists and force is false.
func (b *Builder) BuildLatestSkiaChromeBranch(force bool) (string, *vcsinfo.LongCommit, error) {
	versions, err := prepDirectory(b.fiddleRoot)
	if err != nil {
		return "", nil, err
	}
	branches, err := buildskia.GetSkiaBranches(nil)
	if err != nil {
		return "", nil, fmt.Errorf("Failed to retrieve branch info: %s", err)
	}
	if len(branches) == 0 {
		return "", nil, fmt.Errorf("There must be at least one branch.")
	}

	branchNums := []branch{}
	for name, br := range branches {
		if match := branchRegex.FindStringSubmatch(name); match != nil {
			n, err := strconv.Atoi(match[1])
			if err != nil {
				glog.Errorf("Failed to parse branch number: %s", err)
				continue
			}
			branchNums = append(branchNums, branch{N: n, Name: name, Hash: br.Value})
		}
	}
	sort.Sort(branchSlice(branchNums))
	if len(branchNums) == 0 {
		return "", nil, fmt.Errorf("Failed to find any appropriate branches.")
	}

	branchName := fmt.Sprintf("m%d", branchNums[0].N)
	glog.Infof("Target branch number is: %d", branchName)

	checkout := path.Join(versions, branchName)

	fi, err := os.Stat(checkout)
	// If the file is present and a directory then only proceed if 'force' is true.
	if err == nil && fi.IsDir() == true && !force {
		return "", nil, AlreadyExistsErr
	}

	res, err := buildskia.DownloadSkia(branchNums[0].Name, branchNums[0].Hash, checkout, b.depotTools, false, false)
	if err != nil {
		return "", nil, fmt.Errorf("Failed to fetch: %s", err)
	}

	if err := buildLib(checkout, b.depotTools); err != nil {
		return "", nil, err
	}
	return branchName, res, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 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 autoscale

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"
	"k8s.io/klog/v2"

	autoscalingv1 "k8s.io/api/autoscaling/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/cli-runtime/pkg/resource"
	autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
	"k8s.io/client-go/scale"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scheme"
	"k8s.io/kubectl/pkg/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

var (
	autoscaleLong = templates.LongDesc(i18n.T(`
		Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.

		Looks up a Deployment, ReplicaSet, StatefulSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.
		An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))

	autoscaleExample = templates.Examples(i18n.T(`
		# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used:
		kubectl autoscale deployment foo --min=2 --max=10

		# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%:
		kubectl autoscale rc foo --max=5 --cpu-percent=80`))
)

// AutoscaleOptions declares the arguments accepted by the Autoscale command
type AutoscaleOptions struct {
	FilenameOptions *resource.FilenameOptions

	RecordFlags *genericclioptions.RecordFlags
	Recorder    genericclioptions.Recorder

	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	Name       string
	Min        int32
	Max        int32
	CPUPercent int32

	createAnnotation bool
	args             []string
	enforceNamespace bool
	namespace        string
	dryRunStrategy   cmdutil.DryRunStrategy
	dryRunVerifier   *resource.DryRunVerifier
	builder          *resource.Builder
	fieldManager     string

	HPAClient         autoscalingv1client.HorizontalPodAutoscalersGetter
	scaleKindResolver scale.ScaleKindResolver

	genericclioptions.IOStreams
}

// NewAutoscaleOptions creates the options for autoscale
func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
	return &AutoscaleOptions{
		PrintFlags:      genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
		FilenameOptions: &resource.FilenameOptions{},
		RecordFlags:     genericclioptions.NewRecordFlags(),
		Recorder:        genericclioptions.NoopRecorder{},

		IOStreams: ioStreams,
	}
}

// NewCmdAutoscale returns the autoscale Cobra command
func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewAutoscaleOptions(ioStreams)

	validArgs := []string{"deployment", "replicaset", "replicationcontroller"}

	cmd := &cobra.Command{
		Use:                   "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Auto-scale a Deployment, ReplicaSet, or ReplicationController"),
		Long:                  autoscaleLong,
		Example:               autoscaleExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.Run())
		},
		ValidArgs: validArgs,
	}

	// bind flag structs
	o.RecordFlags.AddFlags(cmd)
	o.PrintFlags.AddFlags(cmd)
	cmd.Flags().String("generator", "horizontalpodautoscaler/v1", i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
	cmd.Flags().MarkDeprecated("generator", "has no effect and will be removed in the future.")
	cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
	cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
	cmd.MarkFlagRequired("max")
	cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
	cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
	cmdutil.AddDryRunFlag(cmd)
	cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
	cmdutil.AddApplyAnnotationFlags(cmd)
	cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-autoscale")
	return cmd
}

// Complete verifies command line arguments and loads data from the command environment
func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
	if err != nil {
		return err
	}
	dynamicClient, err := f.DynamicClient()
	if err != nil {
		return err
	}
	discoveryClient, err := f.ToDiscoveryClient()
	if err != nil {
		return err
	}
	o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
	o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
	o.builder = f.NewBuilder()
	o.scaleKindResolver = scale.NewDiscoveryScaleKindResolver(discoveryClient)
	o.args = args
	o.RecordFlags.Complete(cmd)

	o.Recorder, err = o.RecordFlags.ToRecorder()
	if err != nil {
		return err
	}

	kubeClient, err := f.KubernetesClientSet()
	if err != nil {
		return err
	}
	o.HPAClient = kubeClient.AutoscalingV1()

	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
		o.PrintFlags.NamePrintFlags.Operation = operation
		cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)

		return o.PrintFlags.ToPrinter()
	}

	return nil
}

// Validate checks that the provided attach options are specified.
func (o *AutoscaleOptions) Validate() error {
	if o.Max < 1 {
		return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
	}
	if o.Max < o.Min {
		return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
	}

	return nil
}

// Run performs the execution
func (o *AutoscaleOptions) Run() error {
	r := o.builder.
		Unstructured().
		ContinueOnError().
		NamespaceParam(o.namespace).DefaultNamespace().
		FilenameParam(o.enforceNamespace, o.FilenameOptions).
		ResourceTypeOrNameArgs(false, o.args...).
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}

	count := 0
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		mapping := info.ResourceMapping()
		gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
		if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
			return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
		}

		hpa := o.createHorizontalPodAutoscaler(info.Name, mapping)

		if err := o.Recorder.Record(hpa); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		}

		if o.dryRunStrategy == cmdutil.DryRunClient {
			count++

			printer, err := o.ToPrinter("created")
			if err != nil {
				return err
			}
			return printer.PrintObj(hpa, o.Out)
		}

		if err := util.CreateOrUpdateAnnotation(o.createAnnotation, hpa, scheme.DefaultJSONEncoder()); err != nil {
			return err
		}

		createOptions := metav1.CreateOptions{}
		if o.fieldManager != "" {
			createOptions.FieldManager = o.fieldManager
		}
		if o.dryRunStrategy == cmdutil.DryRunServer {
			if err := o.dryRunVerifier.HasSupport(hpa.GroupVersionKind()); err != nil {
				return err
			}
			createOptions.DryRun = []string{metav1.DryRunAll}
		}
		actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), hpa, createOptions)
		if err != nil {
			return err
		}

		count++
		printer, err := o.ToPrinter("autoscaled")
		if err != nil {
			return err
		}
		return printer.PrintObj(actualHPA, o.Out)
	})
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("no objects passed to autoscale")
	}
	return nil
}

func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
	name := o.Name
	if len(name) == 0 {
		name = refName
	}

	scaler := autoscalingv1.HorizontalPodAutoscaler{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
			ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
				APIVersion: mapping.GroupVersionKind.GroupVersion().String(),
				Kind:       mapping.GroupVersionKind.Kind,
				Name:       refName,
			},
			MaxReplicas: o.Max,
		},
	}

	if o.Min > 0 {
		v := int32(o.Min)
		scaler.Spec.MinReplicas = &v
	}
	if o.CPUPercent >= 0 {
		c := int32(o.CPUPercent)
		scaler.Spec.TargetCPUUtilizationPercentage = &c
	}

	return &scaler
}
<commit_msg>Add statefulset to kubectl autoscale bash completions<commit_after>/*
Copyright 2015 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 autoscale

import (
	"context"
	"fmt"

	"github.com/spf13/cobra"
	"k8s.io/klog/v2"

	autoscalingv1 "k8s.io/api/autoscaling/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/cli-runtime/pkg/resource"
	autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
	"k8s.io/client-go/scale"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/scheme"
	"k8s.io/kubectl/pkg/util"
	"k8s.io/kubectl/pkg/util/i18n"
	"k8s.io/kubectl/pkg/util/templates"
)

var (
	autoscaleLong = templates.LongDesc(i18n.T(`
		Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster.

		Looks up a Deployment, ReplicaSet, StatefulSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference.
		An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))

	autoscaleExample = templates.Examples(i18n.T(`
		# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used:
		kubectl autoscale deployment foo --min=2 --max=10

		# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%:
		kubectl autoscale rc foo --max=5 --cpu-percent=80`))
)

// AutoscaleOptions declares the arguments accepted by the Autoscale command
type AutoscaleOptions struct {
	FilenameOptions *resource.FilenameOptions

	RecordFlags *genericclioptions.RecordFlags
	Recorder    genericclioptions.Recorder

	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	Name       string
	Min        int32
	Max        int32
	CPUPercent int32

	createAnnotation bool
	args             []string
	enforceNamespace bool
	namespace        string
	dryRunStrategy   cmdutil.DryRunStrategy
	dryRunVerifier   *resource.DryRunVerifier
	builder          *resource.Builder
	fieldManager     string

	HPAClient         autoscalingv1client.HorizontalPodAutoscalersGetter
	scaleKindResolver scale.ScaleKindResolver

	genericclioptions.IOStreams
}

// NewAutoscaleOptions creates the options for autoscale
func NewAutoscaleOptions(ioStreams genericclioptions.IOStreams) *AutoscaleOptions {
	return &AutoscaleOptions{
		PrintFlags:      genericclioptions.NewPrintFlags("autoscaled").WithTypeSetter(scheme.Scheme),
		FilenameOptions: &resource.FilenameOptions{},
		RecordFlags:     genericclioptions.NewRecordFlags(),
		Recorder:        genericclioptions.NoopRecorder{},

		IOStreams: ioStreams,
	}
}

// NewCmdAutoscale returns the autoscale Cobra command
func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := NewAutoscaleOptions(ioStreams)

	validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}

	cmd := &cobra.Command{
		Use:                   "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
		DisableFlagsInUseLine: true,
		Short:                 i18n.T("Auto-scale a Deployment, ReplicaSet, StatefulSet, or ReplicationController"),
		Long:                  autoscaleLong,
		Example:               autoscaleExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.Run())
		},
		ValidArgs: validArgs,
	}

	// bind flag structs
	o.RecordFlags.AddFlags(cmd)
	o.PrintFlags.AddFlags(cmd)
	cmd.Flags().String("generator", "horizontalpodautoscaler/v1", i18n.T("The name of the API generator to use. Currently there is only 1 generator."))
	cmd.Flags().MarkDeprecated("generator", "has no effect and will be removed in the future.")
	cmd.Flags().Int32Var(&o.Min, "min", -1, "The lower limit for the number of pods that can be set by the autoscaler. If it's not specified or negative, the server will apply a default value.")
	cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
	cmd.MarkFlagRequired("max")
	cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used."))
	cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
	cmdutil.AddDryRunFlag(cmd)
	cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
	cmdutil.AddApplyAnnotationFlags(cmd)
	cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-autoscale")
	return cmd
}

// Complete verifies command line arguments and loads data from the command environment
func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
	var err error
	o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
	if err != nil {
		return err
	}
	dynamicClient, err := f.DynamicClient()
	if err != nil {
		return err
	}
	discoveryClient, err := f.ToDiscoveryClient()
	if err != nil {
		return err
	}
	o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
	o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
	o.builder = f.NewBuilder()
	o.scaleKindResolver = scale.NewDiscoveryScaleKindResolver(discoveryClient)
	o.args = args
	o.RecordFlags.Complete(cmd)

	o.Recorder, err = o.RecordFlags.ToRecorder()
	if err != nil {
		return err
	}

	kubeClient, err := f.KubernetesClientSet()
	if err != nil {
		return err
	}
	o.HPAClient = kubeClient.AutoscalingV1()

	o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {
		o.PrintFlags.NamePrintFlags.Operation = operation
		cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)

		return o.PrintFlags.ToPrinter()
	}

	return nil
}

// Validate checks that the provided attach options are specified.
func (o *AutoscaleOptions) Validate() error {
	if o.Max < 1 {
		return fmt.Errorf("--max=MAXPODS is required and must be at least 1, max: %d", o.Max)
	}
	if o.Max < o.Min {
		return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
	}

	return nil
}

// Run performs the execution
func (o *AutoscaleOptions) Run() error {
	r := o.builder.
		Unstructured().
		ContinueOnError().
		NamespaceParam(o.namespace).DefaultNamespace().
		FilenameParam(o.enforceNamespace, o.FilenameOptions).
		ResourceTypeOrNameArgs(false, o.args...).
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}

	count := 0
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		mapping := info.ResourceMapping()
		gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
		if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
			return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
		}

		hpa := o.createHorizontalPodAutoscaler(info.Name, mapping)

		if err := o.Recorder.Record(hpa); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		}

		if o.dryRunStrategy == cmdutil.DryRunClient {
			count++

			printer, err := o.ToPrinter("created")
			if err != nil {
				return err
			}
			return printer.PrintObj(hpa, o.Out)
		}

		if err := util.CreateOrUpdateAnnotation(o.createAnnotation, hpa, scheme.DefaultJSONEncoder()); err != nil {
			return err
		}

		createOptions := metav1.CreateOptions{}
		if o.fieldManager != "" {
			createOptions.FieldManager = o.fieldManager
		}
		if o.dryRunStrategy == cmdutil.DryRunServer {
			if err := o.dryRunVerifier.HasSupport(hpa.GroupVersionKind()); err != nil {
				return err
			}
			createOptions.DryRun = []string{metav1.DryRunAll}
		}
		actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), hpa, createOptions)
		if err != nil {
			return err
		}

		count++
		printer, err := o.ToPrinter("autoscaled")
		if err != nil {
			return err
		}
		return printer.PrintObj(actualHPA, o.Out)
	})
	if err != nil {
		return err
	}
	if count == 0 {
		return fmt.Errorf("no objects passed to autoscale")
	}
	return nil
}

func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
	name := o.Name
	if len(name) == 0 {
		name = refName
	}

	scaler := autoscalingv1.HorizontalPodAutoscaler{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
			ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
				APIVersion: mapping.GroupVersionKind.GroupVersion().String(),
				Kind:       mapping.GroupVersionKind.Kind,
				Name:       refName,
			},
			MaxReplicas: o.Max,
		},
	}

	if o.Min > 0 {
		v := int32(o.Min)
		scaler.Spec.MinReplicas = &v
	}
	if o.CPUPercent >= 0 {
		c := int32(o.CPUPercent)
		scaler.Spec.TargetCPUUtilizationPercentage = &c
	}

	return &scaler
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 machine

import (
	"runtime"

	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/style"
)

// MaybeDisplayAdvice will provide advice without exiting, so minikube has a chance to try the failover
func MaybeDisplayAdvice(err error, driver string) {
	if errors.Is(err, oci.ErrDaemonInfo) {
		out.ErrLn("")
		out.ErrT(style.Conflict, "{{.driver_name}} couldn't proceed because {{.driver_name}} service is not healthy.", out.V{"driver_name": driver})
	}

	if errors.Is(err, oci.ErrExitedUnexpectedly) {
		out.ErrLn("")
		out.ErrT(style.Conflict, "The minikube {{.driver_name}} container exited unexpectedly.", out.V{"driver_name": driver})
	}

	if errors.Is(err, oci.ErrExitedUnexpectedly) || errors.Is(err, oci.ErrDaemonInfo) {
		out.T(style.Tip, "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:", out.V{"driver_name": driver})
		out.T(style.Empty, `
	- Prune unused {{.driver_name}} images, volumes, networks and abandoned containers.
		docker system prune --volumes`, out.V{"driver_name": driver})
		out.T(style.Empty, `
	- Restart your {{.driver_name}} service`, out.V{"driver_name": driver})
		if runtime.GOOS != "linux" {
			out.T(style.Empty, `
	- Ensure your {{.driver_name}} daemon has access to enough CPU/memory resources. `, out.V{"driver_name": driver})
			if runtime.GOOS == "darwin" && driver == oci.Docker {
				out.T(style.Empty, `
	- Docs https://docs.docker.com/docker-for-mac/#resources`, out.V{"driver_name": driver})
			}
			if runtime.GOOS == "windows" && driver == oci.Docker {
				out.T(style.Empty, `
	- Docs https://docs.docker.com/docker-for-windows/#resources`, out.V{"driver_name": driver})
			}
		}
		out.T(style.Empty, `
	- Delete and recreate minikube cluster
		minikube delete
		minikube start --driver={{.driver_name}}`, out.V{"driver_name": driver})
		// TODO #8348: maybe advice user if to set the --force-systemd https://github.com/kubernetes/minikube/issues/8348
	}
}
<commit_msg>Add leading newline for clarity<commit_after>/*
Copyright 2020 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 machine

import (
	"runtime"

	"github.com/pkg/errors"
	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/style"
)

// MaybeDisplayAdvice will provide advice without exiting, so minikube has a chance to try the failover
func MaybeDisplayAdvice(err error, driver string) {
	if errors.Is(err, oci.ErrDaemonInfo) {
		out.ErrLn("")
		out.ErrT(style.Conflict, "{{.driver_name}} couldn't proceed because {{.driver_name}} service is not healthy.", out.V{"driver_name": driver})
	}

	if errors.Is(err, oci.ErrExitedUnexpectedly) {
		out.ErrLn("")
		out.ErrT(style.Conflict, "The minikube {{.driver_name}} container exited unexpectedly.", out.V{"driver_name": driver})
	}

	if errors.Is(err, oci.ErrExitedUnexpectedly) || errors.Is(err, oci.ErrDaemonInfo) {
		out.T(style.Tip, "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:", out.V{"driver_name": driver})
		out.T(style.Empty, `
	- Prune unused {{.driver_name}} images, volumes, networks and abandoned containers.

		docker system prune --volumes`, out.V{"driver_name": driver})
		out.T(style.Empty, `
	- Restart your {{.driver_name}} service`, out.V{"driver_name": driver})
		if runtime.GOOS != "linux" {
			out.T(style.Empty, `
	- Ensure your {{.driver_name}} daemon has access to enough CPU/memory resources. `, out.V{"driver_name": driver})
			if runtime.GOOS == "darwin" && driver == oci.Docker {
				out.T(style.Empty, `
	- Docs https://docs.docker.com/docker-for-mac/#resources`, out.V{"driver_name": driver})
			}
			if runtime.GOOS == "windows" && driver == oci.Docker {
				out.T(style.Empty, `
	- Docs https://docs.docker.com/docker-for-windows/#resources`, out.V{"driver_name": driver})
			}
		}
		out.T(style.Empty, `
	- Delete and recreate minikube cluster
		minikube delete
		minikube start --driver={{.driver_name}}`, out.V{"driver_name": driver})
		// TODO #8348: maybe advice user if to set the --force-systemd https://github.com/kubernetes/minikube/issues/8348
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

import (
	"math/rand"
	"testing"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)

func expectSchedule(scheduler Scheduler, pod api.Pod, expected string, t *testing.T) {
	actual, err := scheduler.Schedule(pod)
	expectNoError(t, err)
	if actual != expected {
		t.Errorf("Unexpected scheduling value: %d, expected %d", actual, expected)
	}
}

func TestRoundRobinScheduler(t *testing.T) {
	scheduler := MakeRoundRobinScheduler([]string{"m1", "m2", "m3", "m4"})
	expectSchedule(scheduler, api.Pod{}, "m1", t)
	expectSchedule(scheduler, api.Pod{}, "m2", t)
	expectSchedule(scheduler, api.Pod{}, "m3", t)
	expectSchedule(scheduler, api.Pod{}, "m4", t)
}

func TestRandomScheduler(t *testing.T) {
	random := rand.New(rand.NewSource(0))
	scheduler := MakeRandomScheduler([]string{"m1", "m2", "m3", "m4"}, *random)
	_, err := scheduler.Schedule(api.Pod{})
	expectNoError(t, err)
}

func TestFirstFitSchedulerNothingScheduled(t *testing.T) {
	mockRegistry := MockPodRegistry{}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	expectSchedule(scheduler, api.Pod{}, "m3", t)
}

func makePod(host string, hostPorts ...int) api.Pod {
	networkPorts := []api.Port{}
	for _, port := range hostPorts {
		networkPorts = append(networkPorts, api.Port{HostPort: port})
	}
	return api.Pod{
		CurrentState: api.PodState{
			Host: host,
		},
		DesiredState: api.PodState{
			Manifest: api.ContainerManifest{
				Containers: []api.Container{
					{
						Ports: networkPorts,
					},
				},
			},
		},
	}
}

func TestFirstFitSchedulerFirstScheduled(t *testing.T) {
	mockRegistry := MockPodRegistry{
		pods: []api.Pod{
			makePod("m1", 8080),
		},
	}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	expectSchedule(scheduler, makePod("", 8080), "m3", t)
}

func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) {
	mockRegistry := MockPodRegistry{
		pods: []api.Pod{
			makePod("m1", 80, 8080),
			makePod("m2", 8081, 8082, 8083),
			makePod("m3", 80, 443, 8085),
		},
	}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	expectSchedule(scheduler, makePod("", 8080, 8081), "m3", t)
}

func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) {
	mockRegistry := MockPodRegistry{
		pods: []api.Pod{
			makePod("m1", 8080),
			makePod("m2", 8081),
			makePod("m3", 8080),
		},
	}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	_, err := scheduler.Schedule(makePod("", 8080, 8081))
	if err == nil {
		t.Error("Unexpected non-error.")
	}
}
<commit_msg>minor fixes to scheduler_test<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package registry

import (
	"math/rand"
	"testing"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)

func expectSchedule(scheduler Scheduler, pod api.Pod, expected string, t *testing.T) {
	actual, err := scheduler.Schedule(pod)
	expectNoError(t, err)
	if actual != expected {
		t.Errorf("Unexpected scheduling value: %v, expected %v", actual, expected)
	}
}

func TestRoundRobinScheduler(t *testing.T) {
	scheduler := MakeRoundRobinScheduler([]string{"m1", "m2", "m3", "m4"})
	expectSchedule(scheduler, api.Pod{}, "m1", t)
	expectSchedule(scheduler, api.Pod{}, "m2", t)
	expectSchedule(scheduler, api.Pod{}, "m3", t)
	expectSchedule(scheduler, api.Pod{}, "m4", t)
}

func TestRandomScheduler(t *testing.T) {
	random := rand.New(rand.NewSource(0))
	scheduler := MakeRandomScheduler([]string{"m1", "m2", "m3", "m4"}, *random)
	_, err := scheduler.Schedule(api.Pod{})
	expectNoError(t, err)
}

func TestFirstFitSchedulerNothingScheduled(t *testing.T) {
	mockRegistry := MockPodRegistry{}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	expectSchedule(scheduler, api.Pod{}, "m3", t)
}

func makePod(host string, hostPorts ...int) api.Pod {
	networkPorts := []api.Port{}
	for _, port := range hostPorts {
		networkPorts = append(networkPorts, api.Port{HostPort: port})
	}
	return api.Pod{
		CurrentState: api.PodState{
			Host: host,
		},
		DesiredState: api.PodState{
			Manifest: api.ContainerManifest{
				Containers: []api.Container{
					{
						Ports: networkPorts,
					},
				},
			},
		},
	}
}

func TestFirstFitSchedulerFirstScheduled(t *testing.T) {
	mockRegistry := MockPodRegistry{
		pods: []api.Pod{
			makePod("m1", 8080),
		},
	}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	expectSchedule(scheduler, makePod("", 8080), "m3", t)
}

func TestFirstFitSchedulerFirstScheduledComplicated(t *testing.T) {
	mockRegistry := MockPodRegistry{
		pods: []api.Pod{
			makePod("m1", 80, 8080),
			makePod("m2", 8081, 8082, 8083),
			makePod("m3", 80, 443, 8085),
		},
	}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	expectSchedule(scheduler, makePod("", 8080, 8081), "m3", t)
}

func TestFirstFitSchedulerFirstScheduledImpossible(t *testing.T) {
	mockRegistry := MockPodRegistry{
		pods: []api.Pod{
			makePod("m1", 8080),
			makePod("m2", 8081),
			makePod("m3", 8080),
		},
	}
	r := rand.New(rand.NewSource(0))
	scheduler := MakeFirstFitScheduler([]string{"m1", "m2", "m3"}, &mockRegistry, r)
	_, err := scheduler.Schedule(makePod("", 8080, 8081))
	if err == nil {
		t.Error("Unexpected non-error.")
	}
}
<|endoftext|>


<commit_before>package payment

import (
	"database/sql"
	"github.com/fritzpay/paymentd/pkg/paymentd/payment"
	"github.com/fritzpay/paymentd/pkg/paymentd/payment_method"
	"github.com/fritzpay/paymentd/pkg/paymentd/project"
	"github.com/fritzpay/paymentd/pkg/server"
	"github.com/fritzpay/paymentd/pkg/service"
	"github.com/go-sql-driver/mysql"
	"gopkg.in/inconshreveable/log15.v2"
	"net/http"
	"time"
)

type errorID int

func (e errorID) Error() string {
	switch e {
	case ErrDB:
		return "database error"
	case ErrDBLockTimeout:
		return "lock wait timeout"
	case ErrDuplicateIdent:
		return "duplicate ident in payment"
	case ErrPaymentCallbackConfig:
		return "callback config error"
	case ErrPaymentMethodNotFound:
		return "payment method not found"
	case ErrPaymentMethodConflict:
		return "payment method project mismatch"
	case ErrPaymentMethodInactive:
		return "payment method inactive"
	case ErrInternal:
		return "internal error"
	default:
		return "unknown error"
	}
}

const (
	// general database error
	ErrDB errorID = iota
	// lock wait timeout
	ErrDBLockTimeout
	// duplicate Ident in payment
	ErrDuplicateIdent
	// callback config error
	ErrPaymentCallbackConfig
	// payment method not found
	ErrPaymentMethodNotFound
	// payment method project mismatch
	ErrPaymentMethodConflict
	// payment method inactive
	ErrPaymentMethodInactive
	// internal error
	ErrInternal
)

const (
	PaymentTokenMaxAgeDefault = time.Minute * 15
)

// Service is the payment service
type Service struct {
	ctx *service.Context
	log log15.Logger

	idCoder *payment.IDEncoder

	tr *http.Transport
	cl *http.Client
}

// NewService creates a new payment service
func NewService(ctx *service.Context) (*Service, error) {
	s := &Service{
		ctx: ctx,
		log: ctx.Log().New(log15.Ctx{
			"pkg": "github.com/fritzpay/paymentd/pkg/service/payment",
		}),
	}

	var err error
	cfg := ctx.Config()

	s.idCoder, err = payment.NewIDEncoder(cfg.Payment.PaymentIDEncPrime, cfg.Payment.PaymentIDEncXOR)
	if err != nil {
		s.log.Error("error initializing payment ID encoder", log15.Ctx{"err": err})
		return nil, err
	}

	s.tr = &http.Transport{}
	s.cl = &http.Client{
		Transport: s.tr,
	}

	go s.handleContext()

	return s, nil
}

func (s *Service) handleContext() {
	// if attached to a server, this will tell the server to wait with shutting down
	// until the cleanup process is complete
	server.Wait.Add(1)
	defer server.Wait.Done()
	for {
		select {
		case <-s.ctx.Done():
			s.log.Info("service context closed", log15.Ctx{"err": s.ctx.Err()})
			s.log.Info("closing idle connections...")
			s.tr.CloseIdleConnections()
			return
		}
	}
}

// EncodedPaymentID returns a payment id with the id part encoded
func (s *Service) EncodedPaymentID(id payment.PaymentID) payment.PaymentID {
	id.PaymentID = s.idCoder.Hide(id.PaymentID)
	return id
}

// DecodedPaymentID returns a payment id with the id part decoded
func (s *Service) DecodedPaymentID(id payment.PaymentID) payment.PaymentID {
	id.PaymentID = s.idCoder.Show(id.PaymentID)
	return id
}

// CreatePayment creates a new payment
func (s *Service) CreatePayment(tx *sql.Tx, p *payment.Payment) error {
	log := s.log.New(log15.Ctx{
		"method": "CreatePayment",
	})
	if p.Config.HasCallback() {
		callbackProjectKey, err := project.ProjectKeyByKeyTx(tx, p.Config.CallbackProjectKey.String)
		if err != nil {
			if err == project.ErrProjectKeyNotFound {
				log.Error("callback project key not found", log15.Ctx{"callbackProjectKey": p.Config.CallbackProjectKey.String})
				return ErrPaymentCallbackConfig
			}
			log.Error("error retrieving callback project key", log15.Ctx{"err": err})
			return ErrDB
		}
		if callbackProjectKey.Project.ID != p.ProjectID() {
			log.Error("callback project mismatch", log15.Ctx{
				"callbackProjectKey": callbackProjectKey.Key,
				"callbackProjectID":  callbackProjectKey.Project.ID,
				"projectID":          p.ProjectID(),
			})
			return ErrPaymentCallbackConfig
		}
	}
	err := payment.InsertPaymentTx(tx, p)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		_, existErr := payment.PaymentByProjectIDAndIdentTx(tx, p.ProjectID(), p.Ident)
		if existErr != nil && existErr != payment.ErrPaymentNotFound {
			log.Error("error on checking duplicate ident", log15.Ctx{"err": err})
			return ErrDB
		}
		// payment found => duplicate error
		if existErr == nil {
			return ErrDuplicateIdent
		}
		log.Error("error on insert payment", log15.Ctx{"err": err})
		return ErrDB
	}
	err = s.SetPaymentConfig(tx, p)
	if err != nil {
		return err
	}
	err = s.SetPaymentMetadata(tx, p)
	if err != nil {
		return err
	}
	return nil
}

func (s *Service) SetPaymentConfig(tx *sql.Tx, p *payment.Payment) error {
	log := s.log.New(log15.Ctx{"method": "SetPaymentConfig"})
	if p.Config.PaymentMethodID.Valid {
		log = log.New(log15.Ctx{"paymentMethodID": p.Config.PaymentMethodID.Int64})
		meth, err := payment_method.PaymentMethodByIDTx(tx, p.Config.PaymentMethodID.Int64)
		if err != nil {
			if mysqlErr, ok := err.(*mysql.MySQLError); ok {
				if mysqlErr.Number == 1213 {
					return ErrDBLockTimeout
				}
			}
			if err == payment_method.ErrPaymentMethodNotFound {
				log.Warn(ErrPaymentMethodNotFound.Error())
				return ErrPaymentMethodNotFound
			}
			log.Error("error on select payment method", log15.Ctx{"err": err})
			return ErrDB
		}
		if meth.ProjectID != p.ProjectID() {
			log.Warn(ErrPaymentMethodConflict.Error())
			return ErrPaymentMethodConflict
		}
		if meth.Status != payment_method.PaymentMethodStatusActive {
			log.Warn(ErrPaymentMethodInactive.Error())
			return ErrPaymentMethodInactive
		}
	}
	err := payment.InsertPaymentConfigTx(tx, p)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error on insert payment config", log15.Ctx{"err": err})
		return ErrDB
	}
	return nil
}

func (s *Service) SetPaymentMetadata(tx *sql.Tx, p *payment.Payment) error {
	log := s.log.New(log15.Ctx{"method": "SetPaymentMetadata"})
	// payment metadata
	if p.Metadata == nil {
		return nil
	}
	err := payment.InsertPaymentMetadataTx(tx, p)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error on insert payment metadata", log15.Ctx{"err": err})
		return ErrDB
	}
	return nil
}

// IsProcessablePayment returns true if the given payment is considered processable
//
// All required fields are present.
func (s *Service) IsProcessablePayment(p *payment.Payment) bool {
	if !p.Config.IsConfigured() {
		return false
	}
	if !p.Config.Country.Valid {
		return false
	}
	if !p.Config.Locale.Valid {
		return false
	}
	if !p.Config.PaymentMethodID.Valid {
		return false
	}
	return true
}

// IsInitialized returns true when the payment is in a processing state, i.e.
// when there is at least one transaction present
func (s *Service) IsInitialized(p *payment.Payment) bool {
	return p.Status != payment.PaymentStatusNone
}

func (s *Service) SetPaymentTransaction(tx *sql.Tx, paymentTx *payment.PaymentTransaction) error {
	log := s.log.New(log15.Ctx{"method": "SetPaymentTransaction"})
	err := payment.InsertPaymentTransactionTx(tx, paymentTx)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error saving payment transaction", log15.Ctx{"err": err})
		return ErrDB
	}
	var callback Callbacker
	if CanCallback(&paymentTx.Payment.Config) {
		callback = &paymentTx.Payment.Config
	} else {
		pr, err := project.ProjectByIDTx(tx, paymentTx.Payment.ProjectID())
		if err != nil {
			if err == project.ErrProjectNotFound {
				log.Crit("payment with invalid project", log15.Ctx{"projectID": paymentTx.Payment.ProjectID()})
				return ErrInternal
			}
			log.Error("error retrieving project", log15.Ctx{"err": err})
			return ErrDB
		}
		if CanCallback(pr.Config) {
			callback = pr.Config
		}
	}
	if callback != nil {
		s.Notify(callback, paymentTx)
	}
	return nil
}

func (s *Service) PaymentTransaction(tx *sql.Tx, p *payment.Payment) (*payment.PaymentTransaction, error) {
	return payment.PaymentTransactionCurrentTx(tx, p)
}

func (s *Service) CreatePaymentToken(tx *sql.Tx, p *payment.Payment) (*payment.PaymentToken, error) {
	log := s.log.New(log15.Ctx{"method": "CreatePaymentToken"})
	token, err := payment.NewPaymentToken(p.PaymentID())
	if err != nil {
		log.Error("error creating payment token", log15.Ctx{"err": err})
		return nil, ErrInternal
	}
	err = payment.InsertPaymentTokenTx(tx, token)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return nil, ErrDBLockTimeout
			}
		}
		log.Error("error saving payment token", log15.Ctx{"err": err})
		return nil, ErrDB
	}
	return token, nil
}

// TODO use token max age from config
func (s *Service) PaymentByToken(tx *sql.Tx, token string) (*payment.Payment, error) {
	tokenMaxAge := PaymentTokenMaxAgeDefault
	return payment.PaymentByTokenTx(tx, token, tokenMaxAge)
}

func (s *Service) DeletePaymentToken(tx *sql.Tx, token string) error {
	log := s.log.New(log15.Ctx{"method": "DeletePaymentToken"})
	err := payment.DeletePaymentTokenTx(tx, token)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error deleting payment token", log15.Ctx{"err": err})
		return ErrDB
	}
	return nil
}
<commit_msg>separate payment transaction callback method<commit_after>package payment

import (
	"database/sql"
	"github.com/fritzpay/paymentd/pkg/paymentd/payment"
	"github.com/fritzpay/paymentd/pkg/paymentd/payment_method"
	"github.com/fritzpay/paymentd/pkg/paymentd/project"
	"github.com/fritzpay/paymentd/pkg/server"
	"github.com/fritzpay/paymentd/pkg/service"
	"github.com/go-sql-driver/mysql"
	"gopkg.in/inconshreveable/log15.v2"
	"net/http"
	"time"
)

type errorID int

func (e errorID) Error() string {
	switch e {
	case ErrDB:
		return "database error"
	case ErrDBLockTimeout:
		return "lock wait timeout"
	case ErrDuplicateIdent:
		return "duplicate ident in payment"
	case ErrPaymentCallbackConfig:
		return "callback config error"
	case ErrPaymentMethodNotFound:
		return "payment method not found"
	case ErrPaymentMethodConflict:
		return "payment method project mismatch"
	case ErrPaymentMethodInactive:
		return "payment method inactive"
	case ErrInternal:
		return "internal error"
	default:
		return "unknown error"
	}
}

const (
	// general database error
	ErrDB errorID = iota
	// lock wait timeout
	ErrDBLockTimeout
	// duplicate Ident in payment
	ErrDuplicateIdent
	// callback config error
	ErrPaymentCallbackConfig
	// payment method not found
	ErrPaymentMethodNotFound
	// payment method project mismatch
	ErrPaymentMethodConflict
	// payment method inactive
	ErrPaymentMethodInactive
	// internal error
	ErrInternal
)

const (
	PaymentTokenMaxAgeDefault = time.Minute * 15
)

// Service is the payment service
type Service struct {
	ctx *service.Context
	log log15.Logger

	idCoder *payment.IDEncoder

	tr *http.Transport
	cl *http.Client
}

// NewService creates a new payment service
func NewService(ctx *service.Context) (*Service, error) {
	s := &Service{
		ctx: ctx,
		log: ctx.Log().New(log15.Ctx{
			"pkg": "github.com/fritzpay/paymentd/pkg/service/payment",
		}),
	}

	var err error
	cfg := ctx.Config()

	s.idCoder, err = payment.NewIDEncoder(cfg.Payment.PaymentIDEncPrime, cfg.Payment.PaymentIDEncXOR)
	if err != nil {
		s.log.Error("error initializing payment ID encoder", log15.Ctx{"err": err})
		return nil, err
	}

	s.tr = &http.Transport{}
	s.cl = &http.Client{
		Transport: s.tr,
	}

	go s.handleContext()

	return s, nil
}

func (s *Service) handleContext() {
	// if attached to a server, this will tell the server to wait with shutting down
	// until the cleanup process is complete
	server.Wait.Add(1)
	defer server.Wait.Done()
	for {
		select {
		case <-s.ctx.Done():
			s.log.Info("service context closed", log15.Ctx{"err": s.ctx.Err()})
			s.log.Info("closing idle connections...")
			s.tr.CloseIdleConnections()
			return
		}
	}
}

// EncodedPaymentID returns a payment id with the id part encoded
func (s *Service) EncodedPaymentID(id payment.PaymentID) payment.PaymentID {
	id.PaymentID = s.idCoder.Hide(id.PaymentID)
	return id
}

// DecodedPaymentID returns a payment id with the id part decoded
func (s *Service) DecodedPaymentID(id payment.PaymentID) payment.PaymentID {
	id.PaymentID = s.idCoder.Show(id.PaymentID)
	return id
}

// CreatePayment creates a new payment
func (s *Service) CreatePayment(tx *sql.Tx, p *payment.Payment) error {
	log := s.log.New(log15.Ctx{
		"method": "CreatePayment",
	})
	if p.Config.HasCallback() {
		callbackProjectKey, err := project.ProjectKeyByKeyTx(tx, p.Config.CallbackProjectKey.String)
		if err != nil {
			if err == project.ErrProjectKeyNotFound {
				log.Error("callback project key not found", log15.Ctx{"callbackProjectKey": p.Config.CallbackProjectKey.String})
				return ErrPaymentCallbackConfig
			}
			log.Error("error retrieving callback project key", log15.Ctx{"err": err})
			return ErrDB
		}
		if callbackProjectKey.Project.ID != p.ProjectID() {
			log.Error("callback project mismatch", log15.Ctx{
				"callbackProjectKey": callbackProjectKey.Key,
				"callbackProjectID":  callbackProjectKey.Project.ID,
				"projectID":          p.ProjectID(),
			})
			return ErrPaymentCallbackConfig
		}
	}
	err := payment.InsertPaymentTx(tx, p)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		_, existErr := payment.PaymentByProjectIDAndIdentTx(tx, p.ProjectID(), p.Ident)
		if existErr != nil && existErr != payment.ErrPaymentNotFound {
			log.Error("error on checking duplicate ident", log15.Ctx{"err": err})
			return ErrDB
		}
		// payment found => duplicate error
		if existErr == nil {
			return ErrDuplicateIdent
		}
		log.Error("error on insert payment", log15.Ctx{"err": err})
		return ErrDB
	}
	err = s.SetPaymentConfig(tx, p)
	if err != nil {
		return err
	}
	err = s.SetPaymentMetadata(tx, p)
	if err != nil {
		return err
	}
	return nil
}

func (s *Service) SetPaymentConfig(tx *sql.Tx, p *payment.Payment) error {
	log := s.log.New(log15.Ctx{"method": "SetPaymentConfig"})
	if p.Config.PaymentMethodID.Valid {
		log = log.New(log15.Ctx{"paymentMethodID": p.Config.PaymentMethodID.Int64})
		meth, err := payment_method.PaymentMethodByIDTx(tx, p.Config.PaymentMethodID.Int64)
		if err != nil {
			if mysqlErr, ok := err.(*mysql.MySQLError); ok {
				if mysqlErr.Number == 1213 {
					return ErrDBLockTimeout
				}
			}
			if err == payment_method.ErrPaymentMethodNotFound {
				log.Warn(ErrPaymentMethodNotFound.Error())
				return ErrPaymentMethodNotFound
			}
			log.Error("error on select payment method", log15.Ctx{"err": err})
			return ErrDB
		}
		if meth.ProjectID != p.ProjectID() {
			log.Warn(ErrPaymentMethodConflict.Error())
			return ErrPaymentMethodConflict
		}
		if meth.Status != payment_method.PaymentMethodStatusActive {
			log.Warn(ErrPaymentMethodInactive.Error())
			return ErrPaymentMethodInactive
		}
	}
	err := payment.InsertPaymentConfigTx(tx, p)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error on insert payment config", log15.Ctx{"err": err})
		return ErrDB
	}
	return nil
}

func (s *Service) SetPaymentMetadata(tx *sql.Tx, p *payment.Payment) error {
	log := s.log.New(log15.Ctx{"method": "SetPaymentMetadata"})
	// payment metadata
	if p.Metadata == nil {
		return nil
	}
	err := payment.InsertPaymentMetadataTx(tx, p)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error on insert payment metadata", log15.Ctx{"err": err})
		return ErrDB
	}
	return nil
}

// IsProcessablePayment returns true if the given payment is considered processable
//
// All required fields are present.
func (s *Service) IsProcessablePayment(p *payment.Payment) bool {
	if !p.Config.IsConfigured() {
		return false
	}
	if !p.Config.Country.Valid {
		return false
	}
	if !p.Config.Locale.Valid {
		return false
	}
	if !p.Config.PaymentMethodID.Valid {
		return false
	}
	return true
}

// IsInitialized returns true when the payment is in a processing state, i.e.
// when there is at least one transaction present
func (s *Service) IsInitialized(p *payment.Payment) bool {
	return p.Status != payment.PaymentStatusNone
}

func (s *Service) SetPaymentTransaction(tx *sql.Tx, paymentTx *payment.PaymentTransaction) error {
	log := s.log.New(log15.Ctx{"method": "SetPaymentTransaction"})
	err := payment.InsertPaymentTransactionTx(tx, paymentTx)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error saving payment transaction", log15.Ctx{"err": err})
		return ErrDB
	}
	err = s.CallbackPaymentTransaction(tx, paymentTx)
	if err != nil {
		return err
	}
	return nil
}

func (s *Service) CallbackPaymentTransaction(tx *sql.Tx, paymentTx *payment.PaymentTransaction) error {
	log := s.log.New(log15.Ctx{"method": "CallbackPaymentTransaction"})
	var callback Callbacker
	if CanCallback(&paymentTx.Payment.Config) {
		callback = &paymentTx.Payment.Config
	} else {
		pr, err := project.ProjectByIDTx(tx, paymentTx.Payment.ProjectID())
		if err != nil {
			if err == project.ErrProjectNotFound {
				log.Crit("payment with invalid project", log15.Ctx{"projectID": paymentTx.Payment.ProjectID()})
				return ErrInternal
			}
			log.Error("error retrieving project", log15.Ctx{"err": err})
			return ErrDB
		}
		if CanCallback(pr.Config) {
			callback = pr.Config
		}
	}
	if callback != nil {
		s.Notify(callback, paymentTx)
	}
	return nil
}

func (s *Service) PaymentTransaction(tx *sql.Tx, p *payment.Payment) (*payment.PaymentTransaction, error) {
	return payment.PaymentTransactionCurrentTx(tx, p)
}

func (s *Service) CreatePaymentToken(tx *sql.Tx, p *payment.Payment) (*payment.PaymentToken, error) {
	log := s.log.New(log15.Ctx{"method": "CreatePaymentToken"})
	token, err := payment.NewPaymentToken(p.PaymentID())
	if err != nil {
		log.Error("error creating payment token", log15.Ctx{"err": err})
		return nil, ErrInternal
	}
	err = payment.InsertPaymentTokenTx(tx, token)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return nil, ErrDBLockTimeout
			}
		}
		log.Error("error saving payment token", log15.Ctx{"err": err})
		return nil, ErrDB
	}
	return token, nil
}

// TODO use token max age from config
func (s *Service) PaymentByToken(tx *sql.Tx, token string) (*payment.Payment, error) {
	tokenMaxAge := PaymentTokenMaxAgeDefault
	return payment.PaymentByTokenTx(tx, token, tokenMaxAge)
}

func (s *Service) DeletePaymentToken(tx *sql.Tx, token string) error {
	log := s.log.New(log15.Ctx{"method": "DeletePaymentToken"})
	err := payment.DeletePaymentTokenTx(tx, token)
	if err != nil {
		if mysqlErr, ok := err.(*mysql.MySQLError); ok {
			if mysqlErr.Number == 1213 {
				return ErrDBLockTimeout
			}
		}
		log.Error("error deleting payment token", log15.Ctx{"err": err})
		return ErrDB
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 kubernetes

import (
	"bufio"
	"fmt"
	"io"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	restclient "k8s.io/client-go/rest"
)

const defaultRetry int = 5

var colors = []int{
	31, // red
	32, // green
	33, // yellow
	34, // blue
	35, // magenta
	36, // cyan
	37, // lightGray
	90, // darkGray
	91, // lightRed
	92, // lightGreen
	93, // lightYellow
	94, // lightBlue
	95, // lightPurple
	96, // lightCyan
	97, // white
}

// LogAggregator aggregates the logs for all the deployed pods.
type LogAggregator struct {
	Muter

	creationTime   time.Time
	output         io.Writer
	retries        int
	nextColorIndex int
	lockColor      sync.Mutex
}

// NewLogAggregator creates a new LogAggregator for a given output.
func NewLogAggregator(out io.Writer) *LogAggregator {
	return &LogAggregator{
		creationTime: time.Now(),
		output:       out,
		retries:      defaultRetry,
	}
}

const streamRetryDelay = 1 * time.Second

// TODO(@r2d4): Figure out how to mock this out. fake.NewSimpleClient
// won't mock out restclient.Request and will just return a nil stream.
var getStream = func(r *restclient.Request) (io.ReadCloser, error) {
	return r.Stream()
}

func (a *LogAggregator) StreamLogs(client corev1.CoreV1Interface, image string) {
	for i := 0; i < a.retries; i++ {
		if err := a.streamLogs(client, image); err != nil {
			logrus.Infof("Error getting logs %s", err)
		}
		time.Sleep(streamRetryDelay)
	}
}

// nolint: interfacer
func (a *LogAggregator) streamLogs(client corev1.CoreV1Interface, image string) error {
	pods, err := client.Pods("").List(meta_v1.ListOptions{
		IncludeUninitialized: true,
	})
	if err != nil {
		return errors.Wrap(err, "getting pods")
	}

	logrus.Infof("Looking for logs to stream for %s", image)
	for _, p := range pods.Items {
		for _, c := range p.Spec.Containers {
			logrus.Debugf("Found container %s with image %s", c.Name, c.Image)
			if c.Image != image {
				continue
			}

			logrus.Infof("Trying to stream logs from pod: %s container: %s", p.Name, c.Name)
			pods := client.Pods(p.Namespace)
			if err := WaitForPodReady(pods, p.Name); err != nil {
				return errors.Wrap(err, "waiting for pod ready")
			}
			req := pods.GetLogs(p.Name, &v1.PodLogOptions{
				Follow:    true,
				Container: c.Name,
				SinceTime: &meta_v1.Time{
					Time: a.creationTime,
				},
			})
			rc, err := getStream(req)
			if err != nil {
				return errors.Wrap(err, "setting up container log stream")
			}
			defer rc.Close()

			color := a.nextColor()

			header := fmt.Sprintf("\033[1;%dm[%s %s]\033[0m", color, p.Name, c.Name)
			if err := a.streamRequest(header, rc); err != nil {
				return errors.Wrap(err, "streaming request")
			}

			return nil
		}
	}

	return fmt.Errorf("Image %s not found", image)
}

func (a *LogAggregator) nextColor() int {
	a.lockColor.Lock()
	color := colors[a.nextColorIndex]
	a.nextColorIndex++
	a.lockColor.Unlock()

	return color
}

func (a *LogAggregator) streamRequest(header string, rc io.Reader) error {
	r := bufio.NewReader(rc)
	for {
		// Read up to newline
		line, err := r.ReadBytes('\n')
		if err == io.EOF {
			break
		}
		if err != nil {
			return errors.Wrap(err, "reading bytes from log stream")
		}

		if a.IsMuted() {
			continue
		}

		if _, err := fmt.Fprintf(a.output, "%s %s", header, line); err != nil {
			return errors.Wrap(err, "writing to out")
		}
	}
	logrus.Infof("%s exited", header)
	return nil
}

// Muter can be used to mute/unmute logs.
// It's safe to use in multiple go routines.
type Muter struct {
	muted int32
}

// Mute mutes the logs.
func (m *Muter) Mute() {
	atomic.StoreInt32(&m.muted, 1)
}

// Unmute unmute the logs.
func (m *Muter) Unmute() {
	atomic.StoreInt32(&m.muted, 0)
}

// IsMuted says if the logs are to be muted.
func (m *Muter) IsMuted() bool {
	return atomic.LoadInt32(&m.muted) == 1
}
<commit_msg>Inline Muter<commit_after>/*
Copyright 2018 Google LLC

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 kubernetes

import (
	"bufio"
	"fmt"
	"io"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	restclient "k8s.io/client-go/rest"
)

const defaultRetry int = 5

var colors = []int{
	31, // red
	32, // green
	33, // yellow
	34, // blue
	35, // magenta
	36, // cyan
	37, // lightGray
	90, // darkGray
	91, // lightRed
	92, // lightGreen
	93, // lightYellow
	94, // lightBlue
	95, // lightPurple
	96, // lightCyan
	97, // white
}

// LogAggregator aggregates the logs for all the deployed pods.
type LogAggregator struct {
	muted          int32
	creationTime   time.Time
	output         io.Writer
	retries        int
	nextColorIndex int
	lockColor      sync.Mutex
}

// NewLogAggregator creates a new LogAggregator for a given output.
func NewLogAggregator(out io.Writer) *LogAggregator {
	return &LogAggregator{
		creationTime: time.Now(),
		output:       out,
		retries:      defaultRetry,
	}
}

const streamRetryDelay = 1 * time.Second

// TODO(@r2d4): Figure out how to mock this out. fake.NewSimpleClient
// won't mock out restclient.Request and will just return a nil stream.
var getStream = func(r *restclient.Request) (io.ReadCloser, error) {
	return r.Stream()
}

func (a *LogAggregator) StreamLogs(client corev1.CoreV1Interface, image string) {
	for i := 0; i < a.retries; i++ {
		if err := a.streamLogs(client, image); err != nil {
			logrus.Infof("Error getting logs %s", err)
		}
		time.Sleep(streamRetryDelay)
	}
}

// nolint: interfacer
func (a *LogAggregator) streamLogs(client corev1.CoreV1Interface, image string) error {
	pods, err := client.Pods("").List(meta_v1.ListOptions{
		IncludeUninitialized: true,
	})
	if err != nil {
		return errors.Wrap(err, "getting pods")
	}

	logrus.Infof("Looking for logs to stream for %s", image)
	for _, p := range pods.Items {
		for _, c := range p.Spec.Containers {
			logrus.Debugf("Found container %s with image %s", c.Name, c.Image)
			if c.Image != image {
				continue
			}

			logrus.Infof("Trying to stream logs from pod: %s container: %s", p.Name, c.Name)
			pods := client.Pods(p.Namespace)
			if err := WaitForPodReady(pods, p.Name); err != nil {
				return errors.Wrap(err, "waiting for pod ready")
			}
			req := pods.GetLogs(p.Name, &v1.PodLogOptions{
				Follow:    true,
				Container: c.Name,
				SinceTime: &meta_v1.Time{
					Time: a.creationTime,
				},
			})
			rc, err := getStream(req)
			if err != nil {
				return errors.Wrap(err, "setting up container log stream")
			}
			defer rc.Close()

			color := a.nextColor()

			header := fmt.Sprintf("\033[1;%dm[%s %s]\033[0m", color, p.Name, c.Name)
			if err := a.streamRequest(header, rc); err != nil {
				return errors.Wrap(err, "streaming request")
			}

			return nil
		}
	}

	return fmt.Errorf("Image %s not found", image)
}

func (a *LogAggregator) nextColor() int {
	a.lockColor.Lock()
	color := colors[a.nextColorIndex]
	a.nextColorIndex++
	a.lockColor.Unlock()

	return color
}

func (a *LogAggregator) streamRequest(header string, rc io.Reader) error {
	r := bufio.NewReader(rc)
	for {
		// Read up to newline
		line, err := r.ReadBytes('\n')
		if err == io.EOF {
			break
		}
		if err != nil {
			return errors.Wrap(err, "reading bytes from log stream")
		}

		if a.IsMuted() {
			continue
		}

		if _, err := fmt.Fprintf(a.output, "%s %s", header, line); err != nil {
			return errors.Wrap(err, "writing to out")
		}
	}
	logrus.Infof("%s exited", header)
	return nil
}

// Mute mutes the logs.
func (a *LogAggregator) Mute() {
	atomic.StoreInt32(&a.muted, 1)
}

// Unmute unmute the logs.
func (a *LogAggregator) Unmute() {
	atomic.StoreInt32(&a.muted, 0)
}

// IsMuted says if the logs are to be muted.
func (a *LogAggregator) IsMuted() bool {
	return atomic.LoadInt32(&a.muted) == 1
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Skaffold 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 watch

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/rjeczalik/notify"

	"github.com/sirupsen/logrus"
)

// Trigger describes a mechanism that triggers the watch.
type Trigger interface {
	Start() (<-chan bool, func(), error)
	WatchForChanges(io.Writer)
	Debounce() bool
}

// NewTrigger creates a new trigger.
func NewTrigger(opts *config.SkaffoldOptions) (Trigger, error) {
	switch strings.ToLower(opts.Trigger) {
	case "polling":
		return &pollTrigger{
			Interval: time.Duration(opts.WatchPollInterval) * time.Millisecond,
		}, nil
	case "notify":
		return &fsNotifyTrigger{}, nil
	case "manual":
		return &manualTrigger{}, nil
	default:
		return nil, fmt.Errorf("unsupported type of trigger: %s", opts.Trigger)
	}
}

// pollTrigger watches for changes on a given interval of time.
type pollTrigger struct {
	Interval time.Duration
}

// Debounce tells the watcher to debounce rapid sequence of changes.
func (t *pollTrigger) Debounce() bool {
	return true
}

func (t *pollTrigger) WatchForChanges(out io.Writer) {
	color.Yellow.Fprintf(out, "Watching for changes every %v...\n", t.Interval)
}

// Start starts a timer.
func (t *pollTrigger) Start() (<-chan bool, func(), error) {
	trigger := make(chan bool)

	ticker := time.NewTicker(t.Interval)
	go func() {
		for {
			<-ticker.C
			trigger <- true
		}
	}()

	return trigger, ticker.Stop, nil
}

// manualTrigger watches for changes when the user presses a key.
type manualTrigger struct {
}

// Debounce tells the watcher to not debounce rapid sequence of changes.
func (t *manualTrigger) Debounce() bool {
	return false
}

func (t *manualTrigger) WatchForChanges(out io.Writer) {
	color.Yellow.Fprintln(out, "Press any key to rebuild/redeploy the changes")
}

// Start starts listening to pressed keys.
func (t *manualTrigger) Start() (<-chan bool, func(), error) {
	trigger := make(chan bool)

	reader := bufio.NewReader(os.Stdin)
	go func() {
		for {
			_, _, err := reader.ReadRune()
			if err != nil {
				logrus.Debugf("manual trigger error: %s", err)
			}
			trigger <- true
		}
	}()

	return trigger, func() {}, nil
}

// notifyTrigger watches for changes when fsnotify
type fsNotifyTrigger struct {
}

// Debounce tells the watcher to not debounce rapid sequence of changes.
func (t *fsNotifyTrigger) Debounce() bool {
	return false
}

func (t *fsNotifyTrigger) WatchForChanges(out io.Writer) {
	color.Yellow.Fprintln(out, "Watching for changes on directory")
}

// Start Listening for file system changes
func (t *fsNotifyTrigger) Start() (<-chan bool, func(), error) {
	trigger := make(chan bool)
	c := make(chan notify.EventInfo, 1)

	if err := notify.Watch("./...", c, notify.All); err != nil {
		return nil, nil, err
	}

	go func() {
		for {
			ei := <-c
			logrus.Infof("Triggering rebuild because of %s", filepath.Base(ei.Path()))
			trigger <- true
		}
	}()
	return trigger, func() {
		notify.Stop(c)
	}, nil
}
<commit_msg>Second round of changes based on dgageot feedback<commit_after>/*
Copyright 2018 The Skaffold 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 watch

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/rjeczalik/notify"
	"github.com/sirupsen/logrus"
)

// Trigger describes a mechanism that triggers the watch.
type Trigger interface {
	Start() (<-chan bool, func(), error)
	WatchForChanges(io.Writer)
	Debounce() bool
}

// NewTrigger creates a new trigger.
func NewTrigger(opts *config.SkaffoldOptions) (Trigger, error) {
	switch strings.ToLower(opts.Trigger) {
	case "polling":
		return &pollTrigger{
			Interval: time.Duration(opts.WatchPollInterval) * time.Millisecond,
		}, nil
	case "notify":
		return &fsNotifyTrigger{}, nil
	case "manual":
		return &manualTrigger{}, nil
	default:
		return nil, fmt.Errorf("unsupported type of trigger: %s", opts.Trigger)
	}
}

// pollTrigger watches for changes on a given interval of time.
type pollTrigger struct {
	Interval time.Duration
}

// Debounce tells the watcher to debounce rapid sequence of changes.
func (t *pollTrigger) Debounce() bool {
	return true
}

func (t *pollTrigger) WatchForChanges(out io.Writer) {
	color.Yellow.Fprintf(out, "Watching for changes every %v...\n", t.Interval)
}

// Start starts a timer.
func (t *pollTrigger) Start() (<-chan bool, func(), error) {
	trigger := make(chan bool)

	ticker := time.NewTicker(t.Interval)
	go func() {
		for {
			<-ticker.C
			trigger <- true
		}
	}()

	return trigger, ticker.Stop, nil
}

// manualTrigger watches for changes when the user presses a key.
type manualTrigger struct {
}

// Debounce tells the watcher to not debounce rapid sequence of changes.
func (t *manualTrigger) Debounce() bool {
	return false
}

func (t *manualTrigger) WatchForChanges(out io.Writer) {
	color.Yellow.Fprintln(out, "Press any key to rebuild/redeploy the changes")
}

// Start starts listening to pressed keys.
func (t *manualTrigger) Start() (<-chan bool, func(), error) {
	trigger := make(chan bool)

	reader := bufio.NewReader(os.Stdin)
	go func() {
		for {
			_, _, err := reader.ReadRune()
			if err != nil {
				logrus.Debugf("manual trigger error: %s", err)
			}
			trigger <- true
		}
	}()

	return trigger, func() {}, nil
}

// notifyTrigger watches for changes when fsnotify
type fsNotifyTrigger struct {
}

// Debounce tells the watcher to not debounce rapid sequence of changes.
func (t *fsNotifyTrigger) Debounce() bool {
	return false
}

func (t *fsNotifyTrigger) WatchForChanges(out io.Writer) {
	color.Yellow.Fprintln(out, "Watching for changes on directory")
}

// Start Listening for file system changes
func (t *fsNotifyTrigger) Start() (<-chan bool, func(), error) {
	trigger := make(chan bool)
	c := make(chan notify.EventInfo, 1)

	if err := notify.Watch("./...", c, notify.All); err != nil {
		return nil, nil, err
	}

	go func() {
		for {
			ei := <-c
			logrus.Infof("Triggering rebuild because of %s", filepath.Base(ei.Path()))
			trigger <- true
		}
	}()
	return trigger, func() {
		notify.Stop(c)
	}, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"io/ioutil"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

const (
	// New versions of github.com/aws/aws-sdk-go/aws have these consts
	// but the version currently pinned by bosh-cli v2 does not

	// ErrCodeNoSuchBucket for service response error code
	// "NoSuchBucket".
	//
	// The specified bucket does not exist.
	awsErrCodeNoSuchBucket = "NoSuchBucket"

	// ErrCodeNoSuchKey for service response error code
	// "NoSuchKey".
	//
	// The specified key does not exist.
	awsErrCodeNoSuchKey = "NoSuchKey"

	// Returned when calling HEAD on non-existant bucket or object
	awsErrCodeNotFound = "NotFound"
)

// DeleteVersionedBucket deletes and empties a versioned bucket
func DeleteVersionedBucket(name, region string) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	bucketVersioningStatus := "Suspended"
	_, err = client.PutBucketVersioning(&s3.PutBucketVersioningInput{
		Bucket: &name,
		VersioningConfiguration: &s3.VersioningConfiguration{
			Status: &bucketVersioningStatus,
		},
	})
	if err != nil {
		return err
	}

	// Delete all object versions
	versions := []*s3.ObjectVersion{}
	err = client.ListObjectVersionsPages(&s3.ListObjectVersionsInput{Bucket: &name},
		func(output *s3.ListObjectVersionsOutput, _ bool) bool {
			versions = append(versions, output.Versions...)

			return true
		})
	if err != nil {
		return err
	}

	for _, version := range versions {
		_, err = client.DeleteObject(&s3.DeleteObjectInput{
			Bucket:    &name,
			Key:       version.Key,
			VersionId: version.VersionId,
		})
		if err != nil {
			return nil
		}
	}

	_, err = client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &name})
	if err != nil {
		return err
	}

	return nil
}

// EnsureBucketExists checks if the named bucket exists and creates it if it doesn't
func EnsureBucketExists(name, region string) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	_, err = client.HeadBucket(&s3.HeadBucketInput{Bucket: &name})
	if err == nil {
		return nil
	}

	awsErrCode := err.(awserr.Error).Code()
	if awsErrCode != awsErrCodeNotFound && awsErrCode != awsErrCodeNoSuchBucket {
		return err
	}

	_, err = client.CreateBucket(&s3.CreateBucketInput{
		Bucket: &name,
		CreateBucketConfiguration: &s3.CreateBucketConfiguration{
			LocationConstraint: &region,
		},
	})
	if err != nil {
		return err
	}

	versioningStatus := "Enabled"
	_, err = client.PutBucketVersioning(&s3.PutBucketVersioningInput{
		Bucket: &name,
		VersioningConfiguration: &s3.VersioningConfiguration{
			Status: &versioningStatus,
		},
	})

	return err
}

// WriteFile writes the specified S3 object
func WriteFile(bucket, path, region string, contents []byte) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}
	client := s3.New(sess, &aws.Config{Region: &region})

	_, err = client.PutObject(&s3.PutObjectInput{
		Bucket: &bucket,
		Key:    &path,
		Body:   bytes.NewReader(contents),
	})
	return err
}

// HasFile returns true if the specified S3 object exists
func HasFile(bucket, path, region string) (bool, error) {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return false, err
	}
	client := s3.New(sess, &aws.Config{Region: &region})

	_, err = client.HeadObject(&s3.HeadObjectInput{Bucket: &bucket, Key: &path})
	if err != nil {
		awsErrCode := err.(awserr.Error).Code()
		if awsErrCode == awsErrCodeNotFound || awsErrCode == awsErrCodeNoSuchKey {
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// EnsureFileExists checks for the named file in S3 and creates it if it doesn't
// Second argument is true if new file was created
func EnsureFileExists(bucket, path, region string, defaultContents []byte) ([]byte, bool, error) {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return nil, false, err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	output, err := client.GetObject(&s3.GetObjectInput{Bucket: &bucket, Key: &path})
	if err == nil {
		var contents []byte
		contents, err = ioutil.ReadAll(output.Body)
		if err != nil {
			return nil, false, err
		}

		// Successfully loaded file
		return contents, true, nil
	}

	awsErrCode := err.(awserr.Error).Code()
	if awsErrCode != awsErrCodeNoSuchKey && awsErrCode != awsErrCodeNotFound {
		return nil, false, err
	}

	_, err = client.PutObject(&s3.PutObjectInput{
		Bucket: &bucket,
		Key:    &path,
		Body:   bytes.NewReader(defaultContents),
	})
	if err != nil {
		return nil, false, err
	}

	// Created file from given contents
	return defaultContents, true, nil
}

// LoadFile loads a file from S3
func LoadFile(bucket, path, region string) ([]byte, error) {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return nil, err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	output, err := client.GetObject(&s3.GetObjectInput{Bucket: &bucket, Key: &path})
	if err != nil {
		return nil, err
	}

	return ioutil.ReadAll(output.Body)
}

// DeleteFile deletes a file from S3
func DeleteFile(bucket, path, region string) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}

	client := s3.New(sess, &aws.Config{Region: &region})
	_, err = client.DeleteObject(&s3.DeleteObjectInput{
		Bucket: &bucket,
		Key:    &path,
	})

	return err
}
<commit_msg>WIP: attempt at deleting all objects in config bucket<commit_after>package aws

import (
	"bytes"
	"io/ioutil"

	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

const (
	// New versions of github.com/aws/aws-sdk-go/aws have these consts
	// but the version currently pinned by bosh-cli v2 does not

	// ErrCodeNoSuchBucket for service response error code
	// "NoSuchBucket".
	//
	// The specified bucket does not exist.
	awsErrCodeNoSuchBucket = "NoSuchBucket"

	// ErrCodeNoSuchKey for service response error code
	// "NoSuchKey".
	//
	// The specified key does not exist.
	awsErrCodeNoSuchKey = "NoSuchKey"

	// Returned when calling HEAD on non-existant bucket or object
	awsErrCodeNotFound = "NotFound"
)

// DeleteVersionedBucket deletes and empties a versioned bucket
func DeleteVersionedBucket(name, region string) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	bucketVersioningStatus := "Suspended"
	_, err = client.PutBucketVersioning(&s3.PutBucketVersioningInput{
		Bucket: &name,
		VersioningConfiguration: &s3.VersioningConfiguration{
			Status: &bucketVersioningStatus,
		},
	})
	if err != nil {
		return err
	}

	time.Sleep(time.Second)

	// Delete all object versions
	versions := []*s3.ObjectVersion{}
	err = client.ListObjectVersionsPages(&s3.ListObjectVersionsInput{Bucket: &name},
		func(output *s3.ListObjectVersionsOutput, _ bool) bool {
			versions = append(versions, output.Versions...)

			return true
		})
	if err != nil {
		return err
	}

	for _, version := range versions {
		_, err = client.DeleteObject(&s3.DeleteObjectInput{
			Bucket:    &name,
			Key:       version.Key,
			VersionId: version.VersionId,
		})
		if err != nil {
			return nil
		}
	}

	// Delete all objects
	objects := []*s3.Object{}
	err = client.ListObjectsPages(&s3.ListObjectsInput{Bucket: &name},
		func(output *s3.ListObjectsOutput, _ bool) bool {
			objects = append(objects, output.Contents...)

			return true
		})
	if err != nil {
		return err
	}

	for _, object := range objects {
		_, err = client.DeleteObject(&s3.DeleteObjectInput{
			Bucket: &name,
			Key:    object.Key,
		})
		if err != nil {
			return nil
		}
	}

	time.Sleep(time.Second)

	_, err = client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &name})
	if err != nil {
		return err
	}

	return nil
}

// EnsureBucketExists checks if the named bucket exists and creates it if it doesn't
func EnsureBucketExists(name, region string) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	_, err = client.HeadBucket(&s3.HeadBucketInput{Bucket: &name})
	if err == nil {
		return nil
	}

	awsErrCode := err.(awserr.Error).Code()
	if awsErrCode != awsErrCodeNotFound && awsErrCode != awsErrCodeNoSuchBucket {
		return err
	}

	_, err = client.CreateBucket(&s3.CreateBucketInput{
		Bucket: &name,
		CreateBucketConfiguration: &s3.CreateBucketConfiguration{
			LocationConstraint: &region,
		},
	})
	if err != nil {
		return err
	}

	versioningStatus := "Enabled"
	_, err = client.PutBucketVersioning(&s3.PutBucketVersioningInput{
		Bucket: &name,
		VersioningConfiguration: &s3.VersioningConfiguration{
			Status: &versioningStatus,
		},
	})

	return err
}

// WriteFile writes the specified S3 object
func WriteFile(bucket, path, region string, contents []byte) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}
	client := s3.New(sess, &aws.Config{Region: &region})

	_, err = client.PutObject(&s3.PutObjectInput{
		Bucket: &bucket,
		Key:    &path,
		Body:   bytes.NewReader(contents),
	})
	return err
}

// HasFile returns true if the specified S3 object exists
func HasFile(bucket, path, region string) (bool, error) {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return false, err
	}
	client := s3.New(sess, &aws.Config{Region: &region})

	_, err = client.HeadObject(&s3.HeadObjectInput{Bucket: &bucket, Key: &path})
	if err != nil {
		awsErrCode := err.(awserr.Error).Code()
		if awsErrCode == awsErrCodeNotFound || awsErrCode == awsErrCodeNoSuchKey {
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// EnsureFileExists checks for the named file in S3 and creates it if it doesn't
// Second argument is true if new file was created
func EnsureFileExists(bucket, path, region string, defaultContents []byte) ([]byte, bool, error) {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return nil, false, err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	output, err := client.GetObject(&s3.GetObjectInput{Bucket: &bucket, Key: &path})
	if err == nil {
		var contents []byte
		contents, err = ioutil.ReadAll(output.Body)
		if err != nil {
			return nil, false, err
		}

		// Successfully loaded file
		return contents, true, nil
	}

	awsErrCode := err.(awserr.Error).Code()
	if awsErrCode != awsErrCodeNoSuchKey && awsErrCode != awsErrCodeNotFound {
		return nil, false, err
	}

	_, err = client.PutObject(&s3.PutObjectInput{
		Bucket: &bucket,
		Key:    &path,
		Body:   bytes.NewReader(defaultContents),
	})
	if err != nil {
		return nil, false, err
	}

	// Created file from given contents
	return defaultContents, true, nil
}

// LoadFile loads a file from S3
func LoadFile(bucket, path, region string) ([]byte, error) {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return nil, err
	}

	client := s3.New(sess, &aws.Config{Region: &region})

	output, err := client.GetObject(&s3.GetObjectInput{Bucket: &bucket, Key: &path})
	if err != nil {
		return nil, err
	}

	return ioutil.ReadAll(output.Body)
}

// DeleteFile deletes a file from S3
func DeleteFile(bucket, path, region string) error {
	sess, err := session.NewSession(aws.NewConfig().WithCredentialsChainVerboseErrors(true))
	if err != nil {
		return err
	}

	client := s3.New(sess, &aws.Config{Region: &region})
	_, err = client.DeleteObject(&s3.DeleteObjectInput{
		Bucket: &bucket,
		Key:    &path,
	})

	return err
}
<|endoftext|>