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
7 changes: 2 additions & 5 deletions cmd/config_envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"

"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/ory/viper"
"github.com/spf13/cobra"

"knative.dev/func/cmd/common"
Expand Down Expand Up @@ -38,7 +36,7 @@ the current directory or from the directory specified with --path.
return
}

return listEnvs(function, cmd.OutOrStdout(), Format(viper.GetString("output")))
return listEnvs(function, cmd.OutOrStdout(), Format(outputFormat()))
},
}
cfg, err := config.NewDefault()
Expand Down Expand Up @@ -204,8 +202,7 @@ func listEnvs(f fn.Function, w io.Writer, outputFormat Format) error {
}
return nil
case JSON:
enc := json.NewEncoder(w)
return enc.Encode(f.Run.Envs)
return WriteJSONSuccess(w, f.Run.Envs)
default:
return fmt.Errorf("bad format: %v", outputFormat)
}
Expand Down
14 changes: 10 additions & 4 deletions cmd/config_git.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"errors"
"fmt"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -48,9 +49,14 @@ 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) {
// Reporting an empty success envelope here would tell a machine consumer
// the command produced a result when it did nothing at all, so say so as
// a failure instead.
if isJSONEnabled(cmd) {
return errors.New("'config git' is not implemented yet")
}
fmt.Fprintln(cmd.ErrOrStderr(), "--------------------------- Function Git config ---------------------------")
fmt.Fprintln(cmd.ErrOrStderr(), "Not implemented yet.")
return nil
}
7 changes: 2 additions & 5 deletions cmd/config_labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package cmd

import (
"context"
"encoding/json"
"fmt"
"io"
"os"

"github.com/AlecAivazis/survey/v2"
"github.com/ory/viper"
"github.com/spf13/cobra"

"knative.dev/func/cmd/common"
Expand All @@ -35,7 +33,7 @@ the current directory or from the directory specified with --path.
return
}

return listLabels(function, cmd.OutOrStdout(), Format(viper.GetString("output")))
return listLabels(function, cmd.OutOrStdout(), Format(outputFormat()))
},
}

Expand Down Expand Up @@ -178,8 +176,7 @@ func listLabels(f fn.Function, w io.Writer, outputFormat Format) error {
}
return nil
case JSON:
enc := json.NewEncoder(w)
return enc.Encode(f.Deploy.Labels)
return WriteJSONSuccess(w, f.Deploy.Labels)
default:
return fmt.Errorf("invalid format: %v", outputFormat)
}
Expand Down
16 changes: 15 additions & 1 deletion cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,22 @@ func TestListEnvs(t *testing.T) {
t.Fatal(err)
}

// All JSON output, whether requested with --json or --output json, is
// wrapped in the versioned envelope; the payload is under "data".
var envelope struct {
APIVersion string `json:"apiVersion"`
Status string `json:"status"`
Data json.RawMessage `json:"data"`
}
if err = json.Unmarshal(buff.Bytes(), &envelope); err != nil {
t.Fatal(err)
}
if envelope.APIVersion != "v1" || envelope.Status != "ok" {
t.Fatalf("unexpected envelope: %s", buff.Bytes())
}

var data []fn.Env
err = json.Unmarshal(buff.Bytes(), &data)
err = json.Unmarshal(envelope.Data, &data)
if err != nil {
t.Fatal(err)
}
Expand Down
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
27 changes: 24 additions & 3 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,42 @@ 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
_, err = client.Remove(cmd.Context(), cfg.Name, cfg.Namespace, fn.Function{}, cfg.All)
return err
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
}
deletedName = f.Name
deletedNamespace = f.Deploy.Namespace
// updates f.Deploy.<Deployer|Namespace> (clears them on success)
f, err = client.Remove(cmd.Context(), "", "", f, cfg.All)
if err != nil {
return err
}
return f.Write()
if err = f.Write(); 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
46 changes: 41 additions & 5 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,12 +312,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 @@ -329,6 +330,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 @@ -343,7 +345,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 @@ -399,6 +404,18 @@ 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)
}
// Deploy does not return the resulting URL, so --json fetches it with
// a follow-up Describe. A failure there is not fatal — the deploy
// itself succeeded — but it must not be silent either, or the caller
// cannot tell an absent url from a function which has none.
if isJSONEnabled(cmd) {
inst, descErr := client.Describe(cmd.Context(), "", "", f)
if descErr != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: deployed, but could not determine the function URL: %v\n", descErr)
} else if len(inst.Routes) > 0 {
deployedURL = inst.Routes[0]
}
}
}

// Write
Expand All @@ -410,7 +427,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 @@ -1173,17 +1173,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
2 changes: 1 addition & 1 deletion cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func newDescribeConfig(cmd *cobra.Command, args []string) (cfg describeConfig, e
cfg = describeConfig{
Name: name,
Namespace: viper.GetString("namespace"),
Output: viper.GetString("output"),
Output: outputFormat(),
Path: viper.GetString("path"),
Verbose: viper.GetBool("verbose"),
}
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
Loading
Loading