Skip to content

Commit

Permalink
Merge pull request #63 from sp-yduck/feature/image-format
Browse files Browse the repository at this point in the history
Feature/image format
  • Loading branch information
sp-yduck authored Aug 10, 2023
2 parents e6bf195 + 86e7a06 commit c5d6211
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,21 @@ kubectl delete cluster cappx-test

- No need to prepare vm templates. You can specify any vm image in `ProxmoxMachine.Spec.Image`. CAPPX bootstrap your vm from scratch.

- Supports qcow2 image format. CAPPX uses VNC websocket for downloading/installing node images so it can support raw image format not ISO (Proxmox API can only support ISO)
- Supports mutiple image format. CAPPX uses VNC websocket for downloading/installing node images so it can support multiple image format not only ISO (Proxmox API can only support ISO)

- Supports custom cloud-config (user data). CAPPX uses VNC websockert for bootstrapping nodes so it can applies custom cloud-config that can not be achieved by only Proxmox API.

### Node Images

CAPPX is compatible with `qcow2` image. You can build your own node image and use it for `ProxmoxMachine`.
CAPPX is compatible with `iso`, `qcow2`, `qed`, `raw`, `vdi`, `vpc`, `vmdk` format of image. You can build your own node image and use it for `ProxmoxMachine`.

CAPPX relies on a few prerequisites which have to be already installed in the used operating system images, e.g. a container runtime, kubelet, kubeadm,.. .

