package cmd import ( "os" "fmt" "sync" "strings" "io/ioutil" "github.com/spf13/cobra" "github.com/spf13/viper" // TODO : Use log log "github.com/sirupsen/logrus" ) const ( gVersion = "0.38" // TODO : Get from $jail_zpool/defaults.json MIN_DYN_DEVFS_RULESET = 1000 ) type createArgs struct { Release string BaseTemplate string Datastore string JailType string } var ( gJailHost JailHost gJails []Jail gDatastores []Datastore gUseSudo bool gForce bool gDebug bool gCreateArgs createArgs 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 gFetchRelease string gFetchIntoDS string gFetchFrom string gUpgradeRelease string // For a based jail, these are directories binded to basejail gBaseDirs = []string{"bin", "boot", "lib", "libexec", "rescue", "sbin", "usr/bin", "usr/include", "usr/lib", "usr/lib32", "usr/libdata", "usr/libexec", "usr/sbin", "usr/share"} // These directories are to be created empty gEmptyDirs = []string{"dev", "media", "mnt", "net", "proc"} // Copy these from base template gCopyDirs = []string{"etc", "root", "tmp", "var"} gMdevfs sync.Mutex 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) }, } /* TODO Initialize datastore(s) /iocage, /iocage/jails Put defaults.json, update it with hostid,interfaces, and maybe other necessary fields Initialize bridge initCmd = &cobra.Command{ Use: "init", Short: "Initialize 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) }, } destroyCmd = &cobra.Command{ Use: "destroy", Short: "destroy jails", Long: `Destroy jail filesystem, snapshots and configuration file.`, Run: func(cmd *cobra.Command, args []string) { ListJails(args, false) DestroyJails(args) }, } stopCmd = &cobra.Command{ Use: "stop", Short: "stop jail", Long: "shutdown jail", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) if len(args) == 0 { StopAllRunningJails() } else { StopJail(args) } }, } startCmd = &cobra.Command{ Use: "start", Short: "start jail", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) if len(args) == 0 { StartJailsAtBoot() } else { StartJail(args) } }, } restartCmd = &cobra.Command{ Use: "restart", Short: "restart jail", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) StopJail(args) StartJail(args) }, } 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) }, } 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 snapshots separated by comma.`, 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) }, } 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) }, } fetchCmd = &cobra.Command{ Use: "fetch", Short: "Fetch FreeBSD release to local datastore", Run: func(cmd *cobra.Command, args []string) { err := fetchRelease(gFetchRelease, "http", gJailHost.arch, gFetchIntoDS, gFetchFrom) if err != nil { fmt.Printf("%v\n", err) } else { extractRelease(gFetchRelease, gFetchIntoDS) } }, } updateCmd = &cobra.Command{ Use: "update", Short: "Update FreeBSD release", Run: func(cmd *cobra.Command, args []string) { ListJails(args, false) UpdateJail(args) }, } upgradeCmd = &cobra.Command{ Use: "upgrade", Short: "Upgrade FreeBSD release", Run: func(cmd *cobra.Command, args []string) { ListJails(args, false) UpgradeJail(args) }, } createCmd = &cobra.Command{ Use: "create", Short: "Create jail", Run: func(cmd *cobra.Command, args []string) { ListJails(args, false) CreateJail(args) }, } 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.") rootCmd.PersistentFlags().BoolVar(&gDebug, "debug", false, "Debug mode") // 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,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.") destroyCmd.Flags().BoolVarP(&gForce, "force", "f", false, "Force stop jail if running") 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") fetchCmd.Flags().StringVarP(&gFetchRelease, "release", "r", "", "Release to fetch (e.g.: \"13.1-RELEASE\"") fetchCmd.Flags().StringVarP(&gFetchIntoDS, "datastore", "o", "", "Datastore release will be saved to") fetchCmd.Flags().StringVarP(&gFetchFrom, "from", "d", "", "Repository to download from. Should contain XY.Z-RELEASE. File protocol supported") fetchCmd.MarkFlagRequired("release") fetchCmd.MarkFlagRequired("datastore") upgradeCmd.Flags().StringVarP(&gUpgradeRelease, "release", "r", "", "Release to upgrade to (e.g.: \"13.1-RELEASE\"") upgradeCmd.MarkFlagRequired("release") createCmd.Flags().StringVarP(&gCreateArgs.Release, "release", "r", "", "Release for the jail (e.g.: \"13.1-RELEASE\"") createCmd.Flags().StringVarP(&gCreateArgs.BaseTemplate, "basetpl", "b", "", "Base template. This will create a jail based on basetpl, so every up(date|grade) made to basetpl will immediately propagate to new jail\n") createCmd.Flags().StringVarP(&gCreateArgs.Datastore, "datastore", "d", "", "Datastore to create the jail on. Defaults to first declared in config.") // Now declare commands rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(listCmd) listCmd.AddCommand(listPropsCmd) rootCmd.AddCommand(stopCmd) rootCmd.AddCommand(startCmd) rootCmd.AddCommand(restartCmd) rootCmd.AddCommand(destroyCmd) rootCmd.AddCommand(shellCmd) rootCmd.AddCommand(getCmd) rootCmd.AddCommand(setCmd) rootCmd.AddCommand(snapshotCmd) rootCmd.AddCommand(migrateCmd) rootCmd.AddCommand(datastoreCmd) rootCmd.AddCommand(fetchCmd) rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(upgradeCmd) rootCmd.AddCommand(createCmd) 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) } if gDebug { log.SetLevel(log.DebugLevel) log.Debugf("Debug mode enabled\n") } } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }