Skip to content

Commit

Permalink
Merge pull request #307 from thedadams/capi-machine-ssh
Browse files Browse the repository at this point in the history
  • Loading branch information
Donnie Adams authored Nov 2, 2021
2 parents f119635 + 1a2f5d2 commit 7e32bb2
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 43 deletions.
21 changes: 20 additions & 1 deletion cliclient/cliclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/rancher/cli/config"
"github.com/rancher/norman/clientbase"
ntypes "github.com/rancher/norman/types"
capiClient "github.com/rancher/rancher/pkg/client/generated/cluster/v1alpha4"
clusterClient "github.com/rancher/rancher/pkg/client/generated/cluster/v3"
managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3"
projectClient "github.com/rancher/rancher/pkg/client/generated/project/v3"
Expand All @@ -21,6 +22,7 @@ type MasterClient struct {
ManagementClient *managementClient.Client
ProjectClient *projectClient.Client
UserConfig *config.ServerConfig
CAPIClient *capiClient.Client
}

// NewMasterClient returns a new MasterClient with Cluster, Management and Project
Expand All @@ -40,6 +42,7 @@ func NewMasterClient(config *config.ServerConfig) (*MasterClient, error) {
g.Go(mc.newManagementClient)
g.Go(mc.newClusterClient)
g.Go(mc.newProjectClient)
g.Go(mc.newCAPIClient)

if err := g.Wait(); err != nil {
return nil, err
Expand Down Expand Up @@ -143,8 +146,24 @@ func (mc *MasterClient) newProjectClient() error {
return nil
}

func (mc *MasterClient) newCAPIClient() error {
options := createClientOpts(mc.UserConfig)
options.URL = strings.TrimSuffix(options.URL, "/v3") + "/v1"

// Setup the CAPI client
cc, err := capiClient.NewClient(options)
if err != nil {
return err
}
mc.CAPIClient = cc

return nil
}

func (mc *MasterClient) ByID(resource *ntypes.Resource, respObject interface{}) error {
if _, ok := mc.ManagementClient.APIBaseClient.Types[resource.Type]; ok {
if strings.HasPrefix(resource.Type, "cluster.x-k8s.io") {
return mc.CAPIClient.ByID(resource.Type, resource.ID, &respObject)
} else if _, ok := mc.ManagementClient.APIBaseClient.Types[resource.Type]; ok {
return mc.ManagementClient.ByID(resource.Type, resource.ID, &respObject)
} else if _, ok := mc.ProjectClient.APIBaseClient.Types[resource.Type]; ok {
return mc.ProjectClient.ByID(resource.Type, resource.ID, &respObject)
Expand Down
13 changes: 13 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ func GetResourceType(c *cliclient.MasterClient, resource string) (string, error)
}
}
}
if c.CAPIClient != nil {
for key := range c.CAPIClient.APIBaseClient.Types {
lowerKey := strings.ToLower(key)
if strings.HasPrefix(lowerKey, "cluster.x-k8s.io") && lowerKey == strings.ToLower(resource) {
return key, nil
}
}
}
return "", fmt.Errorf("unknown resource type: %s", resource)
}

Expand All @@ -347,6 +355,11 @@ func Lookup(c *cliclient.MasterClient, name string, types ...string) (*ntypes.Re
}
var schemaClient clientbase.APIBaseClientInterface
// the schemaType dictates which client we need to use
if c.CAPIClient != nil {
if strings.HasPrefix(rt, "cluster.x-k8s.io") {
schemaClient = c.CAPIClient
}
}
if c.ManagementClient != nil {
if _, ok := c.ManagementClient.APIBaseClient.Types[rt]; ok {
schemaClient = c.ManagementClient
Expand Down
129 changes: 129 additions & 0 deletions cmd/machine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package cmd

import (
"fmt"

"github.com/rancher/cli/cliclient"
capiClient "github.com/rancher/rancher/pkg/client/generated/cluster/v1alpha4"
"github.com/urfave/cli"
)

type MachineData struct {
ID string
Machine capiClient.Machine
Name string
}

func MachineCommand() cli.Command {
return cli.Command{
Name: "machines",
Aliases: []string{"machine"},
Usage: "Operations on machines",
Action: defaultAction(machineLs),
Subcommands: []cli.Command{
{
Name: "ls",
Usage: "List machines",
Description: "\nLists all machines in the current cluster.",
ArgsUsage: "None",
Action: machineLs,
Flags: []cli.Flag{
cli.StringFlag{
Name: "format",
Usage: "'json', 'yaml' or Custom format: '{{.Machine.ID}} {{.Machine.Name}}'",
},
quietFlag,
},
},
},
}
}

func machineLs(ctx *cli.Context) error {
c, err := GetClient(ctx)
if err != nil {
return err
}

collection, err := getMachinesList(ctx, c)
if err != nil {
return err
}

writer := NewTableWriter([][]string{
{"ID", "ID"},
{"NAME", "Name"},
{"PHASE", "Machine.Status.Phase"},
}, ctx)

defer writer.Close()

for _, item := range collection.Data {
writer.Write(&MachineData{
ID: item.ID,
Machine: item,
Name: getMachineName(item),
})
}

return writer.Err()
}

func getMachinesList(
ctx *cli.Context,
c *cliclient.MasterClient,
) (*capiClient.MachineCollection, error) {
filter := defaultListOpts(ctx)
return c.CAPIClient.Machine.List(filter)
}

func getMachineByNodeName(
ctx *cli.Context,
c *cliclient.MasterClient,
nodeName string,
) (capiClient.Machine, error) {
machineCollection, err := getMachinesList(ctx, c)
if err != nil {
return capiClient.Machine{}, err
}

for _, machine := range machineCollection.Data {
if machine.Status.NodeRef != nil && machine.Status.NodeRef.Name == nodeName {
return machine, nil
}
}

return capiClient.Machine{}, fmt.Errorf("no machine found with associated to node [%s], run "+
"`rancher machines` to see available nodes", nodeName)
}

func getMachineByID(
ctx *cli.Context,
c *cliclient.MasterClient,
machineID string,
) (capiClient.Machine, error) {
machineCollection, err := getMachinesList(ctx, c)
if err != nil {
return capiClient.Machine{}, err
}

for _, machine := range machineCollection.Data {
if machine.ID == machineID {
return machine, nil
}
}

return capiClient.Machine{}, fmt.Errorf("no machine found with the ID [%s], run "+
"`rancher machines` to see available nodes", machineID)
}

func getMachineName(machine capiClient.Machine) string {
if machine.Name != "" {
return machine.Name
} else if machine.Status.NodeRef != nil {
return machine.Status.NodeRef.Name
} else if machine.InfrastructureRef != nil {
return machine.InfrastructureRef.Name
}
return machine.ID
}
101 changes: 74 additions & 27 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -78,25 +79,14 @@ func nodeSSH(ctx *cli.Context) error {
return err
}

resource, err := Lookup(c, nodeName, "node")
if err != nil {
return err
}

sshNode, err := getNodeByID(ctx, c, resource.ID)
sshNode, key, err := getNodeAndKey(ctx, c, nodeName)
if err != nil {
return err
}

if user == "" {
user = sshNode.SshUser
}

key, err := getSSHKey(c, sshNode)
if err != nil {
return err
}

ipAddress := sshNode.IPAddress
if ctx.Bool("external") {
ipAddress = sshNode.ExternalIPAddress
Expand All @@ -105,6 +95,40 @@ func nodeSSH(ctx *cli.Context) error {
return processExitCode(callSSH(key, ipAddress, user, args))
}

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

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

link := sshNode.Links["nodeConfig"]
if link == "" {
// Get the machine and use that instead.
machine, err := getMachineByNodeName(ctx, c, nodeName)
if err != nil {
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, key, nil
}

func callSSH(content []byte, ip string, user string, args []string) error {
dest := fmt.Sprintf("%s@%s", user, ip)

Expand Down Expand Up @@ -134,15 +158,14 @@ func callSSH(content []byte, ip string, user string, args []string) error {
return cmd.Run()
}

func getSSHKey(c *cliclient.MasterClient, node managementClient.Node) ([]byte, error) {
link, ok := node.Links["nodeConfig"]
if !ok {
return nil, fmt.Errorf("failed to find SSH key for %s", getNodeName(node))
func getSSHKey(c *cliclient.MasterClient, link, nodeName string) ([]byte, string, error) {
if link == "" {
return nil, "", fmt.Errorf("failed to find SSH key for %s", nodeName)
}

req, err := http.NewRequest("GET", link, nil)
if err != nil {
return nil, err
return nil, "", err
}
req.SetBasicAuth(c.UserConfig.AccessKey, c.UserConfig.SecretKey)
req.Header.Add("Accept-Encoding", "zip")
Expand All @@ -153,7 +176,7 @@ func getSSHKey(c *cliclient.MasterClient, node managementClient.Node) ([]byte, e
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(c.UserConfig.CACerts))
if !ok {
return []byte{}, err
return []byte{}, "", err
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
Expand All @@ -165,33 +188,57 @@ func getSSHKey(c *cliclient.MasterClient, node managementClient.Node) ([]byte, e

resp, err := client.Do(req)
if err != nil {
return nil, err
return nil, "", err
}
defer resp.Body.Close()

zipFiles, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
return nil, "", err
}

if resp.StatusCode != 200 {
return nil, fmt.Errorf("%s", zipFiles)
return nil, "", fmt.Errorf("%s", zipFiles)
}

zipReader, err := zip.NewReader(bytes.NewReader(zipFiles), resp.ContentLength)
if err != nil {
return nil, err
return nil, "", err
}

var sshKey []byte
var sshUser string
for _, file := range zipReader.File {
if path.Base(file.Name) == "id_rsa" {
r, err := file.Open()
sshKey, err = readFile(file)
if err != nil {
return nil, err
return nil, "", err
}
defer r.Close()
return ioutil.ReadAll(r)
} else if path.Base(file.Name) == "config.json" {
config, err := readFile(file)
if err != nil {
return nil, "", err
}

var data map[string]interface{}
err = json.Unmarshal(config, &data)
if err != nil {
return nil, "", err
}
sshUser, _ = data["SSHUser"].(string)
}
}
return nil, errors.New("can't find private key file")
if len(sshKey) == 0 {
return sshKey, "", errors.New("can't find private key file")
}
return sshKey, sshUser, nil
}

func readFile(file *zip.File) ([]byte, error) {
r, err := file.Open()
if err != nil {
return nil, err
}
defer r.Close()
return ioutil.ReadAll(r)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/rancher/norman v0.0.0-20200820172041-261460ee9088
github.com/rancher/rancher/pkg/client v0.0.0-20210622180446-e02a217721e8
github.com/rancher/rancher/pkg/client v0.0.0-20211102002137-7a574e4a17ae
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.5
Expand Down
Loading

0 comments on commit 7e32bb2

Please sign in to comment.