To build your custom node image, you can use [kubernetes-sigs/image-builder](https://github.com/kubernetes-sigs/image-builder) project.

Also there are some available out-of-box images published other communities such as [Metal3](https://github.com/metal3-io). For example https://artifactory.nordix.org/ui/native/metal3/images/. Example MD can be found [metal3-ubuntu2204-k8s127.yaml](examples/machine_deployment/metal3-ubuntu2204-k8s127.yaml).

If it isn't possible to pre-install those prerequisites in the image, you can always deploy and execute some custom scripts through the `ProxmoxMachine.spec.cloudInit` or `KubeadmConfig` . Example MD can be found [ubuntu2204.yaml](examples/machine_deployment/ubuntu2204.yaml).
If it isn't possible to pre-install those prerequisites in the image, you can always deploy and execute some custom scripts through the `ProxmoxMachine.spec.cloudInit` or `KubeadmConfig`. Example MD can be found [ubuntu2204.yaml](examples/machine_deployment/ubuntu2204.yaml).

## Compatibility

Expand Down
1 change: 1 addition & 0 deletions api/v1beta1/proxmoxmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type ProxmoxMachineSpec struct {
// +optional
Node string `json:"node,omitempty"`

// +kubebuilder:validation:Minimum:=0
// VMID is proxmox qemu's id
// +optional
VMID *int `json:"vmID,omitempty"`
Expand Down
13 changes: 8 additions & 5 deletions api/v1beta1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ type ServerRef struct {
// endpoint is the address of the Proxmox-VE REST API endpoint.
Endpoint string `json:"endpoint"`

// to do : login type should be an option
// user&pass or token

// to do : client options like insecure tls verify

// SecretRef is a reference for secret which contains proxmox login secrets
// and ssh configs for proxmox nodes
SecretRef *ObjectReference `json:"secretRef"`
}

Expand All @@ -36,12 +32,18 @@ type ObjectReference struct {

// Image is the image to be provisioned
type Image struct {
// +kubebuilder:validation:Pattern:=.*\.(iso|img|qcow2|qed|raw|vdi|vpc|vmdk)$
// URL is a location of an image to deploy.
// supported formats are iso/qcow2/qed/raw/vdi/vpc/vmdk.
URL string `json:"url"`

// Checksum
// Always better to specify checksum otherwise cappx will download
// same image for every time. If checksum is specified, cappx will try
// to avoid downloading existing image.
Checksum string `json:"checksum,omitempty"`

// +kubebuilder:validation:Enum:=sha256;sha256sum;md5;md5sum
// ChecksumType
ChecksumType *string `json:"checksumType,omitempty"`
}
Expand Down Expand Up @@ -100,7 +102,8 @@ type Network struct {
SearchDomain string `json:"searchDomain,omitempty"`
}

// IPConfig defines IP addresses and gateways for corresponding interface
// IPConfig defines IP addresses and gateways for corresponding interface.
// it defaults to using dhcp on IPv4 if neither IP nor IP6 is specified.
type IPConfig struct {
// IPv4 with CIDR
IP string `json:"ip,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions cloud/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type MachineSetter interface {
// SetFailureReason(v capierrors.MachineStatusError)
// SetAnnotation(key, value string)
// SetAddresses(addressList []corev1.NodeAddress)
PatchObject() error
}

// Machine is an interface which can get and set machine information.
Expand Down
53 changes: 37 additions & 16 deletions cloud/services/compute/instance/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/pkg/errors"
"github.com/sp-yduck/proxmox-go/proxmox"
"sigs.k8s.io/controller-runtime/pkg/log"

infrav1 "github.com/sp-yduck/cluster-api-provider-proxmox/api/v1beta1"
)

const (
Expand Down Expand Up @@ -40,9 +42,7 @@ func (s *Service) setCloudImage(ctx context.Context) error {
log.Info("setting cloud image")

image := s.scope.GetImage()
url := image.URL
fileName := path.Base(url)
rawImageFilePath := fmt.Sprintf("%s/%s", rawImageDirPath, fileName)
rawImageFilePath := rawImageFilePath(image)

// workaround
// API does not support something equivalent of "qm importdisk"
Expand All @@ -51,27 +51,24 @@ func (s *Service) setCloudImage(ctx context.Context) error {
return errors.Errorf("failed to create vnc client: %v", err)
}
defer vnc.Close()
out, _, err := vnc.Exec(ctx, fmt.Sprintf("wget %s --directory-prefix %s -nc", url, rawImageDirPath))
if err != nil {
return errors.Errorf("failed to download image: %s : %v", out, err)
}

// checksum
if image.Checksum != "" {
cscmd, err := findValidChecksumCommand(*image.ChecksumType)
// download image
ok, _ := isChecksumOK(vnc, image, rawImageFilePath)
if !ok { // if checksum is ok, it means the image is already there. skip installing
log.Info("downloading node image. this will take few mins.")
out, _, err := vnc.Exec(ctx, fmt.Sprintf("wget %s -O %s", image.URL, rawImageFilePath))
if err != nil {
return err
return errors.Errorf("failed to download image: %s : %v", out, err)
}
cmd := fmt.Sprintf("echo -n '%s %s' | %s --check -", image.Checksum, rawImageFilePath, cscmd)
out, _, err = vnc.Exec(context.TODO(), cmd)
if err != nil {
return errors.Errorf("failed to confirm checksum: %s : %v", out, err)
if _, err = isChecksumOK(vnc, image, rawImageFilePath); err != nil {
return errors.Errorf("failed to confirm checksum: %v", err)
}
}

// convert downloaded image to raw format and set it to storage
vmid := s.scope.GetVMID()
destPath := fmt.Sprintf("%s/images/%d/vm-%d-disk-0.raw", s.scope.GetStorage().Path, *vmid, *vmid)
out, _, err = vnc.Exec(context.TODO(), fmt.Sprintf("/usr/bin/qemu-img convert -O raw %s %s", rawImageFilePath, destPath))
out, _, err := vnc.Exec(context.TODO(), fmt.Sprintf("/usr/bin/qemu-img convert -O raw %s %s", rawImageFilePath, destPath))
if err != nil {
return errors.Errorf("failed to convert iamge : %s : %v", out, err)
}
Expand All @@ -89,3 +86,27 @@ func findValidChecksumCommand(csType string) (string, error) {
return "", errors.Errorf("checksum type %s is not supported", csType)
}
}

func isChecksumOK(client *proxmox.VNCWebSocketClient, image infrav1.Image, path string) (bool, error) {
if image.Checksum != "" {
cscmd, err := findValidChecksumCommand(*image.ChecksumType)
if err != nil {
return false, err
}
cmd := fmt.Sprintf("echo -n '%s %s' | %s --check -", image.Checksum, path, cscmd)
out, _, err := client.Exec(context.TODO(), cmd)
if err != nil {
return false, errors.Errorf("failed to confirm checksum: %s : %v", out, err)
}
return true, nil
}
return false, nil
}

func rawImageFilePath(image infrav1.Image) string {
fileName := path.Base(image.URL)
if image.Checksum != "" {
fileName = image.Checksum + "." + fileName
}
return fmt.Sprintf("%s/%s", rawImageDirPath, fileName)
}
3 changes: 3 additions & 0 deletions cloud/services/compute/instance/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func (s *Service) createQEMU(ctx context.Context, nodeName string, vmid *int) (*
}
vmid = &nextid
s.scope.SetVMID(*vmid)
if err := s.scope.PatchObject(); err != nil {
return nil, err
}
}

vmoption := s.generateVMOptions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ spec:
type: string
secretRef:
description: SecretRef is a reference for secret which contains
proxmox login secrets and ssh configs for proxmox nodes
proxmox login secrets
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,22 @@ spec:
description: Image is the image to be provisioned
properties:
checksum:
description: Checksum
description: Checksum Always better to specify checksum otherwise
cappx will download same image for every time. If checksum is
specified, cappx will try to avoid downloading existing image.
type: string
checksumType:
description: ChecksumType
enum:
- sha256
- sha256sum
- md5
- md5sum
type: string
url:
description: URL is a location of an image to deploy.
description: URL is a location of an image to deploy. supported
formats are iso/qcow2/qed/raw/vdi/vpc/vmdk.
pattern: .*\.(iso|img|qcow2|qed|raw|vdi|vpc|vmdk)$
type: string
required:
- url
Expand Down Expand Up @@ -320,6 +329,7 @@ spec:
type: string
vmID:
description: VMID is proxmox qemu's id
minimum: 0
type: integer
required:
- image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,23 @@ spec:
description: Image is the image to be provisioned
properties:
checksum:
description: Checksum
description: Checksum Always better to specify checksum
otherwise cappx will download same image for every time.
If checksum is specified, cappx will try to avoid downloading
existing image.
type: string
checksumType:
description: ChecksumType
enum:
- sha256
- sha256sum
- md5
- md5sum
type: string
url:
description: URL is a location of an image to deploy.
supported formats are iso/qcow2/qed/raw/vdi/vpc/vmdk.
pattern: .*\.(iso|img|qcow2|qed|raw|vdi|vpc|vmdk)$
type: string
required:
- url
Expand Down Expand Up @@ -351,6 +361,7 @@ spec:
type: string
vmID:
description: VMID is proxmox qemu's id
minimum: 0
type: integer
required:
- image
Expand Down
2 changes: 2 additions & 0 deletions controllers/proxmoxcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func (r *ProxmoxClusterReconciler) reconcile(ctx context.Context, clusterScope *
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}

log.Info("Reconciled ProxmoxCluster")
record.Eventf(clusterScope.ProxmoxCluster, "ProxmoxClusterReconcile", "Got control-plane endpoint - %s", controlPlaneEndpoint.Host)
clusterScope.SetReady()
record.Event(clusterScope.ProxmoxCluster, "ProxmoxClusterReconcile", "Reconciled")
Expand All @@ -155,6 +156,7 @@ func (r *ProxmoxClusterReconciler) reconcileDelete(ctx context.Context, clusterS
}
}

log.Info("Reconciled ProxmoxCluster")
controllerutil.RemoveFinalizer(clusterScope.ProxmoxCluster, infrav1.ClusterFinalizer)
record.Event(clusterScope.ProxmoxCluster, "ProxmoxClusterReconcile", "Reconciled")
return ctrl.Result{}, nil
Expand Down
1 change: 1 addition & 0 deletions controllers/proxmoxmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func (r *ProxmoxMachineReconciler) reconcileDelete(ctx context.Context, machineS

controllerutil.RemoveFinalizer(machineScope.ProxmoxMachine, infrav1.MachineFinalizer)
record.Event(machineScope.ProxmoxMachine, "ProxmoxMachineReconcile", "Reconciled")
log.Info("Reconciled ProxmoxMachine")
return ctrl.Result{}, nil
}

Expand Down

0 comments on commit c5d6211

Please sign in to comment.