<commit_before>package clideployment

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/containerum/chkit/pkg/context"
	"github.com/containerum/chkit/pkg/model/container"
	"github.com/containerum/chkit/pkg/model/deployment"
	"github.com/containerum/chkit/pkg/model/deployment/deplactive"
	"github.com/containerum/chkit/pkg/util/activekit"
	"github.com/containerum/chkit/pkg/util/namegen"
	"github.com/containerum/chkit/pkg/util/pairs"
	"github.com/containerum/chkit/pkg/util/text"
	"github.com/containerum/kube-client/pkg/model"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

func Create(ctx *context.Context) *cobra.Command {
	var file string
	var force bool
	var flagCont container.Container
	var flagDepl deployment.Deployment
	var envs []string
	command := &cobra.Command{
		Use:     "deployment",
		Aliases: aliases,
		Short:   "create new deployment",
		Long: `Creates new deployment.
Has an one-line mode, suitable for integration with other tools, and an interactive wizard mode`,
		Run: func(cmd *cobra.Command, args []string) {
			depl := deplactive.DefaultDeployment()
			if cmd.Flag("file").Changed {
				var err error
				depl, err = deplactive.FromFile(file)
				if err != nil {
					logrus.WithError(err).Errorf("unable to load deployment data from file %s", file)
					fmt.Printf("Unable to load deployment data from file :(\n%v", err)
					os.Exit(1)
				}
			} else if cmd.Flag("force").Changed {
				if cmd.Flag("env").Changed {
					envMap := map[string]string{}
					for _, env := range envs {
						penv, err := pairs.ParseMap(env, ":")
						if err != nil {
							fmt.Printf("invalid env flag\n")
							os.Exit(1)
						}
						for k, v := range penv {
							envMap[k] = v
						}
					}

					for k, v := range envMap {
						flagCont.Env = append(flagCont.Env, model.Env{
							Name:  k,
							Value: v,
						})
					}
				}
				if flagCont.Name == "" {
					flagCont.Name = namegen.Aster() + "-" + flagCont.Image
				}
				flagDepl.Containers = []container.Container{flagCont}
				depl = flagDepl
			}
			if cmd.Flag("force").Changed {
				if err := deplactive.ValidateDeployment(depl); err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
				fmt.Println(depl.RenderTable())
				if err := ctx.Client.CreateDeployment(ctx.Namespace, depl); err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
				fmt.Println("OK")
				return
			}
			depl, err := deplactive.Wizard(deplactive.Config{
				Deployment: &depl,
			})
			if err != nil {
				logrus.WithError(err).Errorf("unable to create deployment")
				fmt.Println(err)
				os.Exit(1)
			}

			var firstItem *activekit.MenuItem
			var created = false
			if activekit.YesNo("Are you sure?") {
				if err := ctx.Client.CreateDeployment(ctx.Namespace, depl); err != nil {
					logrus.WithError(err).Errorf("unable to create deployment %q", depl.Name)
					activekit.Attention(err.Error())
					os.Exit(1)
				}
				fmt.Printf("Congratulations! Deployment %q created!\n", depl.Name)
				created = true
				firstItem = &activekit.MenuItem{
					Label: "Push changes to server",
					Action: func() error {
						if activekit.YesNo("Are you sure?") {
							err := ctx.Client.ReplaceDeployment(ctx.Namespace, depl)
							if err != nil {
								logrus.WithError(err).Errorf("unable to update deployment %q", depl.Name)
								fmt.Println(err)
								return nil
							}
							fmt.Printf("Congratulations! Deployment %q updated!\n", depl.Name)
						}
						return nil
					},
				}
			} else {
				firstItem = &activekit.MenuItem{
					Label: "Create deployment on server",
					Action: func() error {
						if activekit.YesNo("Are you sure?") {
							err := ctx.Client.CreateDeployment(ctx.Namespace, depl)
							if err != nil {
								logrus.WithError(err).Errorf("unable to update deployment %q", depl.Name)
								fmt.Println(err)
								return nil
							}
							fmt.Printf("Congratulations! Deployment %q created!\n", depl.Name)
							created = true
						}
						return nil
					},
				}
			}
			for {
				deploymentMenu(ctx, depl, firstItem).Run()
				if created {
					firstItem = &activekit.MenuItem{
						Label: "Push changes to server",
						Action: func() error {
							if activekit.YesNo("Are you sure?") {
								err := ctx.Client.ReplaceDeployment(ctx.Namespace, depl)
								if err != nil {
									logrus.WithError(err).Errorf("unable to update deployment %q", depl.Name)
									fmt.Println(err)
									return nil
								}
								fmt.Printf("Congratulations! Deployment %q updated!\n", depl.Name)
							}
							return nil
						},
					}
				}
			}
		},
	}
	command.PersistentFlags().
		StringVar(&file, "file", "", "create deployment from file")
	command.PersistentFlags().
		BoolVarP(&force, "force", "f", false, "suppress confirmation")

	command.PersistentFlags().
		StringVar(&flagDepl.Name, "deployment-name", namegen.Color()+"-"+namegen.Aster(), "deployment name, optional")
	command.PersistentFlags().
		IntVar(&flagDepl.Replicas, "replicas", 1, "replicas, optional")
	command.PersistentFlags().
		StringVar(&flagCont.Name, "container-name", "", "container name, equal to image name by default")
	command.PersistentFlags().
		StringVar(&flagCont.Image, "image", "", "container image, required")
	command.PersistentFlags().
		UintVar(&flagCont.Limits.Memory, "memory", 256, "container memory limit im Mb, optional")
	command.PersistentFlags().
		UintVar(&flagCont.Limits.CPU, "cpu", 200, "container CPU limit in mCPU, optional")
	command.PersistentFlags().
		StringSliceVar(&flagCont.Commands, "commands", nil, "container commands")
	command.PersistentFlags().
		StringSliceVar(&envs, "env", []string{}, "container env variable in KEY0:VALUE0 KEY1:VALUE1 format")
	return command
}

func deploymentMenu(ctx *context.Context, depl deployment.Deployment, firstItem *activekit.MenuItem) *activekit.Menu {
	return &activekit.Menu{
		Items: []*activekit.MenuItem{
			firstItem,
			{
				Label: "Edit deployment",
				Action: func() error {
					var err error
					depl, err = deplactive.ReplaceWizard(deplactive.Config{
						Deployment: &depl,
					})
					if err != nil {
						logrus.WithError(err).Errorf("unable to create deployment")
						fmt.Println(err)
						os.Exit(1)
					}
					return nil
				},
			},
			{
				Label: "Print to terminal",
				Action: activekit.ActionWithErr(func() error {
					if data, err := depl.RenderYAML(); err != nil {
						return err
					} else {
						upBorders := strings.Repeat("_", text.Width(data))
						downBorders := strings.Repeat("_", text.Width(data))
						fmt.Printf("%s\n\n%s\n%s\n", upBorders, data, downBorders)
					}
					return nil
				}),
			},
			{
				Label: "Save to file",
				Action: func() error {
					filename, _ := activekit.AskLine("Print filename: ")
					data, err := depl.RenderJSON()
					if err != nil {
						return err
					}
					if err := ioutil.WriteFile(filename, []byte(data), os.ModePerm); err != nil {
						logrus.WithError(err).Errorf("unable to save deployment %q to file", depl.Name)
						fmt.Printf("Unable to save deployment to file :(\n%v", err)
						return nil
					}
					fmt.Printf("OK\n")
					return nil
				},
			},
			{
				Label: "Exit",
				Action: func() error {
					if yes, _ := activekit.Yes("Are you sure you want to exit?"); yes {
						os.Exit(0)
					}
					return nil
				},
			},
		},
	}
}
<commit_msg>fix env flags parsing in deployment creation<commit_after>package clideployment

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/containerum/chkit/pkg/context"
	"github.com/containerum/chkit/pkg/model/container"
	"github.com/containerum/chkit/pkg/model/deployment"
	"github.com/containerum/chkit/pkg/model/deployment/deplactive"
	"github.com/containerum/chkit/pkg/util/activekit"
	"github.com/containerum/chkit/pkg/util/namegen"
	"github.com/containerum/chkit/pkg/util/text"
	"github.com/containerum/kube-client/pkg/model"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

func Create(ctx *context.Context) *cobra.Command {
	var file string
	var force bool
	var flagCont container.Container
	var flagDepl deployment.Deployment
	var envs []string
	command := &cobra.Command{
		Use:     "deployment",
		Aliases: aliases,
		Short:   "create new deployment",
		Long: `Creates new deployment.
Has an one-line mode, suitable for integration with other tools, and an interactive wizard mode`,
		Run: func(cmd *cobra.Command, args []string) {
			depl := deplactive.DefaultDeployment()
			if cmd.Flag("file").Changed {
				var err error
				depl, err = deplactive.FromFile(file)
				if err != nil {
					logrus.WithError(err).Errorf("unable to load deployment data from file %s", file)
					fmt.Printf("Unable to load deployment data from file :(\n%v", err)
					os.Exit(1)
				}
			} else if cmd.Flag("force").Changed {
				if cmd.Flag("env").Changed {
					envMap := map[string]string{}
					for _, env := range envs {
						var tokens = strings.SplitN(env, ":", 2)
						if len(tokens) != 2 {
							fmt.Printf("Inavlid env kev-value pair %q", env)
							os.Exit(1)
						}
						var k, v = tokens[0], tokens[1]
						v = strings.TrimPrefix(k, "\"")
						v = strings.TrimSuffix(k, "\"")
						envMap[k] = v
					}
					for k, v := range envMap {
						flagCont.Env = append(flagCont.Env, model.Env{
							Name:  k,
							Value: v,
						})
					}
				}
				if flagCont.Name == "" {
					flagCont.Name = namegen.Aster() + "-" + flagCont.Image
				}
				flagDepl.Containers = []container.Container{flagCont}
				depl = flagDepl
			}
			if cmd.Flag("force").Changed {
				if err := deplactive.ValidateDeployment(depl); err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
				fmt.Println(depl.RenderTable())
				if err := ctx.Client.CreateDeployment(ctx.Namespace, depl); err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
				fmt.Println("OK")
				return
			}
			depl, err := deplactive.Wizard(deplactive.Config{
				Deployment: &depl,
			})
			if err != nil {
				logrus.WithError(err).Errorf("unable to create deployment")
				fmt.Println(err)
				os.Exit(1)
			}

			var firstItem *activekit.MenuItem
			var created = false
			if activekit.YesNo("Are you sure?") {
				if err := ctx.Client.CreateDeployment(ctx.Namespace, depl); err != nil {
					logrus.WithError(err).Errorf("unable to create deployment %q", depl.Name)
					activekit.Attention(err.Error())
					os.Exit(1)
				}
				fmt.Printf("Congratulations! Deployment %q created!\n", depl.Name)
				created = true
				firstItem = &activekit.MenuItem{
					Label: "Push changes to server",
					Action: func() error {
						if activekit.YesNo("Are you sure?") {
							err := ctx.Client.ReplaceDeployment(ctx.Namespace, depl)
							if err != nil {
								logrus.WithError(err).Errorf("unable to update deployment %q", depl.Name)
								fmt.Println(err)
								return nil
							}
							fmt.Printf("Congratulations! Deployment %q updated!\n", depl.Name)
						}
						return nil
					},
				}
			} else {
				firstItem = &activekit.MenuItem{
					Label: "Create deployment on server",
					Action: func() error {
						if activekit.YesNo("Are you sure?") {
							err := ctx.Client.CreateDeployment(ctx.Namespace, depl)
							if err != nil {
								logrus.WithError(err).Errorf("unable to update deployment %q", depl.Name)
								fmt.Println(err)
								return nil
							}
							fmt.Printf("Congratulations! Deployment %q created!\n", depl.Name)
							created = true
						}
						return nil
					},
				}
			}
			for {
				deploymentMenu(ctx, depl, firstItem).Run()
				if created {
					firstItem = &activekit.MenuItem{
						Label: "Push changes to server",
						Action: func() error {
							if activekit.YesNo("Are you sure?") {
								err := ctx.Client.ReplaceDeployment(ctx.Namespace, depl)
								if err != nil {
									logrus.WithError(err).Errorf("unable to update deployment %q", depl.Name)
									fmt.Println(err)
									return nil
								}
								fmt.Printf("Congratulations! Deployment %q updated!\n", depl.Name)
							}
							return nil
						},
					}
				}
			}
		},
	}
	command.PersistentFlags().
		StringVar(&file, "file", "", "create deployment from file")
	command.PersistentFlags().
		BoolVarP(&force, "force", "f", false, "suppress confirmation")

	command.PersistentFlags().
		StringVar(&flagDepl.Name, "deployment-name", namegen.Color()+"-"+namegen.Aster(), "deployment name, optional")
	command.PersistentFlags().
		IntVar(&flagDepl.Replicas, "replicas", 1, "replicas, optional")
	command.PersistentFlags().
		StringVar(&flagCont.Name, "container-name", "", "container name, equal to image name by default")
	command.PersistentFlags().
		StringVar(&flagCont.Image, "image", "", "container image, required")
	command.PersistentFlags().
		UintVar(&flagCont.Limits.Memory, "memory", 256, "container memory limit im Mb, optional")
	command.PersistentFlags().
		UintVar(&flagCont.Limits.CPU, "cpu", 200, "container CPU limit in mCPU, optional")
	command.PersistentFlags().
		StringSliceVar(&flagCont.Commands, "commands", nil, "container commands")
	command.PersistentFlags().
		StringSliceVar(&envs, "env", []string{}, "container env variable in KEY0:VALUE0 KEY1:VALUE1 format")
	return command
}

func deploymentMenu(ctx *context.Context, depl deployment.Deployment, firstItem *activekit.MenuItem) *activekit.Menu {
	return &activekit.Menu{
		Items: []*activekit.MenuItem{
			firstItem,
			{
				Label: "Edit deployment",
				Action: func() error {
					var err error
					depl, err = deplactive.ReplaceWizard(deplactive.Config{
						Deployment: &depl,
					})
					if err != nil {
						logrus.WithError(err).Errorf("unable to create deployment")
						fmt.Println(err)
						os.Exit(1)
					}
					return nil
				},
			},
			{
				Label: "Print to terminal",
				Action: activekit.ActionWithErr(func() error {
					if data, err := depl.RenderYAML(); err != nil {
						return err
					} else {
						upBorders := strings.Repeat("_", text.Width(data))
						downBorders := strings.Repeat("_", text.Width(data))
						fmt.Printf("%s\n\n%s\n%s\n", upBorders, data, downBorders)
					}
					return nil
				}),
			},
			{
				Label: "Save to file",
				Action: func() error {
					filename, _ := activekit.AskLine("Print filename: ")
					data, err := depl.RenderJSON()
					if err != nil {
						return err
					}
					if err := ioutil.WriteFile(filename, []byte(data), os.ModePerm); err != nil {
						logrus.WithError(err).Errorf("unable to save deployment %q to file", depl.Name)
						fmt.Printf("Unable to save deployment to file :(\n%v", err)
						return nil
					}
					fmt.Printf("OK\n")
					return nil
				},
			},
			{
				Label: "Exit",
				Action: func() error {
					if yes, _ := activekit.Yes("Are you sure you want to exit?"); yes {
						os.Exit(0)
					}
					return nil
				},
			},
		},
	}
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"
	"time"

	"github.com/golang/glog"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"

	platform "kolihub.io/koli/pkg/apis/v1alpha1"
	draft "kolihub.io/koli/pkg/apis/v1alpha1/draft"
)

// AppManagerController controller
type AppManagerController struct {
	kclient kubernetes.Interface
	// sysClient clientset.CoreInterface

	dpInf   cache.SharedIndexInformer
	planInf cache.SharedIndexInformer

	queue    *TaskQueue
	recorder record.EventRecorder
}

// NewAppManagerController creates a ResourceAllocatorCtrl
func NewAppManagerController(dpInf, planInf cache.SharedIndexInformer, client kubernetes.Interface) *AppManagerController {
	c := &AppManagerController{
		kclient:  client,
		dpInf:    dpInf,
		planInf:  planInf,
		recorder: newRecorder(client, "app-manager-controller"),
	}
	c.queue = NewTaskQueue(c.syncHandler)

	c.dpInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.addDeployment,
		UpdateFunc: c.updateDeployment,
		DeleteFunc: c.deleteDeployment,
	})

	return c
}

func (c *AppManagerController) addDeployment(d interface{}) {
	new := d.(*v1beta1.Deployment)
	glog.Infof("add-deployment(%d) - %s/%s", new.Status.ObservedGeneration, new.Namespace, new.Name)
	c.queue.Add(new)
}

func (c *AppManagerController) updateDeployment(o, n interface{}) {
	old := o.(*v1beta1.Deployment)
	new := n.(*v1beta1.Deployment)

	if old.ResourceVersion == new.ResourceVersion {
		return
	}
	c.queue.Add(new)
}

func (c *AppManagerController) deleteDeployment(o interface{}) {
	obj := o.(*v1beta1.Deployment)
	c.queue.Add(obj)
}

// enqueueForNamespace enqueues all Deployments object keys that belong to the given namespace.
func (c *AppManagerController) enqueueForNamespace(namespace string) {
	cache.ListAll(c.dpInf.GetStore(), labels.Everything(), func(obj interface{}) {
		d := obj.(*v1beta1.Deployment)
		if d.Namespace == namespace {
			c.queue.Add(d)
		}
	})
}

// Run the controller.
func (c *AppManagerController) Run(workers int, stopc <-chan struct{}) {
	// don't let panics crash the process
	defer utilruntime.HandleCrash()
	// make sure the work queue is shutdown which will trigger workers to end
	defer c.queue.shutdown()

	glog.Info("Starting App Manager Controller...")

	if !cache.WaitForCacheSync(stopc, c.dpInf.HasSynced, c.planInf.HasSynced) {
		return
	}

	for i := 0; i < workers; i++ {
		// runWorker will loop until "something bad" happens.
		// The .Until will then rekick the worker after one second
		go c.queue.run(time.Second, stopc)
		// go wait.Until(c.runWorker, time.Second, stopc)
	}

	// wait until we're told to stop
	<-stopc
	glog.Info("Shutting down App Manager controller")
}

// TODO: validate if it's a platform resource - OK
// TODO: test with an empty plan - OK
// TODO: Error when creating PVC
// TODO: test with already exist error PVC

func (c *AppManagerController) syncHandler(key string) error {
	obj, exists, err := c.dpInf.GetStore().GetByKey(key)
	if err != nil {
		glog.Warningf("%s - failed retrieving object from store [%s]", key, err)
		return err
	}
	if !exists {
		glog.V(3).Infof("%s - the deployment doesn't exists", key)
		return nil
	}

	d := draft.NewDeployment(obj.(*v1beta1.Deployment))
	if d.DeletionTimestamp != nil {
		glog.V(3).Infof("%s - object marked for deletion", key)
		return nil
	}

	if !d.GetNamespaceMetadata().Valid() {
		glog.V(3).Infof("%s - it's not a valid resource", key)
		return nil
	}

	if len(d.Spec.Template.Spec.Containers) > 0 {
		if d.Spec.Template.Spec.Containers[0].Resources.Requests == nil ||
			d.Spec.Template.Spec.Containers[0].Resources.Limits == nil {
			glog.Warningf("%s - deployment has empty 'limits' or 'requests' resources", key)
		}
	}

	planName, exists := d.GetStoragePlan().Value()
	if !exists || !d.HasSetupPVCAnnotation() {
		glog.V(3).Infof("%s - the object doesn't have a storage plan or an annotation to setup a PVC", key)
		return nil
	}
	var plan *platform.Plan
	cache.ListAll(c.planInf.GetStore(), labels.Everything(), func(obj interface{}) {
		p := obj.(*platform.Plan)
		if p.Name == planName && p.IsStorageType() {
			plan = p
		}
	})
	if plan == nil {
		msg := fmt.Sprintf(`Storage Plan "%s" not found`, planName)
		c.recorder.Event(&d.Deployment, v1.EventTypeWarning, "PlanNotFound", msg)
		return fmt.Errorf(msg)
	}
	_, err = c.kclient.Core().PersistentVolumeClaims(d.Namespace).Create(newPVC(d, plan))
	if err != nil && !apierrors.IsAlreadyExists(err) {
		msg := fmt.Sprintf(`Failed creating PVC [%v]`, err)
		c.recorder.Event(d, v1.EventTypeWarning, "ProvisionError", msg)
		return fmt.Errorf(msg)
	}

	glog.Infof(`%s - PVC "d-%s" created with "%s"`, key, d.Name, plan.Spec.Storage)
	patchData := []byte(fmt.Sprintf(`{"metadata": {"annotations": {"%s": "false"}}}`, platform.AnnotationSetupStorage))
	_, err = c.kclient.Extensions().Deployments(d.Namespace).Patch(d.Name, types.MergePatchType, patchData)
	if err != nil {
		return fmt.Errorf(`%s - failed updating deployment [%v]`, key, err)
	}
	return nil
}

func newPVC(d *draft.Deployment, plan *platform.Plan) *v1.PersistentVolumeClaim {
	return &v1.PersistentVolumeClaim{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("d-%s", d.Name),
			Namespace: d.Namespace,
		},
		Spec: v1.PersistentVolumeClaimSpec{
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
			Resources: v1.ResourceRequirements{
				Requests: v1.ResourceList{v1.ResourceStorage: plan.Spec.Storage},
			},
		},
	}
}
<commit_msg>Correcting logging typo<commit_after>package controller

import (
	"fmt"
	"time"

	"github.com/golang/glog"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"

	platform "kolihub.io/koli/pkg/apis/v1alpha1"
	draft "kolihub.io/koli/pkg/apis/v1alpha1/draft"
)

// AppManagerController controller
type AppManagerController struct {
	kclient kubernetes.Interface
	// sysClient clientset.CoreInterface

	dpInf   cache.SharedIndexInformer
	planInf cache.SharedIndexInformer

	queue    *TaskQueue
	recorder record.EventRecorder
}

// NewAppManagerController creates a ResourceAllocatorCtrl
func NewAppManagerController(dpInf, planInf cache.SharedIndexInformer, client kubernetes.Interface) *AppManagerController {
	c := &AppManagerController{
		kclient:  client,
		dpInf:    dpInf,
		planInf:  planInf,
		recorder: newRecorder(client, "app-manager-controller"),
	}
	c.queue = NewTaskQueue(c.syncHandler)

	c.dpInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    c.addDeployment,
		UpdateFunc: c.updateDeployment,
		DeleteFunc: c.deleteDeployment,
	})

	return c
}

func (c *AppManagerController) addDeployment(d interface{}) {
	new := d.(*v1beta1.Deployment)
	glog.Infof("add-deployment(%d) - %s/%s", new.Status.ObservedGeneration, new.Namespace, new.Name)
	c.queue.Add(new)
}

func (c *AppManagerController) updateDeployment(o, n interface{}) {
	old := o.(*v1beta1.Deployment)
	new := n.(*v1beta1.Deployment)

	if old.ResourceVersion == new.ResourceVersion {
		return
	}
	c.queue.Add(new)
}

func (c *AppManagerController) deleteDeployment(o interface{}) {
	obj := o.(*v1beta1.Deployment)
	c.queue.Add(obj)
}

// enqueueForNamespace enqueues all Deployments object keys that belong to the given namespace.
func (c *AppManagerController) enqueueForNamespace(namespace string) {
	cache.ListAll(c.dpInf.GetStore(), labels.Everything(), func(obj interface{}) {
		d := obj.(*v1beta1.Deployment)
		if d.Namespace == namespace {
			c.queue.Add(d)
		}
	})
}

// Run the controller.
func (c *AppManagerController) Run(workers int, stopc <-chan struct{}) {
	// don't let panics crash the process
	defer utilruntime.HandleCrash()
	// make sure the work queue is shutdown which will trigger workers to end
	defer c.queue.shutdown()

	glog.Info("Starting App Manager Controller...")

	if !cache.WaitForCacheSync(stopc, c.dpInf.HasSynced, c.planInf.HasSynced) {
		return
	}

	for i := 0; i < workers; i++ {
		// runWorker will loop until "something bad" happens.
		// The .Until will then rekick the worker after one second
		go c.queue.run(time.Second, stopc)
		// go wait.Until(c.runWorker, time.Second, stopc)
	}

	// wait until we're told to stop
	<-stopc
	glog.Info("Shutting down App Manager controller")
}

// TODO: validate if it's a platform resource - OK
// TODO: test with an empty plan - OK
// TODO: Error when creating PVC
// TODO: test with already exist error PVC

func (c *AppManagerController) syncHandler(key string) error {
	obj, exists, err := c.dpInf.GetStore().GetByKey(key)
	if err != nil {
		glog.Warningf("%s - failed retrieving object from store [%s]", key, err)
		return err
	}
	if !exists {
		glog.V(3).Infof("%s - the deployment doesn't exists", key)
		return nil
	}

	d := draft.NewDeployment(obj.(*v1beta1.Deployment))
	if d.DeletionTimestamp != nil {
		glog.V(3).Infof("%s - object marked for deletion", key)
		return nil
	}

	if !d.GetNamespaceMetadata().Valid() {
		glog.V(3).Infof("%s - it's not a valid resource", key)
		return nil
	}

	if len(d.Spec.Template.Spec.Containers) > 0 {
		if d.Spec.Template.Spec.Containers[0].Resources.Requests == nil ||
			d.Spec.Template.Spec.Containers[0].Resources.Limits == nil {
			glog.Warningf("%s - deployment has empty 'limits' or 'requests' resources", key)
		}
	}

	planName, exists := d.GetStoragePlan().Value()
	if !exists || !d.HasSetupPVCAnnotation() {
		glog.V(3).Infof("%s - the object doesn't have a storage plan or an annotation to setup a PVC", key)
		return nil
	}
	var plan *platform.Plan
	cache.ListAll(c.planInf.GetStore(), labels.Everything(), func(obj interface{}) {
		p := obj.(*platform.Plan)
		if p.Name == planName && p.IsStorageType() {
			plan = p
		}
	})
	if plan == nil {
		msg := fmt.Sprintf(`Storage Plan "%s" not found`, planName)
		c.recorder.Event(&d.Deployment, v1.EventTypeWarning, "PlanNotFound", msg)
		return fmt.Errorf(msg)
	}
	_, err = c.kclient.Core().PersistentVolumeClaims(d.Namespace).Create(newPVC(d, plan))
	if err != nil && !apierrors.IsAlreadyExists(err) {
		msg := fmt.Sprintf(`Failed creating PVC [%v]`, err)
		c.recorder.Event(d, v1.EventTypeWarning, "ProvisionError", msg)
		return fmt.Errorf(msg)
	}

	glog.Infof(`%s - PVC "d-%s" created with "%s"`, key, d.Name, plan.Spec.Storage.String())
	patchData := []byte(fmt.Sprintf(`{"metadata": {"annotations": {"%s": "false"}}}`, platform.AnnotationSetupStorage))
	_, err = c.kclient.Extensions().Deployments(d.Namespace).Patch(d.Name, types.MergePatchType, patchData)
	if err != nil {
		return fmt.Errorf(`%s - failed updating deployment [%v]`, key, err)
	}
	return nil
}

func newPVC(d *draft.Deployment, plan *platform.Plan) *v1.PersistentVolumeClaim {
	return &v1.PersistentVolumeClaim{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("d-%s", d.Name),
			Namespace: d.Namespace,
		},
		Spec: v1.PersistentVolumeClaimSpec{
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
			Resources: v1.ResourceRequirements{
				Requests: v1.ResourceList{v1.ResourceStorage: plan.Spec.Storage},
			},
		},
	}
}
<|endoftext|>


<commit_before>// Package event contains functions relating to IRC numeric events.
package event

import (
	"fmt"
	"log"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/n7st/quoteDB/pkg/quoteDB/helper"
	"github.com/n7st/quoteDB/pkg/quoteDB/model"

	"github.com/thoj/go-ircevent"
)

const (
	// Bot commands
	quoteLastCmd = "quotelast"
	addQuoteCmd  = "addquote"
	quoteHelpCmd = "quotehelp"
	quotePageCmd = "quotepage"
)

// callbackPrivmsg() runs when the bot receives a message. Every message is
// stored so it can be cycled through to build a quote.
func (q *EventFnProvider) callbackPrivmsg(e *irc.Event) {
	channel := e.Arguments[0]

	if e.Nick != q.qb.Config.Nickname {
		// Log message information for reading back later
		q.qb.History[channel] = append(q.qb.History[channel], map[string]string{
			"nick":      e.Nick,
			"message":   e.Message(),
			"timestamp": time.Now().String(),
		})

		maxLen := q.qb.Config.MaxQuoteSize
		currentLen := len(q.qb.History[channel])

		if currentLen > maxLen {
			// Trim messages past max length out of the slice
			newStart := currentLen - maxLen
			q.qb.History[channel] = q.qb.History[channel][newStart:]
		}
	}

	if q.isCommandAttempt(e.Message()) {
		q.handleCommand(e)
	}
}

// isCommandAttempt() checks if a given message looks like a bot command.
func (q *EventFnProvider) isCommandAttempt(message string) bool {
	if strings.HasPrefix(message, q.qb.Config.Trigger) {
		return true
	}

	return false
}

// handleCommand() organises arguments to a command and calls a function to run
// it.
func (q *EventFnProvider) handleCommand(e *irc.Event) {
	args := strings.Split(e.Message(), " ")

	command := strings.TrimPrefix(args[0], q.qb.Config.Trigger)
	argsWithoutCmd := strings.Join(args[1:], " ")

	switch command {
	case addQuoteCmd:
		if argsWithoutCmd != "" {
			q.parseAddQuote(e, argsWithoutCmd)
		}
	case quoteLastCmd:
		if argsWithoutCmd != "" {
			q.parseQuoteLast(e, argsWithoutCmd)
		}
	case quoteHelpCmd:
		q.parseQuoteHelp(e)
	case quotePageCmd:
		q.parseQuotePage(e)
	}
}

func (q EventFnProvider) parseQuoteLast(e *irc.Event, argsWithoutCmd string) {
	histLen, err := strconv.Atoi(argsWithoutCmd)

	if err == nil && histLen > 0 {
		channel := e.Arguments[0]
		lines := helper.LastNLinesFromHistory(q.qb.History[channel], histLen)

		if len(lines) > 0 {
		}
	} else {
		q.qb.IRC.Privmsgf(e.Arguments[0], "%s is not a positive integer", argsWithoutCmd)
	}
}

// parseQuoteHelp() is run by the "quotehelp" command and displays commands
// available to users of the bot.
func (q EventFnProvider) parseQuoteHelp(e *irc.Event) {
	q.qb.IRC.Privmsgf(e.Arguments[0], "Commands: %saddquote, %squotepage",
		q.qb.Config.Trigger, q.qb.Config.Trigger)
}

// parseQuotePage() runs the "quotepage" command which displays the URL for the
// web output page.
func (q EventFnProvider) parseQuotePage(e *irc.Event) {
	channel := url.PathEscape(e.Arguments[0])
	loc := fmt.Sprintf("%schannel/%s", q.qb.Config.BaseURL, channel)

	q.qb.IRC.Privmsgf(e.Arguments[0], "Quotes for this channel can be found at %s", loc)
}

// parseAddQuote() handles the "addquote" command and needs refactoring.
func (q EventFnProvider) parseAddQuote(e *irc.Event, argsWithoutCmd string) {
	channel := e.Arguments[0]
	options := helper.OptionsFromString(argsWithoutCmd)

	if len(options) < 1 || options[0] == "" || (len(options) == 2 && options[1] == "") {
		q.qb.IRC.Privmsg(channel, `The 'addquote' command requires one or two arguments ("start string" [and "end string"])`)
		return
	}

	if len(options) == 1 {
		// make the start and end the same, just select one line
		options = append(options, options[0])
	}

	lines := helper.LinesFromHistory(q.qb.History[channel], options)

	if len(lines) > q.qb.Config.MaxQuoteSize {
		q.qb.IRC.Privmsgf(channel, "That quote is too long (max %d lines)", q.qb.Config.MaxQuoteSize)
		return
	}

	if len(lines) != 0 {
		var createErrors []error

		tx := q.qb.DB.Begin()

		mChannel := model.Channel{Name: channel}
		tx.Find(&mChannel)

		// TODO: refactor
		if mChannel.ID == 0 {
			if mChannelErr := tx.Create(&mChannel).Error; mChannelErr != nil {
				q.qb.IRC.Privmsg(channel, "An error occurred creating the quote")
				log.Println(mChannelErr)
				return
			}
		}

		head := model.Head{Channel: mChannel}
		if headErr := tx.Create(&head).Error; headErr != nil {
			q.qb.IRC.Privmsg(channel, "An error occurred creating the quote")
			log.Println(headErr)
			return
		}

		for _, line := range lines {
			line := model.Line{
				Content: line["message"],
				Author:  line["nick"],
				Head:    head,
			}

			if err := tx.Create(&line).Error; err != nil {
				log.Println(err)

				createErrors = append(createErrors, err)
			}
		}

		if len(createErrors) == 0 {
			tx.Commit()
			q.qb.IRC.Privmsgf(channel, "Quote added - %sview/%d",
				q.qb.Config.BaseURL, head.ID)
		} else {
			tx.Rollback()
			q.qb.IRC.Privmsg(channel, "An error occurred creating the quote")
		}
	}
}
<commit_msg>Handle quotelast command<commit_after>// Package event contains functions relating to IRC numeric events.
package event

import (
	"fmt"
	"log"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/n7st/quoteDB/pkg/quoteDB/helper"
	"github.com/n7st/quoteDB/pkg/quoteDB/model"

	"github.com/thoj/go-ircevent"
)

const (
	// Bot commands
	quoteLastCmd = "quotelast"
	addQuoteCmd  = "addquote"
	quoteHelpCmd = "quotehelp"
	quotePageCmd = "quotepage"
)

// callbackPrivmsg() runs when the bot receives a message. Every message is
// stored so it can be cycled through to build a quote. Command attempts are
// not logged into history.
func (q *EventFnProvider) callbackPrivmsg(e *irc.Event) {
	channel := e.Arguments[0]

	if q.isCommandAttempt(e.Message()) {
		q.handleCommand(e)
	} else if e.Nick != q.qb.Config.Nickname {
		// Log message information for reading back later
		q.qb.History[channel] = append(q.qb.History[channel], map[string]string{
			"nick":      e.Nick,
			"message":   e.Message(),
			"timestamp": time.Now().String(),
		})

		maxLen := q.qb.Config.MaxQuoteSize
		currentLen := len(q.qb.History[channel])

		if currentLen > maxLen {
			// Trim messages past max length out of the slice
			newStart := currentLen - maxLen
			q.qb.History[channel] = q.qb.History[channel][newStart:]
		}
	}
}

// isCommandAttempt() checks if a given message looks like a bot command.
func (q *EventFnProvider) isCommandAttempt(message string) bool {
	if strings.HasPrefix(message, q.qb.Config.Trigger) {
		return true
	}

	return false
}

// handleCommand() organises arguments to a command and calls a function to run
// it.
func (q *EventFnProvider) handleCommand(e *irc.Event) {
	args := strings.Split(e.Message(), " ")

	command := strings.TrimPrefix(args[0], q.qb.Config.Trigger)
	argsWithoutCmd := strings.Join(args[1:], " ")

	switch command {
	case addQuoteCmd:
		if argsWithoutCmd != "" {
			q.parseAddQuote(e, argsWithoutCmd)
		}
	case quoteLastCmd:
		if argsWithoutCmd != "" {
			q.parseQuoteLast(e, argsWithoutCmd)
		}
	case quoteHelpCmd:
		q.parseQuoteHelp(e)
	case quotePageCmd:
		q.parseQuotePage(e)
	}
}

func (q *EventFnProvider) parseQuoteLast(e *irc.Event, argsWithoutCmd string) {
	histLen, err := strconv.Atoi(argsWithoutCmd)

	if err == nil && histLen > 0 {
		channel := e.Arguments[0]
		lines := helper.LastNLinesFromHistory(q.qb.History[channel], histLen)

		q.addQuoteLines(channel, lines)
	} else {
		q.qb.IRC.Privmsgf(e.Arguments[0], "%s is not a positive integer", argsWithoutCmd)
	}
}

// parseQuoteHelp() is run by the "quotehelp" command and displays commands
// available to users of the bot.
func (q *EventFnProvider) parseQuoteHelp(e *irc.Event) {
	q.qb.IRC.Privmsgf(e.Arguments[0], "Commands: %saddquote, %squotepage",
		q.qb.Config.Trigger, q.qb.Config.Trigger)
}

// parseQuotePage() runs the "quotepage" command which displays the URL for the
// web output page.
func (q *EventFnProvider) parseQuotePage(e *irc.Event) {
	channel := url.PathEscape(e.Arguments[0])
	loc := fmt.Sprintf("%schannel/%s", q.qb.Config.BaseURL, channel)

	q.qb.IRC.Privmsgf(e.Arguments[0], "Quotes for this channel can be found at %s", loc)
}

// parseAddQuote() handles the "addquote" command and needs refactoring.
func (q *EventFnProvider) parseAddQuote(e *irc.Event, argsWithoutCmd string) {
	channel := e.Arguments[0]
	options := helper.OptionsFromString(argsWithoutCmd)

	if len(options) < 1 || options[0] == "" || (len(options) == 2 && options[1] == "") {
		q.qb.IRC.Privmsg(channel, `The 'addquote' command requires one or two arguments ("start string" [and "end string"])`)
		return
	}

	if len(options) == 1 {
		// make the start and end the same, just select one line
		options = append(options, options[0])
	}

	lines := helper.LinesFromHistory(q.qb.History[channel], options)

	if len(lines) > q.qb.Config.MaxQuoteSize {
		q.qb.IRC.Privmsgf(channel, "That quote is too long (max %d lines)", q.qb.Config.MaxQuoteSize)
		return
	}

	q.addQuoteLines(channel, lines)
}

// addQuoteLines() runs a database transaction which adds a quote against a
// channel in the database.
// TODO: this might be better as part of a "repository" package.
func (q *EventFnProvider) addQuoteLines(channel string, lines []map[string]string) {
	if len(lines) != 0 {
		var createErrors []error

		tx := q.qb.DB.Begin()

		mChannel := model.Channel{Name: channel}
		tx.Find(&mChannel)

		if mChannel.ID == 0 {
			if mChannelErr := tx.Create(&mChannel).Error; mChannelErr != nil {
				q.qb.IRC.Privmsg(channel, "An error occurred creating the quote")
				log.Println(mChannelErr)
				return
			}
		}

		head := model.Head{Channel: mChannel}
		if headErr := tx.Create(&head).Error; headErr != nil {
			q.qb.IRC.Privmsg(channel, "An error occurred creating the quote")
			log.Println(headErr)
			return
		}

		for _, line := range lines {
			line := model.Line{
				Content: line["message"],
				Author:  line["nick"],
				Head:    head,
			}

			if err := tx.Create(&line).Error; err != nil {
				log.Println(err)

				createErrors = append(createErrors, err)
			}
		}

		if len(createErrors) == 0 {
			tx.Commit()
			q.qb.IRC.Privmsgf(channel, "Quote added - %sview/%d",
				q.qb.Config.BaseURL, head.ID)
		} else {
			tx.Rollback()
			q.qb.IRC.Privmsg(channel, "An error occurred creating the quote")
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 server

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"golang.org/x/net/http2"
	"k8s.io/klog"

	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/validation"
)

const (
	defaultKeepAlivePeriod = 3 * time.Minute
)

// Serve runs the secure http server. It fails only if certificates cannot be loaded or the initial listen call fails.
// The actual server loop (stoppable by closing stopCh) runs in a go routine, i.e. Serve does not block.
// It returns a stoppedCh that is closed when all non-hijacked active requests have been processed.
func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) (<-chan struct{}, error) {
	if s.Listener == nil {
		return nil, fmt.Errorf("listener must not be nil")
	}

	secureServer := &http.Server{
		Addr:           s.Listener.Addr().String(),
		Handler:        handler,
		MaxHeaderBytes: 1 << 20,
		TLSConfig: &tls.Config{
			NameToCertificate: s.SNICerts,
			// Can't use SSLv3 because of POODLE and BEAST
			// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
			// Can't use TLSv1.1 because of RC4 cipher usage
			MinVersion: tls.VersionTLS12,
			// enable HTTP2 for go's 1.7 HTTP Server
			NextProtos: []string{"h2", "http/1.1"},
		},
	}

	if s.DisableHTTP2 {
		klog.Info("Forcing use of http/1.1 only")
		secureServer.TLSConfig.NextProtos = []string{"http/1.1"}
	}

	if s.MinTLSVersion > 0 {
		secureServer.TLSConfig.MinVersion = s.MinTLSVersion
	}
	if len(s.CipherSuites) > 0 {
		secureServer.TLSConfig.CipherSuites = s.CipherSuites
	}

	if s.Cert != nil {
		secureServer.TLSConfig.Certificates = []tls.Certificate{*s.Cert}
	}

	// append all named certs. Otherwise, the go tls stack will think no SNI processing
	// is necessary because there is only one cert anyway.
	// Moreover, if ServerCert.CertFile/ServerCert.KeyFile are not set, the first SNI
	// cert will become the default cert. That's what we expect anyway.
	for _, c := range s.SNICerts {
		secureServer.TLSConfig.Certificates = append(secureServer.TLSConfig.Certificates, *c)
	}

	if s.ClientCA != nil {
		// Populate PeerCertificates in requests, but don't reject connections without certificates
		// This allows certificates to be validated by authenticators, while still allowing other auth types
		secureServer.TLSConfig.ClientAuth = tls.RequestClientCert
		// Specify allowed CAs for client certificates
		secureServer.TLSConfig.ClientCAs = s.ClientCA
	}

	// At least 99% of serialized resources in surveyed clusters were smaller than 256kb.
	// This should be big enough to accommodate most API POST requests in a single frame,
	// and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`.
	const resourceBody99Percentile = 256 * 1024

	http2Options := &http2.Server{}

	// shrink the per-stream buffer and max framesize from the 1MB default while still accommodating most API POST requests in a single frame
	http2Options.MaxUploadBufferPerStream = resourceBody99Percentile
	http2Options.MaxReadFrameSize = resourceBody99Percentile

	// use the overridden concurrent streams setting or make the default of 250 explicit so we can size MaxUploadBufferPerConnection appropriately
	if s.HTTP2MaxStreamsPerConnection > 0 {
		http2Options.MaxConcurrentStreams = uint32(s.HTTP2MaxStreamsPerConnection)
	} else {
		http2Options.MaxConcurrentStreams = 250
	}

	// increase the connection buffer size from the 1MB default to handle the specified number of concurrent streams
	http2Options.MaxUploadBufferPerConnection = http2Options.MaxUploadBufferPerStream * int32(http2Options.MaxConcurrentStreams)

	if !s.DisableHTTP2 {
		// apply settings to the server
		if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
			return nil, fmt.Errorf("error configuring http2: %v", err)
		}
	}

	klog.Infof("Serving securely on %s", secureServer.Addr)
	return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
}

// RunServer spawns a go-routine continuously serving until the stopCh is
// closed.
// It returns a stoppedCh that is closed when all non-hijacked active requests
// have been processed.
// This function does not block
// TODO: make private when insecure serving is gone from the kube-apiserver
func RunServer(
	server *http.Server,
	ln net.Listener,
	shutDownTimeout time.Duration,
	stopCh <-chan struct{},
) (<-chan struct{}, error) {
	if ln == nil {
		return nil, fmt.Errorf("listener must not be nil")
	}

	// Shutdown server gracefully.
	stoppedCh := make(chan struct{})
	go func() {
		defer close(stoppedCh)
		<-stopCh
		ctx, cancel := context.WithTimeout(context.Background(), shutDownTimeout)
		server.Shutdown(ctx)
		cancel()
	}()

	go func() {
		defer utilruntime.HandleCrash()

		var listener net.Listener
		listener = tcpKeepAliveListener{ln.(*net.TCPListener)}
		if server.TLSConfig != nil {
			listener = tls.NewListener(listener, server.TLSConfig)
		}

		err := server.Serve(listener)

		msg := fmt.Sprintf("Stopped listening on %s", ln.Addr().String())
		select {
		case <-stopCh:
			klog.Info(msg)
		default:
			panic(fmt.Sprintf("%s due to error: %v", msg, err))
		}
	}()

	return stoppedCh, nil
}

type NamedTLSCert struct {
	TLSCert tls.Certificate

	// Names is a list of domain patterns: fully qualified domain names, possibly prefixed with
	// wildcard segments.
	Names []string
}

