Skip to content

Commit 0b3eb2b

Browse files
authored
Merge pull request #1821 from rancher-sandbox/1757-add-set-to-rdctl
Add the 'set' command to rdctl
2 parents 69da71f + 5eda64f commit 0b3eb2b

File tree

7 files changed

+242
-117
lines changed

7 files changed

+242
-117
lines changed

src/go/rdctl/cmd/listSettings.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,23 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20+
"fmt"
2021
"github.com/spf13/cobra"
2122
)
2223

2324
// listSettingsCmd represents the listSettings command
2425
var listSettingsCmd = &cobra.Command{
2526
Use: "list-settings",
2627
Short: "Lists the current settings.",
27-
Long: `Lists the current settings in JSON format.`,
28-
RunE: func(cmd *cobra.Command, args []string) error {
29-
return doRequest("GET", "list-settings")
30-
},
28+
Long: `Lists the current settings in JSON format.`,
29+
RunE: func(cmd *cobra.Command, args []string) error {
30+
result, err := doRequest("GET", "list-settings")
31+
if err != nil {
32+
return err
33+
}
34+
fmt.Println(string(result))
35+
return nil
36+
},
3137
}
3238

3339
func init() {

src/go/rdctl/cmd/root.go

Lines changed: 121 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,28 @@ limitations under the License.
1818
package cmd
1919

2020
import (
21-
"encoding/json"
22-
"fmt"
23-
"io/ioutil"
24-
"log"
25-
"net/http"
26-
"os"
27-
"path/filepath"
28-
"strconv"
29-
30-
"github.com/spf13/cobra"
21+
"bytes"
22+
"encoding/json"
23+
"fmt"
24+
"io/ioutil"
25+
"log"
26+
"net/http"
27+
"os"
28+
"path/filepath"
29+
"strconv"
30+
31+
"github.com/spf13/cobra"
3132
)
3233

3334
var (
34-
// Used for flags and config
35-
configDir string
36-
configPath string
37-
defaultConfigPath string
38-
user string
39-
host string
40-
port string
41-
password string
35+
// Used for flags and config
36+
configDir string
37+
configPath string
38+
defaultConfigPath string
39+
user string
40+
host string
41+
port string
42+
password string
4243
)
4344

4445
const clientVersion = "1.0.0"
@@ -48,7 +49,7 @@ const apiVersion = "v0"
4849
var rootCmd = &cobra.Command{
4950
Use: "rdctl",
5051
Short: "A CLI for Rancher Desktop",
51-
Long: `The eventual goal of this CLI is to enable any UI-based operation to be done from the command-line as well.`,
52+
Long: `The eventual goal of this CLI is to enable any UI-based operation to be done from the command-line as well.`,
5253
}
5354

5455
// Execute adds all child commands to the root command and sets flags appropriately.
@@ -61,101 +62,120 @@ func Execute() {
6162
}
6263

6364
func init() {
64-
var err error
65-
66-
cobra.OnInitialize(initConfig)
67-
configDir, err = os.UserConfigDir()
68-
if err != nil {
69-
log.Fatal("Can't get config-dir: ", err)
70-
}
71-
defaultConfigPath = filepath.Join(configDir, "rancher-desktop", "rd-engine.json")
72-
rootCmd.PersistentFlags().StringVar(&configPath, "config-path", "", fmt.Sprintf("config file (default %s)", defaultConfigPath))
73-
rootCmd.PersistentFlags().StringVar(&user, "user", "", "overrides the user setting in the config file")
74-
rootCmd.PersistentFlags().StringVar(&host, "host", "", "default is localhost; most useful for WSL")
75-
rootCmd.PersistentFlags().StringVar(&port, "port", "", "overrides the port setting in the config file")
76-
rootCmd.PersistentFlags().StringVar(&password, "password", "", "overrides the password setting in the config file")
65+
var err error
66+
67+
cobra.OnInitialize(initConfig)
68+
configDir, err = os.UserConfigDir()
69+
if err != nil {
70+
log.Fatal("Can't get config-dir: ", err)
71+
}
72+
defaultConfigPath = filepath.Join(configDir, "rancher-desktop", "rd-engine.json")
73+
rootCmd.PersistentFlags().StringVar(&configPath, "config-path", "", fmt.Sprintf("config file (default %s)", defaultConfigPath))
74+
rootCmd.PersistentFlags().StringVar(&user, "user", "", "overrides the user setting in the config file")
75+
rootCmd.PersistentFlags().StringVar(&host, "host", "", "default is localhost; most useful for WSL")
76+
rootCmd.PersistentFlags().StringVar(&port, "port", "", "overrides the port setting in the config file")
77+
rootCmd.PersistentFlags().StringVar(&password, "password", "", "overrides the password setting in the config file")
7778
}
7879

79-
func doRequest(method string, command string) error {
80-
req, err := getRequestObject(method, command)
81-
if err != nil {
82-
return err
83-
}
84-
return doRestOfRequest(req)
80+
func doRequest(method string, command string) ([]byte, error) {
81+
req, err := getRequestObject(method, command)
82+
if err != nil {
83+
return nil, err
84+
}
85+
return doRestOfRequest(req)
86+
}
87+
88+
func doRequestWithPayload(method string, command string, payload *bytes.Buffer) ([]byte, error) {
89+
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%s/%s/%s", host, port, apiVersion, command), payload)
90+
if err != nil {
91+
return nil, err
92+
}
93+
req.SetBasicAuth(user, password)
94+
req.Header.Add("Content-Type", "application/json")
95+
req.Close = true
96+
return doRestOfRequest(req)
8597
}
8698

8799
func getRequestObject(method string, command string) (*http.Request, error) {
88-
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%s/%s/%s", host, port, apiVersion, command), nil)
89-
if err != nil {
90-
return nil, err
91-
}
92-
req.SetBasicAuth(user, password)
93-
req.Header.Add("Content-Type", "text/plain")
94-
req.Close = true
95-
return req, nil
100+
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%s/%s/%s", host, port, apiVersion, command), nil)
101+
if err != nil {
102+
return nil, err
103+
}
104+
req.SetBasicAuth(user, password)
105+
req.Header.Add("Content-Type", "text/plain")
106+
req.Close = true
107+
return req, nil
96108
}
97109

98-
func doRestOfRequest(req *http.Request) error {
99-
client := http.Client{}
100-
response, err := client.Do(req)
101-
if err != nil {
102-
return err
103-
}
104-
if response.StatusCode < 200 || response.StatusCode >= 300 {
105-
switch (response.StatusCode) {
106-
case 401:
107-
return fmt.Errorf("user/password not accepted")
108-
case 500:
109-
return fmt.Errorf("server-side problem: please consult the server logs for more information.")
110-
}
111-
return fmt.Errorf("%s", response.Status)
112-
}
113-
114-
defer response.Body.Close()
115-
116-
body, err := ioutil.ReadAll(response.Body)
117-
if err != nil {
118-
return err
119-
}
120-
121-
fmt.Println(string(body))
122-
return nil
110+
func doRestOfRequest(req *http.Request) ([]byte, error) {
111+
client := http.Client{}
112+
response, err := client.Do(req)
113+
if err != nil {
114+
return nil, err
115+
}
116+
statusMessage := ""
117+
if response.StatusCode < 200 || response.StatusCode >= 300 {
118+
switch response.StatusCode {
119+
case 400:
120+
statusMessage = response.Status
121+
// Prefer the error message in the body written by the command-server, not the one from the http server.
122+
break
123+
case 401:
124+
return nil, fmt.Errorf("user/password not accepted")
125+
case 500:
126+
return nil, fmt.Errorf("server-side problem: please consult the server logs for more information")
127+
default:
128+
return nil, fmt.Errorf("server error return-code %d: %s", response.StatusCode, response.Status)
129+
}
130+
}
131+
132+
defer response.Body.Close()
133+
134+
body, err := ioutil.ReadAll(response.Body)
135+
if err != nil {
136+
if statusMessage != "" {
137+
return nil, fmt.Errorf("server error return-code %d: %s", response.StatusCode, statusMessage)
138+
}
139+
return nil, err
140+
} else if statusMessage != "" {
141+
return nil, fmt.Errorf("%s", string(body))
142+
}
143+
144+
return body, nil
123145
}
124146

125147
// The CLIConfig struct is used to store the json data read from the config file.
126148
type CLIConfig struct {
127-
User string
128-
Password string
129-
Port int
149+
User string
150+
Password string
151+
Port int
130152
}
131153

132154
func initConfig() {
133-
if configPath == "" {
134-
configPath = defaultConfigPath
135-
}
136-
content, err := ioutil.ReadFile(configPath)
137-
if err != nil {
138-
log.Fatalf("Error trying to read file %s: %v", configPath, err)
139-
}
140-
141-
var settings CLIConfig
142-
err = json.Unmarshal(content, &settings)
143-
if err != nil {
144-
log.Fatalf("Error trying to json-load file %s: %v", configPath, err)
145-
}
146-
147-
if user == "" {
148-
user = settings.User
149-
}
150-
if password == "" {
151-
password = settings.Password
152-
}
153-
if host == "" {
154-
host = "localhost"
155-
}
156-
if port == "" {
157-
port = strconv.Itoa(settings.Port)
158-
}
159-
}
155+
if configPath == "" {
156+
configPath = defaultConfigPath
157+
}
158+
content, err := ioutil.ReadFile(configPath)
159+
if err != nil {
160+
log.Fatalf("Error trying to read file %s: %v", configPath, err)
161+
}
160162

163+
var settings CLIConfig
164+
err = json.Unmarshal(content, &settings)
165+
if err != nil {
166+
log.Fatalf("Error trying to json-load file %s: %v", configPath, err)
167+
}
161168

169+
if user == "" {
170+
user = settings.User
171+
}
172+
if password == "" {
173+
password = settings.Password
174+
}
175+
if host == "" {
176+
host = "localhost"
177+
}
178+
if port == "" {
179+
port = strconv.Itoa(settings.Port)
180+
}
181+
}

src/go/rdctl/cmd/set.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
Copyright © 2022 SUSE LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"fmt"
23+
"github.com/spf13/cobra"
24+
)
25+
26+
type serverSettings struct {
27+
Kubernetes struct {
28+
ContainerEngine *string `json:"containerEngine,omitempty"`
29+
Enabled *bool `json:"enabled,omitempty"`
30+
Version *string `json:"version,omitempty"`
31+
} `json:"kubernetes,omitempty"`
32+
}
33+
34+
var specifiedSettings struct {
35+
ContainerEngine string
36+
Enabled bool
37+
Version string
38+
}
39+
40+
// setCmd represents the set command
41+
var setCmd = &cobra.Command{
42+
Use: "set",
43+
Short: "Update selected fields in the Rancher Desktop UI and restart the backend.",
44+
Long: `Update selected fields in the Rancher Desktop UI and restart the backend.`,
45+
RunE: func(cmd *cobra.Command, args []string) error {
46+
if len(args) > 0 {
47+
return fmt.Errorf("set command: unrecognized command-line arguments specified: %v", args)
48+
}
49+
return doSetCommand(cmd)
50+
},
51+
}
52+
53+
func init() {
54+
rootCmd.AddCommand(setCmd)
55+
setCmd.Flags().StringVar(&specifiedSettings.ContainerEngine, "container-engine", "", "Set engine to containerd or moby (aka docker).")
56+
setCmd.Flags().BoolVar(&specifiedSettings.Enabled, "kubernetes-enabled", false, "Control whether kubernetes runs in the backend.")
57+
setCmd.Flags().StringVar(&specifiedSettings.Version, "kubernetes-version", "", "Choose which version of kubernetes to run.")
58+
}
59+
60+
func doSetCommand(cmd *cobra.Command) error {
61+
var currentSettings serverSettings
62+
changedSomething := false
63+
64+
if cmd.Flags().Changed("container-engine") {
65+
currentSettings.Kubernetes.ContainerEngine = &specifiedSettings.ContainerEngine
66+
changedSomething = true
67+
}
68+
if cmd.Flags().Changed("kubernetes-enabled") {
69+
currentSettings.Kubernetes.Enabled = &specifiedSettings.Enabled
70+
changedSomething = true
71+
}
72+
if cmd.Flags().Changed("kubernetes-version") {
73+
currentSettings.Kubernetes.Version = &specifiedSettings.Version
74+
changedSomething = true
75+
}
76+
77+
if !changedSomething {
78+
return fmt.Errorf("set command: no settings to change were given")
79+
}
80+
jsonBuffer, err := json.Marshal(currentSettings)
81+
if err != nil {
82+
return err
83+
}
84+
result, err := doRequestWithPayload("PUT", "set", bytes.NewBuffer(jsonBuffer))
85+
if err != nil {
86+
return err
87+
}
88+
if len(result) > 0 {
89+
fmt.Printf("Status: %s.\n", string(result))
90+
} else {
91+
fmt.Printf("Operation successfully returned with no output.")
92+
}
93+
return nil
94+
}

0 commit comments

Comments
 (0)