package cmd import ( "os" "fmt" "strconv" "strings" "io/ioutil" "encoding/json" "github.com/spf13/cobra" "github.com/spf13/viper" ) const ( gVersion = "0.24" ) var ( gJails []Jail gUseSudo bool gConfigFile string gDisplayColumns string gFilterJails string gSortFields string gNoLineSep bool gHostVersion float64 gTimeZone string 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) { fmt.Println("Here we are in the Run") cleanAfterRun() }, } 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) { fmt.Printf("GoCage v.%s on FreeBSD %.1f\n", gVersion, gHostVersion) cleanAfterRun() }, } 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) cleanAfterRun() }, } 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) cleanAfterRun() }, } 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) cleanAfterRun() }, } startCmd = &cobra.Command { Use: "start", Short: "start jail", Run: func(cmd *cobra.Command, args []string) { // Load inventory ListJails(args, false) StartJail(args) cleanAfterRun() }, } 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) cleanAfterRun() }, } 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) cleanAfterRun() }, } 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) cleanAfterRun() }, } ) func init() { 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 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.") // Now declare commands rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(listCmd) listCmd.AddCommand(listPropsCmd) rootCmd.AddCommand(stopCmd) rootCmd.AddCommand(startCmd) rootCmd.AddCommand(getCmd) rootCmd.AddCommand(setCmd) rootCmd.AddCommand(snapshotCmd) snapshotCmd.AddCommand(snapshotListCmd) // Get FreeBSD version out, err := executeCommand("freebsd-version") if err != nil { fmt.Printf("Error running \"freebsd-version\": %s", err.Error()) os.Exit(1) } gHostVersion, _ = strconv.ParseFloat(strings.Split(out, "-")[0], 32) } func initConfig() { if gConfigFile == "" { fmt.Println("No config file!") os.Exit(1) } // fmt.Printf("We are in initConfig(), with config file %s\n", gConfigFile) viper.SetConfigFile(gConfigFile) if err := viper.ReadInConfig(); err != nil { fmt.Printf("ERROR reading config file %s : %s\n", gConfigFile, err.Error()) 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 { gDisplayColumns = viper.GetString("outcol") } if listCmd.Flags().Lookup("nolinesep") != nil && false == listCmd.Flags().Lookup("nolinesep").Changed { gNoLineSep = 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 { gSortFields = viper.GetString("sort") } if len(strings.Split(gSortFields, ",")) > 3 { fmt.Printf("More than 3 sort criteria, this is not supported!\n") os.Exit(1) } } // Called after execution func cleanAfterRun() { for _, j := range gJails { if j.ConfigUpdated { marshaled, err := json.MarshalIndent(j.Config, "", " ") 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) } }