From 20f8af79081c4b3e7d175804609fc4ebd541ca54 Mon Sep 17 00:00:00 2001 From: Lucas Roesler Date: Sat, 26 Feb 2022 11:38:25 +0100 Subject: [PATCH 1/4] feat: Add missing fields to the describe output Add the function * constraints * environment variables * secrets * requests and limits This can be tested as follows: Create a test cluster using KinD ```sh kind create cluster --config=cluster.yaml arkade install openfaas -a=false --function-pull-policy=IfNotPresent --wait faas-cli store deploy nodeinfo --annotation sample=true --label status=418 ``` Now deploy a sample function ```sh $ faas-cli describe nodeinfo Name: nodeinfo Status: Ready Replicas: 1 Available replicas: 1 Invocations: 0 Image: ghcr.io/openfaas/nodeinfo:latest Function process: URL: http://127.0.0.1:8080/function/nodeinfo Async URL: http://127.0.0.1:8080/async-function/nodeinfo Labels faas_function : nodeinfo status : 418 Annotations sample : true prometheus.io.scrape : false Constraints Environment Secrets Requests Limits ``` Now we add some more interesting values, like a secret and env variables. ```sh $ faas-cli secret create db-password --from-literal=password Creating secret: db-password. Created: 202 Accepted $ faas-cli store deploy nodeinfo --annotation sample=true --label status=418 --secret db-password --env db-user=postgres --env db-host=rds.aws.example.com Deployed. 202 Accepted. URL: http://127.0.0.1:8080/function/nodeinfo $ faas-cli describe nodeinfo Name: nodeinfo Status: Ready Replicas: 1 Available replicas: 1 Invocations: 0 Image: ghcr.io/openfaas/nodeinfo:latest Function process: URL: http://127.0.0.1:8080/function/nodeinfo Async URL: http://127.0.0.1:8080/async-function/nodeinfo Labels: faas_function: nodeinfo status: 418 uid: 736815901 Annotations: prometheus.io.scrape: false sample: true Constraints: Environment: Secrets: - db-password Requests: Limits: ``` To see how multiple Secrets and the Requests and Limits are rendered, use ```yaml version: 1.0 provider: name: openfaas gateway: http://127.0.0.1:8080 functions: nodeinfo: lang: Dockerfile image: ghcr.io/openfaas/nodeinfo:latest secrets: - cache-password - db-password environment: db-host: rds.aws.example.com cache-host: redis.aws.example.com labels: status: 481 annotations: sample: "true" requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi ``` then ```sh $ faas-cli secret create cache-password --from-literal=password Creating secret: cache-password. Created: 202 Accepted $ faas-cli deploy Deploying: nodeinfo. Deployed. 202 Accepted. URL: http://127.0.0.1:8080/function/nodeinfo $ faas-cli describe nodeinfo Name: nodeinfo Status: Ready Replicas: 1 Available replicas: 1 Invocations: 0 Image: ghcr.io/openfaas/nodeinfo:latest Function process: URL: http://127.0.0.1:8080/function/nodeinfo Async URL: http://127.0.0.1:8080/async-function/nodeinfo Labels: faas_function: nodeinfo status: 481 uid: 679245186 Annotations: prometheus.io.scrape: false sample: true Constraints: Environment: Secrets: - cache-password - db-password Requests: CPU: 100m Memory: 128Mi Limits: CPU: 200m Memory: 256Mi ``` Signed-off-by: Lucas Roesler --- commands/describe.go | 71 +++++++++++++++++++++++++++++++------------- schema/describe.go | 17 ++++------- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/commands/describe.go b/commands/describe.go index e98cf880..60f8de6a 100644 --- a/commands/describe.go +++ b/commands/describe.go @@ -14,6 +14,7 @@ import ( "github.com/openfaas/faas-cli/proxy" "github.com/openfaas/faas-cli/schema" "github.com/openfaas/faas-cli/stack" + "github.com/openfaas/faas-provider/types" "github.com/spf13/cobra" ) @@ -33,7 +34,7 @@ var describeCmd = &cobra.Command{ Use: "describe FUNCTION_NAME [--gateway GATEWAY_URL]", Short: "Describe an OpenFaaS function", Long: `Display details of an OpenFaaS function`, - Example: `faas-cli describe figlet + Example: `faas-cli describe figlet faas-cli describe env --gateway http://127.0.0.1:8080 faas-cli describe echo -g http://127.0.0.1.8080`, PreRunE: preRunDescribe, @@ -103,20 +104,11 @@ func runDescribe(cmd *cobra.Command, args []string) error { url, asyncURL := getFunctionURLs(gatewayAddress, functionName, functionNamespace) funcDesc := schema.FunctionDescription{ - Name: function.Name, - Status: status, - Replicas: int(function.Replicas), - AvailableReplicas: int(function.AvailableReplicas), - InvocationCount: int(invocationCount), - Image: function.Image, - EnvProcess: function.EnvProcess, - URL: url, - AsyncURL: asyncURL, - Labels: function.Labels, - Annotations: function.Annotations, - } - if function.Usage != nil { - funcDesc.Usage = function.Usage + FunctionStatus: function, + Status: status, + InvocationCount: int(invocationCount), + URL: url, + AsyncURL: asyncURL, } printFunctionDescription(funcDesc) @@ -140,17 +132,26 @@ func getFunctionURLs(gateway string, functionName string, functionNamespace stri func printFunctionDescription(funcDesc schema.FunctionDescription) { w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) + process := "" + if funcDesc.EnvProcess != "" { + process = funcDesc.EnvProcess + } fmt.Fprintln(w, "Name:\t "+funcDesc.Name) fmt.Fprintln(w, "Status:\t "+funcDesc.Status) - fmt.Fprintln(w, "Replicas:\t "+strconv.Itoa(funcDesc.Replicas)) - fmt.Fprintln(w, "Available replicas:\t "+strconv.Itoa(funcDesc.AvailableReplicas)) + fmt.Fprintln(w, "Replicas:\t "+strconv.Itoa(int(funcDesc.Replicas))) + fmt.Fprintln(w, "Available replicas:\t "+strconv.Itoa(int(funcDesc.AvailableReplicas))) fmt.Fprintln(w, "Invocations:\t "+strconv.Itoa(funcDesc.InvocationCount)) fmt.Fprintln(w, "Image:\t "+funcDesc.Image) - fmt.Fprintln(w, "Function process:\t "+funcDesc.EnvProcess) + fmt.Fprintln(w, "Function process:\t "+process) fmt.Fprintln(w, "URL:\t "+funcDesc.URL) fmt.Fprintln(w, "Async URL:\t "+funcDesc.AsyncURL) printMap(w, "Labels", *funcDesc.Labels) printMap(w, "Annotations", *funcDesc.Annotations) + printList(w, "Constraints", funcDesc.Constraints) + printMap(w, "Environment", funcDesc.EnvVars) + printList(w, "Secrets", funcDesc.Secrets) + printResources(w, "Requests", funcDesc.Requests) + printResources(w, "Limits", funcDesc.Limits) if funcDesc.Usage != nil { fmt.Println() @@ -160,14 +161,13 @@ func printFunctionDescription(funcDesc schema.FunctionDescription) { cpu = 1 } fmt.Fprintf(w, "CPU:\t %.0f Mi\n", (cpu)) - } w.Flush() } func printMap(w *tabwriter.Writer, name string, m map[string]string) { - fmt.Fprintf(w, name) + fmt.Fprintf(w, name+":") if len(m) == 0 { fmt.Fprintln(w, " \t ") @@ -175,8 +175,37 @@ func printMap(w *tabwriter.Writer, name string, m map[string]string) { } for key, value := range m { - fmt.Fprintln(w, " \t "+key+" : "+value) + fmt.Fprintln(w, " \t "+key+": "+value) } return } + +func printList(w *tabwriter.Writer, name string, data []string) { + fmt.Fprintf(w, name+":") + + if len(data) == 0 { + fmt.Fprintln(w, " \t ") + return + } + + for _, value := range data { + fmt.Fprintln(w, " \t - "+value) + } + + return +} + +func printResources(w *tabwriter.Writer, name string, data *types.FunctionResources) { + fmt.Fprintf(w, name+":") + + if data == nil { + fmt.Fprintln(w, " \t ") + return + } + + fmt.Fprintln(w, " \t CPU: "+data.CPU) + fmt.Fprintln(w, " \t Memory: "+data.Memory) + + return +} diff --git a/schema/describe.go b/schema/describe.go index 1450cee3..c3b5e298 100644 --- a/schema/describe.go +++ b/schema/describe.go @@ -7,16 +7,9 @@ import "github.com/openfaas/faas-provider/types" //FunctionDescription information related to a function type FunctionDescription struct { - Name string - Status string - Replicas int - AvailableReplicas int - InvocationCount int - Image string - EnvProcess string - URL string - AsyncURL string - Labels *map[string]string - Annotations *map[string]string - Usage *types.FunctionUsage + types.FunctionStatus + Status string + InvocationCount int + URL string + AsyncURL string } From 879b8531651710620bd1d53f855ca7327723f6ac Mon Sep 17 00:00:00 2001 From: Lucas Roesler Date: Wed, 16 Mar 2022 22:08:46 +0100 Subject: [PATCH 2/4] feat: create dynamic printer for the function describe output The printer object accepts a parameter to control how verbose it is. When verbose is false, empty values will be omitted from the describe output. Signed-off-by: Lucas Roesler --- commands/describe.go | 141 +++++++++++++++++++++++++++++--------- commands/describe_test.go | 58 +++++++++++++++- 2 files changed, 167 insertions(+), 32 deletions(-) diff --git a/commands/describe.go b/commands/describe.go index 60f8de6a..5d5bbe3b 100644 --- a/commands/describe.go +++ b/commands/describe.go @@ -6,7 +6,9 @@ package commands import ( "context" "fmt" + "io" "os" + "reflect" "strconv" "strings" "text/tabwriter" @@ -26,6 +28,7 @@ func init() { describeCmd.Flags().BoolVar(&envsubst, "envsubst", true, "Substitute environment variables in stack.yml file") describeCmd.Flags().StringVarP(&token, "token", "k", "", "Pass a JWT token to use instead of basic auth") describeCmd.Flags().StringVarP(&functionNamespace, "namespace", "n", "", "Namespace of the function") + describeCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") faasCmd.AddCommand(describeCmd) } @@ -111,7 +114,7 @@ func runDescribe(cmd *cobra.Command, args []string) error { AsyncURL: asyncURL, } - printFunctionDescription(funcDesc) + printFunctionDescription(cmd.OutOrStdout(), funcDesc, verbose) return nil } @@ -130,43 +133,92 @@ func getFunctionURLs(gateway string, functionName string, functionNamespace stri return url, asyncURL } -func printFunctionDescription(funcDesc schema.FunctionDescription) { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) +func printFunctionDescription(dst io.Writer, funcDesc schema.FunctionDescription, verbose bool) { + w := tabwriter.NewWriter(dst, 0, 0, 1, ' ', tabwriter.TabIndent) + defer w.Flush() + + out := printer{ + w: w, + verbose: verbose, + } + process := "" if funcDesc.EnvProcess != "" { process = funcDesc.EnvProcess } - fmt.Fprintln(w, "Name:\t "+funcDesc.Name) - fmt.Fprintln(w, "Status:\t "+funcDesc.Status) - fmt.Fprintln(w, "Replicas:\t "+strconv.Itoa(int(funcDesc.Replicas))) - fmt.Fprintln(w, "Available replicas:\t "+strconv.Itoa(int(funcDesc.AvailableReplicas))) - fmt.Fprintln(w, "Invocations:\t "+strconv.Itoa(funcDesc.InvocationCount)) - fmt.Fprintln(w, "Image:\t "+funcDesc.Image) - fmt.Fprintln(w, "Function process:\t "+process) - fmt.Fprintln(w, "URL:\t "+funcDesc.URL) - fmt.Fprintln(w, "Async URL:\t "+funcDesc.AsyncURL) - printMap(w, "Labels", *funcDesc.Labels) - printMap(w, "Annotations", *funcDesc.Annotations) - printList(w, "Constraints", funcDesc.Constraints) - printMap(w, "Environment", funcDesc.EnvVars) - printList(w, "Secrets", funcDesc.Secrets) - printResources(w, "Requests", funcDesc.Requests) - printResources(w, "Limits", funcDesc.Limits) - if funcDesc.Usage != nil { - fmt.Println() - - fmt.Fprintf(w, "RAM:\t %.2f MB\n", (funcDesc.Usage.TotalMemoryBytes / 1024 / 1024)) - cpu := funcDesc.Usage.CPU - if cpu < 0 { - cpu = 1 + + out.Printf("Name:\t%s\n", funcDesc.Name) + out.Printf("Status:\t%s\n", funcDesc.Status) + out.Printf("Replicas:\t%s\n", strconv.Itoa(int(funcDesc.Replicas))) + out.Printf("Available Replicas:\t%s\n", strconv.Itoa(int(funcDesc.AvailableReplicas))) + out.Printf("Invocations:\t%s\n", strconv.Itoa(int(funcDesc.InvocationCount))) + out.Printf("Image:\t%s\n", funcDesc.Image) + out.Printf("Function Process:\t%s\n", process) + out.Printf("URL:\t%s\n", funcDesc.URL) + out.Printf("Async URL:\t%s\n", funcDesc.AsyncURL) + out.Printf("Labels", *funcDesc.Labels) + out.Printf("Annotations", *funcDesc.Annotations) + out.Printf("Constraints", funcDesc.Constraints) + out.Printf("Environment", funcDesc.EnvVars) + out.Printf("Secrets", funcDesc.Secrets) + out.Printf("Requests", funcDesc.Requests) + out.Printf("Limits", funcDesc.Limits) + out.Printf("", funcDesc.Usage) +} + +type printer struct { + verbose bool + w io.Writer +} + +func (p *printer) Printf(format string, a interface{}) { + switch v := a.(type) { + case map[string]string: + printMap(p.w, format, v, p.verbose) + case []string: + printList(p.w, format, v, p.verbose) + case *types.FunctionResources: + printResources(p.w, format, v, p.verbose) + case *types.FunctionUsage: + printUsage(p.w, v, p.verbose) + default: + if !p.verbose && isEmpty(a) { + return } - fmt.Fprintf(w, "CPU:\t %.0f Mi\n", (cpu)) + + if p.verbose && isEmpty(a) { + a = "" + } + + fmt.Fprintf(p.w, format, a) + } + +} + +func printUsage(w io.Writer, usage *types.FunctionUsage, verbose bool) { + if !verbose && usage == nil { + return } - w.Flush() + if usage == nil { + fmt.Fprintln(w, "No usage information available") + return + } + + fmt.Fprintln(w) + fmt.Fprintf(w, "RAM:\t %.2f MB\n", (usage.TotalMemoryBytes / 1024 / 1024)) + cpu := usage.CPU + if cpu < 0 { + cpu = 1 + } + fmt.Fprintf(w, "CPU:\t %.0f Mi\n", (cpu)) } -func printMap(w *tabwriter.Writer, name string, m map[string]string) { +func printMap(w io.Writer, name string, m map[string]string, verbose bool) { + if !verbose && len(m) == 0 { + return + } + fmt.Fprintf(w, name+":") if len(m) == 0 { @@ -181,7 +233,11 @@ func printMap(w *tabwriter.Writer, name string, m map[string]string) { return } -func printList(w *tabwriter.Writer, name string, data []string) { +func printList(w io.Writer, name string, data []string, verbose bool) { + if !verbose && len(data) == 0 { + return + } + fmt.Fprintf(w, name+":") if len(data) == 0 { @@ -196,7 +252,11 @@ func printList(w *tabwriter.Writer, name string, data []string) { return } -func printResources(w *tabwriter.Writer, name string, data *types.FunctionResources) { +func printResources(w io.Writer, name string, data *types.FunctionResources, verbose bool) { + if !verbose && data == nil { + return + } + fmt.Fprintf(w, name+":") if data == nil { @@ -209,3 +269,22 @@ func printResources(w *tabwriter.Writer, name string, data *types.FunctionResour return } + +func isEmpty(a interface{}) bool { + v := reflect.ValueOf(a) + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} diff --git a/commands/describe_test.go b/commands/describe_test.go index 29e5c6ab..d5085e17 100644 --- a/commands/describe_test.go +++ b/commands/describe_test.go @@ -1,6 +1,13 @@ package commands -import "testing" +import ( + "bytes" + "regexp" + "testing" + + "github.com/openfaas/faas-cli/schema" + "github.com/openfaas/faas-provider/types" +) func Test_getFunctionURLs(t *testing.T) { cases := []struct { @@ -26,3 +33,52 @@ func Test_getFunctionURLs(t *testing.T) { }) } } + +func TestDescribeOuput(t *testing.T) { + spaces := regexp.MustCompile(`[ ]{2,}`) + cases := []struct { + name string + function schema.FunctionDescription + verbose bool + expectedOutput string + }{ + { + name: "minimal output, non-verbose", + function: schema.FunctionDescription{ + FunctionStatus: types.FunctionStatus{ + Name: "figlet", + Image: "openfaas/figlet:latest", + Labels: &map[string]string{}, + Annotations: &map[string]string{}, + }, + Status: "Ready", + }, + verbose: false, + expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\n", + }, + { + name: "minimal output, verbose", + function: schema.FunctionDescription{ + FunctionStatus: types.FunctionStatus{ + Name: "figlet", + Image: "openfaas/figlet:latest", + Labels: &map[string]string{}, + Annotations: &map[string]string{}, + }, + Status: "Ready", + }, + verbose: true, + expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\nURL:\t\nAsync URL:\t\nLabels:\t\nAnnotations:\t\nConstraints:\t\nEnvironment:\t\nSecrets:\t\nRequests:\t\nLimits:\t\nNo usage information available\n", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var dst bytes.Buffer + printFunctionDescription(&dst, tc.function, tc.verbose) + result := spaces.ReplaceAllString(dst.String(), "\t") + if result != tc.expectedOutput { + t.Fatalf("incorrect output,\nwant: %q\nnorm: %q\ngot: %q", tc.expectedOutput, result, dst.String()) + } + }) + } +} From d7d4a95e5b89f75f0dfccaf3182c202acbdbfc2c Mon Sep 17 00:00:00 2001 From: Lucas Roesler Date: Thu, 17 Mar 2022 09:56:30 +0100 Subject: [PATCH 3/4] fix: remove extra spaces from the describe output Ensure that the values for maps and slices are aligned with the string values. Signed-off-by: Lucas Roesler --- commands/describe.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/commands/describe.go b/commands/describe.go index 5d5bbe3b..2782c04e 100644 --- a/commands/describe.go +++ b/commands/describe.go @@ -222,12 +222,12 @@ func printMap(w io.Writer, name string, m map[string]string, verbose bool) { fmt.Fprintf(w, name+":") if len(m) == 0 { - fmt.Fprintln(w, " \t ") + fmt.Fprintln(w, "\t ") return } for key, value := range m { - fmt.Fprintln(w, " \t "+key+": "+value) + fmt.Fprintln(w, "\t "+key+": "+value) } return @@ -241,12 +241,12 @@ func printList(w io.Writer, name string, data []string, verbose bool) { fmt.Fprintf(w, name+":") if len(data) == 0 { - fmt.Fprintln(w, " \t ") + fmt.Fprintln(w, "\t ") return } for _, value := range data { - fmt.Fprintln(w, " \t - "+value) + fmt.Fprintln(w, "\t - "+value) } return @@ -260,12 +260,12 @@ func printResources(w io.Writer, name string, data *types.FunctionResources, ver fmt.Fprintf(w, name+":") if data == nil { - fmt.Fprintln(w, " \t ") + fmt.Fprintln(w, "\t ") return } - fmt.Fprintln(w, " \t CPU: "+data.CPU) - fmt.Fprintln(w, " \t Memory: "+data.Memory) + fmt.Fprintln(w, "\t CPU: "+data.CPU) + fmt.Fprintln(w, "\t Memory: "+data.Memory) return } From 02d8fc8ac0f27e2fe8044639c599a7ebb61c2324 Mon Sep 17 00:00:00 2001 From: Lucas Roesler Date: Thu, 17 Mar 2022 10:43:02 +0100 Subject: [PATCH 4/4] fix: add section header for usage data in describe output Add the header `Usage:` for the usage data to help it fit with the other fields better Signed-off-by: Lucas Roesler --- commands/describe.go | 18 ++++++------- commands/describe_test.go | 56 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/commands/describe.go b/commands/describe.go index 2782c04e..6763fef4 100644 --- a/commands/describe.go +++ b/commands/describe.go @@ -201,17 +201,17 @@ func printUsage(w io.Writer, usage *types.FunctionUsage, verbose bool) { } if usage == nil { - fmt.Fprintln(w, "No usage information available") + fmt.Fprintln(w, "Usage:\t ") return } - fmt.Fprintln(w) - fmt.Fprintf(w, "RAM:\t %.2f MB\n", (usage.TotalMemoryBytes / 1024 / 1024)) + fmt.Fprintln(w, "Usage:") + fmt.Fprintf(w, " RAM:\t %.2f MB\n", (usage.TotalMemoryBytes / 1024 / 1024)) cpu := usage.CPU if cpu < 0 { cpu = 1 } - fmt.Fprintf(w, "CPU:\t %.0f Mi\n", (cpu)) + fmt.Fprintf(w, " CPU:\t %.0f Mi\n", (cpu)) } func printMap(w io.Writer, name string, m map[string]string, verbose bool) { @@ -219,13 +219,12 @@ func printMap(w io.Writer, name string, m map[string]string, verbose bool) { return } - fmt.Fprintf(w, name+":") - if len(m) == 0 { - fmt.Fprintln(w, "\t ") + fmt.Fprintf(w, "%s:\t \n", name) return } + fmt.Fprintf(w, "%s:\n", name) for key, value := range m { fmt.Fprintln(w, "\t "+key+": "+value) } @@ -238,13 +237,12 @@ func printList(w io.Writer, name string, data []string, verbose bool) { return } - fmt.Fprintf(w, name+":") - if len(data) == 0 { - fmt.Fprintln(w, "\t ") + fmt.Fprintf(w, "%s:\t \n", name) return } + fmt.Fprintf(w, "%s:\n", name) for _, value := range data { fmt.Fprintln(w, "\t - "+value) } diff --git a/commands/describe_test.go b/commands/describe_test.go index d5085e17..12ef9b6b 100644 --- a/commands/describe_test.go +++ b/commands/describe_test.go @@ -43,7 +43,7 @@ func TestDescribeOuput(t *testing.T) { expectedOutput string }{ { - name: "minimal output, non-verbose", + name: "non-verbose minimal output", function: schema.FunctionDescription{ FunctionStatus: types.FunctionStatus{ Name: "figlet", @@ -57,7 +57,7 @@ func TestDescribeOuput(t *testing.T) { expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\n", }, { - name: "minimal output, verbose", + name: "verbose minimal output", function: schema.FunctionDescription{ FunctionStatus: types.FunctionStatus{ Name: "figlet", @@ -68,7 +68,57 @@ func TestDescribeOuput(t *testing.T) { Status: "Ready", }, verbose: true, - expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\nURL:\t\nAsync URL:\t\nLabels:\t\nAnnotations:\t\nConstraints:\t\nEnvironment:\t\nSecrets:\t\nRequests:\t\nLimits:\t\nNo usage information available\n", + expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\nURL:\t\nAsync URL:\t\nLabels:\t\nAnnotations:\t\nConstraints:\t\nEnvironment:\t\nSecrets:\t\nRequests:\t\nLimits:\t\nUsage:\t\n", + }, + { + name: "non-verbose formats output with non-empty labels, env variables, and secrets", + function: schema.FunctionDescription{ + FunctionStatus: types.FunctionStatus{ + Name: "figlet", + Image: "openfaas/figlet:latest", + Labels: &map[string]string{"quadrant": "alpha"}, + Annotations: &map[string]string{}, + EnvVars: map[string]string{"FOO": "bar"}, + Secrets: []string{"db-password"}, + }, + Status: "Ready", + }, + verbose: false, + expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\nLabels:\n quadrant: alpha\nEnvironment:\n FOO: bar\nSecrets:\n - db-password\n", + }, + { + name: "verbose formats output with non-empty labels, env variables, and secrets", + function: schema.FunctionDescription{ + FunctionStatus: types.FunctionStatus{ + Name: "figlet", + Image: "openfaas/figlet:latest", + Labels: &map[string]string{"quadrant": "alpha"}, + Annotations: &map[string]string{}, + EnvVars: map[string]string{"FOO": "bar"}, + Secrets: []string{"db-password"}, + }, + Status: "Ready", + }, + verbose: true, + expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\nURL:\t\nAsync URL:\t\nLabels:\n quadrant: alpha\nAnnotations:\t\nConstraints:\t\nEnvironment:\n FOO: bar\nSecrets:\n - db-password\nRequests:\t\nLimits:\t\nUsage:\t\n", + }, + { + name: "formats non-empty usage", + function: schema.FunctionDescription{ + FunctionStatus: types.FunctionStatus{ + Name: "figlet", + Image: "openfaas/figlet:latest", + Labels: &map[string]string{}, + Annotations: &map[string]string{}, + Usage: &types.FunctionUsage{ + TotalMemoryBytes: 1024 * 1024 * 1024, + CPU: 1.5, + }, + }, + Status: "Ready", + }, + verbose: false, + expectedOutput: "Name:\tfiglet\nStatus:\tReady\nReplicas:\t0\nAvailable Replicas: 0\nInvocations:\t0\nImage:\topenfaas/figlet:latest\nFunction Process:\t\nUsage:\n\tRAM:\t1024.00 MB\n\tCPU:\t2 Mi\n", }, } for _, tc := range cases {