Skip to content

Commit 391557b

Browse files
committed
feat: Add Rancher commands
Signed-off-by: Arthur Amstutz <arthur.amstutz@corp.ovh.com>
1 parent 37b1bdb commit 391557b

10 files changed

Lines changed: 311 additions & 27 deletions

File tree

internal/cmd/cloud_rancher.go

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package cmd
66

77
import (
8+
"github.com/ovh/ovhcloud-cli/internal/assets"
89
"github.com/ovh/ovhcloud-cli/internal/services/cloud"
910
"github.com/spf13/cobra"
1011
)
@@ -37,12 +38,80 @@ func initCloudRancherCommand(cloudCmd *cobra.Command) {
3738
Run: cloud.EditRancher,
3839
Args: cobra.ExactArgs(1),
3940
}
40-
editRancherCmd.Flags().StringVar(&cloud.RancherTargetSpec.Name, "name", "", "Name of the managed Rancher service")
41-
editRancherCmd.Flags().StringVar(&cloud.RancherTargetSpec.Plan, "plan", "", "Plan of the managed Rancher service (OVHCLOUD_EDITION, STANDARD)")
42-
editRancherCmd.Flags().StringVar(&cloud.RancherTargetSpec.Version, "version", "", "Version of the managed Rancher service")
43-
editRancherCmd.Flags().StringArrayVar(&cloud.RancherTargetSpec.CLIIPRestrictions, "ip-restrictions", nil, "List of IP restrictions (expected format: '<cidrBlock>,<description>')")
41+
editRancherCmd.Flags().StringVar(&cloud.RancherSpec.TargetSpec.Name, "name", "", "Name of the managed Rancher service")
42+
editRancherCmd.Flags().StringVar(&cloud.RancherSpec.TargetSpec.Plan, "plan", "", "Plan of the managed Rancher service (OVHCLOUD_EDITION, STANDARD)")
43+
editRancherCmd.Flags().StringVar(&cloud.RancherSpec.TargetSpec.Version, "version", "", "Version of the managed Rancher service")
44+
editRancherCmd.Flags().StringArrayVar(&cloud.RancherSpec.TargetSpec.CLIIPRestrictions, "ip-restrictions", nil, "List of IP restrictions (expected format: '<cidrBlock>,<description>')")
4445
addInteractiveEditorFlag(editRancherCmd)
4546
rancherCmd.AddCommand(editRancherCmd)
4647

48+
rancherCmd.AddCommand(getRancherCreateCmd())
49+
50+
rancherCmd.AddCommand(&cobra.Command{
51+
Use: "delete <rancher_id>",
52+
Short: "Delete a specific Rancher service",
53+
Run: cloud.DeleteRancher,
54+
Args: cobra.ExactArgs(1),
55+
})
56+
4757
cloudCmd.AddCommand(rancherCmd)
4858
}
59+
60+
func getRancherCreateCmd() *cobra.Command {
61+
rancherCreateCmd := &cobra.Command{
62+
Use: "create",
63+
Short: "Create a new Rancher service",
64+
Long: `Use this command to create a managed Rancher service in the given public cloud project.
65+
There are three ways to define the creation parameters:
66+
67+
1. Using only CLI flags:
68+
69+
ovhcloud cloud rancher create --name MyNewRancher --plan OVHCLOUD_EDITION --version 2.11.3
70+
71+
2. Using a configuration file:
72+
73+
First you can generate an example of installation file using the following command:
74+
75+
ovhcloud cloud rancher create --init-file ./params.json
76+
77+
You will be able to choose from several examples of parameters. Once an example has been selected, the content is written in the given file.
78+
After editing the file to set the correct creation parameters, run:
79+
80+
ovhcloud cloud rancher create --from-file ./params.json
81+
82+
Note that you can also pipe the content of the parameters file, like the following:
83+
84+
cat ./params.json | ovhcloud cloud rancher create
85+
86+
In both cases, you can override the parameters in the given file using command line flags, for example:
87+
88+
ovhcloud cloud rancher create --from-file ./params.json --name NameOverriden
89+
90+
3. Using your default text editor:
91+
92+
ovhcloud cloud rancher create --editor
93+
|
94+
You will be able to choose from several examples of parameters. Once an example has been selected, the CLI will open your
95+
default text editor to update the parameters. When saving the file, the creation will start.
96+
97+
Note that it is also possible to override values in the presented examples using command line flags like the following:
98+
99+
ovhcloud cloud rancher create --editor --region BHS5
100+
`,
101+
Run: cloud.CreateRancher,
102+
}
103+
104+
// Specific flags for Rancher creation
105+
rancherCreateCmd.Flags().StringVar(&cloud.RancherSpec.TargetSpec.Name, "name", "", "Name of the managed Rancher service")
106+
rancherCreateCmd.Flags().StringVar(&cloud.RancherSpec.TargetSpec.Plan, "plan", "", "Plan of the managed Rancher service (available plans can be listed using 'cloud reference rancher list-plans' command)")
107+
rancherCreateCmd.Flags().StringVar(&cloud.RancherSpec.TargetSpec.Version, "version", "", "Version of the managed Rancher service (available versions can be listed using 'cloud reference rancher list-versions' command)")
108+
rancherCreateCmd.Flags().BoolVar(&cloud.RancherSpec.TargetSpec.IAMAuthEnabled, "iam-auth-enabled", false, "Allow Rancher to use identities managed by OVHcloud IAM (Identity and Access Management) to control access")
109+
110+
// Common flags for other means to define parameters
111+
addInitParameterFileFlag(rancherCreateCmd, assets.CloudV2OpenapiSchema, "/cloud/project/{serviceName}/rancher", "post", cloud.CloudRancherCreationExample, nil)
112+
addInteractiveEditorFlag(rancherCreateCmd)
113+
addFromFileFlag(rancherCreateCmd)
114+
rancherCreateCmd.MarkFlagsMutuallyExclusive("from-file", "editor")
115+
116+
return rancherCreateCmd
117+
}

internal/cmd/cloud_reference.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func initCloudReferenceCmd(cloudCmd *cobra.Command) {
1717

1818
referenceCmd.PersistentFlags().StringVar(&cloud.CloudProject, "cloud-project", "", "Cloud project ID")
1919

20+
// Flavors
2021
var region string
2122
flavorListCmd := withFilterFlag(&cobra.Command{
2223
Use: "list-flavors",
@@ -29,6 +30,7 @@ func initCloudReferenceCmd(cloudCmd *cobra.Command) {
2930
flavorListCmd.Flags().StringVarP(&region, "region", "r", "", "Region to filter flavors (e.g., GRA9, BHS5)")
3031
referenceCmd.AddCommand(flavorListCmd)
3132

33+
// Images
3234
var osType string
3335
imageListCmd := withFilterFlag(&cobra.Command{
3436
Use: "list-images",
@@ -56,5 +58,30 @@ func initCloudReferenceCmd(cloudCmd *cobra.Command) {
5658
Args: cobra.NoArgs,
5759
}))
5860

61+
// Rancher reference commands
62+
rancherReferenceCmd := &cobra.Command{
63+
Use: "rancher",
64+
Short: "Fetch Rancher reference data in the given cloud project",
65+
}
66+
referenceCmd.AddCommand(rancherReferenceCmd)
67+
68+
rancherVersionsListCmd := withFilterFlag(&cobra.Command{
69+
Use: "list-versions",
70+
Short: "List available Rancher versions in the given cloud project",
71+
Run: cloud.ListRancherAvailableVersions,
72+
Args: cobra.NoArgs,
73+
})
74+
rancherVersionsListCmd.Flags().StringP("rancher-id", "r", "", "Rancher service ID to filter available versions")
75+
rancherReferenceCmd.AddCommand(rancherVersionsListCmd)
76+
77+
rancherPlansListCmd := withFilterFlag(&cobra.Command{
78+
Use: "list-plans",
79+
Short: "List available Rancher plans in the given cloud project",
80+
Run: cloud.ListRancherAvailablePlans,
81+
Args: cobra.NoArgs,
82+
})
83+
rancherPlansListCmd.Flags().StringP("rancher-id", "r", "", "Rancher service ID to filter available plans")
84+
rancherReferenceCmd.AddCommand(rancherPlansListCmd)
85+
5986
cloudCmd.AddCommand(referenceCmd)
6087
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
10+
"github.com/jarcoal/httpmock"
11+
"github.com/maxatome/go-testdeep/td"
12+
"github.com/ovh/ovhcloud-cli/internal/cmd"
13+
)
14+
15+
func (ms *MockSuite) TestCloudReferenceRancherVersionsListCmd(assert, require *td.T) {
16+
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/reference/rancher/version",
17+
httpmock.NewStringResponder(200, `[
18+
{
19+
"cause": "END_OF_SUPPORT",
20+
"changelogUrl": "https://github.com/rancher/rancher/releases/tag/v2.9.4",
21+
"message": "This Rancher version is no more supported, creations and updates to this version have been disabled.",
22+
"name": "2.9.4",
23+
"status": "UNAVAILABLE"
24+
},
25+
{
26+
"changelogUrl": "https://github.com/rancher/rancher/releases/tag/v2.10.4",
27+
"name": "2.10.4",
28+
"status": "AVAILABLE"
29+
},
30+
{
31+
"changelogUrl": "https://github.com/rancher/rancher/releases/tag/v2.11.3",
32+
"name": "2.11.3",
33+
"status": "AVAILABLE"
34+
}
35+
]`).Once())
36+
37+
out, err := cmd.Execute("cloud", "reference", "rancher", "list-versions", "--json", "--cloud-project", "fakeProjectID", "--filter", `status=="AVAILABLE"`)
38+
39+
require.CmpNoError(err)
40+
assert.Cmp(json.RawMessage(out), td.JSON(`[
41+
{
42+
"changelogUrl": "https://github.com/rancher/rancher/releases/tag/v2.10.4",
43+
"name": "2.10.4",
44+
"status": "AVAILABLE"
45+
},
46+
{
47+
"changelogUrl": "https://github.com/rancher/rancher/releases/tag/v2.11.3",
48+
"name": "2.11.3",
49+
"status": "AVAILABLE"
50+
}
51+
]`))
52+
}
53+
54+
func (ms *MockSuite) TestCloudReferenceRancherPlansListCmd(assert, require *td.T) {
55+
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/reference/rancher/plan",
56+
httpmock.NewStringResponder(200, `[
57+
{
58+
"name": "OVHCLOUD_EDITION",
59+
"status": "AVAILABLE"
60+
},
61+
{
62+
"name": "STANDARD",
63+
"status": "AVAILABLE"
64+
}
65+
]`).Once())
66+
67+
out, err := cmd.Execute("cloud", "reference", "rancher", "list-plans", "--cloud-project", "fakeProjectID", "--format", "name")
68+
69+
require.CmpNoError(err)
70+
assert.String(out, `"OVHCLOUD_EDITION"
71+
"STANDARD"
72+
`)
73+
}

internal/cmd/cmd_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/maxatome/go-testdeep/helpers/tdsuite"
1313
"github.com/maxatome/go-testdeep/td"
1414
"github.com/ovh/go-ovh/ovh"
15+
"github.com/ovh/ovhcloud-cli/internal/cmd"
1516
httplib "github.com/ovh/ovhcloud-cli/internal/http"
1617
)
1718

