From 0053fd6c8b437c59ef76ad9040c2b05f1eeaa342 Mon Sep 17 00:00:00 2001 From: yo Date: Tue, 5 Apr 2022 20:58:11 +0200 Subject: [PATCH] WIP on rollback, started "gocage console", default values for jail properties to stay compatible with iocage --- cmd/list.go | 6 +- cmd/root.go | 27 ++++++++- cmd/snapshots.go | 80 ++++++++++++++++++++++++++ cmd/utils.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 3 deletions(-) diff --git a/cmd/list.go b/cmd/list.go index 92c54c4..33fc845 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -18,8 +18,9 @@ import ( *******************************************************************************/ func ListJailsProps(args []string) { var conf Jail - var jailconf JailConfig var result []string + // Mandatory constructor to init default values + jailconf := NewJailConfig() conf.Config = jailconf @@ -213,7 +214,8 @@ func getJailConfig(jailConfigPath string) (JailConfig, error) { content, err := ioutil.ReadFile(jailConfigPath) if err != nil { log.Fatalln(fmt.Sprintf("Unable to read %s, check path and/or rights", jailConfigPath)) } - var jc JailConfig + // Mandatory constructor to init default values + jc := NewJailConfig() err = json.Unmarshal([]byte(content), &jc) if err != nil { log.Fatalln(fmt.Sprintf("Error occured during unmarshaling %s: %s", jailConfigPath, err.Error())) } diff --git a/cmd/root.go b/cmd/root.go index e35d039..9fc4026 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -98,6 +98,16 @@ ex: gocage list srv-db srv-web`, }, } +/* shellCmd = &cobra.Command { + Use: "console", + Short: "Execute shell on jail", + Run: func(cmd *cobra.Command, args []string) { + // Load inventory + ListJails(args, false) + ShellJail(args) + }, + } +*/ setCmd = &cobra.Command { Use: "set", Short: "Set a jail property", @@ -147,7 +157,7 @@ You can specify multiple jails.`, }, } - snapshotCreateCmd = &cobra.Command { + snapshotCreateCmd = &cobra.Command { Use: "create", Short: "create snapshots", Long: `Create snapshot of a jail by specifying snapshot name and jail name.`, @@ -159,6 +169,18 @@ You can specify multiple jails.`, }, } + snapshotRollbackCmd = &cobra.Command { + Use: "rollback", + Short: "Rollback snapshots", + Long: `Rollback jail to specifyed snapshot.`, +// You can specify multiple jails.`, + Run: func(cmd *cobra.Command, args []string) { + // Load inventory + ListJails(args, false) + RollbackJailSnapshot(args) + }, + } + snapshotDeleteCmd = &cobra.Command { Use: "destroy", Short: "destroy snapshots", @@ -195,6 +217,8 @@ func init() { snapshotCreateCmd.MarkFlagRequired("snapname") snapshotDeleteCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to destroy") snapshotDeleteCmd.MarkFlagRequired("snapname") + snapshotRollbackCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to rollback to") + snapshotRollbackCmd.MarkFlagRequired("snapname") // Now declare commands rootCmd.AddCommand(versionCmd) @@ -208,6 +232,7 @@ func init() { snapshotCmd.AddCommand(snapshotListCmd) snapshotCmd.AddCommand(snapshotCreateCmd) snapshotCmd.AddCommand(snapshotDeleteCmd) + snapshotCmd.AddCommand(snapshotRollbackCmd) // Get FreeBSD version out, err := executeCommand("freebsd-version") diff --git a/cmd/snapshots.go b/cmd/snapshots.go index 240c5bc..e1b460e 100644 --- a/cmd/snapshots.go +++ b/cmd/snapshots.go @@ -1,8 +1,11 @@ package cmd import ( + "os" "fmt" "time" + "bufio" + "errors" "regexp" "strings" ) @@ -186,3 +189,80 @@ func deleteJailSnapshot(jail Jail) error { 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 + 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 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 +} diff --git a/cmd/utils.go b/cmd/utils.go index a491ba1..af41cc9 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -11,8 +11,153 @@ import ( "reflect" "strings" "strconv" + "io/ioutil" ) + +/***************************************************************************** + * Mandatory constructor for JailConfig type. It set default values + *****************************************************************************/ +func NewJailConfig() JailConfig { + var jc JailConfig + + hostid, err := ioutil.ReadFile("/etc/hostid") + if err != nil { + hostid = []byte("12345678-abcd-ef98-7654-321012345678") + } + + jc.Allow_chflags = 0 + jc.Allow_mlock = 0 + jc.Allow_mount = 0 + jc.Allow_mount_devfs = 0 + jc.Allow_mount_fusefs = 0 + jc.Allow_mount_nullfs = 0 + jc.Allow_mount_procfs = 0 + jc.Allow_mount_tmpfs = 0 + jc.Allow_mount_zfs = 0 + jc.Allow_quotas = 0 + jc.Allow_raw_sockets = 0 + jc.Allow_socket_af = 0 + jc.Allow_set_hostname = 1 + jc.Allow_sysvipc = 0 + jc.Allow_tun = 0 + jc.Allow_vmm = 0 + jc.Assign_localhost = 0 + jc.Available = "readonly" + jc.Basejail = 0 + jc.Bpf = 0 + jc.Boot = 0 + jc.Children_max = "0" + jc.Comment = "none" + jc.Compression = "lz4" + jc.Compressratio = "readonly" + jc.Coredumpsize = "off" + jc.Count = "1" + jc.Cpuset = "off" + jc.Cputime = "off" + jc.Datasize = "off" + jc.Dedup = "off" + jc.Defaultrouter = "auto" + jc.Defaultrouter6 = "auto" + jc.Depends = "none" + jc.Devfs_ruleset = "4" + jc.Dhcp = 0 + jc.Enforce_statfs = "2" + jc.Exec_clean = 1 + jc.Exec_created = "/usr/bin/true" + jc.Exec_jail_user = "root" + jc.Exec_fib = "0" + jc.Exec_poststart = "/usr/bin/true" + jc.Exec_poststop = "/usr/bin/true" + jc.Exec_prestart = "/usr/bin/true" + jc.Exec_prestop = "/usr/bin/true" + jc.Exec_system_jail_user = "0" + jc.Exec_system_user = "root" + jc.Exec_start = "/bin/sh /etc/rc" + jc.Exec_stop = "/bin/sh /etc/rc.shutdown" + jc.Exec_timeout = "60" + jc.Hostid = string(hostid) + jc.Hostid_strict_check = 0 + jc.Host_time = 1 + jc.Interfaces = "vnet0:bridge0" + jc.Ip4_addr = "none" + jc.Ip4_saddrsel = "1" + jc.Ip4 = "new" + jc.Ip6_addr = "none" + jc.Ip6_saddrsel = "1" + jc.Ip6 = "new" + jc.Ip_hostname = 0 + jc.Jailtype = "jail" + jc.Jail_zfs = 0 + jc.Jail_zfs_mountpoint = "none" + jc.Last_started = "none" + jc.Localhost_ip = "none" + jc.Login_flags = "-f root" + jc.Maxproc = "off" + jc.Min_dyn_devfs_ruleset = "1000" + jc.Memoryuse = "off" + jc.Memorylocked = "off" + jc.Mountpoint = "readonly" + jc.Mount_devfs = 1 + jc.Mount_fdescfs = 1 + jc.Mount_procfs = 0 + jc.Mount_linprocfs = 0 + jc.Msgqqueued = "off" + jc.Msgqsize = "off" + jc.Nat = 0 + jc.Nat_backend = "ipfw" + jc.Nat_forwards = "none" + jc.Nat_interface = "none" + jc.Nat_prefix = "172.16" + jc.Nmsgq = "off" + jc.Notes = "none" + jc.Nsem = "off" + jc.Nsemop = "off" + jc.Nshm = "off" + jc.Nthr = "off" + jc.Openfiles = "off" + jc.Origin = "readonly" + jc.Owner = "root" + jc.Pcpu = "off" + jc.Plugin_name = "none" + jc.Plugin_repository = "none" + jc.Priority = "99" + jc.Pseudoterminals = "off" + jc.Quota = "none" + jc.Readbps = "off" + jc.Readiops = "off" + jc.Reservation = "none" + jc.Resolver = "/etc/resolv.conf" + jc.Rlimits = "off" + jc.Rtsold = 0 + jc.Securelevel = "2" + jc.Shmsize = "off" + jc.Stacksize = "off" + jc.Stop_timeout = "30" + jc.Sync_state = "none" + jc.Sync_target = "none" + jc.Sync_tgt_zpool = "none" + jc.Sysvmsg = "new" + jc.Sysvsem = "new" + jc.Sysvshm = "new" + jc.Swapuse = "off" + jc.Template = 0 + jc.Used = "readonly" + jc.Vmemoryuse = "off" + jc.Vnet = 0 + jc.Vnet0_mac = "none" + jc.Vnet1_mac = "none" + jc.Vnet2_mac = "none" + jc.Vnet3_mac = "none" + jc.Vnet_default_interface = "auto" + jc.Vnet_interfaces = "none" + jc.Wallclock = "off" + jc.Writebps = "off" + jc.Writeiops = "off" + + return jc +} + /***************************************************************************** * * Command execution