From f01c77e10321dbaf45359da6087e029309880be3 Mon Sep 17 00:00:00 2001 From: Florent Gluck <florent.gluck@hesge.ch> Date: Thu, 1 Sep 2022 10:32:11 +0200 Subject: [PATCH] Implemented 2 new routes which allow users to copy a local directory into a VM: GET /vms/importfiles POST /vms/{id}/importfiles --- README.md | 30 ++++++++++++++++-------------- src/exec/VirtCopyIn.go | 5 ++--- src/nexus-server.go | 6 +++--- src/router/router.go | 1 + src/router/routerVMs.go | 29 ++++++++++++++++++++--------- src/utils/utils.go | 4 ++-- 6 files changed, 44 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 5a66712..bc76ef9 100644 --- a/README.md +++ b/README.md @@ -356,20 +356,20 @@ Legend for the "Done" column: | x | `/vms/editaccess` | returns VMs that can have their access changed | GET | | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | | x | `/vms/del` | returns VMs that can be deleted | GET | | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | | x | `/vms/exportdir` | returns VMs that can have a dir downloaded | GET | | `VM_READFS_ANY` | OR | `VM_READFS` | -| | `/vms/importdir` | returns VMs allowing a fs upload | GET | | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | - -| Done | Route | Description | Method | Parameters | Req. user cap. | Op. | Req. VM access cap. | -|--- |--- |--- |--- |--- |--- |--- |--- | -| x | `/vms` | create a VM | POST | name,cpus,ram,nic,template | `VM_CREATE` | | | -| x | `/vms/{id}` | delete a VM | DELETE | | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | -| x | `/vms/{id}` | edit a VM | PUT | name,cpus,ram,nic | `VM_EDIT_ANY` | OR | `VM_EDIT` | -| x | `/vms/{id}/start` | start a VM | PUT | | `VM_START_ANY` | OR | `VM_START` | -| x | `/vms/{id}/stop` | stop a VM (by force) | PUT | | `VM_STOP_ANY` | OR | `VM_STOP` | -| x | `/vms/{id}/shutdown` | gracefully shutdown a VM | PUT | | `VM_STOP_ANY` | OR | `VM_STOP` | -| x | `/vms/{id}/access/{email}` | set VM access for a user | PUT | caps | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | -| x | `/vms/{id}/access/{email}` | del VM access for a user | DELETE | | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | -| x | `/vms/{id}/exportdir` | download a VM's directory | GET | dir | `VM_READFS_ANY` | OR | `VM_READFS` | -| | `/vms/{id}/importfiles` | upload files in a VM's dir | POST | files (.tar archive),dir | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | +| x | `/vms/importfiles` | returns VMs allowing files upload | GET | | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | + +| Done | Route | Description | Method | Parameters | Req. user cap. | Op. | Req. VM access cap. | +|--- |--- |--- |--- |--- |--- |--- |--- | +| x | `/vms` | create a VM | POST | name,cpus,ram,nic,template | `VM_CREATE` | | | +| x | `/vms/{id}` | delete a VM | DELETE | | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | +| x | `/vms/{id}` | edit a VM | PUT | name,cpus,ram,nic | `VM_EDIT_ANY` | OR | `VM_EDIT` | +| x | `/vms/{id}/start` | start a VM | PUT | | `VM_START_ANY` | OR | `VM_START` | +| x | `/vms/{id}/stop` | stop a VM (by force) | PUT | | `VM_STOP_ANY` | OR | `VM_STOP` | +| x | `/vms/{id}/shutdown` | gracefully shutdown a VM | PUT | | `VM_STOP_ANY` | OR | `VM_STOP` | +| x | `/vms/{id}/access/{email}` | set VM access for a user | PUT | caps | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | +| x | `/vms/{id}/access/{email}` | del VM access for a user | DELETE | | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | +| x | `/vms/{id}/exportdir` | download a VM's dir | GET | dir | `VM_READFS_ANY` | OR | `VM_READFS` | +| x | `/vms/{id}/importfiles` | upload files into a VM's dir | POST | files (.tar archive),dir | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | ### Template management @@ -414,6 +414,8 @@ Note that package `guestfish` is required, notably the `virt-copy-out` and `virt sudo chmod 0644 /boot/vmlinuz* ``` +**BEWARE**: whenever a new kernel is installed, its permission **must be changed again**, otherwise importing or exporting files from VMs won't work! + ### Prerequisites The host machine on which **nexus-server** will be running requires the following software: diff --git a/src/exec/VirtCopyIn.go b/src/exec/VirtCopyIn.go index 35c1fb4..c10dd7c 100644 --- a/src/exec/VirtCopyIn.go +++ b/src/exec/VirtCopyIn.go @@ -34,13 +34,12 @@ func CheckVirtCopyIn() error { // into a directory on the VM's filesystem (vmDir). // Note: both directories must exist, otherwise the command will fail. func CopyToVM(vmDiskFile, localDir, vmDir string) error { - log.Info("localDir = ",localDir) cmd := exec.Command(virtcopyinBinary, "-a", vmDiskFile, localDir, vmDir) stdoutStderr, err := cmd.CombinedOutput() if err != nil { output := fmt.Sprintf("[%s]", stdoutStderr) - msg := "Failed writing to \""+vmDir+"\" in qcow ("+vmDiskFile+"): "+output - log.Error(msg) + msg := "Failed writing to \""+vmDir+"\" in qcow ("+vmDiskFile+")" + log.Error(msg+": "+output) return errors.New(msg) } return nil diff --git a/src/nexus-server.go b/src/nexus-server.go index 7634593..e40c13a 100644 --- a/src/nexus-server.go +++ b/src/nexus-server.go @@ -37,9 +37,9 @@ func main() { if err := exec.CheckVirtCopyOut(); err != nil { log.Fatal(err) } - if err := exec.CheckVirtCopyIn(); err != nil { - log.Fatal(err) - } + // if err := exec.CheckVirtCopyIn(); err != nil { + // log.Fatal(err) + // } usage := func() { fmt.Println("Usage of "+path.Base(os.Args[0])+":") diff --git a/src/router/router.go b/src/router/router.go index edf6844..1682d9f 100644 --- a/src/router/router.go +++ b/src/router/router.go @@ -70,6 +70,7 @@ func (router *Router)Start(port int) { vmsGroup.GET("/edit", router.vms.GetEditableVMs) vmsGroup.GET("/editaccess", router.vms.GetEditableVMAccessVMs) vmsGroup.GET("/exportdir", router.vms.GetDirExportableVMs) + vmsGroup.GET("/importfiles", router.vms.GetFilesImportableVMs) vmsGroup.POST("", router.vms.CreateVM) vmsGroup.DELETE("/:id", router.vms.DeleteVMByID) diff --git a/src/router/routerVMs.go b/src/router/routerVMs.go index f8fa7e4..8cb4557 100644 --- a/src/router/routerVMs.go +++ b/src/router/routerVMs.go @@ -135,6 +135,17 @@ func (r *RouterVMs)GetDirExportableVMs(c echo.Context) error { }) } +// Returns VMs that can have files imported (i.e. copied) into their filesystem. +// Requires either capability: +// User cap: VM_WRITEFS_ANY: returns all VMs. +// VM access cap: VM_WRITEFS: returns all VMs with this cap for the logged user. +// curl --cacert ca.pem -X GET https://localhost:1077/vms/importfiles -H "Authorization: Bearer <AccessToken>" +func (r *RouterVMs)GetFilesImportableVMs(c echo.Context) error { + return r.performVMsList(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(vm vms.VM) bool { + return true + }) +} + // Creates a VM. // Requires this capability: // User cap: CAP_VM_CREATE. @@ -476,10 +487,11 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error { // curl --cacert ca.pem -X POST https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/importfiles -H 'Content-Type: multipart/form-data' -F dir="/home/nexus" -F file=@files.tar -H "Authorization: Bearer <AccessToken>" func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { return r.performVMAction(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(c echo.Context, vm *vms.VM) error { - // Retrieve the "dir" argument - vmDir := c.FormValue("dir") + // Retrieves the various client arguments. + vmDir := c.FormValue("vmDir") + localDir := c.FormValue("localDir") - // Retrieve the tar archive (uploadedTarFile) + // Retrieves the tar archive (uploadedTarFile). tarFile, err := c.FormFile("file") if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -503,19 +515,20 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } defer dst.Close() - //defer os.Remove(uploadedTarFile) + defer os.Remove(uploadedTarFile) if _, err = io.Copy(dst, src); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - // Extract the archive into tmpExtractDir - tmpExtractDir := filepath.Join(tmpDir, "extract_"+uuid.String()) + // Extracts the archive into tmpExtractDir. + tmpExtractDir := filepath.Join(tmpDir, "upload_"+uuid.String()) if err := os.Mkdir(tmpExtractDir, 0750); err != nil { msg := "ImportFilesToVM: failed creating dir: "+err.Error() log.Error(msg) return echo.NewHTTPError(http.StatusInternalServerError, msg) } + defer os.RemoveAll(tmpExtractDir) if err := utils.Untar(uploadedTarFile, tmpExtractDir); err != nil { msg := "ImportFilesToVM: failed unarchiving tarball: "+err.Error() @@ -525,11 +538,9 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { // Copy the archive's files into the VM // IMPORTANT: check the VM is NOT running! - if err = r.vms.ImportFilesToVM(vm, tmpExtractDir, vmDir); err != nil { + if err = r.vms.ImportFilesToVM(vm, filepath.Join(tmpExtractDir, localDir), vmDir); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } - - // Remove the files and the archive return c.JSONPretty(http.StatusCreated, jsonMsg("OK"), " ") }) diff --git a/src/utils/utils.go b/src/utils/utils.go index 24392a5..32bf340 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -117,7 +117,7 @@ func IsPortAvailable(port int) bool { // Creates the destTarball .tar archive of srcDir and all its files and subdirectories. // This implementation fails if a symlink points to an absolute path that doesn't exist on the host. -// Source code taken from: https://golangdocs.com/tar-gzip-in-golang +// Source code slightly modified from: https://golangdocs.com/tar-gzip-in-golang func TarDir(srcDir, destTarball string) error { tarFile, err := os.Create(destTarball) if err != nil { @@ -130,7 +130,7 @@ func TarDir(srcDir, destTarball string) error { info, err := os.Stat(srcDir) if err != nil { - return nil + return err } var baseDir string -- GitLab