// GetNamedCertificateMap returns a map of *tls.Certificate by name. It's
// suitable for use in tls.Config#NamedCertificates. Returns an error if any of the certs
// cannot be loaded. Returns nil if len(certs) == 0
func GetNamedCertificateMap(certs []NamedTLSCert) (map[string]*tls.Certificate, error) {
	// register certs with implicit names first, reverse order such that earlier trump over the later
	byName := map[string]*tls.Certificate{}
	for i := len(certs) - 1; i >= 0; i-- {
		if len(certs[i].Names) > 0 {
			continue
		}
		cert := &certs[i].TLSCert

		// read names from certificate common names and DNS names
		if len(cert.Certificate) == 0 {
			return nil, fmt.Errorf("empty SNI certificate, skipping")
		}
		x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
		if err != nil {
			return nil, fmt.Errorf("parse error for SNI certificate: %v", err)
		}
		cn := x509Cert.Subject.CommonName
		if cn == "*" || len(validation.IsDNS1123Subdomain(strings.TrimPrefix(cn, "*."))) == 0 {
			byName[cn] = cert
		}
		for _, san := range x509Cert.DNSNames {
			byName[san] = cert
		}
		// intentionally all IPs in the cert are ignored as SNI forbids passing IPs
		// to select a cert. Before go 1.6 the tls happily passed IPs as SNI values.
	}

	// register certs with explicit names last, overwriting every of the implicit ones,
	// again in reverse order.
	for i := len(certs) - 1; i >= 0; i-- {
		namedCert := &certs[i]
		for _, name := range namedCert.Names {
			byName[name] = &certs[i].TLSCert
		}
	}

	return byName, nil
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// Copied from Go 1.7.2 net/http/server.go
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(defaultKeepAlivePeriod)
	return tc, nil
}
<commit_msg>refactor tlsConfig creation for secure serving<commit_after>/*
Copyright 2016 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 server

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"golang.org/x/net/http2"
	"k8s.io/klog"

	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/validation"
)

const (
	defaultKeepAlivePeriod = 3 * time.Minute
)

// tlsConfig produces the tls.Config to serve with.
func (s *SecureServingInfo) tlsConfig(stopCh <-chan struct{}) (*tls.Config, error) {
	tlsConfig := &tls.Config{
		NameToCertificate: s.SNICerts,
		// Can't use SSLv3 because of POODLE and BEAST
		// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
		// Can't use TLSv1.1 because of RC4 cipher usage
		MinVersion: tls.VersionTLS12,
		// enable HTTP2 for go's 1.7 HTTP Server
		NextProtos: []string{"h2", "http/1.1"},
	}

	// these are static aspects of the tls.Config
	if s.DisableHTTP2 {
		klog.Info("Forcing use of http/1.1 only")
		tlsConfig.NextProtos = []string{"http/1.1"}
	}
	if s.MinTLSVersion > 0 {
		tlsConfig.MinVersion = s.MinTLSVersion
	}
	if len(s.CipherSuites) > 0 {
		tlsConfig.CipherSuites = s.CipherSuites
	}
	if s.Cert != nil {
		tlsConfig.Certificates = []tls.Certificate{*s.Cert}
	}
	// append all named certs. Otherwise, the go tls stack will think no SNI processing
	// is necessary because there is only one cert anyway.
	// Moreover, if ServerCert.CertFile/ServerCert.KeyFile are not set, the first SNI
	// cert will become the default cert. That's what we expect anyway.
	for _, c := range s.SNICerts {
		tlsConfig.Certificates = append(tlsConfig.Certificates, *c)
	}

	// TODO this will become dynamic.
	if s.ClientCA != nil {
		// Populate PeerCertificates in requests, but don't reject connections without certificates
		// This allows certificates to be validated by authenticators, while still allowing other auth types
		tlsConfig.ClientAuth = tls.RequestClientCert
		// Specify allowed CAs for client certificates
		tlsConfig.ClientCAs = s.ClientCA
	}

	return tlsConfig, nil
}

// Serve runs the secure http server. It fails only if certificates cannot be loaded or the initial listen call fails.
// The actual server loop (stoppable by closing stopCh) runs in a go routine, i.e. Serve does not block.
// It returns a stoppedCh that is closed when all non-hijacked active requests have been processed.
func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) (<-chan struct{}, error) {
	if s.Listener == nil {
		return nil, fmt.Errorf("listener must not be nil")
	}

	tlsConfig, err := s.tlsConfig(stopCh)
	if err != nil {
		return nil, err
	}

	secureServer := &http.Server{
		Addr:           s.Listener.Addr().String(),
		Handler:        handler,
		MaxHeaderBytes: 1 << 20,
		TLSConfig:      tlsConfig,
	}

	// At least 99% of serialized resources in surveyed clusters were smaller than 256kb.
	// This should be big enough to accommodate most API POST requests in a single frame,
	// and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`.
	const resourceBody99Percentile = 256 * 1024

	http2Options := &http2.Server{}

	// shrink the per-stream buffer and max framesize from the 1MB default while still accommodating most API POST requests in a single frame
	http2Options.MaxUploadBufferPerStream = resourceBody99Percentile
	http2Options.MaxReadFrameSize = resourceBody99Percentile

	// use the overridden concurrent streams setting or make the default of 250 explicit so we can size MaxUploadBufferPerConnection appropriately
	if s.HTTP2MaxStreamsPerConnection > 0 {
		http2Options.MaxConcurrentStreams = uint32(s.HTTP2MaxStreamsPerConnection)
	} else {
		http2Options.MaxConcurrentStreams = 250
	}

	// increase the connection buffer size from the 1MB default to handle the specified number of concurrent streams
	http2Options.MaxUploadBufferPerConnection = http2Options.MaxUploadBufferPerStream * int32(http2Options.MaxConcurrentStreams)

	if !s.DisableHTTP2 {
		// apply settings to the server
		if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
			return nil, fmt.Errorf("error configuring http2: %v", err)
		}
	}

	klog.Infof("Serving securely on %s", secureServer.Addr)
	return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
}

// RunServer spawns a go-routine continuously serving until the stopCh is
// closed.
// It returns a stoppedCh that is closed when all non-hijacked active requests
// have been processed.
// This function does not block
// TODO: make private when insecure serving is gone from the kube-apiserver
func RunServer(
	server *http.Server,
	ln net.Listener,
	shutDownTimeout time.Duration,
	stopCh <-chan struct{},
) (<-chan struct{}, error) {
	if ln == nil {
		return nil, fmt.Errorf("listener must not be nil")
	}

	// Shutdown server gracefully.
	stoppedCh := make(chan struct{})
	go func() {
		defer close(stoppedCh)
		<-stopCh
		ctx, cancel := context.WithTimeout(context.Background(), shutDownTimeout)
		server.Shutdown(ctx)
		cancel()
	}()

	go func() {
		defer utilruntime.HandleCrash()

		var listener net.Listener
		listener = tcpKeepAliveListener{ln.(*net.TCPListener)}
		if server.TLSConfig != nil {
			listener = tls.NewListener(listener, server.TLSConfig)
		}

		err := server.Serve(listener)

		msg := fmt.Sprintf("Stopped listening on %s", ln.Addr().String())
		select {
		case <-stopCh:
			klog.Info(msg)
		default:
			panic(fmt.Sprintf("%s due to error: %v", msg, err))
		}
	}()

	return stoppedCh, nil
}

type NamedTLSCert struct {
	TLSCert tls.Certificate

	// Names is a list of domain patterns: fully qualified domain names, possibly prefixed with
	// wildcard segments.
	Names []string
}

// GetNamedCertificateMap returns a map of *tls.Certificate by name. It's
// suitable for use in tls.Config#NamedCertificates. Returns an error if any of the certs
// cannot be loaded. Returns nil if len(certs) == 0
func GetNamedCertificateMap(certs []NamedTLSCert) (map[string]*tls.Certificate, error) {
	// register certs with implicit names first, reverse order such that earlier trump over the later
	byName := map[string]*tls.Certificate{}
	for i := len(certs) - 1; i >= 0; i-- {
		if len(certs[i].Names) > 0 {
			continue
		}
		cert := &certs[i].TLSCert

		// read names from certificate common names and DNS names
		if len(cert.Certificate) == 0 {
			return nil, fmt.Errorf("empty SNI certificate, skipping")
		}
		x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
		if err != nil {
			return nil, fmt.Errorf("parse error for SNI certificate: %v", err)
		}
		cn := x509Cert.Subject.CommonName
		if cn == "*" || len(validation.IsDNS1123Subdomain(strings.TrimPrefix(cn, "*."))) == 0 {
			byName[cn] = cert
		}
		for _, san := range x509Cert.DNSNames {
			byName[san] = cert
		}
		// intentionally all IPs in the cert are ignored as SNI forbids passing IPs
		// to select a cert. Before go 1.6 the tls happily passed IPs as SNI values.
	}

	// register certs with explicit names last, overwriting every of the implicit ones,
	// again in reverse order.
	for i := len(certs) - 1; i >= 0; i-- {
		namedCert := &certs[i]
		for _, name := range namedCert.Names {
			byName[name] = &certs[i].TLSCert
		}
	}

	return byName, nil
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// Copied from Go 1.7.2 net/http/server.go
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(defaultKeepAlivePeriod)
	return tc, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 docker

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/docker/docker/builder/dockerignore"
	"github.com/docker/docker/pkg/fileutils"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/karrick/godirwalk"
	"github.com/moby/buildkit/frontend/dockerfile/command"
	"github.com/moby/buildkit/frontend/dockerfile/parser"
	"github.com/moby/buildkit/frontend/dockerfile/shell"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type from struct {
	image string
	as    string
}

// RetrieveImage is overridden for unit testing
var RetrieveImage = retrieveImage

func evaluateBuildArgsValue(nameTemplate string) (string, error) {
	tmpl, err := util.ParseEnvTemplate(nameTemplate)
	if err != nil {
		return "", errors.Wrap(err, "parsing template")
	}

	return util.ExecuteEnvTemplate(tmpl, nil)
}

func expandBuildArgs(nodes []*parser.Node, buildArgs map[string]*string) error {
	for i, node := range nodes {
		if node.Value != command.Arg {
			continue
		}

		// build arg's key
		keyValue := strings.Split(node.Next.Value, "=")
		key := keyValue[0]

		// build arg's value
		var value string
		var err error
		if buildArgs[key] != nil {
			value, err = evaluateBuildArgsValue(*buildArgs[key])
			if err != nil {
				return errors.Wrapf(err, "unable to get value for build arg: %s", key)
			}
		} else if len(keyValue) > 1 {
			value = keyValue[1]
		}

		for _, node := range nodes[i+1:] {
			// Stop replacements if an arg is redefined with the same key
			if node.Value == command.Arg && strings.Split(node.Next.Value, "=")[0] == key {
				break
			}

			// replace $key with value
			for curr := node; curr != nil; curr = curr.Next {
				curr.Value = util.Expand(curr.Value, key, value)
			}
		}
	}
	return nil
}

func fromInstructions(nodes []*parser.Node) []from {
	var list []from

	for _, node := range nodes {
		if node.Value == command.From {
			list = append(list, fromInstruction(node))
		}
	}

	return list
}

func fromInstruction(node *parser.Node) from {
	var as string
	if next := node.Next.Next; next != nil && strings.ToLower(next.Value) == "as" && next.Next != nil {
		as = next.Next.Value
	}

	return from{
		image: node.Next.Value,
		as:    strings.ToLower(as),
	}
}

func onbuildInstructions(nodes []*parser.Node, insecureRegistries map[string]bool) ([]*parser.Node, error) {
	var instructions []string

	stages := map[string]bool{}
	for _, from := range fromInstructions(nodes) {
		// Stage names are case insensitive
		stages[strings.ToLower(from.as)] = true

		// `scratch` is case insensitive
		if strings.ToLower(from.image) == "scratch" {
			continue
		}

		// Stage names are case insensitive
		if _, found := stages[strings.ToLower(from.image)]; found {
			continue
		}

		logrus.Debugf("Checking base image %s for ONBUILD triggers.", from.image)

		// Image names are case SENSITIVE
		img, err := RetrieveImage(from.image, insecureRegistries)
		if err != nil {
			logrus.Warnf("Error processing base image (%s) for ONBUILD triggers: %s. Dependencies may be incomplete.", from.image, err)
			continue
		}

		if len(img.Config.OnBuild) > 0 {
			logrus.Debugf("Found ONBUILD triggers %v in image %s", img.Config.OnBuild, from.image)
			instructions = append(instructions, img.Config.OnBuild...)
		}
	}

	obRes, err := parser.Parse(strings.NewReader(strings.Join(instructions, "\n")))
	if err != nil {
		return nil, errors.Wrap(err, "parsing ONBUILD instructions")
	}

	return obRes.AST.Children, nil
}

func copiedFiles(nodes []*parser.Node) ([][]string, error) {
	var copied [][]string

	envs := map[string]string{}
	for _, node := range nodes {
		switch node.Value {
		case command.Add, command.Copy:
			files, err := processCopy(node, envs)
			if err != nil {
				return nil, err
			}

			if len(files) > 0 {
				copied = append(copied, files)
			}
		case command.Env:
			// one env command may define multiple variables
			for node := node.Next; node != nil && node.Next != nil; node = node.Next.Next {
				envs[node.Value] = node.Next.Value
			}
		}
	}

	return copied, nil
}

func readDockerfile(workspace, absDockerfilePath string, buildArgs map[string]*string, insecureRegistries map[string]bool) ([]string, error) {
	f, err := os.Open(absDockerfilePath)
	if err != nil {
		return nil, errors.Wrapf(err, "opening dockerfile: %s", absDockerfilePath)
	}
	defer f.Close()

	res, err := parser.Parse(f)
	if err != nil {
		return nil, errors.Wrap(err, "parsing dockerfile")
	}

	dockerfileLines := res.AST.Children

	err = expandBuildArgs(dockerfileLines, buildArgs)
	if err != nil {
		return nil, errors.Wrap(err, "putting build arguments")
	}

	instructions, err := onbuildInstructions(dockerfileLines, insecureRegistries)
	if err != nil {
		return nil, errors.Wrap(err, "listing ONBUILD instructions")
	}

	copied, err := copiedFiles(append(instructions, dockerfileLines...))
	if err != nil {
		return nil, errors.Wrap(err, "listing copied files")
	}

	return expandPaths(workspace, copied)
}

func expandPaths(workspace string, copied [][]string) ([]string, error) {
	expandedPaths := make(map[string]bool)
	for _, files := range copied {
		matchesOne := false

		for _, p := range files {
			path := filepath.Join(workspace, p)
			if _, err := os.Stat(path); err == nil {
				expandedPaths[p] = true
				matchesOne = true
				continue
			}

			files, err := filepath.Glob(path)
			if err != nil {
				return nil, errors.Wrap(err, "invalid glob pattern")
			}
			if files == nil {
				continue
			}

			for _, f := range files {
				rel, err := filepath.Rel(workspace, f)
				if err != nil {
					return nil, fmt.Errorf("getting relative path of %s", f)
				}

				expandedPaths[rel] = true
			}
			matchesOne = true
		}

		if !matchesOne {
			return nil, fmt.Errorf("file pattern %s must match at least one file", files)
		}
	}

	var deps []string
	for dep := range expandedPaths {
		deps = append(deps, dep)
	}
	logrus.Debugf("Found dependencies for dockerfile: %v", deps)

	return deps, nil
}

// NormalizeDockerfilePath returns the absolute path to the dockerfile.
func NormalizeDockerfilePath(context, dockerfile string) (string, error) {
	if filepath.IsAbs(dockerfile) {
		return dockerfile, nil
	}

	if !strings.HasPrefix(dockerfile, context) {
		dockerfile = filepath.Join(context, dockerfile)
	}
	return filepath.Abs(dockerfile)
}

// GetDependencies finds the sources dependencies for the given docker artifact.
// All paths are relative to the workspace.
func GetDependencies(ctx context.Context, workspace string, dockerfilePath string, buildArgs map[string]*string, insecureRegistries map[string]bool) ([]string, error) {
	absDockerfilePath, err := NormalizeDockerfilePath(workspace, dockerfilePath)
	if err != nil {
		return nil, errors.Wrap(err, "normalizing dockerfile path")
	}

	deps, err := readDockerfile(workspace, absDockerfilePath, buildArgs, insecureRegistries)
	if err != nil {
		return nil, err
	}

	// Read patterns to ignore
	var excludes []string
	dockerignorePath := filepath.Join(workspace, ".dockerignore")
	if _, err := os.Stat(dockerignorePath); !os.IsNotExist(err) {
		r, err := os.Open(dockerignorePath)
		if err != nil {
			return nil, err
		}
		defer r.Close()

		excludes, err = dockerignore.ReadAll(r)
		if err != nil {
			return nil, err
		}
	}

	files, err := WalkWorkspace(workspace, excludes, deps)
	if err != nil {
		return nil, errors.Wrap(err, "walking workspace")
	}

	// Always add dockerfile even if it's .dockerignored. The daemon will need it anyways.
	if !filepath.IsAbs(dockerfilePath) {
		files[dockerfilePath] = true
	} else {
		files[absDockerfilePath] = true
	}

	// Ignore .dockerignore
	delete(files, ".dockerignore")

	var dependencies []string
	for file := range files {
		dependencies = append(dependencies, file)
	}
	sort.Strings(dependencies)

	return dependencies, nil
}

func WalkWorkspace(workspace string, excludes, deps []string) (map[string]bool, error) {
	pExclude, err := fileutils.NewPatternMatcher(excludes)
	if err != nil {
		return nil, errors.Wrap(err, "invalid exclude patterns")
	}

	// Walk the workspace
	files := make(map[string]bool)
	for _, dep := range deps {
		dep = filepath.Clean(dep)
		absDep := filepath.Join(workspace, dep)

		fi, err := os.Stat(absDep)
		if err != nil {
			return nil, errors.Wrapf(err, "stating file %s", absDep)
		}

		switch mode := fi.Mode(); {
		case mode.IsDir():
			if err := godirwalk.Walk(absDep, &godirwalk.Options{
				Unsorted: true,
				Callback: func(fpath string, info *godirwalk.Dirent) error {
					if fpath == absDep {
						return nil
					}

					relPath, err := filepath.Rel(workspace, fpath)
					if err != nil {
						return err
					}

					ignored, err := pExclude.Matches(relPath)
					if err != nil {
						return err
					}

					if info.IsDir() {
						if ignored {
							return filepath.SkipDir
						}
					} else if !ignored {
						files[relPath] = true
					}

					return nil
				},
			}); err != nil {
				return nil, errors.Wrapf(err, "walking folder %s", absDep)
			}
		case mode.IsRegular():
			ignored, err := pExclude.Matches(dep)
			if err != nil {
				return nil, err
			}

			if !ignored {
				files[dep] = true
			}
		}
	}
	return files, nil
}

func retrieveImage(image string, insecureRegistries map[string]bool) (*v1.ConfigFile, error) {
	localDaemon, err := NewAPIClient(false, insecureRegistries) // Cached after first call
	if err != nil {
		return nil, errors.Wrap(err, "getting docker client")
	}

	return localDaemon.ConfigFile(context.Background(), image)
}

func processCopy(value *parser.Node, envs map[string]string) ([]string, error) {
	var copied []string

	slex := shell.NewLex('\\')
	for {
		// Skip last node, since it is the destination, and stop if we arrive at a comment
		if value.Next.Next == nil || strings.HasPrefix(value.Next.Next.Value, "#") {
			break
		}
		src, err := processShellWord(slex, value.Next.Value, envs)
		if err != nil {
			return nil, errors.Wrap(err, "processing word")
		}
		// If the --from flag is provided, we are dealing with a multi-stage dockerfile
		// Adding a dependency from a different stage does not imply a source dependency
		if hasMultiStageFlag(value.Flags) {
			return nil, nil
		}
		if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
			copied = append(copied, src)
		} else {
			logrus.Debugf("Skipping watch on remote dependency %s", src)
		}

		value = value.Next
	}

	return copied, nil
}

func processShellWord(lex *shell.Lex, word string, envs map[string]string) (string, error) {
	envSlice := []string{}
	for envKey, envVal := range envs {
		envSlice = append(envSlice, fmt.Sprintf("%s=%s", envKey, envVal))
	}
	return lex.ProcessWord(word, envSlice)
}

func hasMultiStageFlag(flags []string) bool {
	for _, f := range flags {
		if strings.HasPrefix(f, "--from=") {
			return true
		}
	}
	return false
}
<commit_msg>Simplify data flow of environment variables<commit_after>/*
Copyright 2019 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 docker

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/docker/docker/builder/dockerignore"
	"github.com/docker/docker/pkg/fileutils"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/karrick/godirwalk"
	"github.com/moby/buildkit/frontend/dockerfile/command"
	"github.com/moby/buildkit/frontend/dockerfile/parser"
	"github.com/moby/buildkit/frontend/dockerfile/shell"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type from struct {
	image string
	as    string
}

// RetrieveImage is overridden for unit testing
var RetrieveImage = retrieveImage

func evaluateBuildArgsValue(nameTemplate string) (string, error) {
	tmpl, err := util.ParseEnvTemplate(nameTemplate)
	if err != nil {
		return "", errors.Wrap(err, "parsing template")
	}

	return util.ExecuteEnvTemplate(tmpl, nil)
}

func expandBuildArgs(nodes []*parser.Node, buildArgs map[string]*string) error {
	for i, node := range nodes {
		if node.Value != command.Arg {
			continue
		}

		// build arg's key
		keyValue := strings.Split(node.Next.Value, "=")
		key := keyValue[0]

		// build arg's value
		var value string
		var err error
		if buildArgs[key] != nil {
			value, err = evaluateBuildArgsValue(*buildArgs[key])
			if err != nil {
				return errors.Wrapf(err, "unable to get value for build arg: %s", key)
			}
		} else if len(keyValue) > 1 {
			value = keyValue[1]
		}

		for _, node := range nodes[i+1:] {
			// Stop replacements if an arg is redefined with the same key
			if node.Value == command.Arg && strings.Split(node.Next.Value, "=")[0] == key {
				break
			}

			// replace $key with value
			for curr := node; curr != nil; curr = curr.Next {
				curr.Value = util.Expand(curr.Value, key, value)
			}
		}
	}
	return nil
}

func fromInstructions(nodes []*parser.Node) []from {
	var list []from

	for _, node := range nodes {
		if node.Value == command.From {
			list = append(list, fromInstruction(node))
		}
	}

	return list
}

func fromInstruction(node *parser.Node) from {
	var as string
	if next := node.Next.Next; next != nil && strings.ToLower(next.Value) == "as" && next.Next != nil {
		as = next.Next.Value
	}

	return from{
		image: node.Next.Value,
		as:    strings.ToLower(as),
	}
}

func onbuildInstructions(nodes []*parser.Node, insecureRegistries map[string]bool) ([]*parser.Node, error) {
	var instructions []string

	stages := map[string]bool{}
	for _, from := range fromInstructions(nodes) {
		// Stage names are case insensitive
		stages[strings.ToLower(from.as)] = true

		// `scratch` is case insensitive
		if strings.ToLower(from.image) == "scratch" {
			continue
		}

		// Stage names are case insensitive
		if _, found := stages[strings.ToLower(from.image)]; found {
			continue
		}

		logrus.Debugf("Checking base image %s for ONBUILD triggers.", from.image)

		// Image names are case SENSITIVE
		img, err := RetrieveImage(from.image, insecureRegistries)
		if err != nil {
			logrus.Warnf("Error processing base image (%s) for ONBUILD triggers: %s. Dependencies may be incomplete.", from.image, err)
			continue
		}

		if len(img.Config.OnBuild) > 0 {
			logrus.Debugf("Found ONBUILD triggers %v in image %s", img.Config.OnBuild, from.image)
			instructions = append(instructions, img.Config.OnBuild...)
		}
	}

	obRes, err := parser.Parse(strings.NewReader(strings.Join(instructions, "\n")))
	if err != nil {
		return nil, errors.Wrap(err, "parsing ONBUILD instructions")
	}

	return obRes.AST.Children, nil
}

func copiedFiles(nodes []*parser.Node) ([][]string, error) {
	var copied [][]string

	envs := make([]string, 0)
	for _, node := range nodes {
		switch node.Value {
		case command.Add, command.Copy:
			files, err := processCopy(node, envs)
			if err != nil {
				return nil, err
			}

			if len(files) > 0 {
				copied = append(copied, files)
			}
		case command.Env:
			// one env command may define multiple variables
			for node := node.Next; node != nil && node.Next != nil; node = node.Next.Next {
				envs = append(envs, fmt.Sprintf("%s=%s", node.Value, node.Next.Value))
			}
		}
	}

	return copied, nil
}

func readDockerfile(workspace, absDockerfilePath string, buildArgs map[string]*string, insecureRegistries map[string]bool) ([]string, error) {
	f, err := os.Open(absDockerfilePath)
	if err != nil {
		return nil, errors.Wrapf(err, "opening dockerfile: %s", absDockerfilePath)
	}
	defer f.Close()

	res, err := parser.Parse(f)
	if err != nil {
		return nil, errors.Wrap(err, "parsing dockerfile")
	}

	dockerfileLines := res.AST.Children

	err = expandBuildArgs(dockerfileLines, buildArgs)
	if err != nil {
		return nil, errors.Wrap(err, "putting build arguments")
	}

	instructions, err := onbuildInstructions(dockerfileLines, insecureRegistries)
	if err != nil {
		return nil, errors.Wrap(err, "listing ONBUILD instructions")
	}

	copied, err := copiedFiles(append(instructions, dockerfileLines...))
	if err != nil {
		return nil, errors.Wrap(err, "listing copied files")
	}

	return expandPaths(workspace, copied)
}

func expandPaths(workspace string, copied [][]string) ([]string, error) {
	expandedPaths := make(map[string]bool)
	for _, files := range copied {
		matchesOne := false

		for _, p := range files {
			path := filepath.Join(workspace, p)
			if _, err := os.Stat(path); err == nil {
				expandedPaths[p] = true
				matchesOne = true
				continue
			}

			files, err := filepath.Glob(path)
			if err != nil {
				return nil, errors.Wrap(err, "invalid glob pattern")
			}
			if files == nil {
				continue
			}

			for _, f := range files {
				rel, err := filepath.Rel(workspace, f)
				if err != nil {
					return nil, fmt.Errorf("getting relative path of %s", f)
				}

				expandedPaths[rel] = true
			}
			matchesOne = true
		}

		if !matchesOne {
			return nil, fmt.Errorf("file pattern %s must match at least one file", files)
		}
	}

	var deps []string
	for dep := range expandedPaths {
		deps = append(deps, dep)
	}
	logrus.Debugf("Found dependencies for dockerfile: %v", deps)

	return deps, nil
}

// NormalizeDockerfilePath returns the absolute path to the dockerfile.
func NormalizeDockerfilePath(context, dockerfile string) (string, error) {
	if filepath.IsAbs(dockerfile) {
		return dockerfile, nil
	}

	if !strings.HasPrefix(dockerfile, context) {
		dockerfile = filepath.Join(context, dockerfile)
	}
	return filepath.Abs(dockerfile)
}

// GetDependencies finds the sources dependencies for the given docker artifact.
// All paths are relative to the workspace.
func GetDependencies(ctx context.Context, workspace string, dockerfilePath string, buildArgs map[string]*string, insecureRegistries map[string]bool) ([]string, error) {
	absDockerfilePath, err := NormalizeDockerfilePath(workspace, dockerfilePath)
	if err != nil {
		return nil, errors.Wrap(err, "normalizing dockerfile path")
	}

	deps, err := readDockerfile(workspace, absDockerfilePath, buildArgs, insecureRegistries)
	if err != nil {
		return nil, err
	}

	// Read patterns to ignore
	var excludes []string
	dockerignorePath := filepath.Join(workspace, ".dockerignore")
	if _, err := os.Stat(dockerignorePath); !os.IsNotExist(err) {
		r, err := os.Open(dockerignorePath)
		if err != nil {
			return nil, err
		}
		defer r.Close()

		excludes, err = dockerignore.ReadAll(r)
		if err != nil {
			return nil, err
		}
	}

	files, err := WalkWorkspace(workspace, excludes, deps)
	if err != nil {
		return nil, errors.Wrap(err, "walking workspace")
	}

	// Always add dockerfile even if it's .dockerignored. The daemon will need it anyways.
	if !filepath.IsAbs(dockerfilePath) {
		files[dockerfilePath] = true
	} else {
		files[absDockerfilePath] = true
	}

	// Ignore .dockerignore
	delete(files, ".dockerignore")

	var dependencies []string
	for file := range files {
		dependencies = append(dependencies, file)
	}
	sort.Strings(dependencies)

	return dependencies, nil
}

func WalkWorkspace(workspace string, excludes, deps []string) (map[string]bool, error) {
	pExclude, err := fileutils.NewPatternMatcher(excludes)
	if err != nil {
		return nil, errors.Wrap(err, "invalid exclude patterns")
	}

	// Walk the workspace
	files := make(map[string]bool)
	for _, dep := range deps {
		dep = filepath.Clean(dep)
		absDep := filepath.Join(workspace, dep)

		fi, err := os.Stat(absDep)
		if err != nil {
			return nil, errors.Wrapf(err, "stating file %s", absDep)
		}

		switch mode := fi.Mode(); {
		case mode.IsDir():
			if err := godirwalk.Walk(absDep, &godirwalk.Options{
				Unsorted: true,
				Callback: func(fpath string, info *godirwalk.Dirent) error {
					if fpath == absDep {
						return nil
					}

					relPath, err := filepath.Rel(workspace, fpath)
					if err != nil {
						return err
					}

					ignored, err := pExclude.Matches(relPath)
					if err != nil {
						return err
					}

					if info.IsDir() {
						if ignored {
							return filepath.SkipDir
						}
					} else if !ignored {
						files[relPath] = true
					}

					return nil
				},
			}); err != nil {
				return nil, errors.Wrapf(err, "walking folder %s", absDep)
			}
		case mode.IsRegular():
			ignored, err := pExclude.Matches(dep)
			if err != nil {
				return nil, err
			}

			if !ignored {
				files[dep] = true
			}
		}
	}
	return files, nil
}

func retrieveImage(image string, insecureRegistries map[string]bool) (*v1.ConfigFile, error) {
	localDaemon, err := NewAPIClient(false, insecureRegistries) // Cached after first call
	if err != nil {
		return nil, errors.Wrap(err, "getting docker client")
	}

	return localDaemon.ConfigFile(context.Background(), image)
}

func processCopy(value *parser.Node, envs []string) ([]string, error) {
	var copied []string

	slex := shell.NewLex('\\')
	for {
		// Skip last node, since it is the destination, and stop if we arrive at a comment
		if value.Next.Next == nil || strings.HasPrefix(value.Next.Next.Value, "#") {
			break
		}
		src, err := slex.ProcessWord(value.Next.Value, envs)
		if err != nil {
			return nil, errors.Wrap(err, "processing word")
		}
		// If the --from flag is provided, we are dealing with a multi-stage dockerfile
		// Adding a dependency from a different stage does not imply a source dependency
		if hasMultiStageFlag(value.Flags) {
			return nil, nil
		}
		if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
			copied = append(copied, src)
		} else {
			logrus.Debugf("Skipping watch on remote dependency %s", src)
		}

		value = value.Next
	}

	return copied, nil
}

func hasMultiStageFlag(flags []string) bool {
	for _, f := range flags {
		if strings.HasPrefix(f, "--from=") {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package expr

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

	"bosun.org/cmd/bosun/expr/parse"
	"bosun.org/models"
	"bosun.org/opentsdb"
	"github.com/MiniProfiler/go/miniprofiler"
	"github.com/influxdata/influxdb/client/v2"
	"github.com/influxdata/influxdb/influxql"
	influxModels "github.com/influxdata/influxdb/models"
)

// Influx is a map of functions to query InfluxDB.
var Influx = map[string]parse.Func{
	"influx": {
		Args:   []models.FuncType{models.TypeString, models.TypeString, models.TypeString, models.TypeString, models.TypeString},
		Return: models.TypeSeriesSet,
		Tags:   influxTag,
		F:      InfluxQuery,
	},
}

func influxTag(args []parse.Node) (parse.Tags, error) {
        fmt.Println(args[1].(*parse.StringNode).Text)
	st, err := influxql.ParseStatement(args[1].(*parse.StringNode).Text)
	if err != nil {
		return nil, err
	}
	s, ok := st.(*influxql.SelectStatement)
	if !ok {
		return nil, fmt.Errorf("influx: expected select statement")
	}

	t := make(parse.Tags, len(s.Dimensions))
	for _, d := range s.Dimensions {
		if _, ok := d.Expr.(*influxql.Call); ok {
			continue
		}
		t[d.String()] = struct{}{}
	}
	return t, nil
}

func InfluxQuery(e *State, T miniprofiler.Timer, db, query, startDuration, endDuration, groupByInterval string) (*Results, error) {
	qres, err := timeInfluxRequest(e, T, db, query, startDuration, endDuration, groupByInterval)
	if err != nil {
		return nil, err
	}
	r := new(Results)
	for _, row := range qres {
		tags := opentsdb.TagSet(row.Tags)
		if e.Squelched(tags) {
			continue
		}
		if len(row.Columns) != 2 {
			return nil, fmt.Errorf("influx: expected exactly one result column")
		}
		values := make(Series, len(row.Values))
		for _, v := range row.Values {
			if len(v) != 2 {
				return nil, fmt.Errorf("influx: expected exactly one result column")
			}
			ts, ok := v[0].(string)
			if !ok {
				return nil, fmt.Errorf("influx: expected time string column")
			}
			t, err := time.Parse(time.RFC3339, ts)
			if err != nil {
				return nil, err
			}
			n, ok := v[1].(json.Number)
			if !ok {
				return nil, fmt.Errorf("influx: expected json.Number")
			}
			f, err := n.Float64()
			if err != nil {
				return nil, fmt.Errorf("influx: bad number: %v", err)
			}
			values[t] = f
		}
		r.Results = append(r.Results, &Result{
			Value: values,
			Group: tags,
		})
	}
	_ = r
	return r, nil
}

// influxQueryDuration adds time WHERE clauses to query for the given start and end durations.
func influxQueryDuration(now time.Time, query, start, end, groupByInterval string) (string, error) {
	sd, err := opentsdb.ParseDuration(start)
	if err != nil {
		return "", err
	}
	ed, err := opentsdb.ParseDuration(end)
	if end == "" {
		ed = 0
	} else if err != nil {
		return "", err
	}
	st, err := influxql.ParseStatement(query)
	if err != nil {
		return "", err
	}
	s, ok := st.(*influxql.SelectStatement)
	if !ok {
		return "", fmt.Errorf("influx: expected select statement")
	}
	isTime := func(n influxql.Node) bool {
		v, ok := n.(*influxql.VarRef)
		if !ok {
			return false
		}
		s := strings.ToLower(v.Val)
		return s == "time"
	}
	influxql.WalkFunc(s.Condition, func(n influxql.Node) {
		b, ok := n.(*influxql.BinaryExpr)
		if !ok {
			return
		}
		if isTime(b.LHS) || isTime(b.RHS) {
			err = fmt.Errorf("influx query must not contain time in WHERE")
		}
	})
	if err != nil {
		return "", err
	}

	//Add New BinaryExpr for time clause
	startExpr := &influxql.BinaryExpr{
		Op:  influxql.GTE,
		LHS: &influxql.VarRef{Val: "time"},
		RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-sd))},
	}

	stopExpr := &influxql.BinaryExpr{
		Op:  influxql.LTE,
		LHS: &influxql.VarRef{Val: "time"},
		RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-ed))},
	}

	if s.Condition != nil {
		s.Condition = &influxql.BinaryExpr{
			Op:  influxql.AND,
			LHS: s.Condition,
			RHS: &influxql.BinaryExpr{
				Op:  influxql.AND,
				LHS: startExpr,
				RHS: stopExpr,
			},
		}
	} else {
		s.Condition = &influxql.BinaryExpr{
			Op:  influxql.AND,
			LHS: startExpr,
			RHS: stopExpr,
		}
	}

	// parse last argument
	if len(groupByInterval) > 0 {
		gbi, err := time.ParseDuration(groupByInterval)
		if err != nil {
			return "", err
		}
		s.Dimensions = append(s.Dimensions,
			&influxql.Dimension{Expr: &influxql.Call{
				Name: "time",
				Args: []influxql.Expr{&influxql.DurationLiteral{Val: gbi}},
			},
			})
	}

	// emtpy aggregate windows should be purged from the result
	// this default resembles the opentsdb results.
	if s.Fill == influxql.NullFill {
		s.Fill = influxql.NoFill
		s.FillValue = nil
	}

	return s.String(), nil
}

func timeInfluxRequest(e *State, T miniprofiler.Timer, db, query, startDuration, endDuration, groupByInterval string) (s []influxModels.Row, err error) {
	q, err := influxQueryDuration(e.now, query, startDuration, endDuration, groupByInterval)
	if err != nil {
		return nil, err
	}
	conn, err := client.NewHTTPClient(e.InfluxConfig)
	if err != nil {
		return nil, err
	}
	T.StepCustomTiming("influx", "query", q, func() {
		getFn := func() (interface{}, error) {
			res, err := conn.Query(client.Query{
				Command:  q,
				Database: db,
			})
			if err != nil {
				return nil, err
			}
			if res.Error() != nil {
				return nil, res.Error()
			}
			if len(res.Results) != 1 {
				return nil, fmt.Errorf("influx: expected one result")
			}

			r := res.Results[0]
			if r.Err == "" {
				return r.Series, nil
			}
			err = fmt.Errorf(r.Err)
			return r.Series, err
		}
		var val interface{}
		var ok bool
		val, err = e.Cache.Get(q, getFn)
		if s, ok = val.([]influxModels.Row); !ok {
			err = fmt.Errorf("influx: did not get a valid result from InfluxDB")
		}
	})
	return
}
<commit_msg>Switched log to slog.<commit_after>package expr

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

	"bosun.org/cmd/bosun/expr/parse"
	"bosun.org/models"
	"bosun.org/opentsdb"
	"github.com/MiniProfiler/go/miniprofiler"
	"github.com/influxdata/influxdb/client/v2"
	"github.com/influxdata/influxdb/influxql"
	influxModels "github.com/influxdata/influxdb/models"

	"bosun.org/slog"
)

// Influx is a map of functions to query InfluxDB.
var Influx = map[string]parse.Func{
	"influx": {
		Args:   []models.FuncType{models.TypeString, models.TypeString, models.TypeString, models.TypeString, models.TypeString},
		Return: models.TypeSeriesSet,
		Tags:   influxTag,
		F:      InfluxQuery,
	},
}

func influxTag(args []parse.Node) (parse.Tags, error) {
        slog.Infoln(args[1].(*parse.StringNode).Text)

	st, err := influxql.ParseStatement(args[1].(*parse.StringNode).Text)
	if err != nil {
		return nil, err
	}
	s, ok := st.(*influxql.SelectStatement)
	if !ok {
		return nil, fmt.Errorf("influx: expected select statement")
	}

	t := make(parse.Tags, len(s.Dimensions))
	for _, d := range s.Dimensions {
		if _, ok := d.Expr.(*influxql.Call); ok {
			continue
		}
		t[d.String()] = struct{}{}
	}
	return t, nil
}

func InfluxQuery(e *State, T miniprofiler.Timer, db, query, startDuration, endDuration, groupByInterval string) (*Results, error) {
	qres, err := timeInfluxRequest(e, T, db, query, startDuration, endDuration, groupByInterval)
	if err != nil {
		return nil, err
	}
	r := new(Results)
	for _, row := range qres {
		tags := opentsdb.TagSet(row.Tags)
		if e.Squelched(tags) {
			continue
		}
		if len(row.Columns) != 2 {
			return nil, fmt.Errorf("influx: expected exactly one result column")
		}
		values := make(Series, len(row.Values))
		for _, v := range row.Values {
			if len(v) != 2 {
				return nil, fmt.Errorf("influx: expected exactly one result column")
			}
			ts, ok := v[0].(string)
			if !ok {
				return nil, fmt.Errorf("influx: expected time string column")
			}
			t, err := time.Parse(time.RFC3339, ts)
			if err != nil {
				return nil, err
			}
			n, ok := v[1].(json.Number)
			if !ok {
				return nil, fmt.Errorf("influx: expected json.Number")
			}
			f, err := n.Float64()
			if err != nil {
				return nil, fmt.Errorf("influx: bad number: %v", err)
			}
			values[t] = f
		}
		r.Results = append(r.Results, &Result{
			Value: values,
			Group: tags,
		})
	}
	_ = r
	return r, nil
}

// influxQueryDuration adds time WHERE clauses to query for the given start and end durations.
func influxQueryDuration(now time.Time, query, start, end, groupByInterval string) (string, error) {
	sd, err := opentsdb.ParseDuration(start)
	if err != nil {
		return "", err
	}
	ed, err := opentsdb.ParseDuration(end)
	if end == "" {
		ed = 0
	} else if err != nil {
		return "", err
	}
	st, err := influxql.ParseStatement(query)
	if err != nil {
		return "", err
	}
	s, ok := st.(*influxql.SelectStatement)
	if !ok {
		return "", fmt.Errorf("influx: expected select statement")
	}
	isTime := func(n influxql.Node) bool {
		v, ok := n.(*influxql.VarRef)
		if !ok {
			return false
		}
		s := strings.ToLower(v.Val)
		return s == "time"
	}
	influxql.WalkFunc(s.Condition, func(n influxql.Node) {
		b, ok := n.(*influxql.BinaryExpr)
		if !ok {
			return
		}
		if isTime(b.LHS) || isTime(b.RHS) {
			err = fmt.Errorf("influx query must not contain time in WHERE")
		}
	})
	if err != nil {
		return "", err
	}

	//Add New BinaryExpr for time clause
	startExpr := &influxql.BinaryExpr{
		Op:  influxql.GTE,
		LHS: &influxql.VarRef{Val: "time"},
		RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-sd))},
	}

	stopExpr := &influxql.BinaryExpr{
		Op:  influxql.LTE,
		LHS: &influxql.VarRef{Val: "time"},
		RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-ed))},
	}

	if s.Condition != nil {
		s.Condition = &influxql.BinaryExpr{
			Op:  influxql.AND,
			LHS: s.Condition,
			RHS: &influxql.BinaryExpr{
				Op:  influxql.AND,
				LHS: startExpr,
				RHS: stopExpr,
			},
		}
	} else {
		s.Condition = &influxql.BinaryExpr{
			Op:  influxql.AND,
			LHS: startExpr,
			RHS: stopExpr,
		}
	}

	// parse last argument
	if len(groupByInterval) > 0 {
		gbi, err := time.ParseDuration(groupByInterval)
		if err != nil {
			return "", err
		}
		s.Dimensions = append(s.Dimensions,
			&influxql.Dimension{Expr: &influxql.Call{
				Name: "time",
				Args: []influxql.Expr{&influxql.DurationLiteral{Val: gbi}},
			},
			})
	}

	// emtpy aggregate windows should be purged from the result
	// this default resembles the opentsdb results.
	if s.Fill == influxql.NullFill {
		s.Fill = influxql.NoFill
		s.FillValue = nil
	}

	return s.String(), nil
}

func timeInfluxRequest(e *State, T miniprofiler.Timer, db, query, startDuration, endDuration, groupByInterval string) (s []influxModels.Row, err error) {
	q, err := influxQueryDuration(e.now, query, startDuration, endDuration, groupByInterval)
	if err != nil {
		return nil, err
	}
	conn, err := client.NewHTTPClient(e.InfluxConfig)
	if err != nil {
		return nil, err
	}
	T.StepCustomTiming("influx", "query", q, func() {
		getFn := func() (interface{}, error) {
			res, err := conn.Query(client.Query{
				Command:  q,
				Database: db,
			})
			if err != nil {
				return nil, err
			}
			if res.Error() != nil {
				return nil, res.Error()
			}
			if len(res.Results) != 1 {
				return nil, fmt.Errorf("influx: expected one result")
			}

			r := res.Results[0]
			if r.Err == "" {
				return r.Series, nil
			}
			err = fmt.Errorf(r.Err)
			return r.Series, err
		}
		var val interface{}
		var ok bool
		val, err = e.Cache.Get(q, getFn)
		if s, ok = val.([]influxModels.Row); !ok {
			err = fmt.Errorf("influx: did not get a valid result from InfluxDB")
		}
	})
	return
}
<|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 main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudcfg"
)

const APP_VERSION = "0.1"

// The flag package provides a default help printer via -h switch
var (
	versionFlag  = flag.Bool("v", false, "Print the version number.")
	httpServer   = flag.String("h", "", "The host to connect to.")
	config       = flag.String("c", "", "Path to the config file.")
	labelQuery   = flag.String("l", "", "Label query to use for listing")
	updatePeriod = flag.Duration("u", 60*time.Second, "Update interarrival in seconds")
	portSpec     = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
	servicePort  = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
	authConfig   = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file.  If missing, prompt the user")
)

func usage() {
	log.Fatal("Usage: cloudcfg -h <host> [-c config/file.json] [-p <hostPort>:<containerPort>,..., <hostPort-n>:<containerPort-n> <method> <path>")
}

// CloudCfg command line tool.
func main() {
	flag.Parse() // Scan the arguments list

	if *versionFlag {
		fmt.Println("Version:", APP_VERSION)
		os.Exit(0)
	}

	if len(flag.Args()) < 2 {
		usage()
	}
	method := flag.Arg(0)
	url := *httpServer + "/api/v1beta1" + flag.Arg(1)
	var request *http.Request
	var err error

	auth, err := cloudcfg.LoadAuthInfo(*authConfig)
	if err != nil {
		log.Fatalf("Error loading auth: %#v", err)
	}

	if method == "get" || method == "list" {
		if len(*labelQuery) > 0 && method == "list" {
			url = url + "?labels=" + *labelQuery
		}
		request, err = http.NewRequest("GET", url, nil)
	} else if method == "delete" {
		request, err = http.NewRequest("DELETE", url, nil)
	} else if method == "create" {
		request, err = cloudcfg.RequestWithBody(*config, url, "POST")
	} else if method == "update" {
		request, err = cloudcfg.RequestWithBody(*config, url, "PUT")
	} else if method == "rollingupdate" {
		client := &kube_client.Client{
			Host: *httpServer,
			Auth: &auth,
		}
		cloudcfg.Update(flag.Arg(1), client, *updatePeriod)
	} else if method == "run" {
		args := flag.Args()
		if len(args) < 4 {
			log.Fatal("usage: cloudcfg -h <host> run <image> <replicas> <name>")
		}
		image := args[1]
		replicas, err := strconv.Atoi(args[2])
		name := args[3]
		if err != nil {
			log.Fatalf("Error parsing replicas: %#v", err)
		}
		err = cloudcfg.RunController(image, name, replicas, kube_client.Client{Host: *httpServer, Auth: &auth}, *portSpec, *servicePort)
		if err != nil {
			log.Fatalf("Error: %#v", err)
		}
		return
	} else if method == "stop" {
		err = cloudcfg.StopController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: &auth})
		if err != nil {
			log.Fatalf("Error: %#v", err)
		}
		return
	} else if method == "rm" {
		err = cloudcfg.DeleteController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: &auth})
		if err != nil {
			log.Fatalf("Error: %#v", err)
		}
		return
	} else {
		log.Fatalf("Unknown command: %s", method)
	}
	if err != nil {
		log.Fatalf("Error: %#v", err)
	}
	var body string
	body, err = cloudcfg.DoRequest(request, auth.User, auth.Password)
	if err != nil {
		log.Fatalf("Error: %#v", err)
	}
	fmt.Println(body)
}
<commit_msg>Add a couple of printers.<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 main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudcfg"
)

const APP_VERSION = "0.1"

// The flag package provides a default help printer via -h switch
var (
	versionFlag  = flag.Bool("v", false, "Print the version number.")
	httpServer   = flag.String("h", "", "The host to connect to.")
	config       = flag.String("c", "", "Path to the config file.")
	labelQuery   = flag.String("l", "", "Label query to use for listing")
	updatePeriod = flag.Duration("u", 60*time.Second, "Update interarrival in seconds")
	portSpec     = flag.String("p", "", "The port spec, comma-separated list of <external>:<internal>,...")
	servicePort  = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'")
	authConfig   = flag.String("auth", os.Getenv("HOME")+"/.kubernetes_auth", "Path to the auth info file.  If missing, prompt the user")
	json         = flag.Bool("json", false, "If true, print raw JSON for responses")
	yaml         = flag.Bool("yaml", false, "If true, print raw YAML for responses")
)

func usage() {
	log.Fatal("Usage: cloudcfg -h <host> [-c config/file.json] [-p <hostPort>:<containerPort>,..., <hostPort-n>:<containerPort-n> <method> <path>")
}

// CloudCfg command line tool.
func main() {
	flag.Parse() // Scan the arguments list

	if *versionFlag {
		fmt.Println("Version:", APP_VERSION)
		os.Exit(0)
	}

	if len(flag.Args()) < 2 {
		usage()
	}
	method := flag.Arg(0)
	url := *httpServer + "/api/v1beta1" + flag.Arg(1)
	var request *http.Request
	var err error

	var printer cloudcfg.ResourcePrinter
	if *json {
		printer = &cloudcfg.IdentityPrinter{}
	} else if *yaml {
		printer = &cloudcfg.YAMLPrinter{}
	} else {
		printer = &cloudcfg.HumanReadablePrinter{}
	}

	auth, err := cloudcfg.LoadAuthInfo(*authConfig)
	if err != nil {
		log.Fatalf("Error loading auth: %#v", err)
	}

	if method == "get" || method == "list" {
		if len(*labelQuery) > 0 && method == "list" {
			url = url + "?labels=" + *labelQuery
		}
		request, err = http.NewRequest("GET", url, nil)
	} else if method == "delete" {
		request, err = http.NewRequest("DELETE", url, nil)
	} else if method == "create" {
		request, err = cloudcfg.RequestWithBody(*config, url, "POST")
	} else if method == "update" {
		request, err = cloudcfg.RequestWithBody(*config, url, "PUT")
	} else if method == "rollingupdate" {
		client := &kube_client.Client{
			Host: *httpServer,
			Auth: &auth,
		}
		cloudcfg.Update(flag.Arg(1), client, *updatePeriod)
	} else if method == "run" {
		args := flag.Args()
		if len(args) < 4 {
			log.Fatal("usage: cloudcfg -h <host> run <image> <replicas> <name>")
		}
		image := args[1]
		replicas, err := strconv.Atoi(args[2])
		name := args[3]
		if err != nil {
			log.Fatalf("Error parsing replicas: %#v", err)
		}
		err = cloudcfg.RunController(image, name, replicas, kube_client.Client{Host: *httpServer, Auth: &auth}, *portSpec, *servicePort)
		if err != nil {
			log.Fatalf("Error: %#v", err)
		}
		return
	} else if method == "stop" {
		err = cloudcfg.StopController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: &auth})
		if err != nil {
			log.Fatalf("Error: %#v", err)
		}
		return
	} else if method == "rm" {
		err = cloudcfg.DeleteController(flag.Arg(1), kube_client.Client{Host: *httpServer, Auth: &auth})
		if err != nil {
			log.Fatalf("Error: %#v", err)
		}
		return
	} else {
		log.Fatalf("Unknown command: %s", method)
	}
	if err != nil {
		log.Fatalf("Error: %#v", err)
	}
	var body string
	body, err = cloudcfg.DoRequest(request, auth.User, auth.Password)
	if err != nil {
		log.Fatalf("Error: %#v", err)
	}
	err = printer.Print(body, os.Stdout)
	if err != nil {
		log.Fatalf("Failed to print: %#v", err)
	}
	fmt.Print("\n")
}
<|endoftext|>