Add workspace CLI (list/add/remove) and registry hash/unregister
This commit is contained in:
parent
2555a47d62
commit
3b4f54984e
3 changed files with 187 additions and 0 deletions
|
|
@ -29,6 +29,7 @@ func init() {
|
||||||
rootCmd.AddCommand(resetCmd)
|
rootCmd.AddCommand(resetCmd)
|
||||||
rootCmd.AddCommand(cleanCmd)
|
rootCmd.AddCommand(cleanCmd)
|
||||||
rootCmd.AddCommand(doctorCmd)
|
rootCmd.AddCommand(doctorCmd)
|
||||||
|
rootCmd.AddCommand(workspaceCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
||||||
146
cmd/qvm/workspace.go
Normal file
146
cmd/qvm/workspace.go
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"qvm/internal/config"
|
||||||
|
"qvm/internal/logging"
|
||||||
|
"qvm/internal/vm"
|
||||||
|
"qvm/internal/workspace"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var workspaceCmd = &cobra.Command{
|
||||||
|
Use: "workspace",
|
||||||
|
Short: "Manage workspace mounts",
|
||||||
|
Long: `Manage workspace mounts for the VM.
|
||||||
|
|
||||||
|
Workspaces are directories that get mounted into the VM at /workspace/{hash}-{dirname}.
|
||||||
|
Use subcommands to list, add, or remove workspace mounts.`,
|
||||||
|
Aliases: []string{"ws"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var workspaceListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List registered workspaces",
|
||||||
|
Long: `List all registered workspaces with their hash, host path, and guest path.`,
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
regResult := workspace.Load(config.WorkspacesFile)
|
||||||
|
if regResult.IsError() {
|
||||||
|
logging.Error(regResult.Error().Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
reg := regResult.MustGet()
|
||||||
|
|
||||||
|
workspaces := reg.List()
|
||||||
|
if len(workspaces) == 0 {
|
||||||
|
fmt.Println("No workspaces registered.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%-10s %-40s %s\n", "HASH", "HOST PATH", "GUEST PATH")
|
||||||
|
fmt.Printf("%-10s %-40s %s\n", "----", "---------", "----------")
|
||||||
|
for _, ws := range workspaces {
|
||||||
|
fmt.Printf("%-10s %-40s %s\n", ws.Hash, ws.HostPath, ws.GuestPath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var workspaceRemoveCmd = &cobra.Command{
|
||||||
|
Use: "remove <hash>",
|
||||||
|
Short: "Remove a workspace mount",
|
||||||
|
Long: `Remove a workspace from the registry by its hash.
|
||||||
|
|
||||||
|
The hash can be found using 'qvm workspace list' or 'qvm status'.
|
||||||
|
You can use a partial hash (minimum 3 characters) for convenience.
|
||||||
|
|
||||||
|
Note: If the VM is running, you'll need to restart it for the change to take effect.`,
|
||||||
|
Aliases: []string{"rm", "unmount"},
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
hash := args[0]
|
||||||
|
|
||||||
|
regResult := workspace.Load(config.WorkspacesFile)
|
||||||
|
if regResult.IsError() {
|
||||||
|
logging.Error(regResult.Error().Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
reg := regResult.MustGet()
|
||||||
|
|
||||||
|
wsOpt := reg.UnregisterByHash(hash)
|
||||||
|
if wsOpt.IsAbsent() {
|
||||||
|
logging.Error(fmt.Sprintf("No workspace found with hash: %s", hash))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ws := wsOpt.MustGet()
|
||||||
|
|
||||||
|
saveResult := reg.Save()
|
||||||
|
if saveResult.IsError() {
|
||||||
|
logging.Error(saveResult.Error().Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Removed workspace: %s -> %s\n", ws.Hash, ws.HostPath)
|
||||||
|
|
||||||
|
// Check if VM is running and warn user
|
||||||
|
statusResult := vm.Status()
|
||||||
|
if statusResult.IsOk() && statusResult.MustGet().Running {
|
||||||
|
logging.Warn("VM is running. Restart it for changes to take effect: qvm stop && qvm start")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var workspaceAddCmd = &cobra.Command{
|
||||||
|
Use: "add [path]",
|
||||||
|
Short: "Add a workspace mount",
|
||||||
|
Long: `Add a directory as a workspace mount.
|
||||||
|
|
||||||
|
If no path is provided, the current directory is used.
|
||||||
|
|
||||||
|
Note: If the VM is running, you'll need to restart it for the mount to be available.`,
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
path := "."
|
||||||
|
if len(args) > 0 {
|
||||||
|
path = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
regResult := workspace.Load(config.WorkspacesFile)
|
||||||
|
if regResult.IsError() {
|
||||||
|
logging.Error(regResult.Error().Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
reg := regResult.MustGet()
|
||||||
|
|
||||||
|
wsResult := reg.Register(path)
|
||||||
|
if wsResult.IsError() {
|
||||||
|
logging.Error(wsResult.Error().Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
ws := wsResult.MustGet()
|
||||||
|
|
||||||
|
saveResult := reg.Save()
|
||||||
|
if saveResult.IsError() {
|
||||||
|
logging.Error(saveResult.Error().Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Registered workspace: %s -> %s\n", ws.Hash, ws.HostPath)
|
||||||
|
fmt.Printf("Guest path: %s\n", ws.GuestPath)
|
||||||
|
|
||||||
|
// Check if VM is running and warn user
|
||||||
|
statusResult := vm.Status()
|
||||||
|
if statusResult.IsOk() && statusResult.MustGet().Running {
|
||||||
|
logging.Warn("VM is running. Restart it for the new mount to be available: qvm stop && qvm start")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
workspaceCmd.AddCommand(workspaceListCmd)
|
||||||
|
workspaceCmd.AddCommand(workspaceRemoveCmd)
|
||||||
|
workspaceCmd.AddCommand(workspaceAddCmd)
|
||||||
|
}
|
||||||
|
|
@ -138,3 +138,43 @@ func (r *Registry) Find(hostPath string) mo.Option[Workspace] {
|
||||||
}
|
}
|
||||||
return mo.None[Workspace]()
|
return mo.None[Workspace]()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindByHash looks up a workspace by its hash (or hash prefix)
|
||||||
|
func (r *Registry) FindByHash(hash string) mo.Option[Workspace] {
|
||||||
|
for _, ws := range r.workspaces {
|
||||||
|
if ws.Hash == hash {
|
||||||
|
return mo.Some(ws)
|
||||||
|
}
|
||||||
|
// Also support prefix matching for convenience
|
||||||
|
if len(hash) >= 3 && len(hash) < 8 && ws.Hash[:len(hash)] == hash {
|
||||||
|
return mo.Some(ws)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mo.None[Workspace]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister removes a workspace from the registry by host path
|
||||||
|
func (r *Registry) Unregister(hostPath string) bool {
|
||||||
|
absPath, err := filepath.Abs(hostPath)
|
||||||
|
if err != nil {
|
||||||
|
absPath = hostPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := r.workspaces[absPath]; exists {
|
||||||
|
delete(r.workspaces, absPath)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterByHash removes a workspace from the registry by its hash
|
||||||
|
func (r *Registry) UnregisterByHash(hash string) mo.Option[Workspace] {
|
||||||
|
wsOpt := r.FindByHash(hash)
|
||||||
|
if wsOpt.IsAbsent() {
|
||||||
|
return mo.None[Workspace]()
|
||||||
|
}
|
||||||
|
|
||||||
|
ws := wsOpt.MustGet()
|
||||||
|
delete(r.workspaces, ws.HostPath)
|
||||||
|
return mo.Some(ws)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue