Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
feat(CNV-48772): export from multiple sources
Browse files Browse the repository at this point in the history
Currently kubevirt-disk-uploader is
hardcoded to export only from a single
source "VirtualMachine" (kind).

Extend API to allow export from multiple sources:

- VirtualMachine
- VirtualMachineSnapshot
- PersistentVolumeClaim

Signed-off-by: Ben Oukhanov <[email protected]>
  • Loading branch information
codingben committed Oct 6, 2024
1 parent 9f03527 commit 4c54a4b
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 69 deletions.
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,39 @@ Extracts disk and uploads it to a container registry.

## About

A tool designed to automate the extraction of disks from KubeVirt Virtual Machines, package them into [Container Disks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk), and upload them to the Container Registry.
A tool designed to automate the extraction of disk, rebuild as [Container Disk](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk) and upload to the Container Registry.

## Workflow
## Usage

KubeVirt Disk Uploader -> Download VM Disk -> Build New Container Disk -> Push To Container Registry
These are the supported export sources:

## Installation
- VirtualMachine (VM)
- VirtualMachineSnapshot (VM Snapshot)
- PersistentVolumeClaim (PVC)

Data from the source can be exported only when it is not used.

**Prerequisites**

1. Ensure Virtual Machine (VM) is powered off. Data from VM can be exported only when it is not used.
2. Modify [kubevirt-disk-uploader](https://github.com/codingben/kubevirt-disk-uploader/blob/main/kubevirt-disk-uploader.yaml#L58) arguments (VM Namespace, VM Name, Volume Name, Image Destination, Enable or Disable System Preparation and Push Timeout).
3. Modify [kubevirt-disk-uploader-credentials](https://github.com/codingben/kubevirt-disk-uploader/blob/main/kubevirt-disk-uploader.yaml#L65-L74) of the external container registry (Username, Password and Hostname).
- Modify [kubevirt-disk-uploader](https://github.com/codingben/kubevirt-disk-uploader/blob/main/kubevirt-disk-uploader.yaml#L58) arguments.
- Modify [kubevirt-disk-uploader-credentials](https://github.com/codingben/kubevirt-disk-uploader/blob/main/kubevirt-disk-uploader.yaml#L65-L74) of the external container registry.

**Parameters**

- **Export Source Kind**: Specify the export source kind (`vm`, `vmsnapshot`, `pvc`).
- **Export Source Namespace**: The namespace of the export source.
- **Export Source Name**: The name of the export source.
- **Volume Name**: The name of the volume to export data.
- **Image Destination**: Destination of the image in container registry (`$HOST/$OWNER/$REPO:$TAG`).
- **Push Timeout**: The push timeout of container disk to registry.

Deploy `kubevirt-disk-uploader` within the same namespace as the Virtual Machine (VM):
Deploy `kubevirt-disk-uploader` within the same namespace of Export Source (VM, VM Snapshot, PVC):

```
kubectl apply -f kubevirt-disk-uploader.yaml -n $POD_NAMESPACE
```

**Note**: If both `POD_NAMESPACE` and `--vmnamespace` argument are set, `POD_NAMESPACE` will be used.
Setting of environment variable `POD_NAMESPACE` overrides the value in `--export-source-namespace` if passed.

## KubeVirt Documentation

Expand Down
64 changes: 40 additions & 24 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,53 @@ const (
kvExportTokenHeader string = "x-kubevirt-export-token"
)

func run(client kubecli.KubevirtClient, vmNamespace, vmName, volumeName, imageDestination string, pushTimeout int) error {
log.Printf("Creating a new Secret '%s/%s' object...", vmNamespace, vmName)
type RunOptions struct {
client kubecli.KubevirtClient
exportSourceKind string
exportSourceNamespace string
exportSourceName string
volumeName string
imageDestination string
pushTimeout int
}

func run(opts RunOptions) error {
client := opts.client
kind := opts.exportSourceKind
name := opts.exportSourceName
namespace := opts.exportSourceNamespace
volumeName := opts.volumeName
imageDestination := opts.imageDestination
imagePushTimeout := opts.pushTimeout

if err := secrets.CreateVirtualMachineExportSecret(client, vmNamespace, vmName); err != nil {
log.Printf("Creating a new Secret '%s/%s' object...", namespace, name)

if err := secrets.CreateVirtualMachineExportSecret(client, namespace, name); err != nil {
return err
}

log.Printf("Creating a new VirtualMachineExport '%s/%s' object...", vmNamespace, vmName)
log.Printf("Creating a new VirtualMachineExport '%s/%s' object...", namespace, name)

if err := vmexport.CreateVirtualMachineExport(client, vmNamespace, vmName); err != nil {
if err := vmexport.CreateVirtualMachineExport(client, kind, namespace, name); err != nil {
return err
}

log.Println("Waiting for VirtualMachineExport status to be ready...")

if err := vmexport.WaitUntilVirtualMachineExportReady(client, vmNamespace, vmName); err != nil {
if err := vmexport.WaitUntilVirtualMachineExportReady(client, namespace, name); err != nil {
return err
}

log.Println("Getting raw disk URL from the VirtualMachineExport object status...")

rawDiskUrl, err := vmexport.GetRawDiskUrlFromVolumes(client, vmNamespace, vmName, volumeName)
rawDiskUrl, err := vmexport.GetRawDiskUrlFromVolumes(client, namespace, name, volumeName)
if err != nil {
return err
}

log.Println("Creating TLS certificate file from the VirtualMachineExport object status...")

certificateData, err := certificate.GetCertificateFromVirtualMachineExport(client, vmNamespace, vmName)
certificateData, err := certificate.GetCertificateFromVirtualMachineExport(client, namespace, name)
if err != nil {
return err
}
Expand All @@ -59,7 +77,7 @@ func run(client kubecli.KubevirtClient, vmNamespace, vmName, volumeName, imageDe

log.Println("Getting export token from the Secret object...")

kvExportToken, err := secrets.GetTokenFromVirtualMachineExportSecret(client, vmNamespace, vmName)
kvExportToken, err := secrets.GetTokenFromVirtualMachineExportSecret(client, namespace, name)
if err != nil {
return err
}
Expand All @@ -79,7 +97,7 @@ func run(client kubecli.KubevirtClient, vmNamespace, vmName, volumeName, imageDe

log.Println("Pushing new container image to the container registry...")

if err := image.Push(containerImage, imageDestination, pushTimeout); err != nil {
if err := image.Push(containerImage, imageDestination, imagePushTimeout); err != nil {
return err
}

Expand All @@ -88,12 +106,7 @@ func run(client kubecli.KubevirtClient, vmNamespace, vmName, volumeName, imageDe
}

func main() {
var vmNamespace string
var vmName string
var volumeName string
var imageDestination string
var pushTimeout int

var opts RunOptions
var command = &cobra.Command{
Use: "kubevirt-disk-uploader",
Short: "Extracts disk and uploads it to a container registry",
Expand All @@ -102,24 +115,27 @@ func main() {
if err != nil {
log.Panicln(err)
}
opts.client = client

namespace := os.Getenv("POD_NAMESPACE")
if namespace != "" {
vmNamespace = namespace
opts.exportSourceNamespace = namespace
}

if err := run(client, namespace, vmName, volumeName, imageDestination, pushTimeout); err != nil {
if err := run(opts); err != nil {
log.Panicln(err)
}
},
}

command.Flags().StringVar(&vmNamespace, "vmnamespace", "", "namespace of the virtual machine")
command.Flags().StringVar(&vmName, "vmname", "", "name of the virtual machine")
command.Flags().StringVar(&volumeName, "volumename", "", "volume name of the virtual machine")
command.Flags().StringVar(&imageDestination, "imagedestination", "", "destination of the image in container registry")
command.Flags().IntVar(&pushTimeout, "pushtimeout", 60, "containerdisk push timeout in minutes")
command.MarkFlagRequired("vmname")
command.Flags().StringVar(&opts.exportSourceKind, "export-source-kind", "", "specify the export source kind (vm, vmsnapshot, pvc)")
command.Flags().StringVar(&opts.exportSourceNamespace, "export-source-namespace", "", "namespace of the export source")
command.Flags().StringVar(&opts.exportSourceName, "export-source-name", "", "name of the export source")
command.Flags().StringVar(&opts.volumeName, "volumename", "", "name of the volume (if source kind is 'pvc', then volume name is equal to source name)")
command.Flags().StringVar(&opts.imageDestination, "imagedestination", "", "destination of the image in container registry")
command.Flags().IntVar(&opts.pushTimeout, "pushtimeout", 60, "push timeout of container disk to registry")
command.MarkFlagRequired("export-source-kind")
command.MarkFlagRequired("export-source-name")
command.MarkFlagRequired("volumename")
command.MarkFlagRequired("imagedestination")

Expand Down
36 changes: 24 additions & 12 deletions examples/kubevirt-disk-uploader-tekton.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,14 @@ metadata:
name: kubevirt-disk-uploader-task
spec:
params:
- name: POD_NAME
description: The name of the virtual machine
- name: EXPORT_SOURCE_KIND
description: The name of the export source kind
type: string
- name: EXPORT_SOURCE_NAME
description: The name of the export source
type: string
- name: VOLUME_NAME
description: The volume name of the virtual machine
description: The volume name (If source kind is PVC, then volume name is equal to source name)
type: string
- name: IMAGE_DESTINATION
description: Destination of the image in container registry
Expand Down Expand Up @@ -192,8 +195,10 @@ spec:
fieldPath: metadata.name
command: ["/usr/local/bin/kubevirt-disk-uploader"]
args:
- "--vmname"
- $(params.POD_NAME)
- "--export-source-kind"
- $(params.EXPORT_SOURCE_KIND)
- "--export-source-name"
- $(params.EXPORT_SOURCE_NAME)
- "--volumename"
- $(params.VOLUME_NAME)
- "--imagedestination"
Expand All @@ -212,17 +217,20 @@ metadata:
name: kubevirt-disk-uploader-pipeline
spec:
params:
- name: POD_NAME
description: "Name of the virtual machine"
- name: EXPORT_SOURCE_KIND
description: "Kind of the export source"
type: string
- name: EXPORT_SOURCE_NAME
description: "Name of the export source"
type: string
- name: VOLUME_NAME
description: "Volume name of the virtual machine"
description: "Volume name (If source kind is PVC, then volume name is equal to source name)"
type: string
- name: IMAGE_DESTINATION
description: "Destination of the image in container registry"
type: string
- name: PUSH_TIMEOUT
description: "ContainerDisk push timeout in minutes"
description: "Push timeout of container disk to registry"
type: string
tasks:
- name: deploy-example-vm
Expand All @@ -234,8 +242,10 @@ spec:
runAfter:
- deploy-example-vm
params:
- name: POD_NAME
value: "$(params.POD_NAME)"
- name: EXPORT_SOURCE_KIND
value: "$(params.EXPORT_SOURCE_KIND)"
- name: EXPORT_SOURCE_NAME
value: "$(params.EXPORT_SOURCE_NAME)"
- name: VOLUME_NAME
value: "$(params.VOLUME_NAME)"
- name: IMAGE_DESTINATION
Expand All @@ -256,7 +266,9 @@ spec:
pipelineRef:
name: kubevirt-disk-uploader-pipeline
params:
- name: POD_NAME
- name: EXPORT_SOURCE_KIND
value: vm
- name: EXPORT_SOURCE_NAME
value: example-vm-tekton
- name: VOLUME_NAME
value: example-dv-tekton
Expand Down
2 changes: 1 addition & 1 deletion kubevirt-disk-uploader.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ spec:
fieldRef:
fieldPath: metadata.name
command: ["/usr/local/bin/kubevirt-disk-uploader"]
# args: ["--vmname", "example-vm", "--volumename", "example-dv", "--imagedestination", "quay.io/boukhano/example-vm-exported:latest", "--pushtimeout", "120"]
# args: ["--export-source-kind", "vm", "--export-source-name", "example-vm", "--volumename", "example-dv", "--imagedestination", "quay.io/boukhano/example-vm-exported:latest", "--pushtimeout", "120"]
resources:
requests:
memory: 3Gi
Expand Down
4 changes: 2 additions & 2 deletions pkg/certificate/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
kubecli "kubevirt.io/client-go/kubecli"
)

func GetCertificateFromVirtualMachineExport(client kubecli.KubevirtClient, vmNamespace, vmName string) (string, error) {
vmExport, err := client.VirtualMachineExport(vmNamespace).Get(context.Background(), vmName, metav1.GetOptions{})
func GetCertificateFromVirtualMachineExport(client kubecli.KubevirtClient, namespace, name string) (string, error) {
vmExport, err := client.VirtualMachineExport(namespace).Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
return "", err
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
kubecli "kubevirt.io/client-go/kubecli"
)

func CreateVirtualMachineExportSecret(client kubecli.KubevirtClient, vmNamespace, vmName string) error {
func CreateVirtualMachineExportSecret(client kubecli.KubevirtClient, namespace, name string) error {
length := 20
token, err := GenerateSecureRandomString(length)
if err != nil {
Expand All @@ -23,8 +23,8 @@ func CreateVirtualMachineExportSecret(client kubecli.KubevirtClient, vmNamespace

v1Secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: vmName,
Namespace: vmNamespace,
Name: name,
Namespace: namespace,
},
StringData: map[string]string{
"token": token,
Expand All @@ -35,19 +35,19 @@ func CreateVirtualMachineExportSecret(client kubecli.KubevirtClient, vmNamespace
return err
}

_, err = client.CoreV1().Secrets(vmNamespace).Create(context.Background(), v1Secret, metav1.CreateOptions{})
_, err = client.CoreV1().Secrets(namespace).Create(context.Background(), v1Secret, metav1.CreateOptions{})
return err
}

func GetTokenFromVirtualMachineExportSecret(client kubecli.KubevirtClient, vmNamespace, vmName string) (string, error) {
secret, err := client.CoreV1().Secrets(vmNamespace).Get(context.Background(), vmName, metav1.GetOptions{})
func GetTokenFromVirtualMachineExportSecret(client kubecli.KubevirtClient, namespace, name string) (string, error) {
secret, err := client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
return "", err
}

data := secret.Data["token"]
if len(data) == 0 {
return "", fmt.Errorf("failed to get export token from '%s/%s'", vmNamespace, vmName)
return "", fmt.Errorf("failed to get export token from '%s/%s'", namespace, name)
}
return string(data), nil
}
Expand Down
Loading

0 comments on commit 4c54a4b

Please sign in to comment.