Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,26 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
}
// Stamp is a performance optimization: treat the function as being built
// (cached) unless the fs changes.
return f.Stamp()
if err = f.Stamp(); err != nil {
return
}
if isJSONEnabled(cmd) {
image := f.Build.Image
if image == "" {
image = f.Image
}
err = writeJSONSuccess(cmd.OutOrStdout(), buildJSONResult{
Name: f.Name,
Image: image,
})
}
return
}

// buildJSONResult is the data payload emitted on success when --json is set.
type buildJSONResult struct {
Name string `json:"name"`
Image string `json:"image,omitempty"`
}

// warnRegistryInsecureChange checks if the registry has changed but
Expand Down
4 changes: 4 additions & 0 deletions cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"errors"
"fmt"
"os"

"github.com/spf13/cobra"
Expand All @@ -27,6 +28,9 @@ source <(func completion bash)
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if isJSONEnabled(cmd) {
return fmt.Errorf("--json is not supported for 'completion': it outputs raw shell completion scripts")
}
if len(args) < 1 {
return errors.New("missing argument")
}
Expand Down
10 changes: 6 additions & 4 deletions cmd/config_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ the current directory or from the directory specified with --path.
return cmd
}

func runConfigGitCmd(_ *cobra.Command, _ ClientFactory) (err error) {
fmt.Printf("--------------------------- Function Git config ---------------------------\n")
fmt.Printf("Not implemented yet.\n")

func runConfigGitCmd(cmd *cobra.Command, _ ClientFactory) (err error) {
if isJSONEnabled(cmd) {
return writeJSONSuccess(cmd.OutOrStdout(), nil)
}
fmt.Fprintln(cmd.ErrOrStderr(), "--------------------------- Function Git config ---------------------------")
fmt.Fprintln(cmd.ErrOrStderr(), "Not implemented yet.")
return nil
}
18 changes: 17 additions & 1 deletion cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func runCreate(cmd *cobra.Command, args []string, newClient ClientFactory) (err
}

// Create
_, err = client.Init(fn.Function{
f, err := client.Init(fn.Function{
Name: cfg.Name,
Root: cfg.Path,
Runtime: cfg.Runtime,
Expand All @@ -145,11 +145,27 @@ func runCreate(cmd *cobra.Command, args []string, newClient ClientFactory) (err
if err != nil {
return err
}
if isJSONEnabled(cmd) {
return writeJSONSuccess(cmd.OutOrStdout(), createJSONResult{
Name: f.Name,
Path: f.Root,
Runtime: f.Runtime,
Template: cfg.Template,
})
}
// Confirm
fmt.Fprintf(cmd.OutOrStderr(), "Created %v function in %v\n", cfg.Runtime, cfg.Path)
return nil
}

// createJSONResult is the data payload emitted on success when --json is set.
type createJSONResult struct {
Name string `json:"name"`
Path string `json:"path"`
Runtime string `json:"runtime"`
Template string `json:"template,omitempty"`
}

type createConfig struct {
Path string // Absolute path to function source
Runtime string // Language Runtime
Expand Down
26 changes: 24 additions & 2 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,37 @@ func runDelete(cmd *cobra.Command, args []string, newClient ClientFactory) (err
client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()

var deletedName, deletedNamespace string
if cfg.Name != "" { // Delete by name if provided
return client.Remove(cmd.Context(), cfg.Name, cfg.Namespace, fn.Function{}, cfg.All)
deletedName = cfg.Name
deletedNamespace = cfg.Namespace
if err = client.Remove(cmd.Context(), cfg.Name, cfg.Namespace, fn.Function{}, cfg.All); err != nil {
return
}
} else { // Otherwise; delete the function at path (cwd by default)
f, err := fn.NewFunction(cfg.Path)
if err != nil {
return err
}
return client.Remove(cmd.Context(), "", "", f, cfg.All)
deletedName = f.Name
deletedNamespace = f.Deploy.Namespace
if err = client.Remove(cmd.Context(), "", "", f, cfg.All); err != nil {
return err
}
}
if isJSONEnabled(cmd) {
err = writeJSONSuccess(cmd.OutOrStdout(), deleteJSONResult{
Name: deletedName,
Namespace: deletedNamespace,
})
}
return
}

// deleteJSONResult is the data payload emitted on success when --json is set.
type deleteJSONResult struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
}

type deleteConfig struct {
Expand Down
41 changes: 36 additions & 5 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,13 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
if changingNamespace(f) && k8s.IsOpenShift() && k8s.IsOpenShiftInternalRegistry(f.Registry) {
f.Registry = "image-registry.openshift-image-registry.svc:5000/" + f.Namespace
if cfg.Verbose {
fmt.Fprintf(cmd.OutOrStdout(), "Info: Overriding openshift registry to %s\n", f.Registry)
fmt.Fprintf(cmd.ErrOrStderr(), "Info: Overriding openshift registry to %s\n", f.Registry)
}
}

// Informative non-error messages regarding the final deployment request
printDeployMessages(cmd.OutOrStdout(), f)
// Informative non-error messages: always go to stderr so that --json
// output on stdout is not contaminated with human-readable status text.
printDeployMessages(cmd.ErrOrStderr(), f)

// Get options based on the value of the config such as concrete impls
// of builders and pushers based on the value of the --builder flag
Expand All @@ -317,6 +318,7 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
defer done()

// Deploy
var deployedURL string
if cfg.Remote {
// Write func.yaml before the pipeline uploads sources to the PVC,
// so that the on-cluster deploy step sees the latest config
Expand All @@ -331,7 +333,10 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
if url, f, err = client.RunPipeline(cmd.Context(), f); err != nil {
return wrapDeploymentError(err)
}
fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url)
deployedURL = url
if !isJSONEnabled(cmd) {
fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url)
}
} else {
var buildOptions []fn.BuildOption
if buildOptions, err = cfg.buildOptions(); err != nil {
Expand Down Expand Up @@ -387,6 +392,13 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
if f, err = client.Deploy(cmd.Context(), f, fn.WithDeploySkipBuildCheck(cfg.Build == "false")); err != nil {
return wrapDeploymentError(err)
}
// Capture the deployed URL for --json output. The URL is not
// returned directly by Deploy; a lightweight Describe call fetches it.
if isJSONEnabled(cmd) {
if inst, descErr := client.Describe(cmd.Context(), "", "", f); descErr == nil && len(inst.Routes) > 0 {
deployedURL = inst.Routes[0]
}
}
}

// Write
Expand All @@ -398,7 +410,26 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
// Updates the build stamp because building must have been accomplished
// during this process, and a future call to deploy without any appreciable
// changes to the filesystem should not rebuild again unless `--build`
return f.Stamp()
if err = f.Stamp(); err != nil {
return
}
if isJSONEnabled(cmd) {
err = writeJSONSuccess(cmd.OutOrStdout(), deployJSONResult{
Name: f.Name,
Namespace: f.Deploy.Namespace,
URL: deployedURL,
Image: f.Deploy.Image,
})
}
return
}

// deployJSONResult is the data payload emitted on success when --json is set.
type deployJSONResult struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
URL string `json:"url,omitempty"`
Image string `json:"image,omitempty"`
}

// build determines if the function should be built based on given flag
Expand Down
11 changes: 6 additions & 5 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1165,17 +1165,18 @@ func TestDeploy_NamespaceRedeployWarning(t *testing.T) {
fn.WithRegistry(TestRegistry),
))
cmd.SetArgs([]string{})
stdout := strings.Builder{}
cmd.SetOut(&stdout)
stderr := strings.Builder{}
cmd.SetErr(&stderr)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}

expected := "Warning: namespace chosen is 'funcns', but currently active namespace is 'mynamespace'. Continuing with deployment to 'funcns'."

// Ensure output contained warning if changing namespace
if !strings.Contains(stdout.String(), expected) {
t.Log("STDOUT:\n" + stdout.String())
// Ensure warning appears on stderr (deploy messages always go to stderr
// so they don't contaminate stdout when --json is active).
if !strings.Contains(stderr.String(), expected) {
t.Log("STDERR:\n" + stderr.String())
t.Fatalf("Expected warning not found:\n%v", expected)
}

Expand Down
9 changes: 9 additions & 0 deletions cmd/describe.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -89,6 +90,14 @@ func runDescribe(cmd *cobra.Command, args []string, newClient ClientFactory) (er
}
}

if isJSONEnabled(cmd) {
var buf bytes.Buffer
if err = info(details).JSON(&buf); err != nil {
return
}
var raw json.RawMessage = buf.Bytes()
return writeJSONSuccess(cmd.OutOrStdout(), raw)
}
write(os.Stdout, info(details), cfg.Output)
return
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ func runEnvironment(cmd *cobra.Command, newClient ClientFactory, v *Version) (er
environment.Instance = instance
}

if isJSONEnabled(cmd) {
return writeJSONSuccess(cmd.OutOrStdout(), environment)
}

var s []byte
switch cfg.Format {
case "json":
Expand Down
20 changes: 16 additions & 4 deletions cmd/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
return err
}

// When --json: emit structured envelope only – no human text on stdout.
if isJSONEnabled(cmd) {
return writeJSONSuccess(cmd.OutOrStdout(), invokeJSONResult{
Response: body,
})
}

// When Verbose
// - Print an explicit "Received response" indicator
// - Print metadata (headers for HTTP requests, CloudEvents already include
Expand All @@ -221,17 +228,17 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
// stdout could be confusing on a first-time run, viewing a proper echo.
// user feedback suggests this actually be placed behind the --verbose
// setting:
fmt.Println("Function invoked. Response:")
fmt.Fprintln(cmd.ErrOrStderr(), "Function invoked. Response:")

if len(metadata) > 0 {
fmt.Println(" Metadata:")
fmt.Fprintln(cmd.ErrOrStderr(), " Metadata:")
}
for k, vv := range metadata {
values := strings.Join(vv, ";")
fmt.Fprintf(cmd.OutOrStdout(), " %v: %v\n", k, values)
fmt.Fprintf(cmd.ErrOrStderr(), " %v: %v\n", k, values)
}
if len(metadata) > 0 {
fmt.Println(" Content:")
fmt.Fprintln(cmd.ErrOrStderr(), " Content:")
}
}

Expand All @@ -241,6 +248,11 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
return
}

// invokeJSONResult is the data payload emitted on success when --json is set.
type invokeJSONResult struct {
Response string `json:"response"`
}

type invokeConfig struct {
Path string
Target string
Expand Down
Loading
Loading