Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 4 additions & 130 deletions cmd/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"slices"
"strconv"
"strings"

Expand Down Expand Up @@ -71,28 +69,10 @@ func ClusterCommand() *cli.Command {
Name: "description",
Usage: "Description to apply to the cluster",
},
&cli.BoolFlag{
Name: "disable-docker-version",
Usage: "Allow unsupported versions of docker on the nodes, [default=true]",
Value: true,
},
&cli.BoolFlag{
Name: "import",
Usage: "Mark the cluster for import, this is required if the cluster is going to be used to import an existing k8s cluster",
},
&cli.StringFlag{
Name: "k8s-version",
Usage: "Kubernetes version to use for the cluster, pass in 'list' to see available versions",
},
&cli.StringFlag{
Name: "network-provider",
Usage: "Network provider for the cluster (flannel, canal, calico)",
Value: "canal",
},
&cli.StringFlag{
Name: "rke-config",
Usage: "Location of an rke config file to import. Can be JSON or YAML format",
},
},
},
{
Expand Down Expand Up @@ -273,26 +253,9 @@ func clusterCreate(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}

k8sVersion := cmd.String("k8s-version")
if k8sVersion != "" {
k8sVersions, err := getClusterK8sOptions(c)
if err != nil {
return err
}

if slices.Contains(k8sVersions, k8sVersion) {
fmt.Println("Available Kubernetes versions:")
for _, val := range k8sVersions {
fmt.Println(val)
}
return nil
}
}

config, err := getClusterConfig(cmd)
if err != nil {
return err
config := &managementClient.Cluster{
Name: cmd.Args().First(),
Description: cmd.String("description"),
}

createdCluster, err := c.ManagementClient.Cluster.Create(config)
Expand Down Expand Up @@ -365,18 +328,7 @@ func clusterAddNode(ctx context.Context, cmd *cli.Command) error {
return err
}

if cluster.Driver == "rancherKubernetesEngine" || cluster.Driver == "" {
filter := defaultListOpts(cmd)
filter.Filters["clusterId"] = cluster.ID
nodePools, err := c.ManagementClient.NodePool.List(filter)
if err != nil {
return err
}

if len(nodePools.Data) > 0 {
return errors.New("a node can't be manually registered to a cluster utilizing node-pools")
}
} else {
if cluster.Driver == "" {
return errors.New("a node can only be manually registered to a custom cluster")
}

Expand Down Expand Up @@ -716,14 +668,6 @@ func getClusterProvider(cluster managementClient.Cluster) string {
return "K3S"
case "rke2":
return "RKE2"
case "rancherKubernetesEngine":
return "Rancher Kubernetes Engine"
case "azureKubernetesService", "AKS":
return "Azure Kubernetes Service"
case "googleKubernetesEngine", "GKE":
return "Google Kubernetes Engine"
case "EKS":
return "Elastic Kubernetes Service"
default:
return "Unknown"
}
Expand Down Expand Up @@ -773,73 +717,3 @@ func parseResourceString(mem string) string {
func getClusterPods(cluster managementClient.Cluster) string {
return cluster.Requested["pods"] + "/" + cluster.Allocatable["pods"]
}

func getClusterK8sOptions(c *cliclient.MasterClient) ([]string, error) {
var options []string

setting, err := c.ManagementClient.Setting.ByID("k8s-version-to-images")
if err != nil {
return nil, err
}

var objmap map[string]*json.RawMessage
err = json.Unmarshal([]byte(setting.Value), &objmap)
if err != nil {
return nil, err
}

for key := range objmap {
options = append(options, key)
}
return options, nil
}

func getClusterConfig(cmd *cli.Command) (*managementClient.Cluster, error) {
config := managementClient.Cluster{}
config.Name = cmd.Args().First()
config.Description = cmd.String("description")

if !cmd.Bool("import") {
config.RancherKubernetesEngineConfig = new(managementClient.RancherKubernetesEngineConfig)
ignoreDockerVersion := cmd.Bool("disable-docker-version")
config.RancherKubernetesEngineConfig.IgnoreDockerVersion = &ignoreDockerVersion

if cmd.String("k8s-version") != "" {
config.RancherKubernetesEngineConfig.Version = cmd.String("k8s-version")
}

if cmd.String("network-provider") != "" {
config.RancherKubernetesEngineConfig.Network = &managementClient.NetworkConfig{
Plugin: cmd.String("network-provider"),
}
}

if cmd.String("rke-config") != "" {
bytes, err := readFileReturnJSON(cmd.String("rke-config"))
if err != nil {
return nil, err
}

var jsonObject map[string]interface{}
if err = json.Unmarshal(bytes, &jsonObject); err != nil {
return nil, err
}

// Most values in RancherKubernetesEngineConfig are defined with struct tags for both JSON and YAML in camelCase.
// Changing the tags will be a breaking change. For proper deserialization, we must convert all keys to camelCase.
// Note that we ignore kebab-case keys. Users themselves should ensure any relevant keys
// (especially top-level keys in `services`, like `kube-api` or `kube-controller`) are camelCase or snake-case in cluster config.
convertSnakeCaseKeysToCamelCase(jsonObject)

marshalled, err := json.Marshal(jsonObject)
if err != nil {
return nil, err
}
if err = json.Unmarshal(marshalled, &config); err != nil {
return nil, err
}
}
}

return &config, nil
}
39 changes: 0 additions & 39 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,11 @@ import (
"syscall"
"text/template"
"time"
"unicode"

"github.com/ghodss/yaml"
"github.com/rancher/cli/cliclient"
"github.com/rancher/cli/config"
"github.com/rancher/norman/clientbase"
ntypes "github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v3"
Expand Down Expand Up @@ -552,42 +549,6 @@ func parseClusterAndProjectID(id string) (string, string, error) {
return "", "", fmt.Errorf("unable to extract clusterid and projectid from [%s]", id)
}

// Return a JSON blob of the file at path
func readFileReturnJSON(path string) ([]byte, error) {
file, err := os.ReadFile(path)
if err != nil {
return []byte{}, err
}
// This is probably already JSON if true
if hasPrefix(file, []byte("{")) {
return file, nil
}
return yaml.YAMLToJSON(file)
}

// renameKeys renames the keys in a given map of arbitrary depth with a provided function for string keys.
func renameKeys(input map[string]interface{}, f func(string) string) {
for k, v := range input {
delete(input, k)
newKey := f(k)
input[newKey] = v
if innerMap, ok := v.(map[string]interface{}); ok {
renameKeys(innerMap, f)
}
}
}

// convertSnakeCaseKeysToCamelCase takes a map and recursively transforms all snake_case keys into camelCase keys.
func convertSnakeCaseKeysToCamelCase(input map[string]interface{}) {
renameKeys(input, convert.ToJSONKey)
}

// Return true if the first non-whitespace bytes in buf is prefix.
func hasPrefix(buf []byte, prefix []byte) bool {
trim := bytes.TrimLeftFunc(buf, unicode.IsSpace)
return bytes.HasPrefix(trim, prefix)
}

// getClusterNames maps cluster ID to name and defaults to ID if name is blank
func getClusterNames(cmd *cli.Command, c *cliclient.MasterClient) (map[string]string, error) {
clusterNames := make(map[string]string)
Expand Down
36 changes: 0 additions & 36 deletions cmd/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -73,41 +72,6 @@ func TestParseClusterAndProjectID(t *testing.T) {
}
}

func TestConvertSnakeCaseKeysToCamelCase(t *testing.T) {
t.Parallel()

tests := []struct {
input map[string]any
want map[string]any
}{
{
map[string]any{"foo_bar": "hello"},
map[string]any{"fooBar": "hello"},
},
{
map[string]any{"fooBar": "hello"},
map[string]any{"fooBar": "hello"},
},
{
map[string]any{"foobar": "hello", "some_key": "valueUnmodified", "bar-baz": "bar-baz"},
map[string]any{"foobar": "hello", "someKey": "valueUnmodified", "bar-baz": "bar-baz"},
},
{
map[string]any{"foo_bar": "hello", "backup_config": map[string]any{"hello_world": true}, "config_id": 123},
map[string]any{"fooBar": "hello", "backupConfig": map[string]any{"helloWorld": true}, "configId": 123},
},
}

for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()

convertSnakeCaseKeysToCamelCase(test.input)
assert.Equal(t, test.input, test.want)
})
}
}

func TestParsePrincipalID(t *testing.T) {
t.Parallel()

Expand Down
29 changes: 0 additions & 29 deletions cmd/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ func nodeLs(ctx context.Context, cmd *cli.Command) error {
return err
}

nodePools, err := getNodePools(cmd, c)
if err != nil {
return err
}

writer := NewTableWriter([][]string{
{"ID", "ID"},
{"NAME", "Name"},
Expand All @@ -81,7 +76,6 @@ func nodeLs(ctx context.Context, cmd *cli.Command) error {
ID: item.ID,
Node: item,
Name: getNodeName(item),
Pool: getNodePoolName(item, nodePools),
})
}

Expand Down Expand Up @@ -168,26 +162,3 @@ func getNodeName(node managementClient.Node) string {
}
return node.ID
}

func getNodePools(
cmd *cli.Command,
c *cliclient.MasterClient,
) (*managementClient.NodePoolCollection, error) {
filter := defaultListOpts(cmd)
filter.Filters["clusterId"] = c.UserConfig.GetCurrentCluster()

collection, err := c.ManagementClient.NodePool.List(filter)
if err != nil {
return nil, err
}
return collection, nil
}

func getNodePoolName(node managementClient.Node, pools *managementClient.NodePoolCollection) string {
for _, pool := range pools.Data {
if node.NodePoolID == pool.ID {
return pool.HostnamePrefix
}
}
return ""
}
19 changes: 8 additions & 11 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ func nodeSSH(ctx context.Context, cmd *cli.Command) error {
return err
}

sshNode, key, err := getNodeAndKey(cmd, c, nodeName)
sshNode, key, sshUser, err := getNodeAndKey(cmd, c, nodeName)
if err != nil {
return err
}

if user == "" {
user = sshNode.SshUser
user = sshUser
}
ipAddress := sshNode.IPAddress
if cmd.Bool("external") {
Expand All @@ -98,38 +98,35 @@ func nodeSSH(ctx context.Context, cmd *cli.Command) error {
return processExitCode(callSSH(key, ipAddress, user, args))
}

func getNodeAndKey(cmd *cli.Command, c *cliclient.MasterClient, nodeName string) (managementClient.Node, []byte, error) {
func getNodeAndKey(cmd *cli.Command, c *cliclient.MasterClient, nodeName string) (managementClient.Node, []byte, string, error) {
sshNode := managementClient.Node{}
resource, err := Lookup(c, nodeName, "node")
if err != nil {
return sshNode, nil, err
return sshNode, nil, "", err
}

sshNode, err = getNodeByID(cmd, c, resource.ID)
if err != nil {
return sshNode, nil, err
return sshNode, nil, "", err
}

link := sshNode.Links["nodeConfig"]
if link == "" {
// Get the machine and use that instead.
machine, err := getMachineByNodeName(cmd, c, sshNode.NodeName)
if err != nil {
return sshNode, nil, fmt.Errorf("failed to find SSH key for node [%s]", nodeName)
return sshNode, nil, "", fmt.Errorf("failed to find SSH key for node [%s]", nodeName)
}

link = machine.Links["sshkeys"]
}

key, sshUser, err := getSSHKey(c, link, getNodeName(sshNode))
if err != nil {
return sshNode, nil, err
}
if sshUser != "" {
sshNode.SshUser = sshUser
return sshNode, nil, "", err
}

return sshNode, key, nil
return sshNode, key, sshUser, nil
}

func callSSH(content []byte, ip string, user string, args []string) error {
Expand Down
Loading
Loading