From e41ce2ce7e6948aaeb4d6d4aed443a18a133fc78 Mon Sep 17 00:00:00 2001 From: ishandhanani <82981111+ishandhanani@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:51:59 -0700 Subject: [PATCH] release (#23) * added goreleaser * makefile * working on lint errs * lint * added lint workflow * update workflow * ignoring preflight for now * lint * lint again * reverse that lint lol --- .github/workflows/build.yaml | 30 +++++++++++ .gitignore | 2 + .golangci.yml | 3 ++ .goreleaser.yaml | 55 ++++++++++++++++++++ Makefile | 6 +++ cmd/auth/auth.go | 92 +++++++++++++++++---------------- cmd/completion.go | 26 +++++++--- cmd/function/function_create.go | 6 +-- cmd/function/function_delete.go | 4 +- cmd/function/function_deploy.go | 32 ++++++++---- cmd/function/function_stop.go | 12 +++-- cmd/function/function_watch.go | 4 +- collections/collections.go | 5 +- config/config.go | 7 ++- main.go | 5 +- output/output.go | 36 +++++++++---- 16 files changed, 241 insertions(+), 84 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .golangci.yml create mode 100644 .goreleaser.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..813a243 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,30 @@ +name: lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + + - name: Install dependencies + run: go mod download + + - name: Install deps + run: make deps-lint + + - name: lint + run: make lint diff --git a/.gitignore b/.gitignore index 4f2f791..3859bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ nvcf deploy.yaml + +dist/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..791150d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,3 @@ +run: + skip-files: + - "preflight/containerutil/container_smoketest_grpc.go" diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..e9a509d --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,55 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +before: + hooks: + - go mod tidy + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +brews: + - name: nvcf + homepage: https://github.com/brevdev/nvcf + description: CLI tool for working with NVIDIA cloud functions + repository: + name: tap + owner: brevdev + name: homebrew-nvcf + commit_author: + name: github-actions + email: 41898282+github-actions[bot]@users.noreply.github.com + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/Makefile b/Makefile index f55ba3e..31b8eed 100644 --- a/Makefile +++ b/Makefile @@ -66,5 +66,11 @@ docs: cleandocs: rm -rf ./docs +dry-release: + goreleaser release --snapshot --clean + +release: + goreleaser release + .PHONY: all build clean test check fmt vet lint q deps-lint help diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index 92edfbf..2f64b6e 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -46,13 +46,12 @@ func authLoginCmd() *cobra.Command { return &cobra.Command{ Use: "login", Short: "Authenticate with NVIDIA Cloud", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { apiKey := output.Prompt("Enter your NVIDIA Cloud API key: ", true) err := config.SetAPIKey(apiKey) if err != nil { - output.Error(cmd, "Error saving API key", err) - return + return output.Error(cmd, "Error saving API key", err) } // Use the API key to get the first org @@ -60,36 +59,32 @@ func authLoginCmd() *cobra.Command { orgsInfo := map[string]interface{}{} err = client.Get(cmd.Context(), "/v2/orgs", nil, &orgsInfo) if err != nil { - output.Error(cmd, "Failed to fetch organization information", err) - return + return output.Error(cmd, "Failed to fetch organization information", err) } organizations, ok := orgsInfo["organizations"].([]interface{}) if !ok || len(organizations) == 0 { - output.Error(cmd, "No organizations found", nil) - return + return output.Error(cmd, "No organizations found", nil) } firstOrg, ok := organizations[0].(map[string]interface{}) if !ok { - output.Error(cmd, "Failed to parse organization information", nil) - return + return output.Error(cmd, "Failed to parse organization information", nil) } orgID, ok := firstOrg["name"].(string) if !ok { - output.Error(cmd, "Organization ID not found", nil) - return + return output.Error(cmd, "Organization ID not found", nil) } err = config.SetOrgID(orgID) if err != nil { - output.Error(cmd, "Error saving Org ID", err) - return + return output.Error(cmd, "Error saving Org ID", err) } output.PrintASCIIArt(cmd) output.Success(cmd, fmt.Sprintf("Authentication successful. You are now authenticated with organization ID: %s", orgID)) + return nil }, } } @@ -98,29 +93,27 @@ func authConfigureDockerCmd() *cobra.Command { return &cobra.Command{ Use: "configure-docker", Short: "Configure Docker to use NGC API key for nvcr.io", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { apiKey := config.GetAPIKey() if apiKey == "" { - output.Error(cmd, "NGC API key not found. Please run 'nvcf auth login' first.", nil) - return + return output.Error(cmd, "NGC API key not found. Please run 'nvcf auth login' first.", nil) } // Check if Docker is installed _, err := exec.LookPath("docker") if err != nil { - output.Error(cmd, "Docker is not installed or not in the system PATH", err) - return + return output.Error(cmd, "Docker is not installed or not in the system PATH", err) } // TODO: check for existing nvcr.io config? dockerCmd := exec.Command("docker", "login", "nvcr.io", "-u", "$oauthtoken", "--password-stdin") dockerCmd.Stdin = strings.NewReader(apiKey) out, err := dockerCmd.CombinedOutput() if err != nil { - output.Error(cmd, "Failed to configure Docker", err) cmd.Println(string(out)) - return + return output.Error(cmd, "Failed to configure Docker", err) } output.Success(cmd, "Docker configured successfully for nvcr.io") cmd.Println(string(out)) + return nil }, } } @@ -165,13 +158,22 @@ func authLogoutCmd() *cobra.Command { return &cobra.Command{ Use: "logout", Short: "Logout from NVIDIA Cloud", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if !config.IsAuthenticated() { output.Info(cmd, "You are currently not logged in") - return + return nil + } + err := config.ClearAPIKey() + if err != nil { + return output.Error(cmd, "Failed to clear API key", err) + } + err = config.ClearOrgID() + if err != nil { + + return output.Error(cmd, "Failed to clear Org ID", err) } - config.ClearAPIKey() output.Success(cmd, "Logged out successfully") + return nil }, } } @@ -182,19 +184,21 @@ func authWhoAmICmd() *cobra.Command { return &cobra.Command{ Use: "whoami", Short: "Display information about the authenticated user", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { client := api.NewClient(config.GetAPIKey()) whoamiInfo := map[string]any{} err := client.Get(cmd.Context(), whoamiURL, nil, &whoamiInfo) if err != nil { - output.Error(cmd, "Failed to fetch user information", err) - return + return output.Error(cmd, "Failed to fetch user information", err) } jsonMode, _ := cmd.Flags().GetBool("json") if jsonMode { - json.NewEncoder(cmd.OutOrStdout()).Encode(whoamiInfo) - return + err = json.NewEncoder(cmd.OutOrStdout()).Encode(whoamiInfo) + if err != nil { + return output.Error(cmd, "Failed to encode user information", err) + } + return nil } userInfo, _ := whoamiInfo["user"].(map[string]any) table := tablewriter.NewWriter(cmd.OutOrStdout()) @@ -205,6 +209,7 @@ func authWhoAmICmd() *cobra.Command { userInfo["name"].(string), }) table.Render() + return nil }, } } @@ -213,23 +218,24 @@ func authOrgsCmd() *cobra.Command { cmd := &cobra.Command{ Use: "orgs", Short: "Display organization and team information for the authenticated user", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { client := api.NewClient(config.GetAPIKey()) userInfo := map[string]interface{}{} err := client.Get(cmd.Context(), "/v2/users/me", nil, &userInfo) if err != nil { - output.Error(cmd, "Failed to fetch user information", err) - return + return output.Error(cmd, "Failed to fetch user information", err) } jsonMode, _ := cmd.Flags().GetBool("json") if jsonMode { - json.NewEncoder(cmd.OutOrStdout()).Encode(userInfo) - return + err = json.NewEncoder(cmd.OutOrStdout()).Encode(userInfo) + if err != nil { + return output.Error(cmd, "Failed to encode user information", err) + } + return nil } userRoles, ok := userInfo["userRoles"].([]interface{}) if !ok { - output.Error(cmd, "Failed to parse user roles information", nil) - return + return output.Error(cmd, "Failed to parse user roles information", nil) } type OrgTeamInfo struct { OrgName string @@ -287,6 +293,7 @@ func authOrgsCmd() *cobra.Command { } } table.Render() + return nil }, } @@ -298,34 +305,31 @@ func authOrgIDCmd() *cobra.Command { return &cobra.Command{ Use: "org-id", Short: "Display the name of the first organization", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { client := api.NewClient(config.GetAPIKey()) orgsInfo := map[string]interface{}{} err := client.Get(cmd.Context(), "/v2/orgs", nil, &orgsInfo) if err != nil { - output.Error(cmd, "Failed to fetch organization information", err) - return + return output.Error(cmd, "Failed to fetch organization information", err) } organizations, ok := orgsInfo["organizations"].([]interface{}) if !ok || len(organizations) == 0 { - output.Error(cmd, "No organizations found", nil) - return + return output.Error(cmd, "No organizations found", nil) } firstOrg, ok := organizations[0].(map[string]interface{}) if !ok { - output.Error(cmd, "Failed to parse organization information", nil) - return + return output.Error(cmd, "Failed to parse organization information", nil) } name, ok := firstOrg["name"].(string) if !ok { - output.Error(cmd, "Organization name not found", nil) - return + return output.Error(cmd, "Organization name not found", nil) } fmt.Println(name) + return nil }, } } diff --git a/cmd/completion.go b/cmd/completion.go index f870371..bb5f753 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -3,6 +3,7 @@ package cmd import ( "os" + "github.com/brevdev/nvcf/output" "github.com/spf13/cobra" ) @@ -52,18 +53,31 @@ PowerShell: DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, Hidden: true, - Args: cobra.ExactValidArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { switch args[0] { case "bash": - cmd.Root().GenBashCompletion(os.Stdout) + err := cmd.Root().GenBashCompletion(os.Stdout) + if err != nil { + return output.Error(cmd, "Failed to generate bash completion", err) + } case "zsh": - cmd.Root().GenZshCompletion(os.Stdout) + err := cmd.Root().GenZshCompletion(os.Stdout) + if err != nil { + return output.Error(cmd, "Failed to generate zsh completion", err) + } case "fish": - cmd.Root().GenFishCompletion(os.Stdout, true) + err := cmd.Root().GenFishCompletion(os.Stdout, true) + if err != nil { + return output.Error(cmd, "Failed to generate fish completion", err) + } case "powershell": - cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + err := cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + if err != nil { + return output.Error(cmd, "Failed to generate powershell completion", err) + } } + return nil }, } diff --git a/cmd/function/function_create.go b/cmd/function/function_create.go index 9b74b1a..543ee0e 100644 --- a/cmd/function/function_create.go +++ b/cmd/function/function_create.go @@ -405,8 +405,7 @@ func prepareFunctionVersionParamsFromFile(fnImage string, fn FunctionDef) nvcf.F func createAndDeployFunctionVersionFromFile(cmd *cobra.Command, client *api.Client, existingFunctionID string, params nvcf.FunctionVersionNewParams, deploy bool, gpu, instanceType, backend string, maxInstances, minInstances, maxRequestConcurrency int64) error { resp, err := client.Functions.Versions.New(cmd.Context(), existingFunctionID, params) if err != nil { - output.Error(cmd, "error creating function version", err) - return nil + return output.Error(cmd, "error creating function version", err) } output.Success(cmd, fmt.Sprintf("Version %s created for function %s", resp.Function.VersionID, existingFunctionID)) @@ -466,7 +465,6 @@ func createAndDeployFunctionFromFile(cmd *cobra.Command, client *api.Client, par func WaitForDeployment(cmd *cobra.Command, client *api.Client, functionID, versionID string) error { spinner := output.NewSpinner("Waiting for deployment to complete...") output.StartSpinner(spinner) - defer output.StopSpinner(spinner) err := timeout.DoWithTimeout(func(ctx context.Context) error { for ctx.Err() == nil { @@ -493,6 +491,8 @@ func WaitForDeployment(cmd *cobra.Command, client *api.Client, functionID, versi return output.Error(cmd, "Error waiting for deployment", err) } + output.StopSpinner(spinner) + output.Success(cmd, fmt.Sprintf("\nFunction deployed (ID: %s, Version: %s)", functionID, versionID)) return nil } diff --git a/cmd/function/function_delete.go b/cmd/function/function_delete.go index cdbef97..f083adc 100644 --- a/cmd/function/function_delete.go +++ b/cmd/function/function_delete.go @@ -22,8 +22,8 @@ func functionDeleteCmd() *cobra.Command { RunE: runFunctionDelete, } cmd.Flags().String("version-id", "", "The ID of the version") - cmd.Flags().Bool("all", false, "Delete all versions of the function") - cmd.Flags().Bool("force", false, "Forcefully delete a deployed function") + cmd.Flags().BoolP("all", "a", false, "Delete all versions of the function") + cmd.Flags().BoolP("force", "f", false, "Forcefully delete a deployed function") return cmd } diff --git a/cmd/function/function_deploy.go b/cmd/function/function_deploy.go index cc350cf..935cca3 100644 --- a/cmd/function/function_deploy.go +++ b/cmd/function/function_deploy.go @@ -20,7 +20,16 @@ func functionDeployCmd() *cobra.Command { Long: `Deploy an existing NVCF function. If you want to deploy a specific version, use the --version-id flag.`, Example: "nvcf function deploy fid --version-id vid --gpu A100 --instance-type g5.4xlarge", Args: cobra.ExactArgs(1), - RunE: runFunctionDeploy, + PreRunE: func(cmd *cobra.Command, args []string) error { + requiredFlags := []string{"gpu", "instance-type", "backend"} + for _, flag := range requiredFlags { + if err := cmd.MarkFlagRequired(flag); err != nil { + return err + } + } + return nil + }, + RunE: runFunctionDeploy, } cmd.Flags().String("version-id", "", "The ID of the version to deploy") @@ -32,10 +41,6 @@ func functionDeployCmd() *cobra.Command { cmd.Flags().Int64("max-request-concurrency", 1, "Maximum number of concurrent requests") cmd.Flags().BoolP("detached", "d", false, "Detach from the deployment and return to the prompt") - cmd.MarkFlagRequired("gpu") - cmd.MarkFlagRequired("instance-type") - cmd.MarkFlagRequired("backend") - return cmd } @@ -64,9 +69,18 @@ func runFunctionDeploy(cmd *cobra.Command, args []string) error { } } - gpu, _ := cmd.Flags().GetString("gpu") - instanceType, _ := cmd.Flags().GetString("instance-type") - backend, _ := cmd.Flags().GetString("backend") + gpu, err := cmd.Flags().GetString("gpu") + if err != nil { + return output.Error(cmd, "Error getting gpu", err) + } + instanceType, err := cmd.Flags().GetString("instance-type") + if err != nil { + return output.Error(cmd, "Error getting instance-type", err) + } + backend, err := cmd.Flags().GetString("backend") + if err != nil { + return output.Error(cmd, "Error getting backend", err) + } minInstances, _ := cmd.Flags().GetInt64("min-instances") maxInstances, _ := cmd.Flags().GetInt64("max-instances") maxRequestConcurrency, _ := cmd.Flags().GetInt64("max-request-concurrency") @@ -83,7 +97,7 @@ func runFunctionDeploy(cmd *cobra.Command, args []string) error { }}), } - _, err := client.FunctionDeployment.Functions.Versions.InitiateDeployment( + _, err = client.FunctionDeployment.Functions.Versions.InitiateDeployment( cmd.Context(), functionId, versionId, diff --git a/cmd/function/function_stop.go b/cmd/function/function_stop.go index 73cef0b..206a124 100644 --- a/cmd/function/function_stop.go +++ b/cmd/function/function_stop.go @@ -49,16 +49,22 @@ func runFunctionStop(cmd *cobra.Command, args []string) error { } if all { for _, version := range deployedVersionsToStop { - client.FunctionDeployment.Functions.Versions.DeleteDeployment(cmd.Context(), functionId, version, nvcf.FunctionDeploymentFunctionVersionDeleteDeploymentParams{ + _, err = client.FunctionDeployment.Functions.Versions.DeleteDeployment(cmd.Context(), functionId, version, nvcf.FunctionDeploymentFunctionVersionDeleteDeploymentParams{ Graceful: nvcf.Bool(force), }) + if err != nil { + return output.Error(cmd, fmt.Sprintf("Error stopping function %s version %s", functionId, version), err) + } output.Success(cmd, fmt.Sprintf("Function %s version %s stopped successfully", functionId, version)) } } else { - client.FunctionDeployment.Functions.Versions.DeleteDeployment(cmd.Context(), functionId, deployedVersionsToStop[0], nvcf.FunctionDeploymentFunctionVersionDeleteDeploymentParams{ + _, err := client.FunctionDeployment.Functions.Versions.DeleteDeployment(cmd.Context(), functionId, deployedVersionsToStop[0], nvcf.FunctionDeploymentFunctionVersionDeleteDeploymentParams{ Graceful: nvcf.Bool(force), }) - output.Success(cmd, fmt.Sprintf("Function %s version %s stopped successfully", functionId, deployedVersionsToStop[0])) + if err != nil { + return output.Error(cmd, fmt.Sprintf("Error stopping function %s version %s", functionId, deployedVersionsToStop[0]), err) + } + output.Success(cmd, fmt.Sprintf("Function %s version %s stopped successfully", functionId, versionId)) } } return nil diff --git a/cmd/function/function_watch.go b/cmd/function/function_watch.go index 4b90d69..5b7c4ba 100644 --- a/cmd/function/function_watch.go +++ b/cmd/function/function_watch.go @@ -63,7 +63,7 @@ func runFunctionWatch(cmd *cobra.Command, args []string) error { functionID := args[0] versions, err := client.Functions.Versions.List(cmd.Context(), functionID) if err != nil { - output.Error(cmd, "Error getting function", err) + _ = output.Error(cmd, "Error getting function", err) return } functions = &nvcf.ListFunctionsResponse{ @@ -74,7 +74,7 @@ func runFunctionWatch(cmd *cobra.Command, args []string) error { Visibility: nvcf.F(visibilityParams), }) if err != nil { - output.Error(cmd, "Error listing functions", err) + _ = output.Error(cmd, "Error listing functions", err) return } } diff --git a/collections/collections.go b/collections/collections.go index 4f2d4a5..b272bd7 100644 --- a/collections/collections.go +++ b/collections/collections.go @@ -16,10 +16,7 @@ func ListContains[K comparable](list []K, item K) bool { func ListHas[K any](list []K, has func(l K) bool) bool { k := Find(list, has) - if k != nil { - return true - } - return false + return k != nil } func Find[T any](list []T, f func(T) bool) *T { diff --git a/config/config.go b/config/config.go index 28b0c10..b8744fb 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "encoding/json" + "fmt" "os" "path/filepath" ) @@ -23,7 +24,11 @@ func Init() { configPath := filepath.Join(homeDir, ".nvcf", "config.json") data, err := os.ReadFile(configPath) if err == nil { - json.Unmarshal(data, &cfg) + err = json.Unmarshal(data, &cfg) + if err != nil { + fmt.Println("Failed to load config") + panic(err) + } } if v := os.Getenv("NGC_API_KEY"); v != "" { diff --git a/main.go b/main.go index 843759e..17e9597 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,10 @@ Environment variables: PersistentPreRunE: preRunAuthCheck, Run: func(cmd *cobra.Command, args []string) { output.PrintASCIIArt(cmd) - cmd.Usage() + err := cmd.Usage() + if err != nil { + return + } }, DisableAutoGenTag: true, } diff --git a/output/output.go b/output/output.go index 302ff81..5df619c 100644 --- a/output/output.go +++ b/output/output.go @@ -36,20 +36,23 @@ func isQuiet(cmd *cobra.Command) bool { return quiet } -func printJSON(cmd *cobra.Command, data interface{}) { +func printJSON(cmd *cobra.Command, data interface{}) error { json, err := json.MarshalIndent(data, "", " ") if err != nil { - Error(cmd, "Error formatting JSON", err) - return + return Error(cmd, "Error formatting JSON", err) } fmt.Println(string(json)) + return nil } // TODO: Implement secure input for secrets func Prompt(message string, isSecret bool) string { Type(message) var input string - fmt.Scanln(&input) + _, err := fmt.Scanln(&input) + if err != nil { + return "" + } return input } @@ -72,7 +75,10 @@ func StopSpinner(s *spinner.Spinner) { func Functions(cmd *cobra.Command, functions []nvcf.ListFunctionsResponseFunction) { if isJSON(cmd) { - printJSON(cmd, functions) + err := printJSON(cmd, functions) + if err != nil { + return + } } else { printFunctionsTable(cmd, functions) } @@ -90,7 +96,10 @@ func printFunctionsTable(cmd *cobra.Command, functions []nvcf.ListFunctionsRespo func SingleFunction(cmd *cobra.Command, fn nvcf.FunctionResponseFunction) { if isJSON(cmd) { - printJSON(cmd, fn) + err := printJSON(cmd, fn) + if err != nil { + return + } } else { printSingleFunctionTable(cmd, fn) } @@ -106,7 +115,10 @@ func printSingleFunctionTable(cmd *cobra.Command, fn nvcf.FunctionResponseFuncti func Deployments(cmd *cobra.Command, deployments []nvcf.DeploymentResponse) { if isJSON(cmd) { - printJSON(cmd, deployments) + err := printJSON(cmd, deployments) + if err != nil { + return + } } else { printDeploymentsTable(cmd, deployments) } @@ -124,7 +136,10 @@ func printDeploymentsTable(cmd *cobra.Command, deployments []nvcf.DeploymentResp func SingleDeployment(cmd *cobra.Command, deployment nvcf.DeploymentResponse) { if isJSON(cmd) { - printJSON(cmd, deployment) + err := printJSON(cmd, deployment) + if err != nil { + return + } } else { printSingleDeploymentTable(cmd, deployment) } @@ -140,7 +155,10 @@ func printSingleDeploymentTable(cmd *cobra.Command, deployment nvcf.DeploymentRe func GPUs(cmd *cobra.Command, clusterGroups []nvcf.ClusterGroupsResponseClusterGroup) { if isJSON(cmd) { - printJSON(cmd, clusterGroups) + err := printJSON(cmd, clusterGroups) + if err != nil { + return + } } else { printGPUsTable(cmd, clusterGroups) }