Skip to content
Snippets Groups Projects
Commit f01c77e1 authored by Florent Gluck's avatar Florent Gluck
Browse files

Implemented 2 new routes which allow users to copy a local directory into a VM:

GET  /vms/importfiles
POST /vms/{id}/importfiles
parent 13443081
No related branches found
No related tags found
No related merge requests found
...@@ -356,7 +356,7 @@ Legend for the "Done" column: ...@@ -356,7 +356,7 @@ 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/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/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` | | 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` | | 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. | | Done | Route | Description | Method | Parameters | Req. user cap. | Op. | Req. VM access cap. |
|--- |--- |--- |--- |--- |--- |--- |--- | |--- |--- |--- |--- |--- |--- |--- |--- |
...@@ -368,8 +368,8 @@ Legend for the "Done" column: ...@@ -368,8 +368,8 @@ Legend for the "Done" column:
| x | `/vms/{id}/shutdown` | gracefully shutdown a VM | 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}` | 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}/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` | | x | `/vms/{id}/exportdir` | download a VM's dir | 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/{id}/importfiles` | upload files into a VM's dir | POST | files (.tar archive),dir | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` |
### Template management ### Template management
...@@ -414,6 +414,8 @@ Note that package `guestfish` is required, notably the `virt-copy-out` and `virt ...@@ -414,6 +414,8 @@ Note that package `guestfish` is required, notably the `virt-copy-out` and `virt
sudo chmod 0644 /boot/vmlinuz* 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 ### Prerequisites
The host machine on which **nexus-server** will be running requires the following software: The host machine on which **nexus-server** will be running requires the following software:
... ...
......
...@@ -34,13 +34,12 @@ func CheckVirtCopyIn() error { ...@@ -34,13 +34,12 @@ func CheckVirtCopyIn() error {
// into a directory on the VM's filesystem (vmDir). // into a directory on the VM's filesystem (vmDir).
// Note: both directories must exist, otherwise the command will fail. // Note: both directories must exist, otherwise the command will fail.
func CopyToVM(vmDiskFile, localDir, vmDir string) error { func CopyToVM(vmDiskFile, localDir, vmDir string) error {
log.Info("localDir = ",localDir)
cmd := exec.Command(virtcopyinBinary, "-a", vmDiskFile, localDir, vmDir) cmd := exec.Command(virtcopyinBinary, "-a", vmDiskFile, localDir, vmDir)
stdoutStderr, err := cmd.CombinedOutput() stdoutStderr, err := cmd.CombinedOutput()
if err != nil { if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr) output := fmt.Sprintf("[%s]", stdoutStderr)
msg := "Failed writing to \""+vmDir+"\" in qcow ("+vmDiskFile+"): "+output msg := "Failed writing to \""+vmDir+"\" in qcow ("+vmDiskFile+")"
log.Error(msg) log.Error(msg+": "+output)
return errors.New(msg) return errors.New(msg)
} }
return nil return nil
... ...
......
...@@ -37,9 +37,9 @@ func main() { ...@@ -37,9 +37,9 @@ func main() {
if err := exec.CheckVirtCopyOut(); err != nil { if err := exec.CheckVirtCopyOut(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := exec.CheckVirtCopyIn(); err != nil { // if err := exec.CheckVirtCopyIn(); err != nil {
log.Fatal(err) // log.Fatal(err)
} // }
usage := func() { usage := func() {
fmt.Println("Usage of "+path.Base(os.Args[0])+":") fmt.Println("Usage of "+path.Base(os.Args[0])+":")
... ...
......
...@@ -70,6 +70,7 @@ func (router *Router)Start(port int) { ...@@ -70,6 +70,7 @@ func (router *Router)Start(port int) {
vmsGroup.GET("/edit", router.vms.GetEditableVMs) vmsGroup.GET("/edit", router.vms.GetEditableVMs)
vmsGroup.GET("/editaccess", router.vms.GetEditableVMAccessVMs) vmsGroup.GET("/editaccess", router.vms.GetEditableVMAccessVMs)
vmsGroup.GET("/exportdir", router.vms.GetDirExportableVMs) vmsGroup.GET("/exportdir", router.vms.GetDirExportableVMs)
vmsGroup.GET("/importfiles", router.vms.GetFilesImportableVMs)
vmsGroup.POST("", router.vms.CreateVM) vmsGroup.POST("", router.vms.CreateVM)
vmsGroup.DELETE("/:id", router.vms.DeleteVMByID) vmsGroup.DELETE("/:id", router.vms.DeleteVMByID)
... ...
......
...@@ -135,6 +135,17 @@ func (r *RouterVMs)GetDirExportableVMs(c echo.Context) error { ...@@ -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. // Creates a VM.
// Requires this capability: // Requires this capability:
// User cap: CAP_VM_CREATE. // User cap: CAP_VM_CREATE.
...@@ -476,10 +487,11 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error { ...@@ -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>" // 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 { 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 { 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 // Retrieves the various client arguments.
vmDir := c.FormValue("dir") vmDir := c.FormValue("vmDir")
localDir := c.FormValue("localDir")
// Retrieve the tar archive (uploadedTarFile) // Retrieves the tar archive (uploadedTarFile).
tarFile, err := c.FormFile("file") tarFile, err := c.FormFile("file")
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
...@@ -503,19 +515,20 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { ...@@ -503,19 +515,20 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
defer dst.Close() defer dst.Close()
//defer os.Remove(uploadedTarFile) defer os.Remove(uploadedTarFile)
if _, err = io.Copy(dst, src); err != nil { if _, err = io.Copy(dst, src); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
// Extract the archive into tmpExtractDir // Extracts the archive into tmpExtractDir.
tmpExtractDir := filepath.Join(tmpDir, "extract_"+uuid.String()) tmpExtractDir := filepath.Join(tmpDir, "upload_"+uuid.String())
if err := os.Mkdir(tmpExtractDir, 0750); err != nil { if err := os.Mkdir(tmpExtractDir, 0750); err != nil {
msg := "ImportFilesToVM: failed creating dir: "+err.Error() msg := "ImportFilesToVM: failed creating dir: "+err.Error()
log.Error(msg) log.Error(msg)
return echo.NewHTTPError(http.StatusInternalServerError, msg) return echo.NewHTTPError(http.StatusInternalServerError, msg)
} }
defer os.RemoveAll(tmpExtractDir)
if err := utils.Untar(uploadedTarFile, tmpExtractDir); err != nil { if err := utils.Untar(uploadedTarFile, tmpExtractDir); err != nil {
msg := "ImportFilesToVM: failed unarchiving tarball: "+err.Error() msg := "ImportFilesToVM: failed unarchiving tarball: "+err.Error()
...@@ -525,12 +538,10 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { ...@@ -525,12 +538,10 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
// Copy the archive's files into the VM // Copy the archive's files into the VM
// IMPORTANT: check the VM is NOT running! // 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()) return echo.NewHTTPError(http.StatusBadRequest, err.Error())
} }
// Remove the files and the archive
return c.JSONPretty(http.StatusCreated, jsonMsg("OK"), " ") return c.JSONPretty(http.StatusCreated, jsonMsg("OK"), " ")
}) })
} }
\ No newline at end of file
...@@ -117,7 +117,7 @@ func IsPortAvailable(port int) bool { ...@@ -117,7 +117,7 @@ func IsPortAvailable(port int) bool {
// Creates the destTarball .tar archive of srcDir and all its files and subdirectories. // 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. // 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 { func TarDir(srcDir, destTarball string) error {
tarFile, err := os.Create(destTarball) tarFile, err := os.Create(destTarball)
if err != nil { if err != nil {
...@@ -130,7 +130,7 @@ func TarDir(srcDir, destTarball string) error { ...@@ -130,7 +130,7 @@ func TarDir(srcDir, destTarball string) error {
info, err := os.Stat(srcDir) info, err := os.Stat(srcDir)
if err != nil { if err != nil {
return nil return err
} }
var baseDir string var baseDir string
... ...
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment