package cmd import ( "bufio" "errors" "fmt" "os" "regexp" "strconv" "strings" "reflect" "time" ) /******************************************************************************** * List all snapshots jails have *******************************************************************************/ func ListJailsSnapshots(args []string) { var jailNames []string var snapshots []Snapshot /*************************************************************** / Filter snapshots by jailname /**************************************************************/ if len(args) > 0 { for _, a := range args { /*if countOfJailsWithThisName(a) > 1 { fmt.Printf("Nope") return }*/ jailNames = append(jailNames, a) } } if len(jailNames) == 0 || len(args) == 0 { for _, j := range gJails { snapshots = append(snapshots, listJailSnapshots(j)...) } } else { for _, cj := range gJails { for _, jn := range jailNames { if strings.EqualFold(cj.Name, jn) { snapshots = append(snapshots, listJailSnapshots(cj)...) } } } } fields := strings.Split(gDisplaySColumns, ",") /*************************************************************** / Sort snapshots / We support 3 sort criteria max /**************************************************************/ if len(gSortSnapFields) > 0 && gSortSnapFields != "none" { ss := initSnapshotSortStruct() // The way we manage criteria quantity is not very elegant... var fct1, fct2, fct3 *reflect.Value for i, c := range strings.Split(gSortSnapFields, ",") { var fctName string if strings.HasPrefix(c, "-") { fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1)) } else { // Par defaut (pas de prefix +/-) on considere un tri incremental fctName = fmt.Sprintf("%sInc", strings.Replace(c, "+", "", 1)) } // Get function by its name fct, _, err := getStructFieldValue(ss, fctName) if err != nil { fieldName := strings.Replace(strings.Replace(c, "-", "", 1), "+", "", 1) fmt.Printf("ERROR getting SnapshotSort struct field %s. Please check the field name: %s\n", fctName, fieldName) return } switch i + 1 { case 1: fct1 = fct case 2: fct2 = fct case 3: fct3 = fct } } switch len(strings.Split(gSortSnapFields, ",")) { case 1: SnapshotsOrderedBy(fct1.Interface().(snapshotLessFunc)).Sort(snapshots) case 2: SnapshotsOrderedBy(fct1.Interface().(snapshotLessFunc), fct2.Interface().(snapshotLessFunc)).Sort(snapshots) case 3: SnapshotsOrderedBy(fct1.Interface().(snapshotLessFunc), fct2.Interface().(snapshotLessFunc), fct3.Interface().(snapshotLessFunc)).Sort(snapshots) } } displaySnapshotsFields(snapshots, fields) } /******************************************************************************** * List all snapshots a jail have *******************************************************************************/ func listJailSnapshots(jail Jail) []Snapshot { var snapshots []Snapshot // 1. List all datasets // TODO : Include mounted filesystems? curDS, err := getDatastoreFromArray(jail.Datastore, gDatastores) if err != nil { fmt.Printf("Error getting datastore \"%s\": %v\n", jail.Datastore, err) return snapshots } rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name) cmd := fmt.Sprintf("zfs list -p -r -H -o name,mountpoint,used,referenced,creation -t snapshot %s", rootDataset) out, err := executeCommand(cmd) if err != nil { fmt.Printf("Error: %s\n", err.Error()) return snapshots } for _, line := range strings.Split(out, "\n") { if len(line) > 0 { ls := strings.Split(line, "\t") // Parse creation date so we can use it to sort snapshots //creationts, err := time.ParseInLocation(dateLayout, ls[4], loc) creationts, err := strconv.ParseInt(ls[4], 10, 64) if err != nil { fmt.Println("Error while parsing date %s:", ls[4], err) return snapshots } // Get subdir to append to snapshot name subdir := strings.Replace(strings.Split(ls[0], "@")[0], rootDataset, "", 1) u, _ := strconv.ParseUint(ls[2], 10, 64) r, _ := strconv.ParseUint(ls[3], 10, 64) snapshots = append(snapshots, Snapshot{Datastore: curDS.Name, Name: fmt.Sprintf("%s%s", strings.Split(ls[0], "@")[1], subdir), Jailname: jail.Name, Mountpoint: ls[1], Used: u, Referenced: r, Creation: time.Unix(creationts, 0)}) } } // Sort snapshots by creation date ss := initSnapshotSortStruct() SnapshotsOrderedBy(ss.CreationInc).Sort(snapshots) 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 { curDS, _ := getDatastoreFromArray(jail.Datastore, gDatastores) rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name) 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 curDS, _ := getDatastoreFromArray(jail.Datastore, gDatastores) rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name) 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, "@") for _, sn := range strings.Split(gSnapshotName, ",") { matched, _ := regexp.Match(fmt.Sprintf("^%s(\\/.*)?$", sn), []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 } func RollbackJailSnapshot(args []string) error { 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) { rollbackJailSnapshot(cj) } } } return nil } /******************************************************************************** * rollback jail to snapshot gSnapshotName, destroy this snapshots and more * recents snapshots and bookmarks *******************************************************************************/ func rollbackJailSnapshot(jail Jail) error { var snaptorb []string if jail.Running { fmt.Printf("Jail should be stoped to rollback, should we stop and rollback? (y/n)\n") scanr := bufio.NewScanner(os.Stdin) if scanr.Scan() { if !strings.EqualFold(scanr.Text(), "y") { return errors.New("Jail is running") } else { err := stopJail(&jail) if err != nil { return err } } } } // We need to rollback parent and childs // Get all recursive snapshots curDS, _ := getDatastoreFromArray(jail.Datastore, gDatastores) rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name) 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 err } 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 { snaptorb = append(snaptorb, strings.Join(ls, "@")) } } } for _, s := range snaptorb { cmd := fmt.Sprintf("zfs rollback -r %s", s) _, err := executeCommand(cmd) if err != nil { fmt.Printf("Error rolling back snapshot %s: %s\n", s, err.Error()) return err } } fmt.Printf("Jail is back to %s\n", gSnapshotName) return nil }