diff --git a/cmd/root.go b/cmd/root.go index ed13cff..e35d039 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,7 +13,7 @@ import ( ) const ( - gVersion = "0.24" + gVersion = "0.25" ) var ( @@ -30,7 +30,7 @@ var ( gHostVersion float64 gTimeZone string - + gSnapshotName string rootCmd = & cobra.Command { Use: "gocage", @@ -144,7 +144,30 @@ You can specify multiple jails.`, // Load inventory ListJails(args, false) ListJailsSnapshots(args) - cleanAfterRun() + }, + } + + snapshotCreateCmd = &cobra.Command { + Use: "create", + Short: "create snapshots", + Long: `Create snapshot of a jail by specifying snapshot name and jail name.`, +// You can specify multiple jails.`, + Run: func(cmd *cobra.Command, args []string) { + // Load inventory + ListJails(args, false) + CreateJailSnapshot(args) + }, + } + + snapshotDeleteCmd = &cobra.Command { + Use: "destroy", + Short: "destroy snapshots", + Long: `Destroy snapshot of a jail by specifying snapshot name and jail name.`, + // You can specify multiple jails.`, + Run: func(cmd *cobra.Command, args []string) { + // Load inventory + ListJails(args, false) + DeleteJailSnapshot(args) }, } ) @@ -160,10 +183,18 @@ func init() { rootCmd.PersistentFlags().StringVarP(&gTimeZone, "timezone", "t", "", "Specify timezone. Will get from /var/db/zoneinfo if not set.") // Command dependant switches + + // These are persistent so we can reuse them in "gocage list snapshot myjail" command (TODO) listCmd.PersistentFlags().StringVarP(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output") listCmd.PersistentFlags().BoolVarP(&gNoLineSep, "nolinesep", "l", false, "Do not display line separator between jails") listCmd.PersistentFlags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails") listCmd.PersistentFlags().StringVarP(&gSortFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.") + + // This is local flag : Only available to gocage snapshot create command + snapshotCreateCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to create") + snapshotCreateCmd.MarkFlagRequired("snapname") + snapshotDeleteCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to destroy") + snapshotDeleteCmd.MarkFlagRequired("snapname") // Now declare commands rootCmd.AddCommand(versionCmd) @@ -175,7 +206,9 @@ func init() { rootCmd.AddCommand(setCmd) rootCmd.AddCommand(snapshotCmd) snapshotCmd.AddCommand(snapshotListCmd) - + snapshotCmd.AddCommand(snapshotCreateCmd) + snapshotCmd.AddCommand(snapshotDeleteCmd) + // Get FreeBSD version out, err := executeCommand("freebsd-version") if err != nil { diff --git a/cmd/snapshots.go b/cmd/snapshots.go index 31d0549..240c5bc 100644 --- a/cmd/snapshots.go +++ b/cmd/snapshots.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "time" + "regexp" "strings" ) @@ -45,7 +46,7 @@ func listJailSnapshots(jail Jail) []Snapshot { // 1. List all datasets // TODO : Include mounted filesystems? rs := strings.Split(jail.RootPath, "/") - rootDataset := strings.Join(rs[:len(rs)-1], "/") + rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/")) cmd := fmt.Sprintf("zfs list -r -H -o name,mountpoint,used,referenced,creation -t snapshot %s", rootDataset) out, err := executeCommand(cmd) if err != nil { @@ -66,8 +67,7 @@ func listJailSnapshots(jail Jail) []Snapshot { return snapshots } // Get subdir to append to snapshot name - subdir := strings.Replace(strings.Split(ls[0], "@")[0], - fmt.Sprintf("%s%s", jail.Zpool, rootDataset), "", 1) + subdir := strings.Replace(strings.Split(ls[0], "@")[0], rootDataset, "", 1) snapshots = append(snapshots, Snapshot{Dsname: ls[0], Name: fmt.Sprintf("%s%s", strings.Split(ls[0], "@")[1], subdir), @@ -85,3 +85,104 @@ func listJailSnapshots(jail Jail) []Snapshot { return snapshots } + + +/******************************************************************************** + * Create snapshot for jail(s) + *******************************************************************************/ +func CreateJailSnapshot(args []string) { + var jailNames []string + + if len(args) > 0 { + for _, a := range args { + jailNames = append(jailNames, a) + } + } + + for _, cj := range gJails { + for _, jn := range jailNames { + if strings.EqualFold(cj.Name, jn) { + createJailSnapshot(cj) + } + } + } +} + +/******************************************************************************** + * Create snapshot for a jail + *******************************************************************************/ +func createJailSnapshot(jail Jail) error { + rs := strings.Split(jail.RootPath, "/") + rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/")) + cmd := fmt.Sprintf("zfs snapshot -r %s@%s", rootDataset, gSnapshotName) + + _, err := executeCommand(cmd) + if err != nil { + fmt.Printf("Error creating snapshot %s@%s: %s\n", rootDataset, gSnapshotName, err.Error()) + return err + } + fmt.Printf("Snapshot %s@%s created\n", rootDataset, gSnapshotName) + + return nil +} + +/******************************************************************************** + * Delete snapshot for jail(s) + *******************************************************************************/ +func DeleteJailSnapshot(args []string) { + var jailNames []string + + if len(args) > 0 { + for _, a := range args { + jailNames = append(jailNames, a) + } + } + + for _, cj := range gJails { + for _, jn := range jailNames { + if strings.EqualFold(cj.Name, jn) { + deleteJailSnapshot(cj) + } + } + } +} + +/******************************************************************************** + * Delete snapshot for a jail + *******************************************************************************/ +func deleteJailSnapshot(jail Jail) error { + var snaptodel []string + + // Get all recursive snapshots + rs := strings.Split(jail.RootPath, "/") + rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/")) + cmd := fmt.Sprintf("zfs list -r -H -o name -t snapshot %s", rootDataset) + out, err := executeCommand(cmd) + if err != nil { + fmt.Printf("Error: listing snapshots: %s\n", err.Error()) + return nil + } + + for _, line := range strings.Split(out, "\n") { + if len(line) > 0 { + ls := strings.Split(line, "@") + + matched, _ := regexp.Match(fmt.Sprintf("^%s(\\/.*)?$", gSnapshotName), []byte(ls[1])) + if matched { + snaptodel = append(snaptodel, strings.Join(ls, "@")) + } + } + } + + for _, s := range snaptodel { + cmd := fmt.Sprintf("zfs destroy %s", s) + _, err := executeCommand(cmd) + if err != nil { + fmt.Printf("Error deleting snapshot %s: %s\n", s, err.Error()) + return nil + } + fmt.Printf("Snapshot %s deleted\n", s) + } + + return nil +}