Skip to content

Commit 0d3f905

Browse files
committed
fix: Respect output format in all commands
Signed-off-by: Arthur Amstutz <arthur.amstutz@corp.ovh.com>
1 parent 2d5c595 commit 0d3f905

62 files changed

Lines changed: 947 additions & 767 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ require (
1818
github.com/maxatome/go-testdeep v1.14.0
1919
github.com/maxatome/tdhttpmock v1.0.0
2020
github.com/ovh/go-ovh v1.9.0
21-
github.com/spf13/cobra v1.9.1
22-
github.com/spf13/pflag v1.0.6
21+
github.com/spf13/cobra v1.10.1
22+
github.com/spf13/pflag v1.0.9
2323
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561
2424
golang.org/x/sync v0.16.0
2525
gopkg.in/ini.v1 v1.67.0

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,12 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
127127
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
128128
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
129129
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
130+
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
131+
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
130132
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
131133
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
134+
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
135+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
132136
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
133137
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
134138
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=

internal/cmd/account_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-FileCopyrightText: 2025 OVH SAS <opensource@ovh.net>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package cmd_test
6+
7+
import (
8+
"encoding/json"
9+
"net/http"
10+
11+
"github.com/jarcoal/httpmock"
12+
"github.com/maxatome/go-testdeep/td"
13+
"github.com/maxatome/tdhttpmock"
14+
"github.com/ovh/ovhcloud-cli/internal/cmd"
15+
)
16+
17+
func (ms *MockSuite) TestOauth2ClientCreateCmd(assert, require *td.T) {
18+
httpmock.RegisterMatcherResponder(http.MethodPost,
19+
"https://eu.api.ovh.com/1.0/me/api/oauth2/client",
20+
tdhttpmock.JSONBody(td.JSON(`
21+
{
22+
"callbackUrls": [
23+
"https://example.com/callback"
24+
],
25+
"description": "Test OAuth2 client",
26+
"flow": "AUTHORIZATION_CODE",
27+
"name": "test-client"
28+
}`),
29+
),
30+
httpmock.NewStringResponder(200, `{"clientId": "client-12345", "clientSecret": "sicrette"}`),
31+
)
32+
33+
out, err := cmd.Execute("account", "api", "oauth2", "client", "create", "--name", "test-client",
34+
"--flow", "AUTHORIZATION_CODE", "--callback-urls", "https://example.com/callback",
35+
"--description", "Test OAuth2 client", "--json")
36+
require.CmpNoError(err)
37+
assert.Cmp(json.RawMessage(out), td.JSON(`
38+
{
39+
"message": "✅ OAuth2 client created successfully (client ID: client-12345, client secret: sicrette)",
40+
"details": {
41+
"clientId": "client-12345",
42+
"clientSecret": "sicrette"
43+
}
44+
}`),
45+
)
46+
}

internal/cmd/cloud_rancher_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package cmd_test
66

77
import (
8+
"encoding/json"
89
"net/http"
910

1011
"github.com/jarcoal/httpmock"
@@ -29,7 +30,69 @@ func (ms *MockSuite) TestCloudRancherCreateCmd(assert, require *td.T) {
2930
)
3031

3132
out, err := cmd.Execute("cloud", "rancher", "create", "--cloud-project", "fakeProjectID", "--name", "test-rancher", "--plan", "OVHCLOUD_EDITION", "--version", "2.11.3")
32-
3333
require.CmpNoError(err)
3434
assert.String(out, `✅ Rancher test-rancher created successfully (id: rancher-12345)`)
3535
}
36+
37+
func (ms *MockSuite) TestCloudRancherCreateCmdJSONFormat(assert, require *td.T) {
38+
httpmock.RegisterMatcherResponder(http.MethodPost,
39+
"https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/rancher",
40+
tdhttpmock.JSONBody(td.JSON(`
41+
{
42+
"targetSpec": {
43+
"name": "test-rancher",
44+
"plan": "OVHCLOUD_EDITION",
45+
"version": "2.11.3"
46+
}
47+
}`),
48+
),
49+
httpmock.NewStringResponder(200, `{"id": "rancher-12345"}`),
50+
)
51+
52+
out, err := cmd.Execute("cloud", "rancher", "create", "--cloud-project", "fakeProjectID", "--name", "test-rancher", "--plan", "OVHCLOUD_EDITION", "--version", "2.11.3", "--json")
53+
require.CmpNoError(err)
54+
assert.Cmp(json.RawMessage(out), td.JSON(`{"details":{"id": "rancher-12345"}, "message": "✅ Rancher test-rancher created successfully (id: rancher-12345)"}`))
55+
}
56+
57+
func (ms *MockSuite) TestCloudRancherCreateCmdYAMLFormat(assert, require *td.T) {
58+
httpmock.RegisterMatcherResponder(http.MethodPost,
59+
"https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/rancher",
60+
tdhttpmock.JSONBody(td.JSON(`
61+
{
62+
"targetSpec": {
63+
"name": "test-rancher",
64+
"plan": "OVHCLOUD_EDITION",
65+
"version": "2.11.3"
66+
}
67+
}`),
68+
),
69+
httpmock.NewStringResponder(200, `{"id": "rancher-12345"}`),
70+
)
71+
72+
out, err := cmd.Execute("cloud", "rancher", "create", "--cloud-project", "fakeProjectID", "--name", "test-rancher", "--plan", "OVHCLOUD_EDITION", "--version", "2.11.3", "--yaml")
73+
require.CmpNoError(err)
74+
assert.String(out, `details:
75+
id: rancher-12345
76+
message: '✅ Rancher test-rancher created successfully (id: rancher-12345)'
77+
`)
78+
}
79+
80+
func (ms *MockSuite) TestCloudRancherCreateCmdCustomFormat(assert, require *td.T) {
81+
httpmock.RegisterMatcherResponder(http.MethodPost,
82+
"https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/rancher",
83+
tdhttpmock.JSONBody(td.JSON(`
84+
{
85+
"targetSpec": {
86+
"name": "test-rancher",
87+
"plan": "OVHCLOUD_EDITION",
88+
"version": "2.11.3"
89+
}
90+
}`),
91+
),
92+
httpmock.NewStringResponder(200, `{"id": "rancher-12345"}`),
93+
)
94+
95+
out, err := cmd.Execute("cloud", "rancher", "create", "--cloud-project", "fakeProjectID", "--name", "test-rancher", "--plan", "OVHCLOUD_EDITION", "--version", "2.11.3", "--format", "[details.id]")
96+
require.CmpNoError(err)
97+
assert.String(out, `["rancher-12345"]`)
98+
}

internal/cmd/cmd_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/ovh/go-ovh/ovh"
1515
"github.com/ovh/ovhcloud-cli/internal/cmd"
1616
httplib "github.com/ovh/ovhcloud-cli/internal/http"
17+
"github.com/spf13/cobra"
18+
"github.com/spf13/pflag"
1719
)
1820

