pkg/cmd/cli/cmd/set/triggers.go
304a2b2b
 package set
 
 import (
 	"fmt"
 	"io"
 	"os"
 	"reflect"
 	"strings"
 	"text/tabwriter"
 
 	"github.com/golang/glog"
 	"github.com/spf13/cobra"
 
 	kapi "k8s.io/kubernetes/pkg/api"
 	"k8s.io/kubernetes/pkg/api/meta"
5a556e67
 	"k8s.io/kubernetes/pkg/api/unversioned"
304a2b2b
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
 	"k8s.io/kubernetes/pkg/kubectl/resource"
 	"k8s.io/kubernetes/pkg/runtime"
 
 	buildapi "github.com/openshift/origin/pkg/build/api"
 	buildutil "github.com/openshift/origin/pkg/build/util"
6267dded
 	"github.com/openshift/origin/pkg/cmd/templates"
304a2b2b
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
 	"github.com/openshift/origin/pkg/generate/app"
 	imageapi "github.com/openshift/origin/pkg/image/api"
 	"k8s.io/kubernetes/pkg/util/sets"
 )
 
6267dded
 var (
 	triggersLong = templates.LongDesc(`
 		Set or remove triggers for build configs and deployment configs
304a2b2b
 
6267dded
 		All build configs and deployment configs may have a set of triggers that result in a new deployment
 		or build being created. This command enables you to alter those triggers - making them automatic or
 		manual, adding new entries, or changing existing entries.
304a2b2b
 
6267dded
 		Deployments support triggering off of image changes and on config changes. Config changes are any
 		alterations to the pod template, while image changes will result in the container image value being
 		updated whenever an image stream tag is updated.
304a2b2b
 
6267dded
 		Build configs support triggering off of image changes, config changes, and webhooks (both GitHub-specific
 		and generic). The config change trigger for a build config will only trigger the first build.`)
304a2b2b
 
6267dded
 	triggersExample = templates.Examples(`
 		# Print the triggers on the registry
 	  %[1]s triggers dc/registry
304a2b2b
 
6267dded
 	  # Set all triggers to manual
 	  %[1]s triggers dc/registry --manual
304a2b2b
 
6267dded
 	  # Enable all automatic triggers
 	  %[1]s triggers dc/registry --auto
304a2b2b
 
6267dded
 	  # Reset the GitHub webhook on a build to a new, generated secret
 	  %[1]s triggers bc/webapp --from-github
 	  %[1]s triggers bc/webapp --from-webhook
304a2b2b
 
6267dded
 	  # Remove all triggers
 	  %[1]s triggers bc/webapp --remove-all
304a2b2b
 
6267dded
 	  # Stop triggering on config change
 	  %[1]s triggers dc/registry --from-config --remove
304a2b2b
 
6267dded
 	  # Add an image trigger to a build config
 	  %[1]s triggers bc/webapp --from-image=namespace1/image:latest`)
304a2b2b
 )
 
 type TriggersOptions struct {
 	Out io.Writer
 	Err io.Writer
 
 	Filenames []string
 	Selector  string
 	All       bool
 
 	Builder *resource.Builder
 	Infos   []*resource.Info
 
 	Encoder runtime.Encoder
 
5a556e67
 	ShortOutput   bool
 	Mapper        meta.RESTMapper
 	OutputVersion unversioned.GroupVersion
304a2b2b
 
 	PrintTable  bool
0650f99d
 	PrintObject func([]*resource.Info) error
304a2b2b
 
 	Remove    bool
 	RemoveAll bool
 	Auto      bool
 	Manual    bool
 	Reset     bool
 
150c7c58
 	ContainerNames      string
 	FromConfig          bool
 	FromGitHub          *bool
 	FromWebHook         *bool
 	FromWebHookAllowEnv *bool
 	FromImage           string
304a2b2b
 	// FromImageNamespace is the namespace for the FromImage
 	FromImageNamespace string
 }
 
 // NewCmdTriggers implements the set triggers command
 func NewCmdTriggers(fullName string, f *clientcmd.Factory, out, errOut io.Writer) *cobra.Command {
 	options := &TriggersOptions{
 		Out: out,
 		Err: errOut,
 	}
 	cmd := &cobra.Command{
 		Use:     "triggers RESOURCE/NAME [--from-config|--from-image|--from-github|--from-webhook] [--auto|--manual]",
 		Short:   "Update the triggers on a build or deployment config",
 		Long:    triggersLong,
 		Example: fmt.Sprintf(triggersExample, fullName),
 		Run: func(cmd *cobra.Command, args []string) {
 			kcmdutil.CheckErr(options.Complete(f, cmd, args))
 			kcmdutil.CheckErr(options.Validate())
 			if err := options.Run(); err != nil {
d7da290f
 				// TODO: move me to kcmdutil
304a2b2b
 				if err == cmdutil.ErrExit {
 					os.Exit(1)
 				}
 				kcmdutil.CheckErr(err)
 			}
 		},
 	}
 
 	kcmdutil.AddPrinterFlags(cmd)
 	cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on")
9c93da29
 	cmd.Flags().BoolVar(&options.All, "all", options.All, "If true, select all resources in the namespace of the specified resource types")
304a2b2b
 	cmd.Flags().StringSliceVarP(&options.Filenames, "filename", "f", options.Filenames, "Filename, directory, or URL to file to use to edit the resource.")
 
 	cmd.Flags().BoolVar(&options.Remove, "remove", options.Remove, "If true, remove the specified trigger(s).")
 	cmd.Flags().BoolVar(&options.RemoveAll, "remove-all", options.RemoveAll, "If true, remove all triggers.")
9c93da29
 	cmd.Flags().BoolVar(&options.Auto, "auto", options.Auto, "If true, enable all triggers, or just the specified trigger")
 	cmd.Flags().BoolVar(&options.Manual, "manual", options.Manual, "If true, set all triggers to manual, or just the specified trigger")
304a2b2b
 
 	cmd.Flags().BoolVar(&options.FromConfig, "from-config", options.FromConfig, "If set, configuration changes will result in a change")
 	cmd.Flags().StringVarP(&options.ContainerNames, "containers", "c", options.ContainerNames, "Comma delimited list of container names this trigger applies to on deployments; defaults to the name of the only container")
 	cmd.Flags().StringVar(&options.FromImage, "from-image", options.FromImage, "An image stream tag to trigger off of")
9c93da29
 	options.FromGitHub = cmd.Flags().Bool("from-github", false, "If true, a GitHub webhook - a secret value will be generated automatically")
 	options.FromWebHook = cmd.Flags().Bool("from-webhook", false, "If true, a generic webhook - a secret value will be generated automatically")
 	options.FromWebHookAllowEnv = cmd.Flags().Bool("from-webhook-allow-env", false, "If true, a generic webhook which can provide environment variables - a secret value will be generated automatically")
304a2b2b
 
 	cmd.MarkFlagFilename("filename", "yaml", "yml", "json")
 
 	return cmd
 }
 
 func (o *TriggersOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
 	cmdNamespace, explicit, err := f.DefaultNamespace()
 	if err != nil {
 		return err
 	}
 
5a556e67
 	clientConfig, err := f.ClientConfig()
 	if err != nil {
 		return err
 	}
 
 	o.OutputVersion, err = kcmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
 	if err != nil {
 		return err
 	}
 
304a2b2b
 	if !cmd.Flags().Lookup("from-github").Changed {
 		o.FromGitHub = nil
 	}
 	if !cmd.Flags().Lookup("from-webhook").Changed {
 		o.FromWebHook = nil
 	}
150c7c58
 	if !cmd.Flags().Lookup("from-webhook-allow-env").Changed {
 		o.FromWebHookAllowEnv = nil
 	}
304a2b2b
 
 	if len(o.FromImage) > 0 {
 		ref, err := imageapi.ParseDockerImageReference(o.FromImage)
 		if err != nil {
 			return fmt.Errorf("the value of --from-image does not appear to be a valid reference to an image: %v", err)
 		}
 		if len(ref.Registry) > 0 || len(ref.ID) > 0 {
 			return fmt.Errorf("the value of --from-image must point to an image stream tag on this server")
 		}
 		if len(ref.Tag) == 0 {
 			return fmt.Errorf("the value of --from-image must include the tag you wish to pull from")
 		}
 		o.FromImage = ref.NameString()
 		o.FromImageNamespace = defaultNamespace(ref.Namespace, cmdNamespace)
 	}
 
 	count := o.count()
 	o.Reset = count == 0 && (o.Auto || o.Manual)
 	switch {
 	case count == 0 && !o.Remove && !o.RemoveAll && !o.Auto && !o.Manual:
 		o.PrintTable = true
 	case !o.RemoveAll && !o.Auto && !o.Manual:
 		o.Auto = true
 	}
 
59e6f9d2
 	mapper, typer := f.Object(false)
304a2b2b
 	o.Builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()).
 		ContinueOnError().
 		NamespaceParam(cmdNamespace).DefaultNamespace().
