diff --git a/cmd/run.go b/cmd/run.go index fb29ccec6..452d329f5 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "encoding/json" "errors" "fmt" "os" @@ -28,7 +29,7 @@ NAME SYNOPSIS {{rootCmdUse}} run [-t|--container] [-r|--registry] [-i|--image] [-e|--env] [--build] [-b|--builder] [--builder-image] [-c|--confirm] - [--address] [-v|--verbose] + [--address] [--json] [-v|--verbose] DESCRIPTION Run the function locally. @@ -71,9 +72,12 @@ EXAMPLES o Run the function locally on a specific address. $ {{rootCmdUse}} run --address=0.0.0.0:8081 + + o Run the function locally and output JSON with the service address. + $ {{rootCmdUse}} run --json `, SuggestFor: []string{"rnu"}, - PreRunE: bindEnv("address", "build", "builder", "builder-image", "confirm", "container", "env", "image", "path", "registry", "start-timeout", "verbose"), + PreRunE: bindEnv("build", "builder", "builder-image", "confirm", "container", "env", "image", "path", "registry", "start-timeout", "verbose", "address", "json"), RunE: func(cmd *cobra.Command, _ []string) error { return runRun(cmd, newClient) }, @@ -129,6 +133,7 @@ EXAMPLES cmd.Flags().Lookup("build").NoOptDefVal = "true" // register `--build` as equivalient to `--build=true` cmd.Flags().String("address", "", "Interface and port on which to bind and listen. Default is 127.0.0.1:8080, or an available port if 8080 is not available. ($FUNC_ADDRESS)") + cmd.Flags().Bool("json", false, "Output as JSON. ($FUNC_JSON)") // Oft-shared flags: addConfirmFlag(cmd, cfg.Confirm) @@ -171,6 +176,11 @@ func runRun(cmd *cobra.Command, newClient ClientFactory) (err error) { return } + // Ignore the verbose flag if JSON output + if cfg.JSON { + cfg.Verbose = false + } + // Client clientOptions, err := cfg.clientOptions() if err != nil { @@ -249,7 +259,27 @@ func runRun(cmd *cobra.Command, newClient ClientFactory) (err error) { } }() - fmt.Fprintf(cmd.OutOrStderr(), "Running on host port %v\n", job.Port) + // Output based on format + if cfg.JSON { + // Create JSON output structure + output := struct { + Address string `json:"address"` + Host string `json:"host"` + Port string `json:"port"` + }{ + Address: fmt.Sprintf("http://%s:%s", job.Host, job.Port), + Host: job.Host, + Port: job.Port, + } + + jsonData, err := json.Marshal(output) + if err != nil { + return fmt.Errorf("failed to marshal JSON output: %w", err) + } + fmt.Fprintln(cmd.OutOrStdout(), string(jsonData)) + } else { + fmt.Fprintf(cmd.OutOrStderr(), "Running on host port %v\n", job.Port) + } select { case <-cmd.Context().Done(): @@ -293,6 +323,9 @@ type runConfig struct { // Address is the interface and port to bind (e.g. "0.0.0.0:8081") Address string + + // JSON output format + JSON bool } func newRunConfig(cmd *cobra.Command) (c runConfig) { @@ -303,6 +336,7 @@ func newRunConfig(cmd *cobra.Command) (c runConfig) { Container: viper.GetBool("container"), StartTimeout: viper.GetDuration("start-timeout"), Address: viper.GetString("address"), + JSON: viper.GetBool("json"), } // NOTE: .Env should be viper.GetStringSlice, but this returns unparsed // results and appears to be an open issue since 2017: diff --git a/cmd/run_test.go b/cmd/run_test.go index 5cc6b9fa8..8eb0cd752 100644 --- a/cmd/run_test.go +++ b/cmd/run_test.go @@ -21,6 +21,7 @@ func TestRun_Run(t *testing.T) { runError error // Set the runner to yield this error buildInvoked bool // should Builder.Build be invoked? runInvoked bool // should Runner.Run be invoked? + jsonOutput bool // expect JSON output format }{ { name: "run and build by default", @@ -100,6 +101,14 @@ func TestRun_Run(t *testing.T) { buildInvoked: true, runInvoked: false, }, + { + name: "run with json output", + desc: "Should output JSON format when --json flag is used", + args: []string{"--json"}, + buildInvoked: true, + runInvoked: true, + jsonOutput: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/docs/reference/func_run.md b/docs/reference/func_run.md index a36fe3f46..bb6667abf 100644 --- a/docs/reference/func_run.md +++ b/docs/reference/func_run.md @@ -11,7 +11,7 @@ NAME SYNOPSIS func run [-t|--container] [-r|--registry] [-i|--image] [-e|--env] [--build] [-b|--builder] [--builder-image] [-c|--confirm] - [--address] [-v|--verbose] + [--address] [--json] [-v|--verbose] DESCRIPTION Run the function locally. @@ -55,6 +55,9 @@ EXAMPLES o Run the function locally on a specific address. $ func run --address=0.0.0.0:8081 + o Run the function locally and output JSON with the service address. + $ func run --json + ``` func run @@ -72,6 +75,7 @@ func run -e, --env stringArray Environment variable to set in the form NAME=VALUE. You may provide this flag multiple times for setting multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). -h, --help help for run -i, --image string Full image name in the form [registry]/[namespace]/[name]:[tag]. This option takes precedence over --registry. Specifying tag is optional. ($FUNC_IMAGE) + --json Output as JSON. ($FUNC_JSON) -p, --path string Path to the function. Default is current directory ($FUNC_PATH) -r, --registry string Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY) -v, --verbose Print verbose logs ($FUNC_VERBOSE)