package cmd import ( "encoding/json" "fmt" "io/ioutil" "os" "strings" "github.com/spf13/cobra" "github.com/spf13/viper" // TODO : Use log //log "github.com/sirupsen/logrus" ) const ( gVersion = "0.28a" // TODO : Get from $jail_zpool/defaults.json MIN_DYN_DEVFS_RULESET = 1000 ) var ( gJailHost JailHost gJails []Jail gDatastores []Datastore gUseSudo bool gConfigFile string gDisplayJColumns string gDisplaySColumns string gDisplayDColumns string gFilterJails string gFilterSnaps string gFilterDS string gSortJailFields string gSortSnapFields string gSortDSFields string gNoJailLineSep bool gNoSnapLineSep bool gNoDSLineSep bool gHostVersion float64 gTimeZone string gSnapshotName string gMigrateDestDatastore string gYesToAll bool rootCmd = &cobra.Command{ Use: "gocage", Short: "GoCage is a FreeBSD Jail management tool", Long: `GoCage is a jail management tool. It support VNET, host-only, NAT networks. Provides snapshots and cloning. It support iocage jails and can coexist with iocage.`, Run: func(cmd *cobra.Command, args []string) { fv, _ := getFreeBSDVersion() fmt.Printf("GoCage v.%s on FreeBSD %d.%d-%s\n", gVersion, fv.major, fv.minor, fv.flavor) fmt.Printf("Use -h flag to display help\n") }, } versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number of GoCage", Long: `Let this show you how much fail I had to get this *cough* perfect`, Run: func(cmd *cobra.Command, args []string) { fv, _ := getFreeBSDVersion() fmt.Printf("GoCage v.%s on FreeBSD %d.%d-%s\n", gVersion, fv.major, fv.minor, fv.flavor) }, } listCmd = &cobra.Command{ Use: "list", Short: "Print jails", Long: `Display jails, their IP and OS. Jail list can be restricted by adding name on command line ex: gocage list srv-db srv-web`, Run: func(cmd *cobra.Command, args []string) { ListJails(args, true) }, } listPropsCmd = &cobra.Command{ Use: "properties", Short: "Print jails properties", Long: "Display jails properties. You can use properties to filter, get or set them.", Run: func(cmd *cobra.Command, args []string) { ListJailsProps(args) }, } stopCmd = &cobra.Command{ Use: "stop", Short: "stop jail", Long: "shutdown jail", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) StopJail(args) }, } startCmd = &cobra.Command{ Use: "start", Short: "start jail", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) StartJail(args) WriteConfigToDisk(false) }, } 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", Long: `Set jail property value. Specify property=value, end command with jail name. Multiples properties can be specified, separated with space (Ex: gocage set allow_mlock=1 boot=1 myjail)`, Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) SetJailProperties(args) WriteConfigToDisk(true) }, } getCmd = &cobra.Command{ Use: "get", Short: "Get a jail property", Long: `Get jail property value. Specify property, end command with jail name. Multiples properties can be specified, separated with space (Ex: gocage get allow_mlock boot myjail) For all properties specify "all" (Ex: gocage get all myjail)`, Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) GetJailProperties(args) }, } snapshotCmd = &cobra.Command{ Use: "snapshot", Short: "snapshot jail", Long: "Commands to manage jail snapshots. If no arguments given, ", Run: func(cmd *cobra.Command, args []string) { }, } snapshotListCmd = &cobra.Command{ Use: "list", Short: "list snapshots", Long: `List snapshots of a jail by specifying its name. List all snapshots if no jail name specified. You can specify multiple jails.`, Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) ListJailsSnapshots(args) }, } 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) }, } 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", 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) }, } migrateCmd = &cobra.Command{ Use: "migrate", Short: "Migrate jail to another datastore", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) MigrateJail(args) WriteConfigToDisk(false) }, } migrateCleanCmd = &cobra.Command{ Use: "clean", Short: "Clean previous aborted/in error jail migration", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) err := CleanMigrateMess(args) if err != nil { fmt.Printf("%v", err) } }, } datastoreCmd = &cobra.Command{ Use: "datastore", Short: "list datastores", Long: "Commands to manage datastores. If no arguments given, list them.", Run: func(cmd *cobra.Command, args []string) { ListDatastores(args, true) }, } datastoreListCmd = &cobra.Command{ Use: "list", Short: "list datastores", Long: `List datastore by specifying its name. List all datastores if no name specified. You can specify multiple datastores.`, Run: func(cmd *cobra.Command, args []string) { ListDatastores(args, true) }, } testCmd = &cobra.Command{ Use: "test", Short: "temporary command to test some code snippet", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Nope\n") }, } ) // TODO : Init log level and log output func init() { var err error cobra.OnInitialize(initConfig) // Global switches rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file") rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands") rootCmd.PersistentFlags().StringVarP(&gTimeZone, "timezone", "t", "", "Specify timezone. Will get from /var/db/zoneinfo if not set.") // Command dependant switches // We reuse these flags in "gocage snapshot list myjail" and 'gocage datastore list" commands listCmd.Flags().StringVarP(&gDisplayJColumns, "outcol", "o", "JID,Name,Datastore,Config.Release,Config.Ip4_addr,Running", "Show these columns in output") listCmd.Flags().BoolVarP(&gNoJailLineSep, "nolinesep", "l", false, "Do not display line separator between jails") listCmd.Flags().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.Flags().StringVarP(&gSortJailFields, "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.") snapshotListCmd.Flags().StringVarP(&gDisplaySColumns, "outcol", "o", "Jailname,Name,Creation,Referenced,Used", "Show these columns in output") snapshotListCmd.Flags().BoolVarP(&gNoSnapLineSep, "nolinesep", "l", false, "Do not display line separator between snapshots") snapshotListCmd.Flags().StringVarP(&gFilterSnaps, "filter", "f", "none", "Only display snapshots with these values. Ex: \"gocage snapshot list -f Config.Boot=1\" will only list started on boot jails") snapshotListCmd.Flags().StringVarP(&gSortSnapFields, "sort", "s", "none", "Display snapshots sorted by field values. Ex: \"gocage snapshot list -s +Jailname,-Used\" will sort snapshots by jail decreasing name, then increasing used space. 3 critera max supported.") datastoreListCmd.Flags().StringVarP(&gDisplayDColumns, "outcol", "o", "Name,Mountpoint,ZFSDataset,Available,Used,Referenced", "Show these columns in output") datastoreListCmd.Flags().BoolVarP(&gNoDSLineSep, "nolinesep", "l", false, "Do not display line separator between datastores") datastoreListCmd.Flags().StringVarP(&gFilterDS, "filter", "f", "none", "Only display datastores with these values. Ex: \"gocage datastore list -f Config.Boot=1\" will only list started on boot jails") datastoreListCmd.Flags().StringVarP(&gSortDSFields, "sort", "s", "none", "Display datastores sorted by field values. Ex: \"gocage datastore list -s +Jailname,-Used\" will sort snapshots by jail decreasing name, then increasing used space. 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") snapshotRollbackCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to rollback to") snapshotRollbackCmd.MarkFlagRequired("snapname") migrateCmd.Flags().StringVarP(&gMigrateDestDatastore, "datastore", "d", "", "Path of destination datastore for jail (Ex: \"/iocage\")") migrateCmd.Flags().BoolVarP(&gYesToAll, "yes", "y", false, "Answer yes to all questions") migrateCmd.MarkFlagRequired("datastore") // Now declare commands rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(listCmd) listCmd.AddCommand(listPropsCmd) rootCmd.AddCommand(stopCmd) rootCmd.AddCommand(startCmd) rootCmd.AddCommand(shellCmd) rootCmd.AddCommand(getCmd) rootCmd.AddCommand(setCmd) rootCmd.AddCommand(snapshotCmd) rootCmd.AddCommand(migrateCmd) rootCmd.AddCommand(datastoreCmd) rootCmd.AddCommand(testCmd) snapshotCmd.AddCommand(snapshotListCmd) snapshotCmd.AddCommand(snapshotCreateCmd) snapshotCmd.AddCommand(snapshotDeleteCmd) snapshotCmd.AddCommand(snapshotRollbackCmd) migrateCmd.AddCommand(migrateCleanCmd) datastoreCmd.AddCommand(datastoreListCmd) // Get FreeBSD version, hostname, hostid gJailHost, err = NewJailHost() if err != nil { fmt.Printf("Error initializing JailHost properties: %v\n", err) os.Exit(1) } } func initConfig() { if gConfigFile == "" { fmt.Println("No config file!") os.Exit(1) } viper.SetConfigFile(gConfigFile) if err := viper.ReadInConfig(); err != nil { fmt.Printf("ERROR reading config file %s : %s\n", gConfigFile, err.Error()) os.Exit(1) } // Load default configs from datastores err := ListDatastores(viper.GetStringSlice("datastore"), false) if err != nil { fmt.Printf("ERROR: error checking datastores: %v\n", err) os.Exit(1) } // fmt.Println("Using config file:", viper.ConfigFileUsed()) // fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore")) // fmt.Printf("datastore.0 in config : %s\n", viper.GetStringSlice("datastore.0")) // Command line flags have priority on config file if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed { gUseSudo = viper.GetBool("sudo") } if rootCmd.Flags().Lookup("timezone") != nil && false == rootCmd.Flags().Lookup("timezone").Changed { gTimeZone = viper.GetString("timezone") } // If neither on cmdline nor config file, get from /var/db/zoneinfo if len(gTimeZone) == 0 { tz, err := ioutil.ReadFile("/var/db/zoneinfo") if err != nil { fmt.Println("Error reading /var/db/zoneinfo: %s\n", err.Error()) os.Exit(1) } gTimeZone = strings.Trim(string(tz), "\n") } if listCmd.Flags().Lookup("outcol") != nil && false == listCmd.Flags().Lookup("outcol").Changed { gDisplayJColumns = viper.GetString("outcol") } if listCmd.Flags().Lookup("nolinesep") != nil && false == listCmd.Flags().Lookup("nolinesep").Changed { gNoJailLineSep = viper.GetBool("nolinesep") } if listCmd.Flags().Lookup("filter") != nil && false == listCmd.Flags().Lookup("filter").Changed { gFilterJails = viper.GetString("filter") } if listCmd.Flags().Lookup("sort") != nil && false == listCmd.Flags().Lookup("sort").Changed { gSortJailFields = viper.GetString("sort") } if len(strings.Split(gSortJailFields, ",")) > 3 { fmt.Printf("More than 3 sort criteria is not supported!\n") os.Exit(1) } } /******************************************************************************** * Write jails config which been updated to disk. * If changeauto not set, values which are in "auto" mode on disk * won't be overwritten (p.ex defaultrouter wont be overwritten with current * default route, so if route change on jailhost this will reflect on jail next * start) *******************************************************************************/ func WriteConfigToDisk(changeauto bool) { for _, j := range gJails { if j.ConfigUpdated { //log.Debug("%s config has changed, write changes to disk\n", j.Name) // we will manipulate properties so get a copy jc := j.Config if changeauto == false { // Overwrite "auto" properties ondiskjc, err := getJailConfig(j.ConfigPath) if err != nil { panic(err) } // TODO : List all fields, then call getStructFieldValue to compare value with "auto" // If "auto" then keep it that way before writing ondiskjc to disk var properties []string properties = getStructFieldNames(ondiskjc, properties, "") for _, p := range properties { v, _, err := getStructFieldValue(ondiskjc, p) if err != nil { panic(err) } if v.String() == "auto" { err = setStructFieldValue(&jc, p, "auto") if err != nil { fmt.Printf("ERROR sanitizing config: %s\n", err.Error()) os.Exit(1) } } } } marshaled, err := json.MarshalIndent(jc, "", " ") if err != nil { fmt.Printf("ERROR marshaling config: %s\n", err.Error()) } //fmt.Printf(string(marshaled)) if os.WriteFile(j.ConfigPath, []byte(marshaled), 0644); err != nil { fmt.Printf("Error writing config file %s: %v\n", j.ConfigPath, err) os.Exit(1) } } } } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }