Skip to content
This repository has been archived by the owner on Dec 14, 2023. It is now read-only.

Commit

Permalink
adding ssh key support
Browse files Browse the repository at this point in the history
Signed-off-by: Moath Qasim <[email protected]>
  • Loading branch information
moadqassem committed Aug 17, 2020
1 parent 4fd487f commit 6fea3e2
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 13 deletions.
3 changes: 3 additions & 0 deletions pkg/kubevirt/apis/provider_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ type KubeVirtProviderSpec struct {
// DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.
// +optional
DNSPolicy string `json:"dnsPolicy,omitempty"`
// SSHKeys is an optional array of ssh public keys to deploy to VM (may already be included in UserData)
// +optional
SSHKeys []string `json:"sshKeys,omitempty"`
}
26 changes: 20 additions & 6 deletions pkg/kubevirt/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"fmt"
"strconv"
"strings"
"time"

api "github.com/gardener/machine-controller-manager-provider-kubevirt/pkg/kubevirt/apis"
Expand Down Expand Up @@ -60,8 +61,8 @@ func NewPluginSPIImpl(client ClientFunc) (*PluginSPIImpl, error) {

// CreateMachine creates a kubevirt virtual machine based on the passed provider spec with an associated data volume based on the
// DataVolumeTemplate. It also creates a secret where the userdata(cloud-init) are saved and mounted on the vm.
func (p PluginSPIImpl) CreateMachine(ctx context.Context, machineName string, providerSpec *api.KubeVirtProviderSpec, secrets *corev1.Secret) (providerID string, err error) {
c, err := p.client(secrets)
func (p PluginSPIImpl) CreateMachine(ctx context.Context, machineName string, providerSpec *api.KubeVirtProviderSpec, secret *corev1.Secret) (providerID string, err error) {
c, err := p.client(secret)
if err != nil {
return "", fmt.Errorf("failed to create kubevirt client: %v", err)
}
Expand Down Expand Up @@ -98,6 +99,19 @@ func (p PluginSPIImpl) CreateMachine(ctx context.Context, machineName string, pr
}
}

userData := string(secret.Data["userData"])
if len(providerSpec.SSHKeys) > 0 {
var userSSHKeys []string
for _, sshKey := range providerSpec.SSHKeys {
userSSHKeys = append(userSSHKeys, strings.TrimSpace(sshKey))
}

userData, err = addUserSSHKeysToUserData(userData, userSSHKeys)
if err != nil {
return "", fmt.Errorf("failed to add ssh keys to cloud-init: %v", err)
}
}

virtualMachine := &kubevirtv1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: machineName,
Expand Down Expand Up @@ -188,20 +202,20 @@ func (p PluginSPIImpl) CreateMachine(ctx context.Context, machineName string, pr
return "", fmt.Errorf("failed to create vmi: %v", err)
}

secret := &corev1.Secret{
userDataSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: userdataSecretName,
Namespace: virtualMachine.Namespace,
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(virtualMachine, kubevirtv1.VirtualMachineGroupVersionKind)},
},
Data: map[string][]byte{"userdata": []byte(secrets.Data["userData"])},
Data: map[string][]byte{"userdata": []byte(userData)},
}

if err := c.Create(ctx, secret); err != nil {
if err := c.Create(ctx, userDataSecret); err != nil {
return "", fmt.Errorf("failed to create secret for userdata: %v", err)
}

return p.machineProviderID(ctx, secrets, machineName, providerSpec.Namespace)
return p.machineProviderID(ctx, secret, machineName, providerSpec.Namespace)
}

// DeleteMachine delete the virtual machine which then delete tha cirtual machine instance and all associated resources such DataVolume.
Expand Down
33 changes: 26 additions & 7 deletions pkg/kubevirt/core/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@
package core

import (
"errors"
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func encodeProviderID(machineID string) string {
if machineID == "" {
return ""
}
return fmt.Sprintf("%s/%s", ProviderName, machineID)
}

// KubevirtClient creates kubevirt client based on the kubeconfig of the kubevirt cluster which is saved in the secret that
// is passed to it.
func KubevirtClient(secret *corev1.Secret) (client.Client, error) {
Expand All @@ -44,3 +39,27 @@ func KubevirtClient(secret *corev1.Secret) (client.Client, error) {

return client.New(config, client.Options{})
}

func encodeProviderID(machineID string) string {
if machineID == "" {
return ""
}
return fmt.Sprintf("%s/%s", ProviderName, machineID)
}

func addUserSSHKeysToUserData(userData string, sshKeys []string) (string, error) {
var userDataBuilder strings.Builder
if strings.Contains(userData, "ssh_authorized_keys:") {
return "", errors.New("userdata already contains key `ssh_authorized_keys`")
}

userDataBuilder.WriteString(userData)
userDataBuilder.WriteString("\nssh_authorized_keys:\n")
for _, key := range sshKeys {
userDataBuilder.WriteString("- ")
userDataBuilder.WriteString(key)
userDataBuilder.WriteString("\n")
}

return userDataBuilder.String(), nil
}
51 changes: 51 additions & 0 deletions pkg/kubevirt/core/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package core

import (
"strings"
"testing"
)

var (
testCases = []struct {
name string
userData string
sshKeys []string
expectedUserData string
expectedError bool
}{
{
name: "`ssh_authorized_keys` key already exists error",
userData: "#cloud-config\nchpasswd:\nexpire: false\npassword: pass\nuser: test\nssh_authorized_keys:\n- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDdOIhYmzCK5DSVLu",
sshKeys: []string{"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDdOIhYmzCK5DSVLu3b"},
expectedUserData: "",
expectedError: true,
},
{
name: "add user ssh key to userdata successfully",
userData: "#cloud-config\nchpasswd:\nexpire: false\npassword: pass\nuser: test",
sshKeys: []string{"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDdOIhYmzCK5DSVLu3b"},
expectedUserData: "#cloud-config\nchpasswd:\nexpire: false\npassword: pass\nuser: test\nssh_authorized_keys:\n- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDdOIhYmzCK5DSVLu3b",
expectedError: false,
},
}
)

func TestAddUserSSHKeysToUserData(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
u, err := addUserSSHKeysToUserData(testCase.userData, testCase.sshKeys)
if testCase.expectedError && err == nil {
t.Fatal("expected an error but got error: nil")
}

if err != nil && !testCase.expectedError {
t.Fatalf("unexpected error was encoutred: %v", err)
}

if strings.TrimSpace(testCase.expectedUserData) != strings.TrimSpace(u) {
t.Fatalf("expecting userdata: %v and got: %v", testCase.expectedUserData, u)
}

})
}
}

0 comments on commit 6fea3e2

Please sign in to comment.