@@ -43,6 +44,7 @@ func (ms *MockSuite) PreTest(t *td.T, testName string) error {
4344

4445
func (ms *MockSuite) PostTest(_ *td.T, _ string) error {
4546
httpmock.Reset()
47+
cmd.PostExecute()
4648
return nil
4749
}
4850

internal/cmd/root.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,24 @@ func Execute(args ...string) (string, error) {
6363
}
6464

6565
func PostExecute() {
66+
// Reset output variables
6667
display.ResultString = ""
6768
display.ResultError = nil
68-
}
6969

70-
func init() {
71-
http.InitClient()
70+
// Reset all flags to default values
71+
flags.GenericFilters = nil
72+
flags.OutputFormatConfig = display.OutputFormat{}
73+
flags.ParametersViaEditor = false
74+
flags.ParametersFile = ""
7275

73-
// Load configuration files by order of increasing priority. All configuration
74-
// files are optional. Only load file from user home if home could be resolve
75-
flags.CliConfig, flags.CliConfigPath = config.LoadINI()
76+
// Reinit root command to mark persistent flags as not parsed
77+
rootCmd.ResetFlags()
78+
initRootCmd()
79+
}
7680

81+
func initRootCmd() {
7782
rootCmd.DisableAutoGenTag = true
83+
7884
rootCmd.PersistentFlags().BoolVarP(&flags.Debug, "debug", "d", false, "Activate debug mode (will log all HTTP requests details)")
7985
rootCmd.PersistentFlags().BoolVarP(&flags.IgnoreErrors, "ignore-errors", "e", false, "Ignore errors in API calls when it is not fatal to the execution")
8086
rootCmd.PersistentFlags().BoolVarP(&flags.OutputFormatConfig.JsonOutput, "json", "j", false, "Output in JSON")
@@ -91,6 +97,16 @@ func init() {
9197
}
9298
}
9399

100+
func init() {
101+
http.InitClient()
102+
103+
// Load configuration files by order of increasing priority. All configuration
104+
// files are optional. Only load file from user home if home could be resolve
105+
flags.CliConfig, flags.CliConfigPath = config.LoadINI()
106+
107+
initRootCmd()
108+
}
109+
94110
func WasmCleanCommands() {
95111
// Remove commands that are not relevant in WASM mode
96112
for _, child := range rootCmd.Commands() {

internal/cmd/vps_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ import (
1414

1515
func (ms *MockSuite) TestVpsListCmd(assert, require *td.T) {
1616
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/vps",
17-
httpmock.NewStringResponder(200, `["vps-12345","vps-67890"]`))
17+
httpmock.NewStringResponder(200, `["vps-12345","vps-67890"]`).Once())
1818

1919
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/vps/vps-12345",
20-
httpmock.NewStringResponder(200, `{"name": "vps-12345", "displayName": "VPS 12345", "state": "running", "zone": "Region OpenStack: os-waw2"}`))
20+
httpmock.NewStringResponder(200, `{"name": "vps-12345", "displayName": "VPS 12345", "state": "running", "zone": "Region OpenStack: os-waw2"}`).Once())
2121

2222
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/vps/vps-67890",
23-
httpmock.NewStringResponder(200, `{"name": "vps-67890", "displayName": "VPS 67890", "state": "stopped", "zone": "Region OpenStack: os-gra1"}`))
23+
httpmock.NewStringResponder(200, `{"name": "vps-67890", "displayName": "VPS 67890", "state": "stopped", "zone": "Region OpenStack: os-gra1"}`).Once())
2424

2525
out, err := cmd.Execute("vps", "ls", "--json")
2626

@@ -43,10 +43,10 @@ func (ms *MockSuite) TestVpsListCmd(assert, require *td.T) {
4343

4444
func (ms *MockSuite) TestVpsGetCmd(assert, require *td.T) {
4545
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/vps/vps-67890",
46-
httpmock.NewStringResponder(200, `{"name": "vps-67890", "displayName": "VPS 67890", "state": "stopped", "zone": "Region OpenStack: os-gra1"}`))
46+
httpmock.NewStringResponder(200, `{"name": "vps-67890", "displayName": "VPS 67890", "state": "stopped", "zone": "Region OpenStack: os-gra1"}`).Once())
4747

4848
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/1.0/vps/vps-67890/datacenter",
49-
httpmock.NewStringResponder(200, `{"country": "fr", "name": "os-gra1", "longName": "Region OpenStack: os-gra1"}`))
49+
httpmock.NewStringResponder(200, `{"country": "fr", "name": "os-gra1", "longName": "Region OpenStack: os-gra1"}`).Once())
5050

5151
out, err := cmd.Execute("vps", "get", "vps-67890", "--json")
5252

internal/display/display.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func renderCustomFormat(value any, format string) {
3535

3636
switch reflect.TypeOf(value).Kind() {
3737
case reflect.Slice:
38+
var output string
3839
for _, val := range value.([]map[string]any) {
3940
out, err := ev(context.Background(), val)
4041
if err != nil {
@@ -45,8 +46,10 @@ func renderCustomFormat(value any, format string) {
4546
if err != nil {
4647
ExitError("error marshalling result")
4748
}
48-
Output(string(outBytes))
49+
output += string(outBytes) + "\n"
4950
}
51+
ResultString = output
52+
fmt.Print(output)
5053
default:
5154
out, err := ev(context.Background(), value)
5255
if err != nil {

0 commit comments

Comments
 (0)