c5dc7f15
 		FilenameParam(explicit, false, o.Filenames...).
304a2b2b
 		SelectorParam(o.Selector).
 		ResourceTypeOrNameArgs(o.All, args...).
 		Flatten()
 
 	output := kcmdutil.GetFlagString(cmd, "output")
0650f99d
 	if len(output) > 0 {
 		o.PrintObject = func(infos []*resource.Info) error {
 			return f.PrintResourceInfos(cmd, infos, o.Out)
 		}
304a2b2b
 	}
 
 	o.Encoder = f.JSONEncoder()
 	o.ShortOutput = kcmdutil.GetFlagString(cmd, "output") == "name"
 	o.Mapper = mapper
 
 	return nil
 }
 
 func (o *TriggersOptions) count() int {
 	count := 0
 	if o.FromConfig {
 		count++
 	}
 	if o.FromGitHub != nil {
 		count++
 	}
 	if o.FromWebHook != nil {
 		count++
 	}
150c7c58
 	if o.FromWebHookAllowEnv != nil {
 		count++
 	}
304a2b2b
 	if len(o.FromImage) > 0 {
 		count++
 	}
 	return count
 }
 
 func (o *TriggersOptions) Validate() error {
 	count := o.count()
 	switch {
 	case o.Auto && o.Manual:
 		return fmt.Errorf("you must specify at most one of --auto or --manual")
 	case o.Remove && o.RemoveAll:
 		return fmt.Errorf("you must specify either --remove or --remove-all")
 	case o.RemoveAll && (count != 0 || o.Auto || o.Manual):
 		return fmt.Errorf("--remove-all may not be used with any other flag")
 	case o.Remove && count < 1:
 		return fmt.Errorf("--remove requires a flag defining a trigger type to be specified")
 	case count > 1:
 		return fmt.Errorf("you may only set one trigger type at a time")
 	case count == 0 && !o.Remove && !o.RemoveAll && !o.Auto && !o.Manual && !o.PrintTable:
 		return fmt.Errorf("specify one of the --from-* flags to add a trigger, --remove to remove, or --auto|--manual to control existing triggers")
 	}
 	return nil
 }
 
 func (o *TriggersOptions) Run() error {
 	infos := o.Infos
 	singular := len(o.Infos) <= 1
 	if o.Builder != nil {
 		loaded, err := o.Builder.Do().IntoSingular(&singular).Infos()
 		if err != nil {
 			return err
 		}
 		infos = loaded
 	}
 
 	if o.PrintTable && o.PrintObject == nil {
 		return o.printTriggers(infos)
 	}
 
 	updateTriggerFn := func(triggers *TriggerDefinition) error {
 		o.updateTriggers(triggers)
 		return nil
 	}
 	patches := CalculatePatches(infos, o.Encoder, func(info *resource.Info) (bool, error) {
 		return UpdateTriggersForObject(info.Object, updateTriggerFn)
 	})
 	if singular && len(patches) == 0 {
 		return fmt.Errorf("%s/%s is not a deployment config or build config", infos[0].Mapping.Resource, infos[0].Name)
 	}
 	if o.PrintObject != nil {
0650f99d
 		return o.PrintObject(infos)
304a2b2b
 	}
 
 	failed := false
 	for _, patch := range patches {
 		info := patch.Info
 		if patch.Err != nil {
 			failed = true
 			fmt.Fprintf(o.Err, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err)
 			continue
 		}
 
 		if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
 			fmt.Fprintf(o.Err, "info: %s %q was not changed\n", info.Mapping.Resource, info.Name)
 			continue
 		}
 
 		glog.V(4).Infof("Calculated patch %s", patch.Patch)
 
 		obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, kapi.StrategicMergePatchType, patch.Patch)
 		if err != nil {
 			handlePodUpdateError(o.Err, err, "triggered")
 			failed = true
 			continue
 		}
 
 		info.Refresh(obj, true)