1921
type MockSuite struct{}
@@ -45,9 +47,31 @@ func (ms *MockSuite) PreTest(t *td.T, testName string) error {
4547
func (ms *MockSuite) PostTest(_ *td.T, _ string) error {
4648
httpmock.Reset()
4749
cmd.PostExecute()
50+
resetSubCommandFlagValues(cmd.GetRootCommand())
4851
return nil
4952
}
5053

5154
func TestMockSuite(t *testing.T) {
5255
tdsuite.Run(t, &MockSuite{})
5356
}
57+
58+
// resetSubCommandFlagValues resets all flags of all subcommands of the given root command to their default values.
59+
func resetSubCommandFlagValues(root *cobra.Command) {
60+
for _, c := range root.Commands() {
61+
c.Flags().VisitAll(func(f *pflag.Flag) {
62+
if f.Changed {
63+
if f.Value.Type() == "stringArray" {
64+
// Special handling for stringArray for which we cannot
65+
// use DefValue since it is equal to "[]".
66+
if r, ok := f.Value.(pflag.SliceValue); ok {
67+
r.Replace(nil)
68+
}
69+
} else {
70+
f.Value.Set(f.DefValue)
71+
}
72+
f.Changed = false
73+
}
74+
})
75+
resetSubCommandFlagValues(c)
76+
}
77+
}

internal/cmd/parameter.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package cmd
77
import (
88
_ "embed"
99
"errors"
10-
"fmt"
1110
"os"
1211
"runtime"
1312

@@ -44,7 +43,7 @@ func addInitParameterFileFlag(cmd *cobra.Command, openapiSchema []byte, path, me
4443

4544
if !replaceParamFile {
4645
if _, err := os.Stat(paramFile); !errors.Is(err, os.ErrNotExist) {
47-
display.ExitError("file %q already exists", paramFile)
46+
display.OutputError(&flags.OutputFormatConfig, "file %q already exists", paramFile)
4847
return
4948
}
5049
}
@@ -57,15 +56,15 @@ func addInitParameterFileFlag(cmd *cobra.Command, openapiSchema []byte, path, me
5756
if replaceValueFn != nil {
5857
replaceValues, err = replaceValueFn(cmd, args)
5958
if err != nil {
60-
display.ExitError("failed to get replacement values: %s", err)
59+
display.OutputError(&flags.OutputFormatConfig, "failed to get replacement values: %s", err)
6160
return
6261
}
6362
}
6463

6564
// Get examples from OpenAPI schema and replace values with provided replacements
6665
examples, err := openapi.GetOperationRequestExamples(openapiSchema, path, method, defaultContent, replaceValues)
6766
if err != nil {
68-
display.ExitError("failed to fetch parameter file examples: %s", err)
67+
display.OutputError(&flags.OutputFormatConfig, "failed to fetch parameter file examples: %s", err)
6968
return
7069
}
7170

@@ -74,30 +73,30 @@ func addInitParameterFileFlag(cmd *cobra.Command, openapiSchema []byte, path, me
7473
if len(examples) > 0 {
7574
_, choice, err = display.RunGenericChoicePicker("Please select a parameter example", examples, 0)
7675
if err != nil {
77-
display.ExitError(err.Error())
76+
display.OutputError(&flags.OutputFormatConfig, "%s", err)
7877
return
7978
}
8079
}
8180

8281
if choice == "" {
83-
display.ExitWarning("No example selected, exiting...")
82+
display.OutputWarning(&flags.OutputFormatConfig, "No example selected, exiting")
8483
return
8584
}
8685

8786
// Write the selected example to the parameter file
8887
tmplFile, err := os.Create(paramFile)
8988
if err != nil {
90-
display.ExitError("failed to create parameter file: %s", err)
89+
display.OutputError(&flags.OutputFormatConfig, "failed to create parameter file: %s", err)
9190
return
9291
}
9392
defer tmplFile.Close()
9493

9594
if _, err := tmplFile.WriteString(choice); err != nil {
96-
display.ExitError("error writing parameter file: %s", err)
95+
display.OutputError(&flags.OutputFormatConfig, "error writing parameter file: %s", err)
9796
return
9897
}
9998

100-
fmt.Printf("\n⚡️ Parameter file written at %s\n", paramFile)
99+
display.OutputInfo(&flags.OutputFormatConfig, nil, "⚡️ Parameter file written at %s", paramFile)
101100
os.Exit(0)
102101
}
103102
}

internal/cmd/root.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ func PostExecute() {
7272
flags.OutputFormatConfig = display.OutputFormat{}
7373
flags.ParametersViaEditor = false
7474
flags.ParametersFile = ""
75-
76-
// Reinit root command to mark persistent flags as not parsed
77-
rootCmd.ResetFlags()
78-
initRootCmd()
7975
}
8076

8177
func initRootCmd() {
@@ -91,7 +87,7 @@ func initRootCmd() {
9187

9288
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
9389
if http.Client == nil {
94-
display.ExitError("API client is not initialized, please run `ovhcloud login` to authenticate")
90+
display.OutputError(&flags.OutputFormatConfig, "API client is not initialized, please run `ovhcloud login` to authenticate")
9591
os.Exit(1) // Force os.Exit even in WASM mode
9692
}
9793
}

internal/display/common.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ type OutputFormat struct {
1414
JsonOutput, YamlOutput, InteractiveOutput bool
1515
CustomFormat string
1616
}
17+
18+
type OutputMessage struct {
19+
Message string `json:"message,omitempty"`
20+
Error bool `json:"error,omitempty"`
21+
Warning bool `json:"warning,omitempty"`
22+
Details any `json:"details,omitempty"`
23+
}

0 commit comments

Comments
 (0)