commands/trigger.go (605 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 commands import ( "errors" "fmt" "github.com/apache/openwhisk-cli/wski18n" "github.com/apache/openwhisk-client-go/whisk" "github.com/fatih/color" "github.com/spf13/cobra" ) const FEED_LIFECYCLE_EVENT = "lifecycleEvent" const FEED_TRIGGER_NAME = "triggerName" const FEED_AUTH_KEY = "authKey" const FEED_CREATE = "CREATE" const FEED_READ = "READ" const FEED_UPDATE = "UPDATE" const FEED_DELETE = "DELETE" // triggerCmd represents the trigger command var triggerCmd = &cobra.Command{ Use: "trigger", Short: wski18n.T("work with triggers"), } var trigger = Trigger{} /** * Constructs the parameters to pass to the feed, consisting of the event type, trigger name, and subject key. * * NOTE: this function will exit in case of a processing error since it indicates a problem parsing parameters. * * @return the feed name and parameters if a feed is specified. */ func feedParameters(feed string, lifecycle string, trigger *QualifiedName, authKey string) (*QualifiedName, []string) { if feed == "" { return nil, make([]string, 0) } whisk.Debug(whisk.DbgInfo, "Trigger has a feed\n") var params []string name := fmt.Sprintf("/%s/%s", trigger.GetNamespace(), trigger.GetEntityName()) params = append(params, getFormattedJSON(FEED_LIFECYCLE_EVENT, lifecycle)) params = append(params, getFormattedJSON(FEED_TRIGGER_NAME, name)) params = append(params, getFormattedJSON(FEED_AUTH_KEY, authKey)) feedQualifiedName, err := NewQualifiedName(feed) if err != nil { ExitOnError(NewQualifiedNameError(feed, err)) } return feedQualifiedName, params } /** * Create or update a trigger. * * NOTE: this function will exit in case of a processing error since it indicates a problem parsing parameters. */ func createOrUpdate(Client *whisk.Client, fqn *QualifiedName, trigger *whisk.Trigger, overwrite bool) { // TODO get rid of these global modifiers Client.Namespace = fqn.GetNamespace() _, _, err := Client.Triggers.Insert(trigger, overwrite) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Insert(%+v, %t) failed: %s\n", trigger, overwrite, err) var errStr string if !overwrite { errStr = wski18n.T("Unable to create trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": err}) } else { errStr = wski18n.T("Unable to update trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": err}) } ExitOnError(whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)) } } var triggerFireCmd = &cobra.Command{ Use: "fire TRIGGER_NAME [PAYLOAD]", Short: wski18n.T("fire trigger event"), SilenceUsage: true, SilenceErrors: true, PreRunE: SetupClientConfig, RunE: func(cmd *cobra.Command, args []string) error { var err error var qualifiedName = new(QualifiedName) if whiskErr := CheckArgs(args, 1, 1, "Trigger fire", wski18n.T("A trigger name is required.")); whiskErr != nil { return whiskErr } if qualifiedName, err = NewQualifiedName(args[0]); err != nil { return NewQualifiedNameError(args[0], err) } parameters := getParameters(Flags.common.param, false, false) // TODO get rid of these global modifiers Client.Namespace = qualifiedName.GetNamespace() trigResp, _, err := Client.Triggers.Fire(qualifiedName.GetEntityName(), parameters) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Fire(%s, %#v) failed: %s\n", qualifiedName.GetEntityName(), parameters, err) errStr := wski18n.T("Unable to fire trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } fmt.Fprintf(color.Output, wski18n.T("{{.ok}} triggered /{{.namespace}}/{{.name}} with id {{.id}}\n", map[string]interface{}{ "ok": color.GreenString("ok:"), "namespace": boldString(qualifiedName.GetNamespace()), "name": boldString(qualifiedName.GetEntityName()), "id": boldString(trigResp.ActivationId)})) return nil }, } var triggerCreateCmd = &cobra.Command{ Use: "create TRIGGER_NAME", Short: wski18n.T("create new trigger"), SilenceUsage: true, SilenceErrors: true, PreRunE: SetupClientConfig, RunE: func(cmd *cobra.Command, args []string) error { return trigger.Create(Client, args) }, } var triggerUpdateCmd = &cobra.Command{ Use: "update TRIGGER_NAME", Short: wski18n.T("update an existing trigger, or create a trigger if it does not exist"), SilenceUsage: true, SilenceErrors: true, PreRunE: SetupClientConfig, RunE: func(cmd *cobra.Command, args []string) error { return trigger.Update(Client, args) }, } var triggerGetCmd = &cobra.Command{ Use: "get TRIGGER_NAME [FIELD_FILTER]", Short: wski18n.T("get trigger"), SilenceUsage: true, SilenceErrors: true, PreRunE: SetupClientConfig, RunE: func(cmd *cobra.Command, args []string) error { var err error var field string var fullFeedName string var qualifiedName = new(QualifiedName) if whiskErr := CheckArgs(args, 1, 2, "Trigger get", wski18n.T("A trigger name is required.")); whiskErr != nil { return whiskErr } if len(args) > 1 { field = args[1] if !fieldExists(&whisk.Trigger{}, field) { errMsg := wski18n.T("Invalid field filter '{{.arg}}'.", map[string]interface{}{"arg": field}) whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return whiskErr } } if qualifiedName, err = NewQualifiedName(args[0]); err != nil { return NewQualifiedNameError(args[0], err) } Client.Namespace = qualifiedName.GetNamespace() retTrigger, _, err := Client.Triggers.Get(qualifiedName.GetEntityName()) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Get(%s) failed: %s\n", qualifiedName.GetEntityName(), err) errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } // Get full feed name from trigger get request as it is needed to get the feed if retTrigger != nil && retTrigger.Annotations != nil { fullFeedName = getValueString(retTrigger.Annotations, "feed") } if len(fullFeedName) > 0 { fullTriggerName := fmt.Sprintf("/%s/%s", qualifiedName.GetNamespace(), qualifiedName.GetEntityName()) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_READ)) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName)) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_AUTH_KEY, Client.Config.AuthToken)) err = configureFeed(qualifiedName.GetEntityName(), fullFeedName, getParameters(Flags.common.param, false, false)) if err != nil { whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qualifiedName.GetEntityName(), fullFeedName, err) } } else { if Flags.trigger.summary { printSummary(retTrigger) } else { if len(field) > 0 { fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName()), "field": boldString(field)})) printField(retTrigger, field) } else { fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName())})) printJSON(retTrigger) } } } return nil }, } var triggerDeleteCmd = &cobra.Command{ Use: "delete TRIGGER_NAME", Short: wski18n.T("delete trigger"), SilenceUsage: true, SilenceErrors: true, PreRunE: SetupClientConfig, RunE: func(cmd *cobra.Command, args []string) error { var err error var retTrigger *whisk.Trigger var fullFeedName string var origParams []string var qualifiedName = new(QualifiedName) if whiskErr := CheckArgs(args, 1, 1, "Trigger delete", wski18n.T("A trigger name is required.")); whiskErr != nil { return whiskErr } if qualifiedName, err = NewQualifiedName(args[0]); err != nil { return NewQualifiedNameError(args[0], err) } Client.Namespace = qualifiedName.GetNamespace() retTrigger, _, err = Client.Triggers.Get(qualifiedName.GetEntityName()) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Get(%s) failed: %s\n", qualifiedName.GetEntityName(), err) errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } // Get full feed name from trigger delete request as it is needed to delete the feed if retTrigger != nil && retTrigger.Annotations != nil { fullFeedName = getValueString(retTrigger.Annotations, "feed") if len(fullFeedName) > 0 { origParams = Flags.common.param fullTriggerName := fmt.Sprintf("/%s/%s", qualifiedName.GetNamespace(), qualifiedName.GetEntityName()) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_DELETE)) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName)) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_AUTH_KEY, Client.Config.AuthToken)) err = configureFeed(qualifiedName.GetEntityName(), fullFeedName, getParameters(Flags.common.param, false, false)) if err != nil { whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qualifiedName.GetEntityName(), fullFeedName, err) } Flags.common.param = origParams Client.Namespace = qualifiedName.GetNamespace() } } retTrigger, _, err = Client.Triggers.Delete(qualifiedName.GetEntityName()) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Delete(%s) failed: %s\n", qualifiedName.GetEntityName(), err) errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } fmt.Fprintf(color.Output, wski18n.T("{{.ok}} deleted trigger {{.name}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName())})) return nil }, } var triggerListCmd = &cobra.Command{ Use: "list [NAMESPACE]", Short: wski18n.T("list all triggers"), SilenceUsage: true, SilenceErrors: true, PreRunE: SetupClientConfig, RunE: func(cmd *cobra.Command, args []string) error { var err error var qualifiedName = new(QualifiedName) if whiskErr := CheckArgs(args, 0, 1, "Trigger list", wski18n.T("An optional namespace is the only valid argument.")); whiskErr != nil { return whiskErr } if len(args) == 1 { if qualifiedName, err = NewQualifiedName(args[0]); err != nil { return NewQualifiedNameError(args[0], err) } if len(qualifiedName.GetEntityName()) > 0 { return entityNameError(qualifiedName.GetEntityName()) } Client.Namespace = qualifiedName.GetNamespace() } options := &whisk.TriggerListOptions{ Skip: Flags.common.skip, Limit: Flags.common.limit, } triggers, _, err := Client.Triggers.List(options) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.List(%#v) for namespace '%s' failed: %s\n", options, Client.Namespace, err) errStr := wski18n.T("Unable to obtain the list of triggers for namespace '{{.name}}': {{.err}}", map[string]interface{}{"name": getClientNamespace(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } sortByName := Flags.common.nameSort printList(triggers, sortByName) return nil }, } func configureFeed(triggerName string, feedName string, parameters interface{}) error { var fullFeedName *QualifiedName var err error if fullFeedName, err = NewQualifiedName(feedName); err != nil { return NewQualifiedNameError(feedName, err) } res, err := invokeAction(*fullFeedName, parameters, true, false) err = printInvocationResponse(*fullFeedName, true, false, res, err) if err != nil { whisk.Debug(whisk.DbgError, "Invoke of action '%s' failed: %s\n", feedName, err) errStr := wski18n.T(FEED_CONFIGURATION_FAILURE, map[string]interface{}{"feedname": feedName, "trigname": triggerName, "err": err}) err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) } else { whisk.Debug(whisk.DbgInfo, "Successfully configured trigger feed via feed action '%s'\n", feedName) } return err } func deleteTrigger(triggerName string) error { args := []string{triggerName} err := triggerDeleteCmd.RunE(nil, args) if err != nil { whisk.Debug(whisk.DbgError, "Trigger '%s' delete failed: %s\n", triggerName, err) errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": triggerName, "err": err}) err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) } return err } func init() { triggerCreateCmd.Flags().StringSliceVarP(&Flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format")) triggerCreateCmd.Flags().StringVarP(&Flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format")) triggerCreateCmd.Flags().StringSliceVarP(&Flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format")) triggerCreateCmd.Flags().StringVarP(&Flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format")) triggerCreateCmd.Flags().StringVarP(&Flags.common.feed, "feed", "f", "", wski18n.T("trigger feed `ACTION_NAME`")) triggerCreateCmd.Flags().StringSliceVarP(&Flags.trigger.feedParam, "feed-param", "F", []string{}, wski18n.T("feed parameter values in `KEY VALUE` format")) triggerCreateCmd.Flags().StringSliceVarP(&Flags.trigger.triggerParam, "trigger-param", "T", []string{}, wski18n.T("trigger parameter values in `KEY VALUE` format")) triggerUpdateCmd.Flags().StringSliceVarP(&Flags.common.annotation, "annotation", "a", []string{}, wski18n.T("annotation values in `KEY VALUE` format")) triggerUpdateCmd.Flags().StringVarP(&Flags.common.annotFile, "annotation-file", "A", "", wski18n.T("`FILE` containing annotation values in JSON format")) triggerUpdateCmd.Flags().StringSliceVarP(&Flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format")) triggerUpdateCmd.Flags().StringVarP(&Flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format")) triggerUpdateCmd.Flags().StringSliceVarP(&Flags.trigger.feedParam, "feed-param", "F", []string{}, wski18n.T("feed parameter values in `KEY VALUE` format")) triggerUpdateCmd.Flags().StringSliceVarP(&Flags.trigger.triggerParam, "trigger-param", "T", []string{}, wski18n.T("trigger parameter values in `KEY VALUE` format")) triggerGetCmd.Flags().BoolVarP(&Flags.trigger.summary, "summary", "s", false, wski18n.T("summarize trigger details; parameters with prefix \"*\" are bound")) triggerFireCmd.Flags().StringSliceVarP(&Flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format")) triggerFireCmd.Flags().StringVarP(&Flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format")) triggerListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of triggers from the result")) triggerListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of triggers from the collection")) triggerListCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("sorts a list alphabetically by entity name; only applicable within the limit/skip returned entity block")) triggerCmd.AddCommand( triggerFireCmd, triggerCreateCmd, triggerUpdateCmd, triggerGetCmd, triggerDeleteCmd, triggerListCmd, ) } type Trigger struct { } func (t *Trigger) Create(Client *whisk.Client, args []string) error { if whiskErr := CheckArgs(args, 1, 1, "Trigger create", wski18n.T("A trigger name is required.")); whiskErr != nil { return whiskErr } //1. if the command line arguments user provides contains only --param flags //2. if the command line arguments user provides contains no --param flags at all //we should process the trigger create command in the old way. if userIndicatesToUseOldTriggerCommand() { triggerName, err := NewQualifiedName(args[0]) if err != nil { return NewQualifiedNameError(args[0], err) } //if user also issued new trigger command then we stop execution if triggerUsageErr := userIssuedNewTriggerCmd(); triggerUsageErr != nil { return triggerUsageErr } annotationArray := Flags.common.annotation authToken := Client.Config.AuthToken // if a feed is specified, create additional parameters which must be passed to the feed feedQualifiedName, additionalFeedParams := feedParameters(Flags.common.feed, FEED_CREATE, triggerName, authToken) // if a feed is specified, add feed annotation the annotations declared on the command line // TODO: add test to ensure that generated annotation has precedence if feedQualifiedName != nil { annotationArray = append(annotationArray, getFormattedJSON("feed", feedQualifiedName.GetFullQualifiedName())) } annotations := getParameters(annotationArray, true, true) // the feed receives all the parameters that are specified on the command line so we merge // the feed lifecycle parameters with the command line ones parameters := getParameters(append(Flags.common.param, additionalFeedParams...), feedQualifiedName == nil, false) trigger := &whisk.Trigger{ Name: triggerName.GetEntityName(), Annotations: annotations.(whisk.KeyValueArr), } if feedQualifiedName == nil { // parameters are only attached to the trigger in there is no feed, otherwise // parameters are passed to the feed instead trigger.Parameters = parameters.(whisk.KeyValueArr) } createOrUpdate(Client, triggerName, trigger, false) // Invoke the specified feed action to configure the trigger feed if feedQualifiedName != nil { res, err := invokeAction(*feedQualifiedName, parameters, true, false) if err != nil { whisk.Debug(whisk.DbgError, "Failed configuring feed '%s' failed: %s\n", feedQualifiedName.GetFullQualifiedName(), err) // TODO: should we do this at all? Keeping for now. printFailedBlockingInvocationResponse(*feedQualifiedName, false, res, err) reason := wski18n.T(FEED_CONFIGURATION_FAILURE, map[string]interface{}{"feedname": feedQualifiedName.GetFullQualifiedName(), "err": err}) errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": reason}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) // Delete trigger that was created for this feed err = deleteTrigger(triggerName.GetEntityName()) if err != nil { whisk.Debug(whisk.DbgWarn, "Ignoring deleteTrigger(%s) failure: %s\n", triggerName.GetEntityName(), err) } return werr } else { whisk.Debug(whisk.DbgInfo, "Successfully configured trigger feed via feed action '%s'\n", Flags.common.feed) // preserve existing behavior where output of feed activation is emitted to console printInvocationMsg(*feedQualifiedName, true, true, res, color.Output) } } fmt.Fprintf(color.Output, wski18n.T("{{.ok}} created trigger {{.name}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(trigger.Name)})) return nil } //1. if user's input command line argument contains either --feed-param or --trigger-param //2. if user's input command line argument contains both --feed-param and --trigger-param //then we process trigger create command in a different way return CreateExtendedVersion(Client, args) } //CreateExtendedVersion only executes when users indicate to create triggers with --feed-param //or --trigger-param flags. func CreateExtendedVersion(Client *whisk.Client, args []string) error { triggerName, err := NewQualifiedName(args[0]) if err != nil { return NewQualifiedNameError(args[0], err) } annotationArray := Flags.common.annotation authToken := Client.Config.AuthToken // if a feed is specified, create additional parameters which must be passed to the feed feedQualifiedName, additionalFeedParams := feedParameters(Flags.common.feed, FEED_CREATE, triggerName, authToken) // if a feed is specified, add feed annotation the annotations declared on the command line // TODO: add test to ensure that generated annotation has precedence if feedQualifiedName != nil { annotationArray = append(annotationArray, getFormattedJSON("feed", feedQualifiedName.GetFullQualifiedName())) } annotations := getParameters(annotationArray, true, true) //if trigger contains no feed but user tries to update feed parameter, then we issue error. if feedQualifiedName == nil && len(Flags.trigger.feedParam) > 0 { errStr := wski18n.T("Incorrect usage. Trigger without a feed cannot have feed parameters") return whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) } triggerParams := getParameters(Flags.trigger.triggerParam, true, false) //if a feed is specified, add feed annotation the annotations declared on the command line //TODO: add test to ensure that generated annotation has precedence feedParams := getParameters(append(Flags.trigger.feedParam, additionalFeedParams...), feedQualifiedName == nil, false) trigger := &whisk.Trigger{ Name: triggerName.GetEntityName(), Annotations: annotations.(whisk.KeyValueArr), Parameters: triggerParams.(whisk.KeyValueArr), } createOrUpdate(Client, triggerName, trigger, false) // Invoke the specified feed action to configure the trigger feed if feedQualifiedName != nil { res, err := invokeAction(*feedQualifiedName, feedParams, true, false) if err != nil { whisk.Debug(whisk.DbgError, "Failed configuring feed '%s' failed: %s\n", feedQualifiedName.GetFullQualifiedName(), err) // TODO: should we do this at all? Keeping for now. printFailedBlockingInvocationResponse(*feedQualifiedName, false, res, err) reason := wski18n.T(FEED_CONFIGURATION_FAILURE, map[string]interface{}{"feedname": feedQualifiedName.GetFullQualifiedName(), "err": err}) errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": reason}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) // Delete trigger that was created for this feed err = deleteTrigger(triggerName.GetEntityName()) if err != nil { whisk.Debug(whisk.DbgWarn, "Ignoring deleteTrigger(%s) failure: %s\n", triggerName.GetEntityName(), err) } return werr } whisk.Debug(whisk.DbgInfo, "Successfully configured trigger feed via feed action '%s'\n", Flags.common.feed) // preserve existing behavior where output of feed activation is emitted to console printInvocationMsg(*feedQualifiedName, true, true, res, color.Output) } fmt.Fprintf(color.Output, wski18n.T("{{.ok}} created trigger {{.name}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(trigger.Name)})) return nil } func (t *Trigger) Update(Client *whisk.Client, args []string) error { var err error var fullFeedName string var qualifiedName = new(QualifiedName) if whiskErr := CheckArgs(args, 1, 1, "Trigger update", wski18n.T("A trigger name is required.")); whiskErr != nil { return whiskErr } if qualifiedName, err = NewQualifiedName(args[0]); err != nil { return NewQualifiedNameError(args[0], err) } Client.Namespace = qualifiedName.GetNamespace() // Convert the trigger's list of default parameters from a string into []KeyValue // The 1 or more --param arguments have all been combined into a single []string // e.g. --p arg1,arg2 --p arg3,arg4 -> [arg1, arg2, arg3, arg4] whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", Flags.common.param) parameters, err := getJSONFromStrings(Flags.common.param, true) if err != nil { whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", Flags.common.param, err) errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}", map[string]interface{}{"param": fmt.Sprintf("%#v", Flags.common.param), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return werr } whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", Flags.common.annotation) annotations, err := getJSONFromStrings(Flags.common.annotation, true) if err != nil { whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", Flags.common.annotation, err) errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}", map[string]interface{}{"annotation": fmt.Sprintf("%#v", Flags.common.annotation), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return werr } retTrigger, httpResp, err := Client.Triggers.Get(qualifiedName.GetEntityName()) if err != nil && httpResp.StatusCode == 404 { if createErr := t.Create(Client, args); createErr != nil { return createErr } return nil } else if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Get(%s) failed: %s\n", qualifiedName.GetEntityName(), err) errStr := wski18n.T("Unable to get trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } else { if !userIndicatesToUseOldTriggerCommand() { return UpdateExtendedVersion(Client, args, retTrigger) } if triggerUsageErr := userIssuedNewTriggerCmd(); triggerUsageErr != nil { return triggerUsageErr } // Get full feed name from trigger get request as it is needed to get the feed if retTrigger != nil && retTrigger.Annotations != nil { fullFeedName = getValueString(retTrigger.Annotations, "feed") } if len(fullFeedName) > 0 { fullTriggerName := fmt.Sprintf("/%s/%s", qualifiedName.GetNamespace(), qualifiedName.GetEntityName()) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_UPDATE)) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName)) Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_AUTH_KEY, Client.Config.AuthToken)) // Invoke the specified feed action to configure the trigger feed err = configureFeed(qualifiedName.GetEntityName(), fullFeedName, getParameters(Flags.common.param, false, false)) if err != nil { whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qualifiedName.GetEntityName(), Flags.common.feed, err) errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } } else { trigger := &whisk.Trigger{ Name: qualifiedName.GetEntityName(), Parameters: parameters.(whisk.KeyValueArr), Annotations: annotations.(whisk.KeyValueArr), } _, _, err = Client.Triggers.Insert(trigger, true) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Insert(%+v,true) failed: %s\n", trigger, err) errStr := wski18n.T("Unable to update trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } } fmt.Fprintf(color.Output, wski18n.T("{{.ok}} updated trigger {{.name}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName())})) } return nil } //UpdateExtendedVersion only executes when users indicate to update triggers with --feed-param //or --trigger-param flags. func UpdateExtendedVersion(Client *whisk.Client, args []string, retTrigger *whisk.Trigger) error { var fullFeedName string var qualifiedName = new(QualifiedName) var err error if qualifiedName, err = NewQualifiedName(args[0]); err != nil { return NewQualifiedNameError(args[0], err) } whisk.Debug(whisk.DbgInfo, "Parsing parameters: %#v\n", Flags.trigger.triggerParam) triggerParameters, err := getJSONFromStrings(Flags.trigger.triggerParam, true) if err != nil { whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", Flags.common.param, err) errStr := wski18n.T("Invalid parameter argument '{{.param}}': {{.err}}", map[string]interface{}{"param": fmt.Sprintf("%#v", Flags.common.param), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return werr } whisk.Debug(whisk.DbgInfo, "Parsing annotations: %#v\n", Flags.common.annotation) annotations, err := getJSONFromStrings(Flags.common.annotation, true) if err != nil { whisk.Debug(whisk.DbgError, "getJSONFromStrings(%#v, true) failed: %s\n", Flags.common.annotation, err) errStr := wski18n.T("Invalid annotation argument '{{.annotation}}': {{.err}}", map[string]interface{}{"annotation": fmt.Sprintf("%#v", Flags.common.annotation), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return werr } trigger := &whisk.Trigger{ Name: qualifiedName.GetEntityName(), Parameters: triggerParameters.(whisk.KeyValueArr), Annotations: annotations.(whisk.KeyValueArr), } // Get full feed name from trigger get request as it is needed to get the feed if retTrigger != nil && retTrigger.Annotations != nil { fullFeedName = getValueString(retTrigger.Annotations, "feed") } _, _, err = Client.Triggers.Insert(trigger, true) if err != nil { whisk.Debug(whisk.DbgError, "Client.Triggers.Insert(%+v,true) failed: %s\n", trigger, err) errStr := wski18n.T("Unable to update trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } //if there is no feed attached to this trigger if len(fullFeedName) < 1 { //but user indicate feed parameter change, we issue error message. if len(Flags.trigger.feedParam) > 0 { whisk.Debug(whisk.DbgError, "trigger %+v is found but it does not contain a feed. \n", trigger) err := errors.New("this trigger does not contain a feed") errStr := wski18n.T(FEED_CONFIGURATION_FAILURE, map[string]interface{}{"feedname": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } } if len(fullFeedName) > 0 && feedParameterChanged(Flags.trigger.feedParam) { //if there is feed, we invoke the action to configure the feed regardless any changes on feed parameters fullTriggerName := fmt.Sprintf("/%s/%s", qualifiedName.GetNamespace(), qualifiedName.GetEntityName()) Flags.trigger.feedParam = append(Flags.trigger.feedParam, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_UPDATE)) Flags.trigger.feedParam = append(Flags.trigger.feedParam, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName)) Flags.trigger.feedParam = append(Flags.trigger.feedParam, getFormattedJSON(FEED_AUTH_KEY, Client.Config.AuthToken)) // Invoke the specified feed action to configure the trigger feed err = configureFeed(qualifiedName.GetEntityName(), fullFeedName, getParameters(Flags.trigger.feedParam, false, false)) if err != nil { whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qualifiedName.GetEntityName(), Flags.common.feed, err) errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return werr } } fmt.Fprintf(color.Output, wski18n.T("{{.ok}} updated trigger {{.name}}\n", map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName())})) return nil } func feedParameterChanged(triggerParam []string) bool { return len(triggerParam) > 0 } //if users are 1. creating a trigger without any feed or parameters // 2. creating a trigger using --param flag //then we use the old way to create the trigger. func userIndicatesToUseOldTriggerCommand() bool { return len(Flags.common.param) > 0 || (len(Flags.trigger.feedParam) == 0 && len(Flags.common.param) == 0 && len(Flags.trigger.triggerParam) == 0) } func userIssuedNewTriggerCmd() error { if len(Flags.trigger.feedParam) > 0 || len(Flags.trigger.triggerParam) > 0 { whisk.Debug(whisk.DbgError, "User tries to mix use of --param or --param-file flags with --feed-param or --trigger-param") errStr := wski18n.T("Incorrect usage. Cannot combine --feed-param or --trigger-param flag with neither --param nor --param-file flag") return whisk.MakeWskError(errors.New(errStr), whisk.NOT_ALLOWED, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) } return nil }