7a2339f4
 		kcmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, false, "updated")
304a2b2b
 	}
 	if failed {
 		return cmdutil.ErrExit
 	}
 	return nil
 }
 
 // printTriggers displays a tabular output of the triggers for each object.
 func (o *TriggersOptions) printTriggers(infos []*resource.Info) error {
 	w := tabwriter.NewWriter(o.Out, 0, 2, 2, ' ', 0)
 	defer w.Flush()
 	fmt.Fprintf(w, "NAME\tTYPE\tVALUE\tAUTO\n")
 	for _, info := range infos {
 		_, err := UpdateTriggersForObject(info.Object, func(triggers *TriggerDefinition) error {
 			fmt.Fprintf(w, "%s/%s\t%s\t%s\t%t\n", info.Mapping.Resource, info.Name, "config", "", triggers.ConfigChange)
 			for _, image := range triggers.ImageChange {
 				var details string
 				switch {
 				case len(image.Names) > 0:
 					if len(image.Namespace) > 0 {
 						details = fmt.Sprintf("%s/%s (%s)", image.Namespace, image.From, strings.Join(image.Names, ", "))
 					} else {
 						details = fmt.Sprintf("%s (%s)", image.From, strings.Join(image.Names, ", "))
 					}
 				case len(image.Namespace) > 0:
 					details = fmt.Sprintf("%s/%s", image.Namespace, image.From)
 				default:
 					details = image.From
 				}
 				fmt.Fprintf(w, "%s/%s\t%s\t%s\t%t\n", info.Mapping.Resource, info.Name, "image", details, image.Auto)
 			}
 			for _, s := range triggers.WebHooks {
 				fmt.Fprintf(w, "%s/%s\t%s\t%s\t%s\n", info.Mapping.Resource, info.Name, "webhook", s, "")
 			}
 			for _, s := range triggers.GitHubWebHooks {
 				fmt.Fprintf(w, "%s/%s\t%s\t%s\t%s\n", info.Mapping.Resource, info.Name, "github", s, "")
 			}
 			return nil
 		})
 		if err != nil {
 			fmt.Fprintf(w, "%s/%s\t%s\t%s\t%t\n", info.Mapping.Resource, info.Name, "<error>", "", false)
 		}
 	}
 	return nil
 }
 
 // updateTriggers updates only those fields with flags set by the user
 func (o *TriggersOptions) updateTriggers(triggers *TriggerDefinition) {
 	// clear everything
 	if o.RemoveAll {
 		*triggers = TriggerDefinition{}
 		return
 	}
 
 	// clear a specific field
 	if o.Remove {
 		if o.FromConfig {
 			triggers.ConfigChange = false
 		}
 		if len(o.FromImage) > 0 {
 			var newTriggers []ImageChangeTrigger
 			for _, trigger := range triggers.ImageChange {
 				if trigger.From != o.FromImage {
 					newTriggers = append(newTriggers, trigger)
 				}
 			}
 			triggers.ImageChange = newTriggers
 		}
 		if o.FromWebHook != nil && *o.FromWebHook {
 			triggers.WebHooks = nil
 		}
150c7c58
 		if o.FromWebHookAllowEnv != nil && *o.FromWebHookAllowEnv {
 			triggers.WebHooks = nil
 			triggers.WebHooksAllowEnv = false
 		}
304a2b2b
 		if o.FromGitHub != nil && *o.FromGitHub {
 			triggers.GitHubWebHooks = nil
 		}
 		return
 	}
 
 	// change the automated status
 	if o.Reset {
 		triggers.ConfigChange = o.Auto
 		for i := range triggers.ImageChange {
 			triggers.ImageChange[i].Auto = o.Auto
 		}
 		return
 	}
 
 	// change individual elements
 	if o.FromConfig {
 		triggers.ConfigChange = true
 	}
 	if len(o.FromImage) > 0 {
 		names := strings.Split(o.ContainerNames, ",")
 		if len(o.ContainerNames) == 0 {
 			names = nil
 		}
 		found := false
 		for i, trigger := range triggers.ImageChange {
 			if trigger.From == o.FromImage && trigger.Namespace == o.FromImageNamespace {
 				found = true
 				triggers.ImageChange[i].Auto = !o.Manual
 				triggers.ImageChange[i].Names = names
 				break
 			}
 		}
 		if !found {
 			triggers.ImageChange = append(triggers.ImageChange, ImageChangeTrigger{
 				From:      o.FromImage,
 				Namespace: o.FromImageNamespace,
 				Auto:      !o.Manual,
 				Names:     names,
 			})
 		}
 	}
 	if o.FromWebHook != nil && *o.FromWebHook {
 		triggers.WebHooks = []string{app.GenerateSecret(20)}
 	}
150c7c58
 	if o.FromWebHookAllowEnv != nil && *o.FromWebHookAllowEnv {
 		triggers.WebHooks = []string{app.GenerateSecret(20)}
 		triggers.WebHooksAllowEnv = true
 	}
304a2b2b
 	if o.FromGitHub != nil && *o.FromGitHub {
 		triggers.GitHubWebHooks = []string{app.GenerateSecret(20)}
 	}
 }
 
 // ImageChangeTrigger represents the capabilities present in deployment config and build
 // config objects in a consistent way.
 type ImageChangeTrigger struct {
 	// If this trigger is automatically applied
 	Auto bool
 	// An ImageStreamTag name to target
 	From string
 	// The target namespace, normalized if set
 	Namespace string
 	// A list of names this trigger targets
 	Names []string
 }
 
 // TriggerDefinition is the abstract representation of triggers for builds and deploymnet configs.
 type TriggerDefinition struct {
150c7c58
 	ConfigChange     bool
 	ImageChange      []ImageChangeTrigger
 	WebHooks         []string
 	WebHooksAllowEnv bool
 	GitHubWebHooks   []string
304a2b2b
 }
 
 // defaultNamespace returns an empty string if the provided namespace matches the default namespace, or
 // returns the namespace.
 func defaultNamespace(namespace, defaultNamespace string) string {
 	if namespace == defaultNamespace {
 		return ""
 	}
 	return namespace
 }
 
 // NewDeploymentConfigTriggers creates a trigger definition from a deployment config.
 func NewDeploymentConfigTriggers(config *deployapi.DeploymentConfig) *TriggerDefinition {
 	t := &TriggerDefinition{}
 	for _, trigger := range config.Spec.Triggers {
 		switch trigger.Type {
 		case deployapi.DeploymentTriggerOnConfigChange:
 			t.ConfigChange = true
 		case deployapi.DeploymentTriggerOnImageChange:
 			t.ImageChange = append(t.ImageChange, ImageChangeTrigger{
 				Auto:      trigger.ImageChangeParams.Automatic,
 				Names:     trigger.ImageChangeParams.ContainerNames,
 				From:      trigger.ImageChangeParams.From.Name,
 				Namespace: defaultNamespace(trigger.ImageChangeParams.From.Namespace, config.Namespace),
 			})
 		}
 	}
 	return t
 }
 
 // NewBuildConfigTriggers creates a trigger definition from a build config.
 func NewBuildConfigTriggers(config *buildapi.BuildConfig) *TriggerDefinition {
 	t := &TriggerDefinition{}
 	setStrategy := false
 	for _, trigger := range config.Spec.Triggers {
 		switch trigger.Type {
 		case buildapi.ConfigChangeBuildTriggerType:
 			t.ConfigChange = true
 		case buildapi.GenericWebHookBuildTriggerType:
 			t.WebHooks = append(t.WebHooks, trigger.GenericWebHook.Secret)
150c7c58
 			t.WebHooksAllowEnv = trigger.GenericWebHook.AllowEnv
304a2b2b
 		case buildapi.GitHubWebHookBuildTriggerType:
 			t.GitHubWebHooks = append(t.GitHubWebHooks, trigger.GitHubWebHook.Secret)
 		case buildapi.ImageChangeBuildTriggerType:
 			if trigger.ImageChange.From == nil {
 				if strategyTrigger := strategyTrigger(config); strategyTrigger != nil {
 					setStrategy = true
 					strategyTrigger.Auto = true
 					t.ImageChange = append(t.ImageChange, *strategyTrigger)
 				}
 				continue
 			}
 			// normalize the trigger
 			trigger.ImageChange.From.Namespace = defaultNamespace(trigger.ImageChange.From.Namespace, config.Namespace)
 			t.ImageChange = append(t.ImageChange, ImageChangeTrigger{
 				Auto:      true,
 				From:      trigger.ImageChange.From.Name,
 				Namespace: trigger.ImageChange.From.Namespace,
 			})
 		}
 	}
 	if !setStrategy {
 		if strategyTrigger := strategyTrigger(config); strategyTrigger != nil {
 			t.ImageChange = append(t.ImageChange, *strategyTrigger)
 		}
 	}
 	return t
 }
 
 // Apply writes a trigger definition back to a build or deployment config.
 func (t *TriggerDefinition) Apply(obj runtime.Object) error {
 	switch c := obj.(type) {
 	case *deployapi.DeploymentConfig:
 		if len(t.GitHubWebHooks) > 0 {
 			return fmt.Errorf("deployment configs do not support GitHub web hooks")
 		}
 		if len(t.WebHooks) > 0 {
 			return fmt.Errorf("deployment configs do not support web hooks")
 		}
 
 		existingTriggers := filterDeploymentTriggers(c.Spec.Triggers, deployapi.DeploymentTriggerOnConfigChange)
 		var triggers []deployapi.DeploymentTriggerPolicy
 		if t.ConfigChange {
 			triggers = append(triggers, deployapi.DeploymentTriggerPolicy{Type: deployapi.DeploymentTriggerOnConfigChange})
 		}
 		allNames := sets.NewString()
 		for _, container := range c.Spec.Template.Spec.Containers {
 			allNames.Insert(container.Name)
 		}
 		for _, trigger := range t.ImageChange {
 			if len(trigger.Names) == 0 {
 				return fmt.Errorf("you must specify --containers when setting --from-image")
 			}
 			if !allNames.HasAll(trigger.Names...) {
 				return fmt.Errorf(
 					"not all container names exist: %s (accepts: %s)",
 					strings.Join(sets.NewString(trigger.Names...).Difference(allNames).List(), ", "),
 					strings.Join(allNames.List(), ", "),
 				)
 			}
 			triggers = append(triggers, deployapi.DeploymentTriggerPolicy{
 				Type: deployapi.DeploymentTriggerOnImageChange,
 				ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
 					Automatic: trigger.Auto,
 					From: kapi.ObjectReference{
 						Kind: "ImageStreamTag",
 						Name: trigger.From,
 					},
 					ContainerNames: trigger.Names,
 				},
 			})
 		}
 		c.Spec.Triggers = mergeDeployTriggers(existingTriggers, triggers)
 		return nil
 
 	case *buildapi.BuildConfig:
 		var triggers []buildapi.BuildTriggerPolicy
 		if t.ConfigChange {
 			triggers = append(triggers, buildapi.BuildTriggerPolicy{Type: buildapi.ConfigChangeBuildTriggerType})
 		}
 		for _, trigger := range t.WebHooks {
 			triggers = append(triggers, buildapi.BuildTriggerPolicy{
 				Type: buildapi.GenericWebHookBuildTriggerType,
 				GenericWebHook: &buildapi.WebHookTrigger{
150c7c58
 					Secret:   trigger,
 					AllowEnv: t.WebHooksAllowEnv,
304a2b2b
 				},
 			})
 		}
 		for _, trigger := range t.GitHubWebHooks {
 			triggers = append(triggers, buildapi.BuildTriggerPolicy{
 				Type: buildapi.GitHubWebHookBuildTriggerType,
 				GitHubWebHook: &buildapi.WebHookTrigger{
 					Secret: trigger,
 				},
 			})
 		}
 
 		// add new triggers, filter out any old triggers that match (if moving from automatic to manual),
 		// and then merge the old triggers and the new triggers to preserve fields like lastTriggeredImageID
 		existingTriggers := c.Spec.Triggers
 		strategyTrigger := strategyTrigger(c)
 		for _, trigger := range t.ImageChange {
 			change := &buildapi.ImageChangeTrigger{
 				From: &kapi.ObjectReference{
 					Kind:      "ImageStreamTag",
 					Name:      trigger.From,
 					Namespace: trigger.Namespace,
 				},
 			}
 
 			// use the canonical ImageChangeTrigger with nil From
 			strategyTrigger.Auto = trigger.Auto
 			if reflect.DeepEqual(strategyTrigger, &trigger) {
 				change.From = nil
 			}
 
 			// if this trigger is not automatic, then we need to remove it from the list of triggers
 			if !trigger.Auto {
 				existingTriggers = filterBuildImageTriggers(existingTriggers, trigger, strategyTrigger)
 				continue
 			}
 
 			triggers = append(triggers, buildapi.BuildTriggerPolicy{
 				Type:        buildapi.ImageChangeBuildTriggerType,
 				ImageChange: change,
 			})
 		}
 		c.Spec.Triggers = mergeBuildTriggers(existingTriggers, triggers)
 		return nil
 
 	default:
 		return fmt.Errorf("the object is not a deployment config or build config")
 	}
 }
 
 // triggerMatchesBuildImageChange identifies whether the image change is equivalent to the trigger
 func triggerMatchesBuildImageChange(trigger ImageChangeTrigger, strategyTrigger *ImageChangeTrigger, imageChange *buildapi.ImageChangeTrigger) bool {
 	if imageChange == nil {
 		return false
 	}
 	if imageChange.From == nil {
 		return strategyTrigger != nil && strategyTrigger.From == trigger.From && strategyTrigger.Namespace == trigger.Namespace
 	}
 	namespace := imageChange.From.Namespace
 	if strategyTrigger != nil {
 		namespace = defaultNamespace(namespace, strategyTrigger.Namespace)
 	}
 	return imageChange.From.Name == trigger.From && namespace == trigger.Namespace
 }
 
 // filterBuildImageTriggers return only triggers that do not match the provided ImageChangeTrigger.  strategyTrigger may be provided
 // if set to remove a BuildTriggerPolicy without a From (which points to the strategy)
 func filterBuildImageTriggers(src []buildapi.BuildTriggerPolicy, trigger ImageChangeTrigger, strategyTrigger *ImageChangeTrigger) []buildapi.BuildTriggerPolicy {
 	var dst []buildapi.BuildTriggerPolicy
 	for i := range src {
 		if triggerMatchesBuildImageChange(trigger, strategyTrigger, src[i].ImageChange) {
 			continue
 		}
 		dst = append(dst, src[i])
 	}
 	return dst
 }
 
 // filterDeploymentTriggers returns only triggers that do not have one of the provided types.
 func filterDeploymentTriggers(src []deployapi.DeploymentTriggerPolicy, types ...deployapi.DeploymentTriggerType) []deployapi.DeploymentTriggerPolicy {
 	var dst []deployapi.DeploymentTriggerPolicy
 Outer:
 	for i := range src {
 		for _, t := range types {
 			if t == src[i].Type {
 				continue Outer
 			}
 		}
 		dst = append(dst, src[i])
 	}
 	return dst
 }
 
 // strategyTrigger returns a synthetic ImageChangeTrigger that represents the image stream tag the build strategy
 // points to, or nil if no such strategy trigger is possible (if the build doesn't point to an ImageStreamTag).
 func strategyTrigger(config *buildapi.BuildConfig) *ImageChangeTrigger {
482a64f4
 	if from := buildutil.GetInputReference(config.Spec.Strategy); from != nil {
304a2b2b
 		if from.Kind == "ImageStreamTag" {
 			// normalize the strategy object reference
 			from.Namespace = defaultNamespace(from.Namespace, config.Namespace)
 			return &ImageChangeTrigger{From: from.Name, Namespace: from.Namespace}
 		}
 	}
 	return nil
 }
 
 // mergeDeployTriggers returns an array of DeploymentTriggerPolicies that have no duplicates.
 func mergeDeployTriggers(dst, src []deployapi.DeploymentTriggerPolicy) []deployapi.DeploymentTriggerPolicy {
 	// never return an empty map, because the triggers on a deployment config default when the map is empty
 	result := []deployapi.DeploymentTriggerPolicy{}
 	for _, current := range dst {
 		if findDeployTrigger(src, current) != -1 {
 			result = append(result, current)
 		}
 	}
 	for _, current := range src {
 		if findDeployTrigger(result, current) == -1 {
 			result = append(result, current)
 		}
 	}
 	return result
 }
 
 // findDeployTrigger finds the position of a deployment trigger in the provided array, or -1 if no such
 // matching trigger is found.
 func findDeployTrigger(dst []deployapi.DeploymentTriggerPolicy, trigger deployapi.DeploymentTriggerPolicy) int {
 	for i := range dst {
 		if reflect.DeepEqual(dst[i], trigger) {
 			return i
 		}
 	}
 	return -1
 }
 
 // mergeBuildTriggers returns an array of BuildTriggerPolicies that have no duplicates, in the same order
 // as they exist in their original arrays (a zip-merge).
 func mergeBuildTriggers(dst, src []buildapi.BuildTriggerPolicy) []buildapi.BuildTriggerPolicy {
 	var result []buildapi.BuildTriggerPolicy
 	for _, current := range dst {
 		if findBuildTrigger(src, current) != -1 {
 			result = append(result, current)
 		}
 	}
 	for _, current := range src {
 		if findBuildTrigger(result, current) == -1 {
 			result = append(result, current)
 		}
 	}
 	return result
 }
 
 // findBuildTrigger finds the equivalent build trigger position in the provided array, or -1 if
 // no such build trigger exists.  Equality only cares about the value of the From field.
 func findBuildTrigger(dst []buildapi.BuildTriggerPolicy, trigger buildapi.BuildTriggerPolicy) int {
 	// make a copy for semantic equality
 	if trigger.ImageChange != nil {
 		trigger.ImageChange = &buildapi.ImageChangeTrigger{From: trigger.ImageChange.From}
 	}
 	for i, copied := range dst {
 		// make a copy for semantic equality
 		if copied.ImageChange != nil {
 			copied.ImageChange = &buildapi.ImageChangeTrigger{From: copied.ImageChange.From}
 		}
 		if reflect.DeepEqual(copied, trigger) {
 			return i
 		}
 	}
 	return -1
 }
 
 // UpdateTriggersForObject extracts a trigger definition from the provided object, passes it to fn, and
 // then applies the trigger definition back on the object. It returns true if the object was mutated
 // and an optional error if the any part of the flow returns error.
 func UpdateTriggersForObject(obj runtime.Object, fn func(*TriggerDefinition) error) (bool, error) {
 	// TODO: replace with a swagger schema based approach (identify pod template via schema introspection)
 	switch t := obj.(type) {
 	case *deployapi.DeploymentConfig:
 		triggers := NewDeploymentConfigTriggers(t)
 		if err := fn(triggers); err != nil {
 			return true, err
 		}
 		return true, triggers.Apply(t)
 	case *buildapi.BuildConfig:
 		triggers := NewBuildConfigTriggers(t)
 		if err := fn(triggers); err != nil {
 			return true, err
 		}
 		return true, triggers.Apply(t)
 	default:
 		return false, fmt.Errorf("the object is not a deployment config or build config")
 	}
 }