diff --git a/cmd/invoke.go b/cmd/invoke.go index 2a60bc2f39..9ff96d37fa 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -106,7 +106,7 @@ EXAMPLES "onvoke", "unvoke", "knvoke", "imvoke", "ihvoke", "ibvoke"}, PreRunE: bindEnv("path", "format", "target", "id", "source", "type", "data", "content-type", "request-type", "file", "insecure", - "confirm", "verbose"), + "confirm", "verbose", "extension"), RunE: func(cmd *cobra.Command, args []string) error { return runInvoke(cmd, args, newClient) }, @@ -129,6 +129,7 @@ EXAMPLES cmd.Flags().StringP("data", "", fn.DefaultInvokeData, "Data to send in the request. ($FUNC_DATA)") cmd.Flags().StringP("file", "", "", "Path to a file to use as data. Overrides --data flag and should be sent with a correct --content-type. ($FUNC_FILE)") cmd.Flags().BoolP("insecure", "i", false, "Allow insecure server connections when using SSL. ($FUNC_INSECURE)") + cmd.Flags().StringSliceP("extension", "e", nil, "Extensions as key=value pairs. Can be repeated. cloudevents only ($FUNC_EXTENSION)") addConfirmFlag(cmd, cfg.Confirm) addPathFlag(cmd) addVerboseFlag(cmd, cfg.Verbose) @@ -165,6 +166,20 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err return fmt.Errorf("no function found in current directory.\nYou need to be inside a function directory to invoke it.\n\nTry this:\n func create --language go myfunction Create a new function\n cd myfunction Go into the function directory\n func invoke Now you can invoke it\n\nOr if you have an existing function:\n cd path/to/your/function Go to your function directory\n func invoke Invoke the function") } + // If extensions were provided, ensure the format is cloudevent, otherwise return an error. + if len(cfg.Extensions) > 0 { + effectiveFormat := cfg.Format + if effectiveFormat == "" { + effectiveFormat = f.Invoke + } + if effectiveFormat != "cloudevent" { + return fmt.Errorf("--extension (-e) is only valid with cloudevents") + } + if effectiveFormat != "" && effectiveFormat != "cloudevent" { + return fmt.Errorf("--extension flag is only valid with cloudevent format") + } + } + // Client instance from env vars, flags, args and user prompts (if --confirm) client, done := newClient(ClientConfig{Verbose: cfg.Verbose, InsecureSkipVerify: cfg.Insecure}) defer done() @@ -179,6 +194,7 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err RequestType: strings.ToUpper(cfg.RequestType), Data: cfg.Data, Format: cfg.Format, + Extensions: cfg.extensionsMap(), } // If --file was specified, use its content for message data @@ -239,6 +255,7 @@ type invokeConfig struct { Confirm bool Verbose bool Insecure bool + Extensions []string } func newInvokeConfig() (cfg invokeConfig, err error) { @@ -256,6 +273,7 @@ func newInvokeConfig() (cfg invokeConfig, err error) { Confirm: viper.GetBool("confirm"), Verbose: viper.GetBool("verbose"), Insecure: viper.GetBool("insecure"), + Extensions: viper.GetStringSlice("extension"), } // If file was passed, read it in as data @@ -293,9 +311,21 @@ func newInvokeConfig() (cfg invokeConfig, err error) { fmt.Printf("Content Type: %v\n", cfg.ContentType) fmt.Printf("File: %v\n", cfg.File) fmt.Printf("Insecure: %v\n", cfg.Insecure) + fmt.Printf("Extensions: %v\n", cfg.Extensions) return } +func (c invokeConfig) extensionsMap() map[string]string { + extensionsMap := make(map[string]string) + for _, ext := range c.Extensions { + parts := strings.SplitN(ext, "=", 2) + if len(parts) == 2 { + extensionsMap[parts[0]] = parts[1] + } + } + return extensionsMap +} + func (c invokeConfig) prompt() (invokeConfig, error) { var qs []*survey.Question diff --git a/docs/reference/func_invoke.md b/docs/reference/func_invoke.md index 0ecaeb3529..21cf277cdb 100644 --- a/docs/reference/func_invoke.md +++ b/docs/reference/func_invoke.md @@ -99,6 +99,7 @@ func invoke -c, --confirm Prompt to confirm options interactively ($FUNC_CONFIRM) --content-type string Content Type of the data. ($FUNC_CONTENT_TYPE) (default "application/json") --data string Data to send in the request. ($FUNC_DATA) (default "{\"message\":\"Hello World\"}") + -e, --extension strings Extensions as key=value pairs. Can be repeated. cloudevents only ($FUNC_EXTENSION) --file string Path to a file to use as data. Overrides --data flag and should be sent with a correct --content-type. ($FUNC_FILE) -f, --format string Format of message to send, 'http' or 'cloudevent(s)'. Default is to choose automatically. ($FUNC_FORMAT) -h, --help help for invoke diff --git a/pkg/functions/invoke.go b/pkg/functions/invoke.go index e0331f7f98..45d08e16ba 100644 --- a/pkg/functions/invoke.go +++ b/pkg/functions/invoke.go @@ -35,6 +35,7 @@ type InvokeMessage struct { Data []byte RequestType string // HTTP Request GET/POST (defaults to POST) Format string // optional override for function-defined message format + Extensions map[string]string } // NewInvokeMessage creates a new InvokeMessage with fields populated @@ -168,6 +169,9 @@ func sendEvent(ctx context.Context, route string, m InvokeMessage, t http.RoundT event.SetID(m.ID) event.SetSource(m.Source) event.SetType(m.Type) + for extension := range m.Extensions { + event.SetExtension(extension, m.Extensions[extension]) + } err = event.SetData(m.ContentType, (m.Data)) if err != nil { return "", fmt.Errorf("cannot set data: %w", err)