Compare commits
	
		
			166 Commits
		
	
	
		
			v0.24
			...
			18d35b9224
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 18d35b9224 | |||
| f41c93368d | |||
| 452b0e4b4e | |||
| dbe9622a01 | |||
| 6ead474a78 | |||
| 4edd0b7414 | |||
| b54ebfd915 | |||
| 69665fdcef | |||
| 7e1c213ff4 | |||
| 26ceb1630a | |||
| 87e9ae894a | |||
| ed5f8f0b1c | |||
| c55262690a | |||
| c5aa547e5d | |||
| cdcb466417 | |||
| bb3136c9ef | |||
| 9208102c84 | |||
| fce64b2939 | |||
| 44b877eae1 | |||
| c2277ce10c | |||
| 549d517cf9 | |||
| 14984f417c | |||
| 346fd52a8e | |||
| 684d97cc21 | |||
| a3dd0a7aa2 | |||
| 56926f7200 | |||
| 9f32cda6b8 | |||
| 00cd0421d3 | |||
| 2c3b4b18f2 | |||
| 46ad79c325 | |||
| 534deb371c | |||
| fe4192da2d | |||
| 26b8973c6c | |||
| 6a8b022165 | |||
| 45e1c57ce4 | |||
| bce37e6541 | |||
| 24439a8181 | |||
| 310564b4af | |||
| 881965e257 | |||
| 853bf5fb10 | |||
| 09b807c78e | |||
| 2ddf51f887 | |||
| 9e057ed1c5 | |||
| 925c3dd96b | |||
| e11fc96e05 | |||
| 956e25c849 | |||
| f9f1d48023 | |||
| 1b679bcd17 | |||
| dc4213a8d5 | |||
| 5eed121f0b | |||
| 812c77790a | |||
| 7575da794e | |||
| 6f9bb504be | |||
| 7c3e14f0f1 | |||
| a4ff9c1d51 | |||
| 00fd283987 | |||
| 7cf4594f34 | |||
| 37fea55e42 | |||
| c15ee68d2e | |||
| 54fd1f8064 | |||
| 89db166040 | |||
| 9c18a83ee8 | |||
| 561ae4386a | |||
| 667c73216e | |||
| 9e506145a8 | |||
| d636d963ff | |||
| 56b4d8ea84 | |||
| abaa4a11f9 | |||
| 74602dc0df | |||
| be756edea7 | |||
| 546382ded7 | |||
| 07eccffbd1 | |||
| 7809107ea4 | |||
| 5ab0a59db4 | |||
| 1b27753718 | |||
| c97f5317dd | |||
| 38266af66e | |||
| fabe9cc330 | |||
| 0637686f91 | |||
| 99f7bed6f3 | |||
| 9b86d786fe | |||
| efd28c03ad | |||
| e1786a4d08 | |||
| bf1037c1d3 | |||
| cbdcb039cb | |||
| 09ac7464f5 | |||
| de1a92dec8 | |||
| d16a2d6789 | |||
| 35b75ee7fb | |||
| 4d92e880c9 | |||
| 52916f9ae9 | |||
| 31961b8e3a | |||
| 128e2aa0c4 | |||
| 0c7293ae66 | |||
| e1f6b4f6f9 | |||
| f29aeb2e23 | |||
| 1cb7c5fec7 | |||
| 3dae685fc4 | |||
| 9b90f9c812 | |||
| e13437b79e | |||
| 484e05e8d1 | |||
| e7a6bdd376 | |||
| 170dce31b3 | |||
| 1295fb86f6 | |||
| d3410c281a | |||
| 51dc7d1588 | |||
| 92d8beb58f | |||
| 745811c39b | |||
| 276d01ed4c | |||
| 7266496cac | |||
| 5b0de24508 | |||
| 2a8836721c | |||
| 0f6b7b8b80 | |||
| 1462c383d3 | |||
| 29ce0d9b58 | |||
| 42e1085ad4 | |||
| 0f4f76a9a2 | |||
| e87699e2dc | |||
| 4d8bf6e0d5 | |||
| d9e1e20afc | |||
| fcf7d68d06 | |||
| f919ff2ec3 | |||
| 1bc248fdcc | |||
| a7aaa11de6 | |||
| 71f345dff4 | |||
| e1410bf209 | |||
| c585678be9 | |||
| 9218ffafe1 | |||
| 1c04f62ed8 | |||
| 2151034a02 | |||
| e84c43c759 | |||
| d4f6b9ddc7 | |||
| 86e08ec0f7 | |||
| b4fd7caca7 | |||
| 29e8736fbc | |||
| 203c4bff3b | |||
| 7356c0d3d0 | |||
| a446a19a08 | |||
| 0bf825ee5a | |||
| 542d2f96f6 | |||
| 6c6cb7edc8 | |||
| 31fa6904db | |||
| fb3ee07585 | |||
| 57c8bba09b | |||
| 249ab19173 | |||
| bc92f29900 | |||
| e4fc9c3a6c | |||
| 43f26d099f | |||
| dbd9153513 | |||
| 7b5ae7ce6e | |||
| 77a2e9dabf | |||
| 6821b14407 | |||
| f9ce3601df | |||
| 4f85f2e6ac | |||
| e0f371693a | |||
| 0f97270a6a | |||
| 7dbbf8a757 | |||
| 0053fd6c8b | |||
| 4aa1c81fea | |||
| 966a3d57c1 | |||
| a12c268be2 | |||
| 7b34495cf6 | |||
| 1c40e29eff | |||
| ef78245902 | |||
| 910be4ea31 | |||
| 285229009f | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,2 @@ | |||||||
| gocage | gocage | ||||||
| go.sum | go.sum | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								CHANGELOG
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | v.0.33b : Support jailing datasets on differents pools : jail_zfs_dataset now have to include the pool name   | ||||||
|  | v.0.33c : Parallelize start/stop of jails with same priority   | ||||||
|  | v.0.34 : jail name can be shortened  | ||||||
|  | v.0.35 : One can now "gocage destroy" | ||||||
							
								
								
									
										130
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								README.md
									
									
									
									
									
								
							| @ -4,8 +4,33 @@ GoCage | |||||||
| Jail management tool for FreeBSD, written in Go.   | Jail management tool for FreeBSD, written in Go.   | ||||||
| Support iocage jails, so they can coexist.   | Support iocage jails, so they can coexist.   | ||||||
| Gocage is meant to be a complete jail management tool with network, snapshots, jail cloning support and a web interface. This is the hypothetic future.   | Gocage is meant to be a complete jail management tool with network, snapshots, jail cloning support and a web interface. This is the hypothetic future.   | ||||||
|  | Gocage can handle multiple datastores, so you can have jails on HDD storage and jails on SSD storage.   | ||||||
|  |  | ||||||
|  | From v0.33b, due to multi ZFS pool support, gocage is no longer 100% compatible with iocage.   | ||||||
|  | Zfs datasets now should be specified with the ZFS pool. e.g. :   | ||||||
|  | <pre><code> | ||||||
|  | Config.Jail_zfs = 1 | ||||||
|  | Config.Jail_zfs_dataset = myzfspool/poudriere | ||||||
|  | Config.Jail_zfs_mountpoint = none | ||||||
|  | </code></pre> | ||||||
|  |  | ||||||
|  | Create jails | ||||||
|  | ------------ | ||||||
|  | For now, we can't pass config at creation time. We have to define config after creation:   | ||||||
|  | <pre><code> | ||||||
|  | gocage create jail1 -r 13.2-RELEASE | ||||||
|  | gocage set Config.Ip4_addr="vnet0|192.168.1.91/24" Config.Vnet=1 jail1 | ||||||
|  | </code></pre> | ||||||
|  |  | ||||||
|  | Create basejail (jail based on a template, system will be nullfs read-only mounted from the template): | ||||||
|  | <pre><code> | ||||||
|  | # First create basetpl from 13.2-RELEASE, set it as a template | ||||||
|  | gocage create -r 13.2-RELEASE basetpl | ||||||
|  | gocage set Config.Jailtype="template" basetpl | ||||||
|  | # Then create basejail1 based on basetpl | ||||||
|  | gocage create -b basetpl basejail1 | ||||||
|  | </code></pre> | ||||||
|  |  | ||||||
| At present time, it list and stops jails.   |  | ||||||
|  |  | ||||||
| List jails | List jails | ||||||
| ---------- | ---------- | ||||||
| @ -118,3 +143,106 @@ Stop jails | |||||||
| ---------- | ---------- | ||||||
| `gocage stop test` | `gocage stop test` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Update jails   | ||||||
|  | ---------- | ||||||
|  | To update jail patch version, use gocage update :   | ||||||
|  | `gocage update test` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Upgrade jails   | ||||||
|  | ---------- | ||||||
|  | To upgrade jail to newer release, use gocage upgrade :   | ||||||
|  | `gocage upgrade -r 13.2-RELEASE test` | ||||||
|  |    | ||||||
|  | A pre-upgrade snapshot wil be made so we can rollback if needed.   | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Delete jails   | ||||||
|  | ---------- | ||||||
|  | `gocage destroy test` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Multi datastore   | ||||||
|  | ---------- | ||||||
|  | A datastore is a ZFS dataset mounted. It should be declared in gocage.conf.yml, specifying its ZFS mountpoint : | ||||||
|  | <pre><code> | ||||||
|  | datastore: | ||||||
|  |   - /iocage | ||||||
|  |   - /fastiocage | ||||||
|  | </pre></code> | ||||||
|  |  | ||||||
|  | In gocage commands, datastore name is the mountpoint without its "/" prefix.   | ||||||
|  |  | ||||||
|  | ### List datastores | ||||||
|  | <pre><code> | ||||||
|  | gocage datastore list  | ||||||
|  | +============+=============+============+===========+==========+============+ | ||||||
|  | | Name       | Mountpoint  | ZFSDataset | Available | Used     | Referenced | | ||||||
|  | +============+=============+============+===========+==========+============+ | ||||||
|  | | iocage     | /iocage     | hdd/iocage | 1.6 TB    | 414.9 GB | 27.5 KB    | | ||||||
|  | +------------+-------------+------------+-----------+----------+------------+ | ||||||
|  | | fastiocage | /fastiocage | ssd/iocage | 1.5 TB    | 65.3 KB  | 34.6 KB    | | ||||||
|  | +------------+-------------+------------+-----------+----------+------------+ | ||||||
|  | </pre></code> | ||||||
|  |  | ||||||
|  | ### Filter datastores   | ||||||
|  | As with jails and snapshots, you can filter by name:   | ||||||
|  | <pre><code> | ||||||
|  | gocage datastore list iocage | ||||||
|  | +============+=============+============+===========+==========+============+ | ||||||
|  | | Name       | Mountpoint  | ZFSDataset | Available | Used     | Referenced | | ||||||
|  | +============+=============+============+===========+==========+============+ | ||||||
|  | | iocage     | /iocage     | hdd/iocage | 1.6 TB    | 414.9 GB | 27.5 KB    | | ||||||
|  | +------------+-------------+------------+-----------+----------+------------+ | ||||||
|  | </pre></code> | ||||||
|  |  | ||||||
|  | ### Sort datastores   | ||||||
|  | You can sort datastores:   | ||||||
|  | <pre><code> | ||||||
|  | gocage datastore list -s -Available | ||||||
|  | +============+=============+============+===========+==========+============+ | ||||||
|  | | Name       | Mountpoint  | ZFSDataset | Available | Used     | Referenced | | ||||||
|  | +============+=============+============+===========+==========+============+ | ||||||
|  | | iocage     | /iocage     | hdd/iocage | 1.6 TB    | 415.0 GB | 27.5 KB    | | ||||||
|  | +------------+-------------+------------+-----------+----------+------------+ | ||||||
|  | | fastiocage | /fastiocage | ssd/iocage | 1.5 TB    | 65.3 KB  | 34.6 KB    | | ||||||
|  | +------------+-------------+------------+-----------+----------+------------+ | ||||||
|  | </pre></code> | ||||||
|  |  | ||||||
|  | See [cmd/struct.go](https://git.nosd.in/yo/gocage/src/branch/master/cmd/struct.go) for field names. | ||||||
|  |  | ||||||
|  | Migrating jails   | ||||||
|  | ---------- | ||||||
|  | With multi datastore comes the need to migrate a jail between datastores.   | ||||||
|  | Migration can be done with a minimal downtime, using zfs differential send/receive.   | ||||||
|  | Source jail datasets are sent to the destination datastore, jail is stopped and a last differential sync is done before starting jail on new datastore.   | ||||||
|  |  | ||||||
|  | ### Warning  | ||||||
|  | Be aware the moment you migrate a jail to another datastore than /iocage default, you lose compatibility with iocage.   | ||||||
|  | Then you need to disable iocage service, and enable gocage so the jails will start automatically at boot.   | ||||||
|  | Also make sure, if you don't destroy source jail, that it won't have the "boot" property set or you will have the 2 jails up at boot.   | ||||||
|  |  | ||||||
|  | <pre><code> | ||||||
|  | gocage migrate -d fastiocage srv-random | ||||||
|  | Snapshot data/iocage/jails/srv-random: Done | ||||||
|  | Snapshot data/iocage/jails/srv-random/root: Done | ||||||
|  | Migrate jail config dataset to fastdata/iocage/jails/srv-random: Done | ||||||
|  | Migrate jail filesystem dataset to fastdata/iocage/jails/srv-random/root: Done | ||||||
|  | </pre></code> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Fetch   | ||||||
|  | ---------- | ||||||
|  | Files can be fetched from custom repository, or from local directory with "from" option.   | ||||||
|  | For example if you destroyed releases/12.3-RELEASE and still have the downloaded files in /iocage/download/12.3-RELEASE:   | ||||||
|  | <pre><code> | ||||||
|  | gocage fetch -r 12.3 -o iocage --from file:/iocage/download | ||||||
|  | </pre></code> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TODO | ||||||
|  | ---------- | ||||||
|  | gocage create from templates   | ||||||
|  | gocage init   | ||||||
|  |   create default pool with defaults.json   | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								TODO.md
									
									
									
									
									
								
							| @ -1,4 +1,19 @@ | |||||||
| Replicating jails between two servers (use zrepl) | Replicating jails between two servers (use zrepl) | ||||||
|  | Manage remote jails : | ||||||
|  |   - Make gocage a service | ||||||
|  |   - All commands should become API endpoint | ||||||
|  |   - How to handle authentication ? | ||||||
|  |  | ||||||
|  | DEBUG:  | ||||||
|  | - cmd/list.go:275: | ||||||
|  |     // FIXME ??? Shouldn't be ioc-$Name ? | ||||||
|  |     j.InternalName = rj.Name | ||||||
|  | - WriteConfigToDisk don't write neither "release" in cmd stop neither "last_started" in cmd start | ||||||
|  |   26/08/2023 : Last_started is updated | ||||||
|  |  | ||||||
|  | BUGS: | ||||||
|  | - unable to set values containing equal sign : | ||||||
|  |   # gocage set Config.Exec_poststart="jail -m allow.mount.linprocfs=1 name=ioc-poudriere-noo" poudriere-noo | ||||||
|  |   Error parsing args: Config.Exec_poststart=jail -m allow.mount.linprocfs=1 name=ioc-poudriere-noo | ||||||
|  | - Fix fstab when migrating jail   | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								cmd/console.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								cmd/console.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"errors" | ||||||
|  | 	"strconv" | ||||||
|  | 	"syscall" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ShellJail(args []string) error { | ||||||
|  | 	// We cant shell more than one jail bc we replace gocage execution with jexec, so there wont be no return to gocage | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		cj, err := getJailFromArray(args[0], []string{"basejail", "jail"}, gJails) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting jail %s: %v\n", args[0], err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err := shellJail(cj); err != nil { | ||||||
|  | 			fmt.Printf("%v\n", err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func shellJail(jail *Jail) error { | ||||||
|  | 	if false == jail.Running { | ||||||
|  | 		return errors.New("Jail is not running") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	jid := strconv.Itoa(jail.JID) | ||||||
|  |  | ||||||
|  | 	err := syscall.Exec("/usr/sbin/jexec", []string{"jexec", jid, "login", "-f", "root"}, os.Environ()) | ||||||
|  |  | ||||||
|  | 	// We should never get here, as syscall.Exec replace the gocage binary execution with jexec | ||||||
|  | 	// This means the moment syscall.Exec fires, gocage execution halt. | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("Exec returned %v\n", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										289
									
								
								cmd/create.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								cmd/create.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,289 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"fmt" | ||||||
|  | 	//"log" | ||||||
|  | 	"time" | ||||||
|  | 	"errors" | ||||||
|  | 	"strings" | ||||||
|  | 	cp "github.com/otiai10/copy" | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TODO : Add a flag to specify which parts of freebsd base we want : Slim jail only need base.txz, neither lib32 nor src.txz | ||||||
|  | func CreateJail(args []string) { | ||||||
|  | 	var err error | ||||||
|  | 	var jtype []string | ||||||
|  |  | ||||||
|  | 	if gCreateArgs.BaseJail && gCreateArgs.Release == "" { | ||||||
|  | 		fmt.Println("Release should be set when creating basejail") | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(gCreateArgs.JailType) > 0 { | ||||||
|  | 		jtype = []string{gCreateArgs.JailType} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	for _, jname := range args { | ||||||
|  | 		// Check if jail exist and is distinctly named | ||||||
|  | 		_, err = getJailFromArray(jname, jtype, gJails) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if strings.EqualFold(err.Error(), "Jail not found") { | ||||||
|  | 				 | ||||||
|  | 			} else { | ||||||
|  | 				fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			fmt.Printf("Jail exist: %s\n", jname) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Printf("  > create jail %s\n", jname) | ||||||
|  |  | ||||||
|  | 		var ds *Datastore | ||||||
|  | 		if len(gCreateArgs.Datastore) > 0 { | ||||||
|  | 			fmt.Printf("DEBUG: Use %s datastore\n", gCreateArgs.Datastore) | ||||||
|  | 			ds, err = getDatastoreFromArray(gCreateArgs.Datastore, gDatastores) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR Getting datastore: %s\n", gCreateArgs.Datastore, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			ds = &gDatastores[0] | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Get base template if specified | ||||||
|  | 		if gCreateArgs.BaseJail { | ||||||
|  | 			/************************************************************************** | ||||||
|  | 			*  Create based jail from a template | ||||||
|  | 			*/ | ||||||
|  | 			log.Debugf("Jail will be created read-only from release %s\n", gCreateArgs.Release) | ||||||
|  |  | ||||||
|  | 			// First check if we got release on the same datastore | ||||||
|  | 			releasePath := fmt.Sprintf("%s/releases/%s/root", ds.Mountpoint, gCreateArgs.Release) | ||||||
|  | 			_, err := os.Stat(releasePath) | ||||||
|  | 			if os.IsNotExist(err) { | ||||||
|  | 				fmt.Printf("ERROR: Release locally not available. Run \"gocage fetch\"\n") | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Create jail datasets | ||||||
|  | 			dstDset := fmt.Sprintf("%s/jails/%s", ds.ZFSDataset, jname) | ||||||
|  | 			fmt.Printf("    > Initialize dataset %s\n", dstDset) | ||||||
|  | 			err = zfsCreateDataset(dstDset, "", "") | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR creating dataset %s: %s\n", dstDset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			// Create jail root datasets | ||||||
|  | 			dstRootDset := fmt.Sprintf("%s/jails/%s/root", ds.ZFSDataset, jname) | ||||||
|  | 			fmt.Printf("    > Initialize dataset %s\n", dstRootDset) | ||||||
|  | 			err = zfsCreateDataset(dstRootDset, "", "") | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR creating dataset %s: %s\n", dstRootDset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Create needed directories with basejail permissions | ||||||
|  | 			fmt.Printf("    > Create base read-only directories\n") | ||||||
|  | 			dstRootDir := fmt.Sprintf("%s/jails/%s/root", ds.Mountpoint, jname) | ||||||
|  | 			for _, d := range append(gBaseDirs, gEmptyDirs...) { | ||||||
|  | 				dstPath := dstRootDir | ||||||
|  | 				srcPath := releasePath | ||||||
|  | 				for _, cd := range strings.Split(d, "/") { | ||||||
|  | 					srcPath = fmt.Sprintf("%s/%s", srcPath, cd) | ||||||
|  | 					dstPath = fmt.Sprintf("%s/%s", dstPath, cd) | ||||||
|  | 					_, err := os.Stat(dstPath) | ||||||
|  | 					if errors.Is(err, os.ErrNotExist) { | ||||||
|  | 						srcPerm, err := getPermissions(srcPath) | ||||||
|  | 						if err != nil { | ||||||
|  | 							fmt.Printf("ERROR getting permissions of %s: %s\n", srcPath, err.Error()) | ||||||
|  | 							return | ||||||
|  | 						} | ||||||
|  | 						err = os.Mkdir(dstPath, srcPerm.Mode().Perm()) | ||||||
|  | 						if err != nil { | ||||||
|  | 							fmt.Printf("ERROR creating directory %s: %s\n", dstPath, err.Error()) | ||||||
|  | 							return | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Copy these from basejail | ||||||
|  | 			fmt.Printf("    > Create base writable directories\n") | ||||||
|  | 			for _, d := range gCopyDirs { | ||||||
|  | 				err := cp.Copy(fmt.Sprintf("%s/%s", releasePath, d), fmt.Sprintf("%s/%s", dstRootDir, d)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("ERROR copying %s to %s: %s\n", fmt.Sprintf("%s/%s", releasePath, d), | ||||||
|  | 						   fmt.Sprintf("%s/%s", dstRootDir, d), err.Error()) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// Copy defaults.json... | ||||||
|  | 			jailConfPath := fmt.Sprintf("%s/jails/%s/config.json", ds.Mountpoint, jname) | ||||||
|  | 			err = copyFile(fmt.Sprintf("%s/defaults.json", ds.Mountpoint), | ||||||
|  | 				       jailConfPath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR creating config.json: %s\n", err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// ... and update it | ||||||
|  | 			jailConf, err := getJailConfig(jailConfPath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Println("ERROR reading jail config from %s", jailConfPath) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Build jail object from config | ||||||
|  | 			jailRootPath := fmt.Sprintf("%s/jails/%s/%s", ds.Mountpoint, jname, "root") | ||||||
|  | 			j := Jail{ | ||||||
|  | 				Name:       jailConf.Host_hostuuid, | ||||||
|  | 				Config:     jailConf, | ||||||
|  | 				ConfigPath: jailConfPath, | ||||||
|  | 				Datastore:  ds.Name, | ||||||
|  | 				RootPath:   jailRootPath, | ||||||
|  | 				Running:    false, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// We need to store the basejail template. We could : | ||||||
|  | 			// 1. Use "origin" ? | ||||||
|  | 			// 2. Add a json item to config ("basejail_template" p.e.), but iocage would delete it once jail is started from iocage | ||||||
|  | 			// 3. Add a gocage specific config ("config.gocage.json" p.e.) | ||||||
|  | 			j.Config.Jailtype = "basejail" | ||||||
|  | 			j.Config.Origin = gCreateArgs.Release | ||||||
|  | 			j.Config.Host_hostname = jname | ||||||
|  | 			j.Config.Host_hostuuid = jname | ||||||
|  |  | ||||||
|  | 			j.WriteConfigToDisk(false) | ||||||
|  |  | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// Create fstab | ||||||
|  | 			fstabHandle, err := os.Create(fmt.Sprintf("%s/jails/%s/fstab", ds.Mountpoint, jname)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR creating fstab: %s", err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			defer fstabHandle.Close() | ||||||
|  |  | ||||||
|  | 			for _, d := range gBaseDirs { | ||||||
|  | 				fmt.Fprintf(fstabHandle, "%s\t%s\tnullfs\tro\t0\t0\n", fmt.Sprintf("%s/%s", releasePath, d), fmt.Sprintf("%s/%s", dstRootDir, d)) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			fmt.Printf("  > Jail created!\n") | ||||||
|  | 		} else { | ||||||
|  | 			/************************************************************************** | ||||||
|  | 			 *  Create normal jail with its own freebsd base | ||||||
|  | 			 */ | ||||||
|  | 			log.Debugf("Creating jail with its own freebsd base\n") | ||||||
|  |  | ||||||
|  | 			// First check if we got release on the same datastore | ||||||
|  | 			_, err := os.Stat(fmt.Sprintf("%s/releases/%s/root", ds.Mountpoint, gCreateArgs.Release)) | ||||||
|  | 			if os.IsNotExist(err) { | ||||||
|  | 				fmt.Printf("ERROR: Release locally not available. Run \"gocage fetch\"\n") | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// Create and populate jail filesystem from release | ||||||
|  | 			dstDset := fmt.Sprintf("%s/jails/%s", ds.ZFSDataset, jname) | ||||||
|  | 			fmt.Printf("    > Initialize dataset %s\n", dstDset) | ||||||
|  | 			sNow := time.Now().Format("20060102150405") | ||||||
|  | 			reldset := fmt.Sprintf("%s/releases/%s", ds.ZFSDataset, gCreateArgs.Release) | ||||||
|  | 			err = zfsSnapshot(reldset, sNow) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR Creating snapshot of %s: %s\n", reldset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			err = zfsCopy(fmt.Sprintf("%s@%s", reldset, sNow), dstDset) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR sending snapshot to %s: %s\n", dstDset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			// Remove snapshot of release, then snapshot of destination dataset | ||||||
|  | 			err = zfsDestroy(fmt.Sprintf("%s@%s", reldset, sNow)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR destroying snapshot %s: %s\n", reldset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			err = zfsDestroy(fmt.Sprintf("%s@%s", dstDset, sNow)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR destroying snapshot %s: %s\n", dstDset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			dstRootDset := fmt.Sprintf("%s/jails/%s/root", ds.ZFSDataset, jname) | ||||||
|  | 			fmt.Printf("    > Initialize dataset %s\n", dstRootDset) | ||||||
|  | 			relrootdset := fmt.Sprintf("%s/releases/%s/root", ds.ZFSDataset, gCreateArgs.Release) | ||||||
|  | 			err = zfsSnapshot(relrootdset, sNow) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR Creating snapshot of %s: %s\n", relrootdset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			err = zfsCopy(fmt.Sprintf("%s@%s", relrootdset, sNow), dstRootDset) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR sending snapshot to %s: %s\n", dstRootDset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			// Remove snapshot of release, then snapshot of destination dataset | ||||||
|  | 			err = zfsDestroy(fmt.Sprintf("%s@%s", relrootdset, sNow)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR destroying snapshot %s: %s\n", relrootdset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			err = zfsDestroy(fmt.Sprintf("%s@%s", dstRootDset, sNow)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR destroying snapshot %s: %s\n", dstRootDset, err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// Copy defaults.json... | ||||||
|  | 			jailConfPath := fmt.Sprintf("%s/jails/%s/config.json", ds.Mountpoint, jname) | ||||||
|  | 			err = copyFile(fmt.Sprintf("%s/defaults.json", ds.Mountpoint),  | ||||||
|  | 					jailConfPath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR creating config.json: %s\n", err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// ... and update it | ||||||
|  | 			jailConf, err := getJailConfig(jailConfPath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Println("ERROR reading jail config from %s", jailConfPath) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Build jail object from config | ||||||
|  | 			jailRootPath := fmt.Sprintf("%s/jails/%s/%s", ds.Mountpoint, jname, "root") | ||||||
|  | 			j := Jail{ | ||||||
|  | 				Name:       jailConf.Host_hostuuid, | ||||||
|  | 				Config:     jailConf, | ||||||
|  | 				ConfigPath: jailConfPath, | ||||||
|  | 				Datastore:  ds.Name, | ||||||
|  | 				RootPath:   jailRootPath, | ||||||
|  | 				Running:    false, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			j.Config.Release = gCreateArgs.Release | ||||||
|  | 			j.Config.Host_hostname = jname | ||||||
|  | 			j.Config.Host_hostuuid = jname | ||||||
|  | 			j.Config.Jailtype = "jail" | ||||||
|  | 						 | ||||||
|  | 			j.WriteConfigToDisk(false) | ||||||
|  |  | ||||||
|  | 			/////////////////////////////////////////////////////////////////////// | ||||||
|  | 			// Create fstab | ||||||
|  | 			fstabHandle, err := os.Create(fmt.Sprintf("%s/jails/%s/fstab", ds.Mountpoint, jname)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR creating fstab: %s", err.Error()) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			defer fstabHandle.Close() | ||||||
|  | 			fmt.Printf("  > Jail created!\n") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										108
									
								
								cmd/datastore.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								cmd/datastore.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"errors" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | )  | ||||||
|  |  | ||||||
|  | func ListDatastores(args []string, display bool) error { | ||||||
|  | 	/*************************************************************** | ||||||
|  | 	/   Filter datastores by names given on command line | ||||||
|  | 	/**************************************************************/ | ||||||
|  | 	for _, d := range args { | ||||||
|  | 		cmd := fmt.Sprintf("zfs list -p -H -o name,used,available,referenced %s", d) | ||||||
|  | 		out, err := executeCommand(cmd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if strings.HasSuffix(err.Error(), "No such file or directory") { | ||||||
|  | 				return errors.New(fmt.Sprintf("Datastore does not exist: %s", d)) | ||||||
|  | 			} else { | ||||||
|  | 				return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		fields := strings.Fields(out) | ||||||
|  | 		if len(fields) < 4 { | ||||||
|  | 			return errors.New(fmt.Sprintf("Error parsing output of \"%s\": Not enough fields", cmd)) | ||||||
|  | 		} | ||||||
|  | 		u, _ := strconv.ParseUint(fields[1], 10, 64) | ||||||
|  | 		a, _ := strconv.ParseUint(fields[2], 10, 64) | ||||||
|  | 		r, _ := strconv.ParseUint(fields[3], 10, 64) | ||||||
|  | 		ds := Datastore{Name: d[1:], Mountpoint: d, ZFSDataset: fields[0], Used: u, Referenced: r, Available: a} | ||||||
|  | 		 | ||||||
|  | 		err = loadDefaultsForDatastore(&ds) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		gDatastores = append(gDatastores, ds) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fields := strings.Split(gDisplayDColumns, ",") | ||||||
|  |  | ||||||
|  | 	if true == display { | ||||||
|  | 		/*************************************************************** | ||||||
|  | 		/   Sort datastores | ||||||
|  | 		/     We support 3 sort criteria max | ||||||
|  | 		/**************************************************************/ | ||||||
|  | 		if len(gSortDSFields) > 0 && gSortDSFields != "none" {		 | ||||||
|  | 			ds := initDatastoreSortStruct() | ||||||
|  |  | ||||||
|  | 			// The way we manage criteria quantity is not very elegant... | ||||||
|  | 			var fct1, fct2, fct3 *reflect.Value | ||||||
|  | 			for i, c := range strings.Split(gSortDSFields, ",") { | ||||||
|  | 				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(ds, fctName) | ||||||
|  | 				 | ||||||
|  | 				if err != nil { | ||||||
|  | 					fieldName := strings.Replace(strings.Replace(c, "-", "", 1), "+", "", 1) | ||||||
|  | 					return errors.New(fmt.Sprintf("ERROR getting DatastoreSort struct field %s. Please check the field name: %s\n", fctName, fieldName)) | ||||||
|  | 				} | ||||||
|  | 				switch i + 1 { | ||||||
|  | 				case 1: | ||||||
|  | 					fct1 = fct | ||||||
|  | 				case 2: | ||||||
|  | 					fct2 = fct | ||||||
|  | 				case 3: | ||||||
|  | 					fct3 = fct | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			switch len(strings.Split(gSortDSFields, ",")) { | ||||||
|  | 			case 1: | ||||||
|  | 				DatastoresOrderedBy(fct1.Interface().(datastoreLessFunc)).Sort(gDatastores) | ||||||
|  | 			case 2: | ||||||
|  | 				DatastoresOrderedBy(fct1.Interface().(datastoreLessFunc), fct2.Interface().(datastoreLessFunc)).Sort(gDatastores) | ||||||
|  | 			case 3: | ||||||
|  | 				DatastoresOrderedBy(fct1.Interface().(datastoreLessFunc), fct2.Interface().(datastoreLessFunc), fct3.Interface().(datastoreLessFunc)).Sort(gDatastores) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		displayDatastoresFields(gDatastores, fields) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /******************************************************************************** | ||||||
|  |  * Load jails default config from $datastore/defaults.json | ||||||
|  |  *******************************************************************************/ | ||||||
|  | func loadDefaultsForDatastore(ds *Datastore) error { | ||||||
|  | 	jc, err := getJailConfig(fmt.Sprintf("%s/defaults.json", ds.Mountpoint)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ds.DefaultJailConfig = jc | ||||||
|  | 	//gDefaultConfig = append(gDefaultConfig, jc) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								cmd/destroy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								cmd/destroy.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	//"log" | ||||||
|  | 	"time" | ||||||
|  | 	//"errors" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func DestroyJails(args []string) { | ||||||
|  | 	for _, a := range args { | ||||||
|  | 		cj, err := getJailFromArray(a, []string{""}, gJails) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting jail: %s\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if cj.Running == true { | ||||||
|  | 			fmt.Printf("Jail %s is running\n", cj.Name) | ||||||
|  | 			if gForce == false { | ||||||
|  | 				var answer string | ||||||
|  | 				fmt.Printf("Stop jail and delete? (y/n) ") | ||||||
|  | 				fmt.Scanln(&answer) | ||||||
|  | 				if false == strings.EqualFold(answer, "y") { | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			fmt.Printf("Stopping jail %s\n", cj.Name) | ||||||
|  | 			StopJail([]string{fmt.Sprintf("%s/%s", cj.Datastore, cj.Name)}) | ||||||
|  | 			// Give some time to the host OS to free all mounts accessing processes | ||||||
|  | 			time.Sleep(1 * time.Second) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Get root and config datasets, then destroy | ||||||
|  | 		dsRootName, err := zfsGetDatasetByMountpoint(cj.RootPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting root dataset: %s\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		//fmt.Printf("DEBUG: Prepare to zfs destroy %s\n", dsRootName) | ||||||
|  | 		if err = zfsDestroy(dsRootName); err != nil { | ||||||
|  | 			fmt.Printf("Error deleting root dataset: %s\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		dsConfName, err := zfsGetDatasetByMountpoint(cj.ConfigPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting config dataset: %s\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		//fmt.Printf("DEBUG: Prepare to zfs destroy %s\n", dsConfName) | ||||||
|  | 		if err = zfsDestroy(dsConfName); err != nil { | ||||||
|  | 			fmt.Printf("Error deleting config dataset: %s\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("Jail %s is no more!\n", cj.Name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										378
									
								
								cmd/fetch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										378
									
								
								cmd/fetch.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,378 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"fmt" | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
|  | 	//"errors" | ||||||
|  | 	"strings" | ||||||
|  | 	"net/http" | ||||||
|  | 	//"archive/tar" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"crypto/sha256" | ||||||
|  |  | ||||||
|  | 	//"github.com/ulikunitz/xz" | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	ReleaseServer = "download.freebsd.org" | ||||||
|  | 	ReleaseRootDir = "ftp/releases" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	// TODO : Make this a config/cmd line setting | ||||||
|  | 	//FetchFiles = []string{"base.txz", "lib32.txz", "src.txz"} | ||||||
|  | 	FetchFiles = []string{"base.txz"} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TODO: Check if files already exist | ||||||
|  | // Fetch release files, verify, put in datastore under ${datastore}/download | ||||||
|  | // Only support http and file protocols | ||||||
|  | func fetchRelease(release string, proto string, arch string, datastore string, fetchFrom string) error { | ||||||
|  | 	var ds Datastore | ||||||
|  | 	 | ||||||
|  | 	log.SetReportCaller(true) | ||||||
|  | 	 | ||||||
|  | 	if len(fetchFrom) > 0 { | ||||||
|  | 		proto = strings.Split(fetchFrom, ":")[0] | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if false == strings.EqualFold(proto, "http") && | ||||||
|  | 		false == strings.EqualFold(proto, "file") { | ||||||
|  | 		return fmt.Errorf("Unsupported protocol: %s\n", proto) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	for _, ds = range gDatastores { | ||||||
|  | 		if strings.EqualFold(datastore, ds.Name) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if false == strings.EqualFold(datastore, ds.Name) { | ||||||
|  | 		return fmt.Errorf("Datastore not found: %s\n", datastore) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Check datastore have a download dataset, and it is mounted | ||||||
|  | 	downloadDsName := fmt.Sprintf("%s/download", ds.ZFSDataset) | ||||||
|  | 	downloadDsMountPoint := fmt.Sprintf("%s/download", ds.Mountpoint) | ||||||
|  | 	exist, err := doZfsDatasetExist(downloadDsName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("Error accessing dataset %s: %v\n", downloadDsName, err) | ||||||
|  | 	} | ||||||
|  | 	if false == exist { | ||||||
|  | 		// Then create dataset | ||||||
|  | 		if err := zfsCreateDataset(downloadDsName, downloadDsMountPoint, "lz4"); err != nil { | ||||||
|  | 			return fmt.Errorf("Error creating dataset %s: %v\n", downloadDsName, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Create download/XX.X dataset if necessary | ||||||
|  | 	thisDownloadDsName := fmt.Sprintf("%s/%s", downloadDsName, release) | ||||||
|  | 	thisDownloadDsMountPoint := fmt.Sprintf("%s/%s", downloadDsMountPoint, release) | ||||||
|  | 	exist, err = doZfsDatasetExist(thisDownloadDsName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("Error accessing dataset %s: %v\n", thisDownloadDsName, err) | ||||||
|  | 	} | ||||||
|  | 	if false == exist { | ||||||
|  | 		// Then create dataset | ||||||
|  | 		if err := zfsCreateDataset(thisDownloadDsName, thisDownloadDsMountPoint, "lz4"); err != nil { | ||||||
|  | 			return fmt.Errorf("Error creating dataset %s: %v\n", thisDownloadDsName, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var fetchUrl string | ||||||
|  | 	if len(fetchFrom) > 0 { | ||||||
|  | 		fetchUrl = fmt.Sprintf("%s/%s", fetchFrom, release) | ||||||
|  | 	} else { | ||||||
|  | 		fetchUrl = fmt.Sprintf("%s://%s/%s/%s/%s", proto, ReleaseServer, ReleaseRootDir, arch, release) | ||||||
|  | 	} | ||||||
|  | 	log.Debugf("FetchURL = %s", fetchUrl) | ||||||
|  | 	 | ||||||
|  | 	// check if proto/server/arch/release is available | ||||||
|  | 	if strings.EqualFold(proto, "http") { | ||||||
|  | 		resp, err := http.Get(fetchUrl) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Can not get %s: %v\n", fetchUrl, err) | ||||||
|  | 		} | ||||||
|  | 		defer resp.Body.Close() | ||||||
|  | 		if resp.StatusCode != 200 { | ||||||
|  | 			return fmt.Errorf("Get %s returned %d, check release name\n", fetchUrl, resp.StatusCode) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Fetch files | ||||||
|  | 	// Get MANIFEST so we get sha256 sums | ||||||
|  | 	if err := fetchFile(proto, fetchUrl, "MANIFEST", thisDownloadDsMountPoint, []byte{}); err != nil { | ||||||
|  | 		return fmt.Errorf("%v\n", err) | ||||||
|  | 	} | ||||||
|  | 	// Build an array of "file;checksum" | ||||||
|  | 	checksumMap, err := buildFileChecksumFromManifest(fmt.Sprintf("%s/MANIFEST", thisDownloadDsMountPoint), FetchFiles) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("%v\n", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Fetch remaining files, verify integrity and write to disk | ||||||
|  | 	for f, c := range checksumMap { | ||||||
|  | 		if err := fetchFile(proto, fetchUrl, f, thisDownloadDsMountPoint, c); err != nil { | ||||||
|  | 			return fmt.Errorf("%v\n", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Extract release files stored in iocage/download/$RELEASE/ to iocage/releases/$RELEASE/root/ | ||||||
|  | func extractRelease(release string, datastore string) { | ||||||
|  | 	log.SetReportCaller(true) | ||||||
|  | 	 | ||||||
|  | 	var ds Datastore | ||||||
|  | 	 | ||||||
|  | 	for _, ds = range gDatastores { | ||||||
|  | 		if strings.EqualFold(datastore, ds.Name) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if false == strings.EqualFold(datastore, ds.Name) { | ||||||
|  | 		fmt.Printf("Datastore not found: %s\n", datastore) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Check datastore have a releases dataset, and it is mounted | ||||||
|  | 	releaseDsName := fmt.Sprintf("%s/releases", ds.ZFSDataset) | ||||||
|  | 	releaseDsMountPoint := fmt.Sprintf("%s/releases", ds.Mountpoint) | ||||||
|  | 	exist, err := doZfsDatasetExist(releaseDsName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error accessing dataset %s: %v\n", releaseDsName, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if false == exist { | ||||||
|  | 		// Then create dataset | ||||||
|  | 		if err := zfsCreateDataset(releaseDsName, releaseDsMountPoint, "lz4"); err != nil { | ||||||
|  | 			fmt.Printf("Error creating dataset %s: %v\n", releaseDsName, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Create releases/XX.X dataset if necessary | ||||||
|  | 	thisReleaseDsName := fmt.Sprintf("%s/%s", releaseDsName, release) | ||||||
|  | 	thisReleaseDsMountPoint := fmt.Sprintf("%s/%s", releaseDsMountPoint, release) | ||||||
|  | 	exist, err = doZfsDatasetExist(thisReleaseDsName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error accessing dataset %s: %v\n", thisReleaseDsName, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if false == exist { | ||||||
|  | 		// Then create dataset | ||||||
|  | 		if err := zfsCreateDataset(thisReleaseDsName, thisReleaseDsMountPoint, "lz4"); err != nil { | ||||||
|  | 			fmt.Printf("Error creating dataset %s: %v\n", thisReleaseDsName, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Create releases/XX.X/root dataset if necessary | ||||||
|  | 	thisReleaseRootDsName := fmt.Sprintf("%s/root", thisReleaseDsName) | ||||||
|  | 	thisReleaseRootDsMountPoint := fmt.Sprintf("%s/root", thisReleaseDsMountPoint) | ||||||
|  | 	exist, err = doZfsDatasetExist(thisReleaseRootDsName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error accessing dataset %s: %v\n", thisReleaseRootDsName, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if false == exist { | ||||||
|  | 		// Then create dataset | ||||||
|  | 		if err := zfsCreateDataset(thisReleaseRootDsName, thisReleaseRootDsMountPoint, "lz4"); err != nil { | ||||||
|  | 			fmt.Printf("Error creating dataset %s: %v\n", thisReleaseRootDsName, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Now extract download/$RELEASE/*.txz to releases/XX.X/root | ||||||
|  | 	downloadDsMountPoint := fmt.Sprintf("%s/download", ds.Mountpoint) | ||||||
|  | 	downloadDir := fmt.Sprintf("%s/%s", downloadDsMountPoint, release) | ||||||
|  | 	 | ||||||
|  | 	d, err := os.Open(downloadDir) | ||||||
|  | 	defer d.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Can not read %s directory: %v\n", downloadDir, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	files, err := d.Readdir(0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Can not browse %s directory: %v\n", downloadDir, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Extract every .txz files in FetchFiles | ||||||
|  | 	for _, fi := range files { | ||||||
|  | 		if false == fi.IsDir() { | ||||||
|  | 			if strings.HasSuffix(fi.Name(), ".txz") { | ||||||
|  | 				if isStringInArray(FetchFiles, fi.Name()) { | ||||||
|  | 					ar := fmt.Sprintf("%s/%s", downloadDir, fi.Name()) | ||||||
|  | 					fmt.Printf("Extracting file %s to %s... ", ar, thisReleaseRootDsMountPoint) | ||||||
|  | 					// pure Go method, sorry this is so slow. Also I did not handle permissions in this | ||||||
|  | 	/*				f, err := os.Open(ar) | ||||||
|  | 					defer f.Close() | ||||||
|  | 					if err != nil { | ||||||
|  | 						fmt.Printf("Can not open %s: %v\n", ar, err) | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 					// xz reader | ||||||
|  | 					r, err := xz.NewReader(f) | ||||||
|  | 					if err != nil { | ||||||
|  | 						fmt.Printf("Can not read %s: %v\n", ar, err) | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 					// tar reader | ||||||
|  | 					tr := tar.NewReader(r) | ||||||
|  | 					// Iterate through the files in the archive. | ||||||
|  | 					for { | ||||||
|  | 						hdr, err := tr.Next() | ||||||
|  | 						if err == io.EOF { | ||||||
|  | 							// end of tar archive | ||||||
|  | 							break | ||||||
|  | 						} | ||||||
|  | 						if err != nil { | ||||||
|  | 							log.Fatal(err) | ||||||
|  | 						} | ||||||
|  | 						switch hdr.Typeflag { | ||||||
|  | 							case tar.TypeDir: | ||||||
|  | 								// create a directory | ||||||
|  | 								dest := fmt.Sprintf("%s/%s", thisReleaseRootDsMountPoint, hdr.Name) | ||||||
|  | 								// FIXME: Access rights? | ||||||
|  | 								err = os.MkdirAll(dest, 0777) | ||||||
|  | 								if err != nil { | ||||||
|  | 									log.Fatal(err) | ||||||
|  | 								} | ||||||
|  | 							case tar.TypeReg, tar.TypeRegA: | ||||||
|  | 								// write a file | ||||||
|  | 								dest := fmt.Sprintf("%s/%s", thisReleaseRootDsMountPoint, hdr.Name) | ||||||
|  | 								w, err := os.Create(dest) | ||||||
|  | 								defer w.Close() | ||||||
|  | 								if err != nil { | ||||||
|  | 									log.Fatal(err) | ||||||
|  | 								} | ||||||
|  | 								_, err = io.Copy(w, tr) | ||||||
|  | 								if err != nil { | ||||||
|  | 									log.Fatal(err) | ||||||
|  | 								} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 	*/ | ||||||
|  | 					cmd := fmt.Sprintf("/usr/bin/tar xpf %s -C %s", ar, thisReleaseRootDsMountPoint) | ||||||
|  | 					out, err := executeCommand(cmd) | ||||||
|  | 					if err != nil && len(out) > 0 { | ||||||
|  | 						fmt.Printf("Error: %v: %s\n", err, out) | ||||||
|  | 					} else { | ||||||
|  | 						fmt.Printf("Done\n") | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func fetchFile(proto, baseUrl, fileName, storeDir string, checksum []byte) error { | ||||||
|  | 	// Check storeDir exist | ||||||
|  | 	_, err := os.Stat(storeDir) | ||||||
|  | 	if os.IsNotExist(err) { | ||||||
|  | 		return fmt.Errorf("Directory does not exist: %s\n", storeDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	url := fmt.Sprintf("%s/%s", baseUrl, fileName) | ||||||
|  | 	fmt.Printf("Fetching %s...", url) | ||||||
|  | 	 | ||||||
|  | 	var body []byte | ||||||
|  | 	if strings.EqualFold(proto, "http") { | ||||||
|  | 		resp, err := http.Get(url) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf(" Error\n") | ||||||
|  | 			return fmt.Errorf("Can not get %s: %v\n", url, err) | ||||||
|  | 		} | ||||||
|  | 		defer resp.Body.Close() | ||||||
|  | 		body, err = io.ReadAll(resp.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Can not read %s response body: %v\n", url, err) | ||||||
|  | 		} | ||||||
|  | 	} else if strings.EqualFold(proto, "file") { | ||||||
|  | 		url = strings.Replace(url, "file:", "", 1) | ||||||
|  | 		f, err := os.Open(url) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Error accessing file %s", url) | ||||||
|  | 		} | ||||||
|  | 		defer f.Close() | ||||||
|  | 		body, err = io.ReadAll(f) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Can not read file %s: %v\n", url, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Check integrity | ||||||
|  | 	if len(checksum) > 0 { | ||||||
|  | 		err = checkIntegrity(body, checksum) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Error checking integrity") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	dest := fmt.Sprintf("%s/%s", storeDir, fileName) | ||||||
|  |  | ||||||
|  | 	f, err := os.Create(dest)       // creates a file at current directory | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("Can not create file %s: %v\n", dest, err) | ||||||
|  | 	} | ||||||
|  | 	defer f.Close() | ||||||
|  | 	f.Write(body) | ||||||
|  | 	fmt.Printf(" Done\n") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | func checkIntegrity(data []byte, checksum []byte) error { | ||||||
|  | 	sum := sha256.Sum256(data) | ||||||
|  | 	 | ||||||
|  | 	if false == bytes.Equal(checksum[:],sum[:]) { | ||||||
|  | 		return fmt.Errorf("Invalid checksum: %x != %x", sum, checksum) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get checksum from manifest, for each file in fileList | ||||||
|  | /* MANIFEST format: | ||||||
|  |  * base-dbg.txz    a5b51f3d54686509e91ca9c30e9f1cd93dc757f25c643609b3c35e7119c0531d        1654    base_dbg        "Base system (Debugging)"       off | ||||||
|  |  * base.txz        e85b256930a2fbc04b80334106afecba0f11e52e32ffa197a88d7319cf059840        26492   base    "Base system (MANDATORY)"       on | ||||||
|  |  * kernel-dbg.txz  6b47a6cb83637af1f489aa8cdb802d9db936ea864887188cfc69d8075762214e        912     kernel_dbg      "Kernel (Debugging)"    on | ||||||
|  |  */ | ||||||
|  | func buildFileChecksumFromManifest(manifest string, fileList []string) (map[string][]byte, error) { | ||||||
|  | 	var ckarr = make(map[string][]byte) | ||||||
|  |  | ||||||
|  | 	rm, err := os.Open(manifest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return ckarr, fmt.Errorf("Unable to open MANIFEST: %v", err) | ||||||
|  | 	} | ||||||
|  | 	fscan := bufio.NewScanner(rm) | ||||||
|  | 	fscan.Split(bufio.ScanLines) | ||||||
|  | 	 | ||||||
|  | 	// For each MANIFEST line... | ||||||
|  | 	for fscan.Scan() { | ||||||
|  | 		fields := strings.Fields(fscan.Text()) | ||||||
|  | 		fn := fields[0] | ||||||
|  | 		fck := fields[1] | ||||||
|  | 		hexSum, err := hex.DecodeString(fck) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return ckarr, fmt.Errorf("Invalid value for checksum %s", fck) | ||||||
|  | 		} | ||||||
|  | 		// ... Find the corresponding file in fileList, then add to checksum array ckarr | ||||||
|  | 		for _, f := range fileList { | ||||||
|  | 			if strings.EqualFold(f, fn) { | ||||||
|  | 				ckarr[fn] = hexSum | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if len(ckarr) < len(fileList) { | ||||||
|  | 		return ckarr, fmt.Errorf("Missing file in MANIFEST") | ||||||
|  | 	} | ||||||
|  | 	return ckarr, nil | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								cmd/freebsd-update.conf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								cmd/freebsd-update.conf.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	fbsdUpdateConfig = ` | ||||||
|  | 	# $FreeBSD$ | ||||||
|  | 	 | ||||||
|  | 	# Trusted keyprint.  Changing this is a Bad Idea unless you've received | ||||||
|  | 	# a PGP-signed email from <security-officer@FreeBSD.org> telling you to | ||||||
|  | 	# change it and explaining why. | ||||||
|  | 	KeyPrint 800651ef4b4c71c27e60786d7b487188970f4b4169cc055784e21eb71d410cc5 | ||||||
|  | 	 | ||||||
|  | 	# Server or server pool from which to fetch updates.  You can change | ||||||
|  | 	# this to point at a specific server if you want, but in most cases | ||||||
|  | 	# using a "nearby" server won't provide a measurable improvement in | ||||||
|  | 	# performance. | ||||||
|  | 	ServerName update.FreeBSD.org | ||||||
|  | 	 | ||||||
|  | 	# Components of the base system which should be kept updated. | ||||||
|  | 	Components world | ||||||
|  | 	 | ||||||
|  | 	# Example for updating the userland and the kernel source code only: | ||||||
|  | 	# Components src/base src/sys world | ||||||
|  | 	 | ||||||
|  | 	# Paths which start with anything matching an entry in an IgnorePaths | ||||||
|  | 	# statement will be ignored. | ||||||
|  | 	IgnorePaths | ||||||
|  | 	 | ||||||
|  | 	# Paths which start with anything matching an entry in an IDSIgnorePaths | ||||||
|  | 	# statement will be ignored by "freebsd-update IDS". | ||||||
|  | 	IDSIgnorePaths /usr/share/man/cat | ||||||
|  | 	IDSIgnorePaths /usr/share/man/whatis | ||||||
|  | 	IDSIgnorePaths /var/db/locate.database | ||||||
|  | 	IDSIgnorePaths /var/log | ||||||
|  | 	 | ||||||
|  | 	# Paths which start with anything matching an entry in an UpdateIfUnmodified | ||||||
|  | 	# statement will only be updated if the contents of the file have not been | ||||||
|  | 	# modified by the user (unless changes are merged; see below). | ||||||
|  | 	UpdateIfUnmodified /etc/ /var/ /root/ /.cshrc /.profile | ||||||
|  | 	 | ||||||
|  | 	# When upgrading to a new FreeBSD release, files which match MergeChanges | ||||||
|  | 	# will have any local changes merged into the version from the new release. | ||||||
|  | 	MergeChanges /etc/ | ||||||
|  | 	 | ||||||
|  | 	### Default configuration options: | ||||||
|  | 	 | ||||||
|  | 	# Directory in which to store downloaded updates and temporary | ||||||
|  | 	# files used by FreeBSD Update. | ||||||
|  | 	WorkDir /iocage/freebsd-update | ||||||
|  | 	 | ||||||
|  | 	# Destination to send output of "freebsd-update cron" if an error | ||||||
|  | 	# occurs or updates have been downloaded. | ||||||
|  | 	# MailTo root | ||||||
|  | 	 | ||||||
|  | 	# Is FreeBSD Update allowed to create new files? | ||||||
|  | 	# AllowAdd yes | ||||||
|  | 	 | ||||||
|  | 	# Is FreeBSD Update allowed to delete files? | ||||||
|  | 	# AllowDelete yes | ||||||
|  | 	 | ||||||
|  | 	# If the user has modified file ownership, permissions, or flags, should | ||||||
|  | 	# FreeBSD Update retain this modified metadata when installing a new version | ||||||
|  | 	# of that file? | ||||||
|  | 	# KeepModifiedMetadata yes | ||||||
|  | 	 | ||||||
|  | 	# When upgrading between releases, should the list of Components be | ||||||
|  | 	# read strictly (StrictComponents yes) or merely as a list of components | ||||||
|  | 	# which *might* be installed of which FreeBSD Update should figure out | ||||||
|  | 	# which actually are installed and upgrade those (StrictComponents no)? | ||||||
|  | 	StrictComponents yes | ||||||
|  | 	 | ||||||
|  | 	# When installing a new kernel perform a backup of the old one first | ||||||
|  | 	# so it is possible to boot the old kernel in case of problems. | ||||||
|  | 	BackupKernel no | ||||||
|  | 	 | ||||||
|  | 	# If BackupKernel is enabled, the backup kernel is saved to this | ||||||
|  | 	# directory. | ||||||
|  | 	# BackupKernelDir /boot/kernel.old | ||||||
|  | 	 | ||||||
|  | 	# When backing up a kernel also back up debug symbol files? | ||||||
|  | 	BackupKernelSymbolFiles no | ||||||
|  | 	 | ||||||
|  | 	# Create a new boot environment when installing patches | ||||||
|  | 	CreateBootEnv no | ||||||
|  | 	` | ||||||
|  | )  | ||||||
							
								
								
									
										241
									
								
								cmd/jailhost.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								cmd/jailhost.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,241 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"golang.org/x/net/route" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var defaultRoute4 = [4]byte{0, 0, 0, 0} | ||||||
|  | var defaultRoute6 = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||||||
|  | var local6 = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} | ||||||
|  |  | ||||||
|  | func Inet4AddrToString(ip4 route.Inet4Addr) string { | ||||||
|  | 	return fmt.Sprintf("%v.%v.%v.%v", ip4.IP[0], ip4.IP[1], ip4.IP[2], ip4.IP[3]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Inet6AddrToString(ip6 route.Inet6Addr) string { | ||||||
|  | 	return fmt.Sprintf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", | ||||||
|  | 		ip6.IP[0], ip6.IP[1], ip6.IP[2], ip6.IP[3], ip6.IP[4], ip6.IP[5], ip6.IP[6], ip6.IP[7], | ||||||
|  | 		ip6.IP[8], ip6.IP[9], ip6.IP[10], ip6.IP[11], ip6.IP[12], ip6.IP[13], ip6.IP[14], ip6.IP[15]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /***************************************************************************** | ||||||
|  | * Initialize default_interface, default_gatway4, default_gateway6 | ||||||
|  | *****************************************************************************/ | ||||||
|  | func (jh *JailHost) InitNetworkProperties() { | ||||||
|  | 	rib, _ := route.FetchRIB(0, route.RIBTypeRoute, 0) | ||||||
|  | 	messages, err := route.ParseRIB(route.RIBTypeRoute, rib) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, message := range messages { | ||||||
|  | 		route_message := message.(*route.RouteMessage) | ||||||
|  | 		addresses := route_message.Addrs | ||||||
|  | 		card_index := route_message.Index | ||||||
|  |  | ||||||
|  | 		if addresses[0].Family() == 2 { | ||||||
|  | 			var destination4, gateway4 *route.Inet4Addr | ||||||
|  | 			ok := false | ||||||
|  |  | ||||||
|  | 			if destination4, ok = addresses[0].(*route.Inet4Addr); !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if gateway4, ok = addresses[1].(*route.Inet4Addr); !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if destination4 == nil || gateway4 == nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if destination4.IP == defaultRoute4 { | ||||||
|  | 				card, _ := net.InterfaceByIndex(card_index) | ||||||
|  | 				//fmt.Printf("Default IPv4 gateway is %v on card %s\n", Inet4AddrToString(*gateway4), card.Name) | ||||||
|  | 				jh.default_interface = card.Name | ||||||
|  | 				jh.default_gateway4 = Inet4AddrToString(*gateway4) | ||||||
|  | 			} | ||||||
|  | 		} else if addresses[0].Family() == 28 { | ||||||
|  | 			var destination6, gateway6 *route.Inet6Addr | ||||||
|  | 			ok := false | ||||||
|  |  | ||||||
|  | 			if destination6, ok = addresses[0].(*route.Inet6Addr); !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if gateway6, ok = addresses[1].(*route.Inet6Addr); !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if destination6 == nil || gateway6 == nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if destination6.IP == defaultRoute6 && gateway6.IP != local6 { | ||||||
|  | 				card, _ := net.InterfaceByIndex(card_index) | ||||||
|  | 				//fmt.Printf("Default IPv6 gateway is %v on card %s\n", Inet6AddrToString(*gateway6), card.Name) | ||||||
|  | 				jh.default_interface = card.Name | ||||||
|  | 				jh.default_gateway6 = Inet6AddrToString(*gateway6) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (jh *JailHost) GetDefaultInterface() string { | ||||||
|  | 	if len(jh.default_interface) == 0 { | ||||||
|  | 		jh.InitNetworkProperties() | ||||||
|  | 	} | ||||||
|  | 	return jh.default_interface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (jh *JailHost) GetDefaultGateway4() string { | ||||||
|  | 	if len(jh.default_gateway4) == 0 { | ||||||
|  | 		jh.InitNetworkProperties() | ||||||
|  | 	} | ||||||
|  | 	return jh.default_gateway4 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (jh *JailHost) GetDefaultGateway6() string { | ||||||
|  | 	if len(jh.default_gateway6) == 0 { | ||||||
|  | 		jh.InitNetworkProperties() | ||||||
|  | 	} | ||||||
|  | 	return jh.default_gateway6 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /***************************************************************************** | ||||||
|  |  * Get all network interfaces | ||||||
|  |  ****************************************************************************/ | ||||||
|  | func (jh *JailHost) GetInterfaces() ([]string, error) { | ||||||
|  | 	var names []string | ||||||
|  | 	 | ||||||
|  | 	interfaces, err := net.Interfaces() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return names, fmt.Errorf("Error listing network interfaces: %v", err) | ||||||
|  | 	} | ||||||
|  | 	for _, n := range interfaces { | ||||||
|  | 		names = append(names, n.Name) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return names, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (jh *JailHost) GetBridgeMTU(bridgeName string) (int, error) { | ||||||
|  | 	bridge, err := net.InterfaceByName(bridgeName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return bridge.MTU, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /***************************************************************************** | ||||||
|  |  * Get all IPv4 currently in use on host | ||||||
|  |  ****************************************************************************/ | ||||||
|  | func getHostInUseIPv4() ([]string, error) { | ||||||
|  | 	var ips []string | ||||||
|  |  | ||||||
|  | 	re := regexp.MustCompile(ifconfigipv4re) | ||||||
|  |  | ||||||
|  | 	out, err := executeCommand("/sbin/ifconfig") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return ips, fmt.Errorf("Error executing \"/sbin/ifconfig\": %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, line := range strings.Split(out, "\n") { | ||||||
|  | 		if re.MatchString(line) { | ||||||
|  | 			ips = append(ips, re.FindStringSubmatch(line)[1]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ips, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getHostname() (string, error) { | ||||||
|  | 	out, err := executeCommand("/bin/hostname") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("Error executing \"/bin/hostname\": %v", err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return strings.Split(out, "\n")[0], nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getHostId() (string, error) { | ||||||
|  | 	var content []byte | ||||||
|  | 	var err     error | ||||||
|  | 	// return empty string if file does not exist | ||||||
|  | 	if content, err = ioutil.ReadFile("/etc/hostid"); err != nil { | ||||||
|  | 		if strings.HasSuffix(err.Error(), "no such file or directory") { | ||||||
|  | 			return "", nil | ||||||
|  | 		} | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return strings.Split(string(content), "\n")[0], nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getArch() (string, error) { | ||||||
|  | 	out, err := executeCommand("/usr/bin/uname -p") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("Error executing \"/usr/bin/uname -p\": %v", err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return strings.Split(out, "\n")[0], nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getFreeBSDVersion() (FreeBSDVersion, error) { | ||||||
|  | 	var version FreeBSDVersion | ||||||
|  | 	regex := `([0-9]{1,2})(\.)?([0-9]{1,2})?\-([^\-]*)(\-)?(p[0-9]{1,2})?` | ||||||
|  | 	 | ||||||
|  | 	re := regexp.MustCompile(regex) | ||||||
|  | 	 | ||||||
|  | 	out, err := executeCommand("/bin/freebsd-version") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return version, fmt.Errorf("Error executing \"/bin/freebsd-version\": %v", err) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	if re.MatchString(out) { | ||||||
|  | 		version.major, err = strconv.Atoi(re.FindStringSubmatch(out)[1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return version, err | ||||||
|  | 		} | ||||||
|  | 		version.minor, err = strconv.Atoi(re.FindStringSubmatch(out)[3]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return version, err | ||||||
|  | 		} | ||||||
|  | 		version.flavor = strings.Trim(re.FindStringSubmatch(out)[4], "\n") | ||||||
|  | 		 | ||||||
|  | 		// Skip the 'p' starting patch level | ||||||
|  | 		if len(re.FindStringSubmatch(out)[6]) > 0 { | ||||||
|  | 			version.patchLevel, err = strconv.Atoi(re.FindStringSubmatch(out)[6][1:]) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return version, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return version, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewJailHost() (JailHost, error) { | ||||||
|  | 	var jh  JailHost | ||||||
|  | 	var err error | ||||||
|  | 	 | ||||||
|  | 	if jh.hostname, err = getHostname(); err != nil { | ||||||
|  | 		return jh, err | ||||||
|  | 	} | ||||||
|  | 	if jh.arch, err = getArch(); err != nil { | ||||||
|  | 		return jh, err | ||||||
|  | 	} | ||||||
|  | 	if jh.hostid, err = getHostId(); err != nil { | ||||||
|  | 		return jh, err | ||||||
|  | 	} | ||||||
|  | 	if jh.version, err = getFreeBSDVersion(); err != nil { | ||||||
|  | 		return jh, err | ||||||
|  | 	} | ||||||
|  | 	return jh, nil | ||||||
|  | } | ||||||
							
								
								
									
										198
									
								
								cmd/list.go
									
									
									
									
									
								
							
							
						
						
									
										198
									
								
								cmd/list.go
									
									
									
									
									
								
							| @ -1,24 +1,29 @@ | |||||||
| package cmd | package cmd | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"os" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"gocage/jail" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"log" | 	"log" | ||||||
|  | 	"os" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"io/ioutil" |  | ||||||
| 	"gocage/jail" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"github.com/spf13/viper" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| /******************************************************************************** | /******************************************************************************** | ||||||
|  * List all properties a jail have, with their internal name |  * List all properties a jail have, with their internal name | ||||||
|  |  * Only print properties name. To get name & values, use GetJailProperties() | ||||||
|  *******************************************************************************/ |  *******************************************************************************/ | ||||||
| func ListJailsProps(args []string) { | func ListJailsProps(args []string) { | ||||||
| 	var conf Jail | 	var conf Jail | ||||||
| 	var jailconf JailConfig |  | ||||||
| 	var result []string | 	var result []string | ||||||
|  | 	// Mandatory constructor to init default values | ||||||
|  | 	jailconf, err := NewJailConfig() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error allocating JailConfig: %s\n", err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	conf.Config = jailconf | 	conf.Config = jailconf | ||||||
|  |  | ||||||
| @ -34,11 +39,76 @@ func ListJailsProps(args []string) { | |||||||
|  *   into gJails global var |  *   into gJails global var | ||||||
|  *******************************************************************************/ |  *******************************************************************************/ | ||||||
| func ListJails(args []string, display bool) { | func ListJails(args []string, display bool) { | ||||||
| 	fields := strings.Split(gDisplayColumns, ",") | 	type uniqueJailName struct { | ||||||
|  | 		jail       string | ||||||
| 	for _, d := range viper.GetStringSlice("datastore") { | 		unique     bool | ||||||
| 		listJailsFromDatastore(d) | 		uniqueName bool | ||||||
| 	} | 	} | ||||||
|  | 	var nameChecked []*uniqueJailName | ||||||
|  | 	var curCheck *uniqueJailName | ||||||
|  | 	var found bool | ||||||
|  | 	var skip bool | ||||||
|  | 	 | ||||||
|  | 	for _, ds := range gDatastores { | ||||||
|  | 		listJailsFromDatastore(ds, args, display) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Only when displaying jails, we accept to process multiple same name jails | ||||||
|  | 	if false == display { | ||||||
|  | 		for _, j := range gJails { | ||||||
|  | 			// If already checked and was using distinctive name, skip this occurence | ||||||
|  | 			for i, n := range nameChecked { | ||||||
|  | 				if strings.EqualFold(n.jail, j.Name) { | ||||||
|  | 					found = true | ||||||
|  | 					if false == n.unique && true == n.uniqueName { | ||||||
|  | 						skip = true | ||||||
|  | 					} else { | ||||||
|  | 						curCheck = nameChecked[i] | ||||||
|  | 					} | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if true == skip { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Initialize if not found in nameChecked | ||||||
|  | 			if false == found { | ||||||
|  | 				curCheck = &uniqueJailName{jail: j.Name, | ||||||
|  | 									unique: true, | ||||||
|  | 									uniqueName: false} | ||||||
|  | 			} else { | ||||||
|  | 				found = false | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if countOfJailsWithThisName(j.Name) > 1 { | ||||||
|  | 				//fmt.Printf("DEBUG: Jail %s exist multiple times, now checking if specified with full name\n", j.Name) | ||||||
|  | 				curCheck.unique = false | ||||||
|  |  | ||||||
|  | 				for _, a := range args { | ||||||
|  | 					//fmt.Printf("DEBUG: comparing %s/%s with %s\n", j.Datastore, j.Name, a) | ||||||
|  | 					if strings.EqualFold(a, fmt.Sprintf("%s/%s", j.Datastore, j.Name)) { | ||||||
|  | 						//fmt.Printf("DEBUG: Was specified with full name, GG!\n") | ||||||
|  | 						curCheck.uniqueName = true | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			nameChecked = append(nameChecked, curCheck) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Now check | ||||||
|  | 		for _, a := range args { | ||||||
|  | 			for _, n := range nameChecked { | ||||||
|  | 				if strings.EqualFold(n.jail, a) && false == n.unique && false == n.uniqueName { | ||||||
|  | 					fmt.Printf("There is more than one jail named \"%s\", please use datastore/jail format\n", n.jail) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fields := strings.Split(gDisplayJColumns, ",") | ||||||
|  |  | ||||||
| 	// This is the structure we will filter, then display | 	// This is the structure we will filter, then display | ||||||
| 	var jails []Jail | 	var jails []Jail | ||||||
| @ -76,7 +146,6 @@ func ListJails(args []string, display bool) { | |||||||
| 		jails = gJails | 		jails = gJails | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	/*************************************************************** | 	/*************************************************************** | ||||||
| 	/   Filter jails by names given on command line | 	/   Filter jails by names given on command line | ||||||
| 	/**************************************************************/ | 	/**************************************************************/ | ||||||
| @ -86,7 +155,6 @@ func ListJails(args []string, display bool) { | |||||||
| 			for _, j := range jails { | 			for _, j := range jails { | ||||||
| 				if j.Name == a { | 				if j.Name == a { | ||||||
| 					js = append(js, j) | 					js = append(js, j) | ||||||
| 					break |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @ -97,12 +165,12 @@ func ListJails(args []string, display bool) { | |||||||
| 	/   Sort jails | 	/   Sort jails | ||||||
| 	/     We support 3 sort criteria max | 	/     We support 3 sort criteria max | ||||||
| 	/**************************************************************/ | 	/**************************************************************/ | ||||||
| 	if len(gSortFields) > 0 && gSortFields != "none" { | 	if len(gSortJailFields) > 0 && gSortJailFields != "none" { | ||||||
| 		js := initSortStruct() | 		js := initJailSortStruct() | ||||||
|  |  | ||||||
| 		// The way we manage criteria quantity is not very elegant... | 		// The way we manage criteria quantity is not very elegant... | ||||||
| 		var fct1, fct2, fct3 *reflect.Value | 		var fct1, fct2, fct3 *reflect.Value | ||||||
| 		for i, c := range strings.Split(gSortFields, ",") { | 		for i, c := range strings.Split(gSortJailFields, ",") { | ||||||
| 			var fctName string | 			var fctName string | ||||||
| 			if strings.HasPrefix(c, "-") { | 			if strings.HasPrefix(c, "-") { | ||||||
| 				fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1)) | 				fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1)) | ||||||
| @ -118,19 +186,22 @@ func ListJails(args []string, display bool) { | |||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			switch i + 1 { | 			switch i + 1 { | ||||||
| 				case 1:	fct1 = fct | 			case 1: | ||||||
| 				case 2: fct2 = fct | 				fct1 = fct | ||||||
| 				case 3: fct3 = fct | 			case 2: | ||||||
|  | 				fct2 = fct | ||||||
|  | 			case 3: | ||||||
|  | 				fct3 = fct | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		switch len(strings.Split(gSortFields, ",")) { | 		switch len(strings.Split(gSortJailFields, ",")) { | ||||||
| 		case 1: | 		case 1: | ||||||
| 				OrderedBy(fct1.Interface().(lessFunc)).Sort(jails) | 			JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails) | ||||||
| 		case 2: | 		case 2: | ||||||
| 				OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc)).Sort(jails) | 			JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc)).Sort(jails) | ||||||
| 		case 3: | 		case 3: | ||||||
| 				OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc), fct3.Interface().(lessFunc)).Sort(jails) | 			JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc), fct3.Interface().(jailLessFunc)).Sort(jails) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -138,29 +209,38 @@ func ListJails(args []string, display bool) { | |||||||
| 	/   Finally, display jails | 	/   Finally, display jails | ||||||
| 	/**************************************************************/ | 	/**************************************************************/ | ||||||
| 	if display { | 	if display { | ||||||
| 		displayStructFields(jails, fields) | 		displayJailsFields(jails, fields) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func listJailsFromDatastore(ds Datastore, args []string, accept_multiple bool) { | ||||||
| 	 | 	 | ||||||
| func listJailsFromDatastore(datastore string) { | 	fileInfo, err := os.Stat(ds.Mountpoint) | ||||||
| 	fileInfo, err := os.Stat(datastore) | 	if err != nil { | ||||||
| 	if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", datastore)) } | 		log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", ds.Mountpoint)) | ||||||
| 	if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", datastore)) } | 	} | ||||||
|  | 	if fileInfo.IsDir() == false { | ||||||
|  | 		log.Fatalln(fmt.Sprintf("%s is not a directory", ds.Mountpoint)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// A datastore have to contain a "jails" directory | 	// A datastore have to contain a "jails" directory | ||||||
| 	jailsDir := fmt.Sprintf("%s/jails", datastore) | 	jailsDir := fmt.Sprintf("%s/jails", ds.Mountpoint) | ||||||
| 	fileInfo, err = os.Stat(jailsDir) | 	fileInfo, err = os.Stat(jailsDir) | ||||||
| 	if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir)) } | 	if err != nil { | ||||||
| 	if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir)) } | 		log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir)) | ||||||
|  | 	} | ||||||
| 	listJailsFromDirectory(jailsDir) | 	if fileInfo.IsDir() == false { | ||||||
|  | 		log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	listJailsFromDirectory(jailsDir, ds.Name) | ||||||
|  | } | ||||||
|  |  | ||||||
| func listJailsFromDirectory(dir string) ([]Jail) { | func listJailsFromDirectory(dir string, dsname string) ([]Jail, error) { | ||||||
| 	files, err := ioutil.ReadDir(dir) | 	files, err := ioutil.ReadDir(dir) | ||||||
| 	if err != nil { log.Fatalln(fmt.Sprintf("Unable to browse %s, check path and/or rights", dir)) } | 	if err != nil { | ||||||
|  | 		log.Fatalln(fmt.Sprintf("Unable to browse %s, check path and/or rights", dir)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, fi := range files { | 	for _, fi := range files { | ||||||
| 		if fi.IsDir() == true { | 		if fi.IsDir() == true { | ||||||
| @ -168,30 +248,45 @@ func listJailsFromDirectory(dir string) ([]Jail) { | |||||||
| 			// 1. Get conf from config.json | 			// 1. Get conf from config.json | ||||||
| 			jailConfPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "config.json") | 			jailConfPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "config.json") | ||||||
| 			jailConf, err := getJailConfig(jailConfPath) | 			jailConf, err := getJailConfig(jailConfPath) | ||||||
| 			if err != nil { log.Println("ERROR reading jail config for %s", jailConfPath) } | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR reading jail config from %s\n", jailConfPath) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// 2. Build jail object from config | 			// 2. Build jail object from config | ||||||
| 			jailRootPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "root") | 			jailRootPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "root") | ||||||
| 			j := Jail{ | 			j := Jail{ | ||||||
| 				Name: jailConf.Host_hostname, | 				Name:       jailConf.Host_hostuuid, | ||||||
| 				Config:     jailConf, | 				Config:     jailConf, | ||||||
| 				ConfigPath: jailConfPath, | 				ConfigPath: jailConfPath, | ||||||
|  | 				Datastore:  dsname, | ||||||
| 				RootPath:   jailRootPath, | 				RootPath:   jailRootPath, | ||||||
| 				Running:    false, | 				Running:    false, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// 3. Add current running informations | 			// 3. Add current running informations | ||||||
| 			rjails, err := jail.GetJails() | 			rjails, err := jail.GetJails() | ||||||
| 			if err != nil { log.Fatalln("Unable to list running jails") } | 			if err != nil { | ||||||
|  | 				log.Fatalln("Unable to list running jails") | ||||||
|  | 			} | ||||||
| 			for _, rj := range rjails { | 			for _, rj := range rjails { | ||||||
| 				if rj.Path == j.RootPath { | 				if rj.Path == j.RootPath { | ||||||
| 					j.JID = rj.Jid | 					j.JID = rj.Jid | ||||||
| 					j.Running = true | 					j.Running = true | ||||||
|  | 					// FIXME ??? Shouldn't be ioc-$Name ? | ||||||
| 					j.InternalName = rj.Name | 					j.InternalName = rj.Name | ||||||
|  | 					j.Devfs_ruleset = rj.Devfs_ruleset | ||||||
|  | 					// Update release | ||||||
|  | 					r, err := getVersion(&j) | ||||||
|  | 					if err != nil { | ||||||
|  | 						fmt.Printf("ERROR getting jail %s version: %s\n", j.Name, err.Error()) | ||||||
|  | 					} else { | ||||||
|  | 						j.Config.Release = r | ||||||
|  | 					} | ||||||
| 					break | 					break | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// 4. Get Zpool | ||||||
| 			/* This op take some 600ms for ~40 jails :^( */ | 			/* This op take some 600ms for ~40 jails :^( */ | ||||||
| 			out, err := executeCommand(fmt.Sprintf("zfs list -H -o name %s", j.RootPath)) | 			out, err := executeCommand(fmt.Sprintf("zfs list -H -o name %s", j.RootPath)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -200,21 +295,40 @@ func listJailsFromDirectory(dir string) ([]Jail) { | |||||||
| 				j.Zpool = strings.Split(strings.TrimSuffix(out, "\n"), "/")[0] | 				j.Zpool = strings.Split(strings.TrimSuffix(out, "\n"), "/")[0] | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// Check if jail with the same name already exist on another DS | ||||||
|  | 			for _, jj := range gJails { | ||||||
|  | 				if strings.EqualFold(jj.Name, j.Name) { | ||||||
|  | 					/*fmt.Printf("Warning: A jail with name %s exist on datastore %s, use full name to interact!\n", j.Name, jj.Datastore) | ||||||
|  | 					fmt.Printf("(Ex: gocage start %s/%s)\n", jj.Datastore, j.Name)*/ | ||||||
|  | 					// Add Datastore  to avoid confusion | ||||||
|  | 					if false == strings.Contains(gDisplayJColumns, "Datastore") { | ||||||
|  | 						gDisplayJColumns += ",Datastore" | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
| 			gJails = append(gJails, j) | 			gJails = append(gJails, j) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return gJails | 	return gJails, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| func getJailConfig(jailConfigPath string) (JailConfig, error) { | func getJailConfig(jailConfigPath string) (JailConfig, error) { | ||||||
| 	content, err := ioutil.ReadFile(jailConfigPath) | 	content, err := ioutil.ReadFile(jailConfigPath) | ||||||
| 	if err != nil { log.Fatalln(fmt.Sprintf("Unable to read %s, check path and/or rights", 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, err := NewJailConfig() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return jc, err | ||||||
|  | 	} | ||||||
| 	err = json.Unmarshal([]byte(content), &jc) | 	err = json.Unmarshal([]byte(content), &jc) | ||||||
| 	if err != nil { log.Fatalln(fmt.Sprintf("Error occured during unmarshaling %s: %s", jailConfigPath, err.Error())) } | 	if err != nil { | ||||||
|  | 		log.Fatalln(fmt.Sprintf("Error occured during unmarshaling %s: %s", jailConfigPath, err.Error())) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return jc, err | 	return jc, err | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										224
									
								
								cmd/migrate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								cmd/migrate.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"fmt" | ||||||
|  | 	"bufio" | ||||||
|  | 	"errors" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// 1MB | ||||||
|  | 	BUFFER_SIZE = 512 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /******************************************************************************** | ||||||
|  |  * Migrate jail to another zfs pool | ||||||
|  |  *******************************************************************************/ | ||||||
|  | func MigrateJail(args []string) { | ||||||
|  | 	var jailNames []string | ||||||
|  |  | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		for _, a := range args { | ||||||
|  | 			jailNames = append(jailNames, a) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, jn := range jailNames { | ||||||
|  | 		cj, err := getJailFromArray(jn, []string{""}, gJails) | ||||||
|  | 		if cj == nil { | ||||||
|  | 			fmt.Printf("Error getting jail %s: Not found\n", jn) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// Check if destination datastore exist & get current DS | ||||||
|  | 		destDS, err := getDatastoreFromArray(gMigrateDestDatastore, gDatastores) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting datastore \"%s\": %v\n", gMigrateDestDatastore, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		curDS, err := getDatastoreFromArray(cj.Datastore, gDatastores) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting datastore \"%s\": %v\n", gMigrateDestDatastore, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if cj.Running == true && gYesToAll == false { | ||||||
|  | 			fmt.Printf("WARNING: Jail %s is running\n", cj.Name) | ||||||
|  | 			fmt.Printf("Migration will stop it for data sync before starting on the new pool. You will be prompted for shutdown.\n") | ||||||
|  | 			fmt.Printf("Continue? (y/n) ") | ||||||
|  | 			scanr := bufio.NewScanner(os.Stdin) | ||||||
|  | 			scanr.Scan() | ||||||
|  | 			if false == strings.EqualFold(scanr.Text(), "y") { | ||||||
|  | 				fmt.Printf("Migration aborted\n") | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/* TODO : Check dest pool (gMigrateDestDatastore) existence | ||||||
|  | 				zfs snapshot /iocage/jails/$jail@gocage_mig_first_snap | ||||||
|  | 				zfs snapshot /iocage/jails/$jail/root@gocage_mig_first_snap | ||||||
|  | 				zfs send jail@gocage_mig_first_snap | zfs receive destpool/jails/jail_name | ||||||
|  | 				zfs send jail/root@gocage_mig_first_snap | zfs receive destpool/jails/jail_name/root | ||||||
|  | 				shutdown jail if needed | ||||||
|  | 				if jail was shutdown | ||||||
|  | 					zfs snapshot /iocage/jails/$jail@gocage_mig_last_snap to get last data | ||||||
|  | 					zfs send -i jail@gocage_mig_first_snap jail/root@gocage_mig_last_snap | zfs receive -F destpool/jails/jail_name | ||||||
|  | 					start jail on new dest | ||||||
|  | 					zfs destroy destpool/jails/jail_name@gocage_mig_first_snap | ||||||
|  | 					zfs destroy destpool/jails/jail_name/root@gocage_mig_first_snap | ||||||
|  | 		*/ | ||||||
|  |  | ||||||
|  | 		// Snapshot config | ||||||
|  | 		dsconf := strings.Join([]string{curDS.ZFSDataset, "jails", jn}, "/") | ||||||
|  | 		fmt.Printf("Snapshot %s: ", dsconf) | ||||||
|  | 		if err = zfsSnapshot(dsconf, "gocage_mig_init"); err != nil { | ||||||
|  | 			fmt.Printf("Error: %v\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("Done\n") | ||||||
|  | 		 | ||||||
|  | 		// Snapshot filesystem | ||||||
|  | 		dsdata := strings.Join([]string{curDS.ZFSDataset, "jails", jn, "root"}, "/") | ||||||
|  | 		fmt.Printf("Snapshot %s: ", dsdata) | ||||||
|  | 		if err := zfsSnapshot(dsdata, "gocage_mig_init"); err != nil { | ||||||
|  | 			fmt.Printf("Error: %v\n", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("Done\n") | ||||||
|  | 		 | ||||||
|  | 		 | ||||||
|  | 		// TODO: Browse jailed ZFS dataset to migrate them (see poudriere for example) | ||||||
|  | 		// zfs send -R src/poudriere@init | zfs receive -Fu dest/poudriere | ||||||
|  | 		 | ||||||
|  | 		dsconfdest := strings.Join([]string{destDS.ZFSDataset, "jails", jn}, "/") | ||||||
|  | 		fmt.Printf("Migrate jail config dataset to %s: ", dsconfdest) | ||||||
|  | 		if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsconf), dsconfdest); err != nil { | ||||||
|  | 			fmt.Printf("Error: %v\n", err) | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("Done\n") | ||||||
|  | 		 | ||||||
|  | 		dsdatadest := strings.Join([]string{destDS.ZFSDataset, "jails", jn, "root"}, "/") | ||||||
|  | 		fmt.Printf("Migrate jail filesystem dataset to %s: ", dsdatadest) | ||||||
|  | 		if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsdata), dsdatadest); err != nil { | ||||||
|  | 			fmt.Printf("Error: %v\n", err) | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("Done\n") | ||||||
|  | 		 | ||||||
|  | 		// Running jail needs a last snapshot for an incremental send/recv after shutting down. | ||||||
|  | 		if cj.Running == true { | ||||||
|  | 			fmt.Printf("Shutdown jail %s for last data sync, this could take some time.\n", cj.Name) | ||||||
|  | 			if gYesToAll == false { | ||||||
|  | 				fmt.Printf("Continue? (y/n) ") | ||||||
|  | 				scanr := bufio.NewScanner(os.Stdin) | ||||||
|  | 				scanr.Scan() | ||||||
|  | 				if false == strings.EqualFold(scanr.Text(), "y") { | ||||||
|  | 					fmt.Printf("Migration aborted. Now cleaning destination pool.\n") | ||||||
|  | 					if err := CleanMigrateMess([]string{cj.Name}); err != nil { | ||||||
|  | 						fmt.Printf("Error: %v\n", err) | ||||||
|  | 					} | ||||||
|  | 					// TODO : Remove destination datasets, or handle the cas at the beginning of current function | ||||||
|  | 					// (when snapshot already exist on source and dest) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			StopJail([]string{cj.Name}) | ||||||
|  | 			 | ||||||
|  | 			fmt.Printf("Snapshot %s: ", dsconf) | ||||||
|  | 			if err = zfsSnapshot(dsconf, "gocage_mig_last_sync"); err != nil { | ||||||
|  | 				fmt.Printf("Error: %v\n", err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			fmt.Printf("Done\n") | ||||||
|  | 			fmt.Printf("Snapshot %s: ", dsdata) | ||||||
|  | 			if err := zfsSnapshot(dsdata, "gocage_mig_last_sync"); err != nil { | ||||||
|  | 				fmt.Printf("Error: %v\n", err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			fmt.Printf("Done\n") | ||||||
|  | 			 | ||||||
|  | 			fmt.Printf("Synchronize jail config to %s: ", dsconfdest) | ||||||
|  | 			if err := zfsCopyIncremental(fmt.Sprintf("%s@gocage_mig_init", dsconf),  | ||||||
|  | 										fmt.Sprintf("%s@gocage_mig_last_sync", dsconf),   | ||||||
|  | 										 dsconfdest); err != nil { | ||||||
|  | 				fmt.Printf("Error: %v\n", err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			fmt.Printf("Done\n") | ||||||
|  | 			 | ||||||
|  | 			fmt.Printf("Synchronize jail filesystem dataset to %s: ", dsdatadest) | ||||||
|  | 			if err := zfsCopyIncremental(fmt.Sprintf("%s@gocage_mig_init", dsdata),  | ||||||
|  | 										fmt.Sprintf("%s@gocage_mig_last_sync", dsdata),   | ||||||
|  | 										dsdatadest); err != nil { | ||||||
|  | 				return | ||||||
|  | 				fmt.Printf("Error: %v\n", err) | ||||||
|  | 			} | ||||||
|  | 			fmt.Printf("Done\n") | ||||||
|  | 			 | ||||||
|  | 			// TODO : Start jail on new datastore (! Currently ListJails won't support 2 jails with same name !) | ||||||
|  | 			 | ||||||
|  | 			// TODO : zfs destroy destpool/jails/jail_name@gocage_mig_first_snap | ||||||
|  | 			// TODO : zfs destroy destpool/jails/jail_name/root@gocage_mig_first_snap | ||||||
|  | 			// TODO : zfs destroy -r srcpool/iocage/jails/$jail | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Clean snapshots from an aborted migration | ||||||
|  | func CleanMigrateMess(args []string) error { | ||||||
|  | 	var jailNames []string | ||||||
|  |  | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		for _, a := range args { | ||||||
|  | 			jailNames = append(jailNames, a) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, jn := range jailNames { | ||||||
|  | 		cj, err := getJailFromArray(jn, []string{""}, gJails) | ||||||
|  | 		if cj == nil { | ||||||
|  | 			return errors.New(fmt.Sprintf("Error getting jail %s: Not found\n", jn)) | ||||||
|  | 		} | ||||||
|  | 		curDS, err := getDatastoreFromArray(cj.Datastore, gDatastores) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.New(fmt.Sprintf("Error getting datastore \"%s\": %v\n", cj.Datastore, err)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		cmd := fmt.Sprintf("zfs destroy %s@gocage_mig_init", strings.Join([]string{curDS.ZFSDataset, "jails", jn}, "/")) | ||||||
|  | 		out, err := executeCommand(cmd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") { | ||||||
|  | 				fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) | ||||||
|  | 				return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_init", strings.Join([]string{curDS.ZFSDataset, "jails", jn, "root"}, "/")) | ||||||
|  | 		out, err = executeCommand(cmd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") { | ||||||
|  | 				fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) | ||||||
|  | 				return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_last_sync", strings.Join([]string{curDS.ZFSDataset, "jails", jn}, "/")) | ||||||
|  | 		out, err = executeCommand(cmd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") { | ||||||
|  | 				fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) | ||||||
|  | 				return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_last_sync", strings.Join([]string{curDS.ZFSDataset, "jails", jn, "root"}, "/")) | ||||||
|  | 		out, err = executeCommand(cmd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") { | ||||||
|  | 				fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) | ||||||
|  | 				return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @ -1,22 +1,28 @@ | |||||||
| package cmd | package cmd | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"os" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" |  | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func GetJailProperties(args []string) { | func GetJailProperties(args []string) { | ||||||
| 	var props []string | 	var props []string | ||||||
| 	var jail Jail | 	var jail *Jail | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
| 	if len(args) > 0 { | 	if len(args) > 0 { | ||||||
| 		for i, a := range args { | 		for i, a := range args { | ||||||
| 			// Last arg is the jail name | 			// Last arg is the jail name | ||||||
| 			if i == len(args)-1 { | 			if i == len(args)-1 { | ||||||
| 				jail.Name = a | 				jail, err = getJailFromArray(a, []string{""}, gJails) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("Error: %s\n", err.Error()) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				props = append(props, a) | 				props = append(props, a) | ||||||
| 			} | 			} | ||||||
| @ -29,8 +35,14 @@ func GetJailProperties(args []string) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
|  | 	if isStringInArray(props, "all") { | ||||||
|  | 		var result []string | ||||||
|  | 		result = getStructFieldNames(*jail, result, "") | ||||||
|  | 		props = result | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, p := range props { | 	for _, p := range props { | ||||||
| 		v, err := getJailProperty(&jail, p) | 		v, err := getJailProperty(jail, p) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Printf("Error: %s\n", err.Error()) | 			fmt.Printf("Error: %s\n", err.Error()) | ||||||
| 			return | 			return | ||||||
| @ -41,9 +53,7 @@ func GetJailProperties(args []string) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func getJailProperty(jail *Jail, propName string) (string, error) { | func getJailProperty(jail *Jail, propName string) (string, error) { | ||||||
| 	for i, j := range gJails { | 	val, _, err := getStructFieldValue(jail, propName) | ||||||
| 		if j.Name == jail.Name { |  | ||||||
| 			val, _, err := getStructFieldValue(&gJails[i], propName) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| @ -58,10 +68,8 @@ func getJailProperty(jail *Jail, propName string) (string, error) { | |||||||
| 	default: | 	default: | ||||||
| 		return "", errors.New(fmt.Sprintf("Error: Unknown type for property %s: %s\n", propName, val.Kind())) | 		return "", errors.New(fmt.Sprintf("Error: Unknown type for property %s: %s\n", propName, val.Kind())) | ||||||
| 	} | 	} | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return "", errors.New(fmt.Sprintf("Jail not found: %s", jail.Name)) | 	return "", errors.New("Unknown error in getJailProperty") | ||||||
| } | } | ||||||
|  |  | ||||||
| func SetJailProperties(args []string) { | func SetJailProperties(args []string) { | ||||||
| @ -98,53 +106,20 @@ func SetJailProperties(args []string) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	cj, err := getJailFromArray(jail.Name, []string{""}, gJails) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error getting jail %s: %v\n", jail.Name, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, p := range props { | 	for _, p := range props { | ||||||
| 		err := setJailProperty(&jail, p.name, p.value) | 		err := setStructFieldValue(cj, p.name, p.value) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Printf("Error: %s\n", err.Error()) | 			fmt.Printf("Error: %s\n", err.Error()) | ||||||
| 			return | 			return | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // setJailProperty takes a string as propValue, whatever the real property type is. |  | ||||||
| //  It will be converted. |  | ||||||
| func setJailProperty(jail *Jail, propName string, propValue string) error { |  | ||||||
| 	for i, j := range gJails { |  | ||||||
| 		if j.Name == jail.Name { |  | ||||||
| 			val, _, err := getStructFieldValue(&gJails[i], propName) |  | ||||||
| 			//val, _, err := getStructFieldValue(&gJails[i].Config, strings.Split(propName, ".")[1]) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			 |  | ||||||
| 			if val.CanSet() { |  | ||||||
| 				switch val.Kind() { |  | ||||||
| 					case reflect.String: |  | ||||||
| 						val.SetString(propValue) |  | ||||||
| 					case reflect.Int: |  | ||||||
| 						ival, err := strconv.ParseInt(propValue, 10, 64) |  | ||||||
| 						if err != nil { |  | ||||||
| 							return err |  | ||||||
| 						} |  | ||||||
| 						val.SetInt(ival) |  | ||||||
| 					case reflect.Bool: |  | ||||||
| 						bval, err := strconv.ParseBool(propValue) |  | ||||||
| 						if err != nil { |  | ||||||
| 							return err |  | ||||||
| 						} |  | ||||||
| 						val.SetBool(bval) |  | ||||||
| 					default: |  | ||||||
| 						return errors.New(fmt.Sprintf("Field is an unkown type: %s: %s", propName, val.Kind().String())) |  | ||||||
| 				} |  | ||||||
| 				fmt.Printf("%s: %s set to %s\n", jail.Name, propName, propValue) |  | ||||||
| 				gJails[i].ConfigUpdated = true |  | ||||||
| 		} else { | 		} else { | ||||||
| 				return errors.New(fmt.Sprintf("Field is not writable : %s", propName)) | 			fmt.Printf("%s: %s set to %s\n", cj.Name, p.name, p.value) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	cj.WriteConfigToDisk(false) | ||||||
| } | } | ||||||
| 	 |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										423
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										423
									
								
								cmd/root.go
									
									
									
									
									
								
							| @ -3,31 +3,77 @@ package cmd | |||||||
| import ( | import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"sync" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"encoding/json" | 	"io/ioutil" | ||||||
|  |  | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
|  | 	// TODO : Use log | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	gVersion = "0.24" | 	gVersion = "0.40" | ||||||
|  | 	 | ||||||
|  | 	// TODO : Get from $jail_zpool/defaults.json | ||||||
|  | 	MIN_DYN_DEVFS_RULESET = 1000 | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type createArgs struct { | ||||||
|  | 	Release      string | ||||||
|  | 	BaseJail     bool | ||||||
|  | 	Datastore    string | ||||||
|  | 	JailType     string | ||||||
|  | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|  | 	gJailHost   JailHost | ||||||
| 	gJails      []Jail | 	gJails      []Jail | ||||||
|  | 	gDatastores []Datastore | ||||||
|  |  | ||||||
| 	gUseSudo bool | 	gUseSudo bool | ||||||
|  | 	gForce   bool | ||||||
|  | 	gDebug   bool | ||||||
|  | 	 | ||||||
|  | 	gCreateArgs      createArgs | ||||||
|  |  | ||||||
| 	gConfigFile      string | 	gConfigFile      string | ||||||
| 	gDisplayColumns	string | 	gDisplayJColumns string | ||||||
|  | 	gDisplaySColumns string | ||||||
|  | 	gDisplayDColumns string | ||||||
| 	gFilterJails     string | 	gFilterJails     string | ||||||
| 	gSortFields		string | 	gFilterSnaps     string | ||||||
| 	gNoLineSep		bool | 	gFilterDS        string | ||||||
|  | 	gSortJailFields  string | ||||||
|  | 	gSortSnapFields  string | ||||||
|  | 	gSortDSFields    string | ||||||
|  | 	gNoJailLineSep   bool | ||||||
|  | 	gNoSnapLineSep   bool | ||||||
|  | 	gNoDSLineSep     bool | ||||||
|  |  | ||||||
| 	gHostVersion float64 | 	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{ | 	rootCmd = &cobra.Command{ | ||||||
| 		Use:   "gocage", | 		Use:   "gocage", | ||||||
| @ -35,8 +81,9 @@ var ( | |||||||
| 		Long: `GoCage is a jail management tool. It support VNET, host-only, NAT networks. Provides snapshots and cloning. | 		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.`, | It support iocage jails and can coexist with iocage.`, | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			fmt.Println("Here we are in the Run") | 			fv, _ := getFreeBSDVersion() | ||||||
| 			cleanAfterRun() | 			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") | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -45,11 +92,25 @@ It support iocage jails and can coexist with iocage.`, | |||||||
| 		Short: "Print the version number of GoCage", | 		Short: "Print the version number of GoCage", | ||||||
| 		Long:  `Let this show you how much fail I had to get this *cough* perfect`, | 		Long:  `Let this show you how much fail I had to get this *cough* perfect`, | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			fmt.Printf("GoCage v.%s on FreeBSD %.1f\n", gVersion, gHostVersion) | 			fv, _ := getFreeBSDVersion() | ||||||
| 			cleanAfterRun() | 			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{ | 	listCmd = &cobra.Command{ | ||||||
| 		Use:   "list", | 		Use:   "list", | ||||||
| 		Short: "Print jails", | 		Short: "Print jails", | ||||||
| @ -58,7 +119,6 @@ Jail list can be restricted by adding name on command line | |||||||
| ex: gocage list srv-db srv-web`, | ex: gocage list srv-db srv-web`, | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			ListJails(args, true) | 			ListJails(args, true) | ||||||
| 			cleanAfterRun() |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -68,7 +128,16 @@ ex: gocage list srv-db srv-web`, | |||||||
| 		Long:  "Display jails properties. You can use properties to filter, get or set them.", | 		Long:  "Display jails properties. You can use properties to filter, get or set them.", | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			ListJailsProps(args) | 			ListJailsProps(args) | ||||||
| 			cleanAfterRun() | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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) | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -77,10 +146,13 @@ ex: gocage list srv-db srv-web`, | |||||||
| 		Short: "stop jail", | 		Short: "stop jail", | ||||||
| 		Long:  "shutdown jail", | 		Long:  "shutdown jail", | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			// Get the inventory | 			// Load inventory | ||||||
| 			ListJails(args, false) | 			ListJails(args, false) | ||||||
|  | 			if len(args) == 0 { | ||||||
|  | 				StopAllRunningJails() | ||||||
|  | 			} else { | ||||||
| 				StopJail(args) | 				StopJail(args) | ||||||
| 			cleanAfterRun() | 			} | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -88,10 +160,34 @@ ex: gocage list srv-db srv-web`, | |||||||
| 		Use:   "start", | 		Use:   "start", | ||||||
| 		Short: "start jail", | 		Short: "start jail", | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			// Get the inventory | 			// Load inventory | ||||||
| 			ListJails(args, false) | 			ListJails(args, false) | ||||||
|  | 			if len(args) == 0 { | ||||||
|  | 				StartJailsAtBoot() | ||||||
|  | 			} else { | ||||||
| 				StartJail(args) | 				StartJail(args) | ||||||
| 			cleanAfterRun() | 			} | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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) | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -101,10 +197,9 @@ ex: gocage list srv-db srv-web`, | |||||||
| 		Long: `Set jail property value. Specify property=value, end command with jail name. | 		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)`, | Multiples properties can be specified, separated with space (Ex: gocage set allow_mlock=1 boot=1 myjail)`, | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			// Get the inventory | 			// Load inventory | ||||||
| 			ListJails(args, false) | 			ListJails(args, false) | ||||||
| 			SetJailProperties(args) | 			SetJailProperties(args) | ||||||
| 			cleanAfterRun() |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -112,30 +207,221 @@ Multiples properties can be specified, separated with space (Ex: gocage set allo | |||||||
| 		Use:   "get", | 		Use:   "get", | ||||||
| 		Short: "Get a jail property", | 		Short: "Get a jail property", | ||||||
| 		Long: `Get jail property value. Specify property, end command with jail name. | 		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)`, | 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) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 		 // Get the inventory | 			// Load inventory | ||||||
| 			ListJails(args, false) | 			ListJails(args, false) | ||||||
| 			GetJailProperties(args) | 			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) | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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() { | func init() { | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
| 	cobra.OnInitialize(initConfig) | 	cobra.OnInitialize(initConfig) | ||||||
|  |  | ||||||
| 	// Global switches | 	// Global switches | ||||||
| 	rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file") | 	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().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 | 	// 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") | 	// We reuse these flags in "gocage snapshot list myjail" and 'gocage datastore list" commands | ||||||
| 	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.Flags().StringVarP(&gDisplayJColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output") | ||||||
| 	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.") | 	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().BoolVarP(&gCreateArgs.BaseJail, "basejail", "b", false, "Basejail. This will create a jail mounted read only from a release, so every up(date|grade) made to this release 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 | 	// Now declare commands | ||||||
| 	rootCmd.AddCommand(versionCmd) | 	rootCmd.AddCommand(versionCmd) | ||||||
| @ -143,17 +429,34 @@ func init() { | |||||||
| 	listCmd.AddCommand(listPropsCmd) | 	listCmd.AddCommand(listPropsCmd) | ||||||
| 	rootCmd.AddCommand(stopCmd) | 	rootCmd.AddCommand(stopCmd) | ||||||
| 	rootCmd.AddCommand(startCmd) | 	rootCmd.AddCommand(startCmd) | ||||||
|  | 	rootCmd.AddCommand(restartCmd) | ||||||
|  | 	rootCmd.AddCommand(destroyCmd) | ||||||
|  | 	rootCmd.AddCommand(shellCmd) | ||||||
| 	rootCmd.AddCommand(getCmd) | 	rootCmd.AddCommand(getCmd) | ||||||
| 	rootCmd.AddCommand(setCmd) | 	rootCmd.AddCommand(setCmd) | ||||||
|  | 	rootCmd.AddCommand(snapshotCmd) | ||||||
|  | 	rootCmd.AddCommand(migrateCmd) | ||||||
|  | 	rootCmd.AddCommand(datastoreCmd) | ||||||
|  | 	rootCmd.AddCommand(fetchCmd) | ||||||
|  | 	rootCmd.AddCommand(updateCmd) | ||||||
|  | 	rootCmd.AddCommand(upgradeCmd) | ||||||
|  | 	rootCmd.AddCommand(createCmd) | ||||||
| 	 | 	 | ||||||
| 	// Get FreeBSD version | 	rootCmd.AddCommand(testCmd) | ||||||
| 	out, err := executeCommand("freebsd-version") | 	 | ||||||
|  | 	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 { | 	if err != nil { | ||||||
| 		fmt.Printf("Error running \"freebsd-version\": %s", err.Error()) | 		fmt.Printf("Error initializing JailHost properties: %v\n", err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	gHostVersion, _ = strconv.ParseFloat(strings.Split(out, "-")[0], 32) |  | ||||||
| 	 |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func initConfig() { | func initConfig() { | ||||||
| @ -162,8 +465,6 @@ func initConfig() { | |||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| //	fmt.Printf("We are in initConfig(), with config file %s\n", gConfigFile) |  | ||||||
|  |  | ||||||
| 	viper.SetConfigFile(gConfigFile) | 	viper.SetConfigFile(gConfigFile) | ||||||
|  |  | ||||||
| 	if err := viper.ReadInConfig(); err != nil { | 	if err := viper.ReadInConfig(); err != nil { | ||||||
| @ -171,6 +472,13 @@ func initConfig() { | |||||||
| 		os.Exit(1) | 		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.Println("Using config file:", viper.ConfigFileUsed()) | ||||||
| 	//	fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore")) | 	//	fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore")) | ||||||
| 	//	fmt.Printf("datastore.0 in config : %s\n", viper.GetStringSlice("datastore.0")) | 	//	fmt.Printf("datastore.0 in config : %s\n", viper.GetStringSlice("datastore.0")) | ||||||
| @ -179,41 +487,42 @@ func initConfig() { | |||||||
| 	if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed { | 	if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed { | ||||||
| 		gUseSudo = viper.GetBool("sudo") | 		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 { | 	if listCmd.Flags().Lookup("outcol") != nil && false == listCmd.Flags().Lookup("outcol").Changed { | ||||||
| 		gDisplayColumns = viper.GetString("outcol") | 		gDisplayJColumns = viper.GetString("outcol") | ||||||
| 	} | 	} | ||||||
| 	if listCmd.Flags().Lookup("nolinesep") != nil && false == listCmd.Flags().Lookup("nolinesep").Changed { | 	if listCmd.Flags().Lookup("nolinesep") != nil && false == listCmd.Flags().Lookup("nolinesep").Changed { | ||||||
| 		gNoLineSep = viper.GetBool("nolinesep") | 		gNoJailLineSep = viper.GetBool("nolinesep") | ||||||
| 	} | 	} | ||||||
| 	if listCmd.Flags().Lookup("filter") != nil && false == listCmd.Flags().Lookup("filter").Changed { | 	if listCmd.Flags().Lookup("filter") != nil && false == listCmd.Flags().Lookup("filter").Changed { | ||||||
| 		gFilterJails = viper.GetString("filter") | 		gFilterJails = viper.GetString("filter") | ||||||
| 	} | 	} | ||||||
| 	if listCmd.Flags().Lookup("sort") != nil && false == listCmd.Flags().Lookup("sort").Changed { | 	if listCmd.Flags().Lookup("sort") != nil && false == listCmd.Flags().Lookup("sort").Changed { | ||||||
| 		gSortFields = viper.GetString("sort") | 		gSortJailFields = viper.GetString("sort") | ||||||
| 	} | 	} | ||||||
| 	if len(strings.Split(gSortFields, ",")) > 3 { | 	if len(strings.Split(gSortJailFields, ",")) > 3 { | ||||||
| 		fmt.Printf("More than 3 sort criteria, this is not supported!\n") | 		fmt.Printf("More than 3 sort criteria is not supported!\n") | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  | 	if gDebug { | ||||||
|  | 		log.SetLevel(log.DebugLevel) | ||||||
|  | 		log.Debugf("Debug mode enabled\n") | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Called after execution |  | ||||||
| func cleanAfterRun() { |  | ||||||
| 	for _, j := range gJails { |  | ||||||
| 		if j.ConfigUpdated { |  | ||||||
| 			//fmt.Printf("Config for jail %s will be updated\n", j.Name) |  | ||||||
| 			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() { | func Execute() { | ||||||
| 	if err := rootCmd.Execute(); err != nil { | 	if err := rootCmd.Execute(); err != nil { | ||||||
| @ -221,5 +530,3 @@ func Execute() { | |||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										330
									
								
								cmd/snapshots.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								cmd/snapshots.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,330 @@ | |||||||
|  | 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 | ||||||
|  | } | ||||||
							
								
								
									
										1389
									
								
								cmd/start.go
									
									
									
									
									
								
							
							
						
						
									
										1389
									
								
								cmd/start.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										204
									
								
								cmd/stop.go
									
									
									
									
									
								
							
							
						
						
									
										204
									
								
								cmd/stop.go
									
									
									
									
									
								
							| @ -4,11 +4,15 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	//"log" | 	//"log" | ||||||
|  | 	"sync" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	//"reflect" | 	//"reflect" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	 | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // TODO : Use SYS_RCTL_GET_RACCT syscall | // TODO : Use SYS_RCTL_GET_RACCT syscall | ||||||
| @ -38,7 +42,6 @@ func removeRctlRules(jail string, rules []string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| // TODO: Validate with  >1 dataset | // TODO: Validate with  >1 dataset | ||||||
| func umountAndUnjailZFS(jail *Jail) error { | func umountAndUnjailZFS(jail *Jail) error { | ||||||
| 	var ds []string | 	var ds []string | ||||||
| @ -48,10 +51,10 @@ func umountAndUnjailZFS(jail *Jail) error { | |||||||
|  |  | ||||||
| 	for _, zd := range ds { | 	for _, zd := range ds { | ||||||
| 		// 1. Get dataset and childs | 		// 1. Get dataset and childs | ||||||
| 		cmd := fmt.Sprintf("zfs list -H -r -o name -S name %s/%s", jail.Zpool, zd) | 		cmd := fmt.Sprintf("zfs list -H -r -o name -S name %s", zd) | ||||||
| 		out, err := executeCommand(cmd) | 		out, err := executeCommand(cmd) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Printf(fmt.Sprintf("ERROR listing dataset %s/%s\n", jail.Zpool, zd)) | 			fmt.Printf(fmt.Sprintf("ERROR listing dataset %s\n", zd)) | ||||||
| 			os.Exit(1) | 			os.Exit(1) | ||||||
| 		} | 		} | ||||||
| 		for _, c := range strings.Split(out, "\n") { | 		for _, c := range strings.Split(out, "\n") { | ||||||
| @ -69,28 +72,43 @@ func umountAndUnjailZFS(jail *Jail) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// 2. Unjail dataset from the host | 	// 2. Unjail dataset from the host | ||||||
| 	cmd := fmt.Sprintf("zfs unjail %s %s/%s", jail.InternalName, jail.Zpool, ds[len(ds)-1]) | 	cmd := fmt.Sprintf("zfs unjail %s %s", jail.InternalName, ds[len(ds)-1]) | ||||||
| 	_, err := executeCommand(cmd) | 	_, err := executeCommand(cmd) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fmt.Printf("ERROR unjailing %s/%s: %s\n", jail.Zpool, ds[len(ds)-1], err.Error()) | 		fmt.Printf("ERROR unjailing %s: %s\n", ds[len(ds)-1], err.Error()) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| func destroyVNetInterfaces(jail *Jail) error { | func destroyVNetInterfaces(jail *Jail) error { | ||||||
|  | 	if !strings.EqualFold(jail.Config.Ip4_addr, "none") { | ||||||
| 		for _, i := range strings.Split(jail.Config.Ip4_addr, ",") { | 		for _, i := range strings.Split(jail.Config.Ip4_addr, ",") { | ||||||
| 			iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID) | 			iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID) | ||||||
| 			fmt.Printf("%s: ", iname) | 			fmt.Printf("%s: ", iname) | ||||||
| 			_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname)) | 			_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname)) | ||||||
|  | 			//_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", iname)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} else { | 			} else { | ||||||
| 				fmt.Printf("OK\n") | 				fmt.Printf("OK\n") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  | 	if !strings.EqualFold(jail.Config.Ip6_addr, "none") { | ||||||
|  | 		for _, i := range strings.Split(jail.Config.Ip6_addr, ",") { | ||||||
|  | 			iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID) | ||||||
|  | 			fmt.Printf("%s: ", iname) | ||||||
|  | 			_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname)) | ||||||
|  | 			//_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", iname)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} else { | ||||||
|  | 				fmt.Printf("OK\n") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @ -101,16 +119,17 @@ func destroyVNetInterfaces(jail *Jail) error { | |||||||
| //  or else it will require a restart of "devfs" service. | //  or else it will require a restart of "devfs" service. | ||||||
| // But, stoppign the jail already removes this >1000 ID. | // But, stoppign the jail already removes this >1000 ID. | ||||||
| // So no need to call this function. | // So no need to call this function. | ||||||
| func deleteDevfsRuleset(jail *Jail) error { | func deleteDevfsRuleset(ruleset int) error { | ||||||
| 	cmd := "devfs rule showsets" | 	cmd := "devfs rule showsets" | ||||||
| 	out, err := executeCommand(cmd) | 	out, err := executeCommand(cmd) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New(fmt.Sprintf("ERROR listing rulesets: %s", err.Error())) | 		return errors.New(fmt.Sprintf("ERROR listing rulesets: %s", err.Error())) | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
|  | 	rs := strconv.Itoa(ruleset) | ||||||
| 	for _, r := range strings.Split(out, "\n") { | 	for _, r := range strings.Split(out, "\n") { | ||||||
| 		if r == jail.Config.Devfs_ruleset { | 		if r == rs { | ||||||
| 			cmd := fmt.Sprintf("devfs rule -s %s delset", jail.Config.Devfs_ruleset) | 			cmd := fmt.Sprintf("devfs rule -s %d delset", ruleset) | ||||||
| 			_, err := executeCommand(cmd) | 			_, err := executeCommand(cmd) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @ -119,8 +138,7 @@ func deleteDevfsRuleset(jail *Jail) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func umountFsFromHost(mountpoint string) error { | ||||||
| func umountJailFsFromHost(jail *Jail, mountpoint string) error { |  | ||||||
| 	cmd := "mount -p" | 	cmd := "mount -p" | ||||||
| 	out, err := executeCommand(cmd) | 	out, err := executeCommand(cmd) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -131,11 +149,11 @@ func umountJailFsFromHost(jail *Jail, mountpoint string) error { | |||||||
| 	for _, l := range strings.Split(out, "\n") { | 	for _, l := range strings.Split(out, "\n") { | ||||||
| 		f := strings.Split(remSpPtrn.ReplaceAllString(l, " "), " ") | 		f := strings.Split(remSpPtrn.ReplaceAllString(l, " "), " ") | ||||||
| 		if len(f) > 2 { | 		if len(f) > 2 { | ||||||
| 			if strings.EqualFold(f[1], fmt.Sprintf("%s%s", jail.RootPath, mountpoint)) { | 			if strings.EqualFold(f[1], mountpoint) { | ||||||
| 				cmd = fmt.Sprintf("umount %s%s", jail.RootPath, mountpoint) | 				cmd = fmt.Sprintf("umount %s", mountpoint) | ||||||
| 				_, err := executeCommand(cmd) | 				_, err := executeCommand(cmd) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return errors.New(fmt.Sprintf("Error umounting %s/%s: %s", jail.RootPath, mountpoint, err.Error())) | 					return errors.New(fmt.Sprintf("Error umounting %s: %s", mountpoint, err.Error())) | ||||||
| 				} | 				} | ||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
| @ -145,6 +163,9 @@ func umountJailFsFromHost(jail *Jail, mountpoint string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func umountJailFsFromHost(jail *Jail, mountpoint string) error { | ||||||
|  | 	return umountFsFromHost(fmt.Sprintf("%s%s", jail.RootPath, mountpoint)) | ||||||
|  | } | ||||||
|  |  | ||||||
| // Internal usage only | // Internal usage only | ||||||
| func stopJail(jail *Jail) error { | func stopJail(jail *Jail) error { | ||||||
| @ -167,6 +188,79 @@ func stopJail(jail *Jail) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Stop all running jails by reverse priority | ||||||
|  | // Parallelize up to gMaxThreads | ||||||
|  | // Only parallelize same priority level jails | ||||||
|  | func StopAllRunningJails() { | ||||||
|  | 	var stopList []Jail | ||||||
|  | 	var wg *sync.WaitGroup | ||||||
|  | 	var curThNb int | ||||||
|  | 	var curPri int | ||||||
|  | 	 | ||||||
|  | 	// Get boot enabled jails | ||||||
|  | 	for _, j := range gJails { | ||||||
|  | 		if j.Running == true { | ||||||
|  | 			stopList = append(stopList, j) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	// Order by priority | ||||||
|  | 	js := initJailSortStruct() | ||||||
|  | 	fct, _, err := getStructFieldValue(js, "Config.PriorityDec") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Errorf("ERROR getting JailSort struct field \"Config.PriorityDec\"\n") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(stopList) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	wg = new(sync.WaitGroup) | ||||||
|  | 	curThNb = 0 | ||||||
|  | 	for i, j := range stopList { | ||||||
|  | 		jFullName := fmt.Sprintf("%s/%s", j.Datastore, j.Name) | ||||||
|  | 		log.Debugf("Stopping %s with priority %s\n", jFullName, j.Config.Priority) | ||||||
|  | 		jailPri, err := strconv.Atoi(j.Config.Priority) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(fmt.Sprintf("Invalid format for Priority (Jail %s)\n", jFullName)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (curThNb >= gMaxThreads || i == 0) { | ||||||
|  | 			// FIXME : Use a pool instead of waiting for all threads to run a new one | ||||||
|  | 			wg.Wait() | ||||||
|  | 			curThNb = 0 | ||||||
|  | 			 | ||||||
|  | 			wg.Add(1) | ||||||
|  | 			curThNb++ | ||||||
|  | 			curPri = jailPri | ||||||
|  | 			go func(jailFullName string) { | ||||||
|  | 				defer wg.Done() | ||||||
|  | 				StopJail([]string{jailFullName}) | ||||||
|  | 			}(jFullName) | ||||||
|  | 		} else { | ||||||
|  | 			if (curPri == jailPri) { | ||||||
|  | 				wg.Add(1) | ||||||
|  | 				curThNb++ | ||||||
|  | 				go func(jailFullName string) { | ||||||
|  | 					defer wg.Done() | ||||||
|  | 					StopJail([]string{jailFullName}) | ||||||
|  | 				}(jFullName) | ||||||
|  | 			} else { | ||||||
|  | 				wg.Wait() | ||||||
|  | 				curThNb = 0 | ||||||
|  | 			 | ||||||
|  | 				wg.Add(1) | ||||||
|  | 				curThNb++ | ||||||
|  | 				curPri = jailPri | ||||||
|  | 				go func(jailFullName string) { | ||||||
|  | 					defer wg.Done() | ||||||
|  | 					StopJail([]string{jailFullName}) | ||||||
|  | 				}(jFullName) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	wg.Wait() | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| 	Stop jail: | 	Stop jail: | ||||||
| 		Remove rctl rules | 		Remove rctl rules | ||||||
| @ -191,25 +285,26 @@ func stopJail(jail *Jail) error { | |||||||
| func StopJail(args []string) { | func StopJail(args []string) { | ||||||
| 	// Current jail were stopping | 	// Current jail were stopping | ||||||
| 	var cj *Jail | 	var cj *Jail | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
| 	for _, j := range args { | 	for _, a := range args { | ||||||
| 		fmt.Printf("> Stopping jail %s\n", j) | 		// Check if jail exist and is distinctly named | ||||||
|  | 		cj, err = getJailFromArray(a, []string{"basejail", "jail"}, gJails) | ||||||
| 		for _, rj := range gJails { | 		if err != nil { | ||||||
| 			if rj.Name == j { | 			fmt.Printf("Error getting jail: %s\n", err) | ||||||
| 				cj = &rj |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if cj == nil { |  | ||||||
| 			fmt.Printf("Jail not found: %s\n", j) |  | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if cj.Running == false { | 		if cj.Running == false { | ||||||
| 			fmt.Printf("Jail %s is not running!\n", cj.Name) | 			fmt.Printf("Jail %s is not running!\n", cj.Name) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Printf("> Stopping jail %s\n", cj.Name) | ||||||
|  |  | ||||||
|  | 		// Get and write new release into config.json | ||||||
|  | 		updateVersion(cj) | ||||||
|  |  | ||||||
| 		out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName)) | 		out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName)) | ||||||
| 		if err == nil && len(out) > 0 { | 		if err == nil && len(out) > 0 { | ||||||
| 			fmt.Printf("  > Remove RCTL rules:\n") | 			fmt.Printf("  > Remove RCTL rules:\n") | ||||||
| @ -222,12 +317,12 @@ func StopJail(args []string) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if len(cj.Config.Exec_prestop) > 0 { | 		if len(cj.Config.Exec_prestop) > 0 { | ||||||
| 			fmt.Printf("  > Execute prestop:\n") | 			fmt.Printf("  > Execute pre-stop:\n") | ||||||
| 			_, err := executeCommand(cj.Config.Exec_prestop) | 			_, err := executeCommand(cj.Config.Exec_prestop) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				fmt.Printf("ERROR: %s\n", err.Error()) | 				fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
| 			} else { | 			} else { | ||||||
| 				fmt.Printf("  > Execute prestop: OK\n") | 				fmt.Printf("  > Execute pre-stop: OK\n") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -261,13 +356,13 @@ func StopJail(args []string) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/*fmt.Printf("  > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset) | 		fmt.Printf("  > Remove devfs ruleset %d: \n", cj.Devfs_ruleset) | ||||||
| 		err = deleteDevfsRuleset(cj) | 		err = deleteDevfsRuleset(cj.Devfs_ruleset) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Printf("ERROR: %s\n", err.Error()) | 			fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
| 		} else { | 		} else { | ||||||
| 			fmt.Printf("  > Remove devfsruleset %s: OK\n", cj.Config.Devfs_ruleset) | 			fmt.Printf("  > Remove devfsruleset %d: OK\n", cj.Devfs_ruleset) | ||||||
| 		}*/ | 		} | ||||||
|  |  | ||||||
| 		fmt.Printf("  > Stop jail %s:\n", cj.Name) | 		fmt.Printf("  > Stop jail %s:\n", cj.Name) | ||||||
| 		err = stopJail(cj) | 		err = stopJail(cj) | ||||||
| @ -297,15 +392,16 @@ func StopJail(args []string) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if cj.Config.Mount_fdescfs > 0 { | 		// FIXME: /dev/fd is mounted even with Mount_fdescfs = 0 ?! | ||||||
|  | 		//if cj.Config.Mount_fdescfs > 0 { | ||||||
| 			fmt.Printf("  > Umount fdescfs:\n") | 			fmt.Printf("  > Umount fdescfs:\n") | ||||||
| 			err := umountJailFsFromHost(cj, "/dev/fd") | 			err = umountJailFsFromHost(cj, "/dev/fd") | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				fmt.Printf("ERROR: %s\n", err.Error()) | 				fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
| 			} else { | 			} else { | ||||||
| 				fmt.Printf("  > Umount fdescfs: OK\n") | 				fmt.Printf("  > Umount fdescfs: OK\n") | ||||||
| 			} | 			} | ||||||
| 		} | 		//} | ||||||
|  |  | ||||||
| 		if cj.Config.Mount_devfs > 0 { | 		if cj.Config.Mount_devfs > 0 { | ||||||
| 			fmt.Printf("  > Umount devfs:\n") | 			fmt.Printf("  > Umount devfs:\n") | ||||||
| @ -318,7 +414,6 @@ func StopJail(args []string) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Remove local mounts from $JAIL/fstab | 		// Remove local mounts from $JAIL/fstab | ||||||
| 		// The way we get fstab is presumptuous |  | ||||||
| 		fstab := strings.Replace(cj.ConfigPath, "config.json", "fstab", 1) | 		fstab := strings.Replace(cj.ConfigPath, "config.json", "fstab", 1) | ||||||
| 		mounts, err := getFstab(fstab) | 		mounts, err := getFstab(fstab) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -328,7 +423,8 @@ func StopJail(args []string) { | |||||||
| 			fmt.Printf("  > Umount mountpoints from %s\n", fstab) | 			fmt.Printf("  > Umount mountpoints from %s\n", fstab) | ||||||
| 			errs := 0 | 			errs := 0 | ||||||
| 			for _, m := range mounts { | 			for _, m := range mounts { | ||||||
| 				err = umountJailFsFromHost(cj, m.Mountpoint) | 				log.Debugf("Umounting %s\n", m.Mountpoint) | ||||||
|  | 				err = umountFsFromHost(m.Mountpoint) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					fmt.Printf("ERROR: %s\n", err.Error()) | 					fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
| 					errs += 1 | 					errs += 1 | ||||||
| @ -338,6 +434,42 @@ func StopJail(args []string) { | |||||||
| 				fmt.Printf("  > Umount mountpoints from %s: OK\n", fstab) | 				fmt.Printf("  > Umount mountpoints from %s: OK\n", fstab) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: Execute poststop | ||||||
|  | 		if len(cj.Config.Exec_poststop) > 0 { | ||||||
|  | 			fmt.Printf("  > Execute post-stop:\n") | ||||||
|  | 			_, err := executeCommand(cj.Config.Exec_poststop) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
|  | 			} else { | ||||||
|  | 				fmt.Printf("  > Execute post-stop: OK\n") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// Remove parameter file | ||||||
|  | 		pfile := fmt.Sprintf("/var/run/jail.%s.conf", cj.InternalName) | ||||||
|  | 		if err = os.Remove(pfile); err != nil { | ||||||
|  | 			fmt.Printf("Error deleting parameter file %s\n", pfile) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// We need this to get a reference to cj.Running (bc cj.Running is just a copy of value in the scope of StopJail()) | ||||||
|  | 		for i, j := range gJails { | ||||||
|  | 			if strings.EqualFold(j.Name, cj.Name) && strings.EqualFold(j.Datastore, cj.Datastore) { | ||||||
|  | 				if err = setStructFieldValue(&gJails[i], "Running", "false"); err != nil { | ||||||
|  | 					fmt.Printf("ERROR: setting Running property to false: %s\n", err.Error()) | ||||||
|  | 				} | ||||||
|  | 				if err = setStructFieldValue(&gJails[i], "JID", "0"); err != nil { | ||||||
|  | 					fmt.Printf("ERROR: setting JID property to 0: %s\n", err.Error()) | ||||||
|  | 				} | ||||||
|  | 				if err = setStructFieldValue(&gJails[i], "InternalName", ""); err != nil { | ||||||
|  | 					fmt.Printf("ERROR: clearing InternalName property: %s\n", err.Error()) | ||||||
|  | 				} | ||||||
|  | 				if err = setStructFieldValue(&gJails[i], "Devfs_ruleset", "0"); err != nil { | ||||||
|  | 					fmt.Printf("ERROR: setting Devfs_ruleset property to 0: %s\n", err.Error()) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		writeConfigToDisk(cj, false) | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										667
									
								
								cmd/struct.go
									
									
									
									
									
								
							
							
						
						
									
										667
									
								
								cmd/struct.go
									
									
									
									
									
								
							| @ -1,5 +1,19 @@ | |||||||
| package cmd | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	IPv4 = 0 | ||||||
|  | 	IPv6 = 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type NatDesc struct { | ||||||
|  | 	Proto    string | ||||||
|  | 	JailPort string | ||||||
|  | 	HostPort string | ||||||
|  | } | ||||||
|  |  | ||||||
| // To allow sorting, just duplicate fields in JailSort below | // To allow sorting, just duplicate fields in JailSort below | ||||||
| type Jail struct { | type Jail struct { | ||||||
| @ -13,7 +27,9 @@ type Jail struct { | |||||||
| 	Running       bool | 	Running       bool | ||||||
| 	// No need, Config.Release always represent what is running (plus it know release for non-running jails) | 	// No need, Config.Release always represent what is running (plus it know release for non-running jails) | ||||||
| 	//Release					string | 	//Release					string | ||||||
|  | 	Devfs_ruleset int	// The effective devfs ruleset generated at runtime | ||||||
| 	Zpool         string | 	Zpool         string | ||||||
|  | 	Datastore     string | ||||||
| } | } | ||||||
|  |  | ||||||
| // iocage struct as stored in config.json | // iocage struct as stored in config.json | ||||||
| @ -83,8 +99,10 @@ type JailConfig struct { | |||||||
| 	Host_time             int    `json:"host_time"` | 	Host_time             int    `json:"host_time"` | ||||||
| 	Hostid                string `json:"hostid"` | 	Hostid                string `json:"hostid"` | ||||||
| 	Hostid_strict_check   int    `json:"hostid_strict_check"` | 	Hostid_strict_check   int    `json:"hostid_strict_check"` | ||||||
|  | // Specify multiple net cards with "vnet0:bridge0,vnet1:bridge1" | ||||||
| 	Interfaces            string `json:"interfaces"` | 	Interfaces            string `json:"interfaces"` | ||||||
| 	Ip4                   string `json:"ip4"` | 	Ip4                   string `json:"ip4"` | ||||||
|  | // Specify multiples IP with "vnet0|192.168.1.42,vnet1|10.0.0.3" | ||||||
| 	Ip4_addr              string `json:"ip4_addr"` | 	Ip4_addr              string `json:"ip4_addr"` | ||||||
| 	Ip4_saddrsel          string `json:"ip4_saddrsel"` | 	Ip4_saddrsel          string `json:"ip4_saddrsel"` | ||||||
| 	Ip6                   string `json:"ip6"` | 	Ip6                   string `json:"ip6"` | ||||||
| @ -174,303 +192,372 @@ type Mount struct { | |||||||
| 	Fs_Passno  int | 	Fs_Passno  int | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type Snapshot struct { | ||||||
|  | 	// snapshot name is stored after '@' in dataset name | ||||||
|  | 	Name       string | ||||||
|  | 	Datastore  string | ||||||
|  | 	Jailname   string | ||||||
|  | 	Mountpoint string | ||||||
|  | 	Used       uint64 | ||||||
|  | 	Referenced uint64 | ||||||
|  | 	Creation   time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
| // This struct hold "sort by jail fields" functions | type FreeBSDVersion struct { | ||||||
| type lessFunc func(j1 *Jail, j2 *Jail) bool | 	major      int | ||||||
|  | 	minor      int | ||||||
|  | 	flavor     string | ||||||
|  | 	patchLevel int | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type JailHost struct { | ||||||
|  | 	hostname          string | ||||||
|  | 	hostid            string | ||||||
|  | 	arch              string | ||||||
|  | 	default_gateway4  string | ||||||
|  | 	default_gateway6  string | ||||||
|  | 	default_interface string | ||||||
|  | 	version           FreeBSDVersion | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Datastore struct { | ||||||
|  | 	Name              string | ||||||
|  | 	Mountpoint        string | ||||||
|  | 	ZFSDataset        string | ||||||
|  | 	DefaultJailConfig JailConfig | ||||||
|  | 	Used              uint64 | ||||||
|  | 	Referenced        uint64 | ||||||
|  | 	Available         uint64 | ||||||
|  | } | ||||||
|  |  | ||||||
| // Fields in this struct are acquired by their name using reflection | // Fields in this struct are acquired by their name using reflection | ||||||
| //  So these char are forbidden for field name: -+. | //  So these char are forbidden for field name: -+. | ||||||
| // |  | ||||||
| type JailSort struct { | type JailSort struct { | ||||||
|     NameInc					lessFunc | 	NameInc         jailLessFunc | ||||||
| 	NameDec					lessFunc | 	NameDec         jailLessFunc | ||||||
|     InternalNameInc			lessFunc | 	InternalNameInc jailLessFunc | ||||||
|     InternalNameDec			lessFunc | 	InternalNameDec jailLessFunc | ||||||
|     JIDInc					lessFunc  | 	JIDInc          jailLessFunc | ||||||
|     JIDDec					lessFunc  | 	JIDDec          jailLessFunc | ||||||
|     RootPathInc				lessFunc | 	RootPathInc     jailLessFunc | ||||||
|     RootPathDec				lessFunc | 	RootPathDec     jailLessFunc | ||||||
| 	ConfigPathInc			lessFunc | 	ConfigPathInc   jailLessFunc | ||||||
| 	ConfigPathDec			lessFunc | 	ConfigPathDec   jailLessFunc | ||||||
|     RunningInc				lessFunc | 	RunningInc      jailLessFunc | ||||||
|     RunningDec				lessFunc | 	RunningDec      jailLessFunc | ||||||
|     ZpoolInc				lessFunc | 	DatastoreInc    jailLessFunc | ||||||
|     ZpoolDec				lessFunc | 	DatastoreDec    jailLessFunc | ||||||
|  | 	ZpoolInc        jailLessFunc | ||||||
|  | 	ZpoolDec        jailLessFunc | ||||||
|  | 	Devfs_rulesetInc jailLessFunc | ||||||
|  | 	Devfs_rulesetDec jailLessFunc | ||||||
| 	Config          JailConfigSort | 	Config          JailConfigSort | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| type JailConfigSort struct { | type JailConfigSort struct { | ||||||
| 	Config_versionInc		lessFunc | 	Config_versionInc         jailLessFunc | ||||||
| 	Config_versionDec		lessFunc | 	Config_versionDec         jailLessFunc | ||||||
| 	Allow_chflagsInc		lessFunc | 	Allow_chflagsInc          jailLessFunc | ||||||
| 	Allow_chflagsDec		lessFunc | 	Allow_chflagsDec          jailLessFunc | ||||||
| 	Allow_mlockInc			lessFunc | 	Allow_mlockInc            jailLessFunc | ||||||
| 	Allow_mlockDec			lessFunc | 	Allow_mlockDec            jailLessFunc | ||||||
| 	Allow_mountInc			lessFunc | 	Allow_mountInc            jailLessFunc | ||||||
| 	Allow_mountDec			lessFunc | 	Allow_mountDec            jailLessFunc | ||||||
| 	Allow_mount_devfsInc	lessFunc | 	Allow_mount_devfsInc      jailLessFunc | ||||||
| 	Allow_mount_devfsDec	lessFunc | 	Allow_mount_devfsDec      jailLessFunc | ||||||
| 	Allow_mount_fusefsInc	lessFunc | 	Allow_mount_fusefsInc     jailLessFunc | ||||||
| 	Allow_mount_fusefsDec	lessFunc | 	Allow_mount_fusefsDec     jailLessFunc | ||||||
| 	Allow_mount_nullfsInc	lessFunc | 	Allow_mount_nullfsInc     jailLessFunc | ||||||
| 	Allow_mount_nullfsDec	lessFunc | 	Allow_mount_nullfsDec     jailLessFunc | ||||||
| 	Allow_mount_procfsInc	lessFunc | 	Allow_mount_procfsInc     jailLessFunc | ||||||
| 	Allow_mount_procfsDec	lessFunc | 	Allow_mount_procfsDec     jailLessFunc | ||||||
| 	Allow_mount_tmpfsInc	lessFunc | 	Allow_mount_tmpfsInc      jailLessFunc | ||||||
| 	Allow_mount_tmpfsDec	lessFunc | 	Allow_mount_tmpfsDec      jailLessFunc | ||||||
| 	Allow_mount_zfsInc		lessFunc | 	Allow_mount_zfsInc        jailLessFunc | ||||||
| 	Allow_mount_zfsDec		lessFunc | 	Allow_mount_zfsDec        jailLessFunc | ||||||
| 	Allow_quotasInc			lessFunc | 	Allow_quotasInc           jailLessFunc | ||||||
| 	Allow_quotasDec			lessFunc | 	Allow_quotasDec           jailLessFunc | ||||||
| 	Allow_raw_socketsInc	lessFunc | 	Allow_raw_socketsInc      jailLessFunc | ||||||
| 	Allow_raw_socketsDec	lessFunc | 	Allow_raw_socketsDec      jailLessFunc | ||||||
| 	Allow_set_hostnameInc	lessFunc | 	Allow_set_hostnameInc     jailLessFunc | ||||||
| 	Allow_set_hostnameDec	lessFunc | 	Allow_set_hostnameDec     jailLessFunc | ||||||
| 	Allow_socket_afInc		lessFunc | 	Allow_socket_afInc        jailLessFunc | ||||||
| 	Allow_socket_afDec		lessFunc | 	Allow_socket_afDec        jailLessFunc | ||||||
| 	Allow_sysvipcInc		lessFunc | 	Allow_sysvipcInc          jailLessFunc | ||||||
| 	Allow_sysvipcDec		lessFunc | 	Allow_sysvipcDec          jailLessFunc | ||||||
| 	Allow_tunInc			lessFunc | 	Allow_tunInc              jailLessFunc | ||||||
| 	Allow_tunDec			lessFunc | 	Allow_tunDec              jailLessFunc | ||||||
| 	Allow_vmmInc			lessFunc | 	Allow_vmmInc              jailLessFunc | ||||||
| 	Allow_vmmDec			lessFunc | 	Allow_vmmDec              jailLessFunc | ||||||
| 	Assign_localhostInc		lessFunc | 	Assign_localhostInc       jailLessFunc | ||||||
| 	Assign_localhostDec		lessFunc | 	Assign_localhostDec       jailLessFunc | ||||||
| 	AvailableInc			lessFunc | 	AvailableInc              jailLessFunc | ||||||
| 	AvailableDec			lessFunc | 	AvailableDec              jailLessFunc | ||||||
| 	BasejailInc				lessFunc | 	BasejailInc               jailLessFunc | ||||||
| 	BasejailDec				lessFunc | 	BasejailDec               jailLessFunc | ||||||
| 	BootInc					lessFunc | 	BootInc                   jailLessFunc | ||||||
| 	BootDec					lessFunc | 	BootDec                   jailLessFunc | ||||||
| 	BpfInc					lessFunc | 	BpfInc                    jailLessFunc | ||||||
| 	BpfDec					lessFunc | 	BpfDec                    jailLessFunc | ||||||
| 	Children_maxInc			lessFunc | 	Children_maxInc           jailLessFunc | ||||||
| 	Children_maxDec			lessFunc | 	Children_maxDec           jailLessFunc | ||||||
| 	Cloned_releaseInc		lessFunc | 	Cloned_releaseInc         jailLessFunc | ||||||
| 	Cloned_releaseDec		lessFunc | 	Cloned_releaseDec         jailLessFunc | ||||||
| 	CommentInc				lessFunc | 	CommentInc                jailLessFunc | ||||||
| 	CommentDec				lessFunc | 	CommentDec                jailLessFunc | ||||||
| 	CompressionInc			lessFunc | 	CompressionInc            jailLessFunc | ||||||
| 	CompressionDec			lessFunc | 	CompressionDec            jailLessFunc | ||||||
| 	CompressratioInc		lessFunc | 	CompressratioInc          jailLessFunc | ||||||
| 	CompressratioDec		lessFunc | 	CompressratioDec          jailLessFunc | ||||||
| 	CoredumpsizeInc			lessFunc | 	CoredumpsizeInc           jailLessFunc | ||||||
| 	CoredumpsizeDec			lessFunc | 	CoredumpsizeDec           jailLessFunc | ||||||
| 	CountInc				lessFunc | 	CountInc                  jailLessFunc | ||||||
| 	CountDec				lessFunc | 	CountDec                  jailLessFunc | ||||||
| 	CpusetInc				lessFunc | 	CpusetInc                 jailLessFunc | ||||||
| 	CpusetDec				lessFunc | 	CpusetDec                 jailLessFunc | ||||||
| 	CputimeInc				lessFunc | 	CputimeInc                jailLessFunc | ||||||
| 	CputimeDec				lessFunc | 	CputimeDec                jailLessFunc | ||||||
| 	DatasizeInc				lessFunc | 	DatasizeInc               jailLessFunc | ||||||
| 	DatasizeDec				lessFunc | 	DatasizeDec               jailLessFunc | ||||||
| 	DedupInc				lessFunc | 	DedupInc                  jailLessFunc | ||||||
| 	DedupDec				lessFunc | 	DedupDec                  jailLessFunc | ||||||
| 	DefaultrouterInc		lessFunc | 	DefaultrouterInc          jailLessFunc | ||||||
| 	DefaultrouterDec		lessFunc | 	DefaultrouterDec          jailLessFunc | ||||||
| 	Defaultrouter6Inc		lessFunc | 	Defaultrouter6Inc         jailLessFunc | ||||||
| 	Defaultrouter6Dec		lessFunc | 	Defaultrouter6Dec         jailLessFunc | ||||||
| 	DependsInc				lessFunc | 	DependsInc                jailLessFunc | ||||||
| 	DependsDec				lessFunc | 	DependsDec                jailLessFunc | ||||||
| 	Devfs_rulesetInc		lessFunc | 	Devfs_rulesetInc          jailLessFunc | ||||||
| 	Devfs_rulesetDec		lessFunc | 	Devfs_rulesetDec          jailLessFunc | ||||||
| 	DhcpInc					lessFunc | 	DhcpInc                   jailLessFunc | ||||||
| 	DhcpDec					lessFunc | 	DhcpDec                   jailLessFunc | ||||||
| 	Enforce_statfsInc		lessFunc | 	Enforce_statfsInc         jailLessFunc | ||||||
| 	Enforce_statfsDec		lessFunc | 	Enforce_statfsDec         jailLessFunc | ||||||
| 	Exec_cleanInc			lessFunc | 	Exec_cleanInc             jailLessFunc | ||||||
| 	Exec_cleanDec			lessFunc | 	Exec_cleanDec             jailLessFunc | ||||||
| 	Exec_createdInc			lessFunc | 	Exec_createdInc           jailLessFunc | ||||||
| 	Exec_createdDec			lessFunc | 	Exec_createdDec           jailLessFunc | ||||||
| 	Exec_fibInc				lessFunc | 	Exec_fibInc               jailLessFunc | ||||||
| 	Exec_fibDec				lessFunc | 	Exec_fibDec               jailLessFunc | ||||||
| 	Exec_jail_userInc		lessFunc | 	Exec_jail_userInc         jailLessFunc | ||||||
| 	Exec_jail_userDec		lessFunc | 	Exec_jail_userDec         jailLessFunc | ||||||
| 	Exec_poststartInc		lessFunc | 	Exec_poststartInc         jailLessFunc | ||||||
| 	Exec_poststartDec		lessFunc | 	Exec_poststartDec         jailLessFunc | ||||||
| 	Exec_poststopInc		lessFunc | 	Exec_poststopInc          jailLessFunc | ||||||
| 	Exec_poststopDec		lessFunc | 	Exec_poststopDec          jailLessFunc | ||||||
| 	Exec_prestartInc		lessFunc | 	Exec_prestartInc          jailLessFunc | ||||||
| 	Exec_prestartDec		lessFunc | 	Exec_prestartDec          jailLessFunc | ||||||
| 	Exec_prestopInc			lessFunc | 	Exec_prestopInc           jailLessFunc | ||||||
| 	Exec_prestopDec			lessFunc | 	Exec_prestopDec           jailLessFunc | ||||||
| 	Exec_startInc			lessFunc | 	Exec_startInc             jailLessFunc | ||||||
| 	Exec_startDec			lessFunc | 	Exec_startDec             jailLessFunc | ||||||
| 	Exec_stopInc			lessFunc | 	Exec_stopInc              jailLessFunc | ||||||
| 	Exec_stopDec			lessFunc | 	Exec_stopDec              jailLessFunc | ||||||
| 	Exec_system_jail_userInc	lessFunc | 	Exec_system_jail_userInc  jailLessFunc | ||||||
| 	Exec_system_jail_userDec	lessFunc | 	Exec_system_jail_userDec  jailLessFunc | ||||||
| 	Exec_system_userInc		lessFunc | 	Exec_system_userInc       jailLessFunc | ||||||
| 	Exec_system_userDec		lessFunc | 	Exec_system_userDec       jailLessFunc | ||||||
| 	Exec_timeoutInc			lessFunc | 	Exec_timeoutInc           jailLessFunc | ||||||
| 	Exec_timeoutDec			lessFunc | 	Exec_timeoutDec           jailLessFunc | ||||||
| 	Host_domainnameInc		lessFunc | 	Host_domainnameInc        jailLessFunc | ||||||
| 	Host_domainnameDec		lessFunc | 	Host_domainnameDec        jailLessFunc | ||||||
| 	Host_hostnameInc		lessFunc | 	Host_hostnameInc          jailLessFunc | ||||||
| 	Host_hostnameDec		lessFunc | 	Host_hostnameDec          jailLessFunc | ||||||
| 	Host_hostuuidInc		lessFunc | 	Host_hostuuidInc          jailLessFunc | ||||||
| 	Host_hostuuidDec		lessFunc | 	Host_hostuuidDec          jailLessFunc | ||||||
| 	Host_timeInc			lessFunc | 	Host_timeInc              jailLessFunc | ||||||
| 	Host_timeDec			lessFunc | 	Host_timeDec              jailLessFunc | ||||||
| 	HostidInc				lessFunc | 	HostidInc                 jailLessFunc | ||||||
| 	HostidDec				lessFunc | 	HostidDec                 jailLessFunc | ||||||
| 	Hostid_strict_checkInc	lessFunc | 	Hostid_strict_checkInc    jailLessFunc | ||||||
| 	Hostid_strict_checkDec	lessFunc | 	Hostid_strict_checkDec    jailLessFunc | ||||||
| 	InterfacesInc			lessFunc | 	InterfacesInc             jailLessFunc | ||||||
| 	InterfacesDec			lessFunc | 	InterfacesDec             jailLessFunc | ||||||
| 	Ip4Inc					lessFunc | 	Ip4Inc                    jailLessFunc | ||||||
| 	Ip4Dec					lessFunc | 	Ip4Dec                    jailLessFunc | ||||||
| 	Ip4_addrInc				lessFunc | 	Ip4_addrInc               jailLessFunc | ||||||
| 	Ip4_addrDec				lessFunc | 	Ip4_addrDec               jailLessFunc | ||||||
| 	Ip4_saddrselInc			lessFunc | 	Ip4_saddrselInc           jailLessFunc | ||||||
| 	Ip4_saddrselDec			lessFunc | 	Ip4_saddrselDec           jailLessFunc | ||||||
| 	Ip6Inc					lessFunc | 	Ip6Inc                    jailLessFunc | ||||||
| 	Ip6Dec					lessFunc | 	Ip6Dec                    jailLessFunc | ||||||
| 	Ip6_addrInc				lessFunc | 	Ip6_addrInc               jailLessFunc | ||||||
| 	Ip6_addrDec				lessFunc | 	Ip6_addrDec               jailLessFunc | ||||||
| 	Ip6_saddrselInc			lessFunc | 	Ip6_saddrselInc           jailLessFunc | ||||||
| 	Ip6_saddrselDec			lessFunc | 	Ip6_saddrselDec           jailLessFunc | ||||||
| 	Ip_hostnameInc			lessFunc | 	Ip_hostnameInc            jailLessFunc | ||||||
| 	Ip_hostnameDec			lessFunc | 	Ip_hostnameDec            jailLessFunc | ||||||
| 	Jail_zfsInc				lessFunc | 	Jail_zfsInc               jailLessFunc | ||||||
| 	Jail_zfsDec				lessFunc | 	Jail_zfsDec               jailLessFunc | ||||||
| 	Jail_zfs_datasetInc		lessFunc | 	Jail_zfs_datasetInc       jailLessFunc | ||||||
| 	Jail_zfs_datasetDec		lessFunc | 	Jail_zfs_datasetDec       jailLessFunc | ||||||
| 	Jail_zfs_mountpointInc	lessFunc | 	Jail_zfs_mountpointInc    jailLessFunc | ||||||
| 	Jail_zfs_mountpointDec	lessFunc | 	Jail_zfs_mountpointDec    jailLessFunc | ||||||
| 	Last_startedInc			lessFunc | 	Last_startedInc           jailLessFunc | ||||||
| 	Last_startedDec			lessFunc | 	Last_startedDec           jailLessFunc | ||||||
| 	Localhost_ipInc			lessFunc | 	Localhost_ipInc           jailLessFunc | ||||||
| 	Localhost_ipDec			lessFunc | 	Localhost_ipDec           jailLessFunc | ||||||
| 	Login_flagsInc			lessFunc | 	Login_flagsInc            jailLessFunc | ||||||
| 	Login_flagsDec			lessFunc | 	Login_flagsDec            jailLessFunc | ||||||
| 	Mac_prefixInc			lessFunc | 	Mac_prefixInc             jailLessFunc | ||||||
| 	Mac_prefixDec			lessFunc | 	Mac_prefixDec             jailLessFunc | ||||||
| 	MaxprocInc				lessFunc | 	MaxprocInc                jailLessFunc | ||||||
| 	MaxprocDec				lessFunc | 	MaxprocDec                jailLessFunc | ||||||
| 	MemorylockedInc			lessFunc | 	MemorylockedInc           jailLessFunc | ||||||
| 	MemorylockedDec			lessFunc | 	MemorylockedDec           jailLessFunc | ||||||
| 	MemoryuseInc			lessFunc | 	MemoryuseInc              jailLessFunc | ||||||
| 	MemoryuseDec			lessFunc | 	MemoryuseDec              jailLessFunc | ||||||
| 	Min_dyn_devfs_rulesetInc	lessFunc | 	Min_dyn_devfs_rulesetInc  jailLessFunc | ||||||
| 	Min_dyn_devfs_rulesetDec	lessFunc | 	Min_dyn_devfs_rulesetDec  jailLessFunc | ||||||
| 	Mount_devfsInc			lessFunc | 	Mount_devfsInc            jailLessFunc | ||||||
| 	Mount_devfsDec			lessFunc | 	Mount_devfsDec            jailLessFunc | ||||||
| 	Mount_fdescfsInc		lessFunc | 	Mount_fdescfsInc          jailLessFunc | ||||||
| 	Mount_fdescfsDec		lessFunc | 	Mount_fdescfsDec          jailLessFunc | ||||||
| 	Mount_linprocfsInc		lessFunc | 	Mount_linprocfsInc        jailLessFunc | ||||||
| 	Mount_linprocfsDec		lessFunc | 	Mount_linprocfsDec        jailLessFunc | ||||||
| 	Mount_procfsInc			lessFunc | 	Mount_procfsInc           jailLessFunc | ||||||
| 	Mount_procfsDec			lessFunc | 	Mount_procfsDec           jailLessFunc | ||||||
| 	MountpointInc			lessFunc | 	MountpointInc             jailLessFunc | ||||||
| 	MountpointDec			lessFunc | 	MountpointDec             jailLessFunc | ||||||
| 	MsgqqueuedInc			lessFunc | 	MsgqqueuedInc             jailLessFunc | ||||||
| 	MsgqqueuedDec			lessFunc | 	MsgqqueuedDec             jailLessFunc | ||||||
| 	MsgqsizeInc				lessFunc | 	MsgqsizeInc               jailLessFunc | ||||||
| 	MsgqsizeDec				lessFunc | 	MsgqsizeDec               jailLessFunc | ||||||
| 	NatInc					lessFunc | 	NatInc                    jailLessFunc | ||||||
| 	NatDec					lessFunc | 	NatDec                    jailLessFunc | ||||||
| 	Nat_backendInc			lessFunc | 	Nat_backendInc            jailLessFunc | ||||||
| 	Nat_backendDec			lessFunc | 	Nat_backendDec            jailLessFunc | ||||||
| 	Nat_forwardsInc			lessFunc | 	Nat_forwardsInc           jailLessFunc | ||||||
| 	Nat_forwardsDec			lessFunc | 	Nat_forwardsDec           jailLessFunc | ||||||
| 	Nat_interfaceInc		lessFunc | 	Nat_interfaceInc          jailLessFunc | ||||||
| 	Nat_interfaceDec		lessFunc | 	Nat_interfaceDec          jailLessFunc | ||||||
| 	Nat_prefixInc			lessFunc | 	Nat_prefixInc             jailLessFunc | ||||||
| 	Nat_prefixDec			lessFunc | 	Nat_prefixDec             jailLessFunc | ||||||
| 	NmsgqInc				lessFunc | 	NmsgqInc                  jailLessFunc | ||||||
| 	NmsgqDec				lessFunc | 	NmsgqDec                  jailLessFunc | ||||||
| 	NotesInc				lessFunc | 	NotesInc                  jailLessFunc | ||||||
| 	NotesDec				lessFunc | 	NotesDec                  jailLessFunc | ||||||
| 	NsemInc					lessFunc | 	NsemInc                   jailLessFunc | ||||||
| 	NsemDec					lessFunc | 	NsemDec                   jailLessFunc | ||||||
| 	NsemopInc				lessFunc | 	NsemopInc                 jailLessFunc | ||||||
| 	NsemopDec				lessFunc | 	NsemopDec                 jailLessFunc | ||||||
| 	NshmInc					lessFunc | 	NshmInc                   jailLessFunc | ||||||
| 	NshmDec					lessFunc | 	NshmDec                   jailLessFunc | ||||||
| 	NthrInc					lessFunc | 	NthrInc                   jailLessFunc | ||||||
| 	NthrDec					lessFunc | 	NthrDec                   jailLessFunc | ||||||
| 	OpenfilesInc			lessFunc | 	OpenfilesInc              jailLessFunc | ||||||
| 	OpenfilesDec			lessFunc | 	OpenfilesDec              jailLessFunc | ||||||
| 	OriginInc				lessFunc | 	OriginInc                 jailLessFunc | ||||||
| 	OriginDec				lessFunc | 	OriginDec                 jailLessFunc | ||||||
| 	OwnerInc				lessFunc | 	OwnerInc                  jailLessFunc | ||||||
| 	OwnerDec				lessFunc | 	OwnerDec                  jailLessFunc | ||||||
| 	PcpuInc					lessFunc | 	PcpuInc                   jailLessFunc | ||||||
| 	PcpuDec					lessFunc | 	PcpuDec                   jailLessFunc | ||||||
| 	Plugin_nameInc			lessFunc | 	Plugin_nameInc            jailLessFunc | ||||||
| 	Plugin_nameDec			lessFunc | 	Plugin_nameDec            jailLessFunc | ||||||
| 	Plugin_repositoryInc	lessFunc | 	Plugin_repositoryInc      jailLessFunc | ||||||
| 	Plugin_repositoryDec	lessFunc | 	Plugin_repositoryDec      jailLessFunc | ||||||
| 	PriorityInc				lessFunc | 	PriorityInc               jailLessFunc | ||||||
| 	PriorityDec				lessFunc | 	PriorityDec               jailLessFunc | ||||||
| 	PseudoterminalsInc		lessFunc | 	PseudoterminalsInc        jailLessFunc | ||||||
| 	PseudoterminalsDec		lessFunc | 	PseudoterminalsDec        jailLessFunc | ||||||
| 	QuotaInc				lessFunc | 	QuotaInc                  jailLessFunc | ||||||
| 	QuotaDec				lessFunc | 	QuotaDec                  jailLessFunc | ||||||
| 	ReadbpsInc				lessFunc | 	ReadbpsInc                jailLessFunc | ||||||
| 	ReadbpsDec				lessFunc | 	ReadbpsDec                jailLessFunc | ||||||
| 	ReadiopsInc				lessFunc | 	ReadiopsInc               jailLessFunc | ||||||
| 	ReadiopsDec				lessFunc | 	ReadiopsDec               jailLessFunc | ||||||
| 	ReleaseInc				lessFunc | 	ReleaseInc                jailLessFunc | ||||||
| 	ReleaseDec				lessFunc | 	ReleaseDec                jailLessFunc | ||||||
| 	ReservationInc			lessFunc | 	ReservationInc            jailLessFunc | ||||||
| 	ReservationDec			lessFunc | 	ReservationDec            jailLessFunc | ||||||
| 	ResolverInc				lessFunc | 	ResolverInc               jailLessFunc | ||||||
| 	ResolverDec				lessFunc | 	ResolverDec               jailLessFunc | ||||||
| 	RlimitsInc				lessFunc | 	RlimitsInc                jailLessFunc | ||||||
| 	RlimitsDec				lessFunc | 	RlimitsDec                jailLessFunc | ||||||
| 	RtsoldInc				lessFunc | 	RtsoldInc                 jailLessFunc | ||||||
| 	RtsoldDec				lessFunc | 	RtsoldDec                 jailLessFunc | ||||||
| 	SecurelevelInc			lessFunc	 | 	SecurelevelInc            jailLessFunc | ||||||
| 	SecurelevelDec			lessFunc	 | 	SecurelevelDec            jailLessFunc | ||||||
| 	ShmsizeInc				lessFunc	 | 	ShmsizeInc                jailLessFunc | ||||||
| 	ShmsizeDec				lessFunc	 | 	ShmsizeDec                jailLessFunc | ||||||
| 	StacksizeInc			lessFunc | 	StacksizeInc              jailLessFunc | ||||||
| 	StacksizeDec			lessFunc | 	StacksizeDec              jailLessFunc | ||||||
| 	Stop_timeoutInc			lessFunc | 	Stop_timeoutInc           jailLessFunc | ||||||
| 	Stop_timeoutDec			lessFunc | 	Stop_timeoutDec           jailLessFunc | ||||||
| 	SwapuseInc				lessFunc | 	SwapuseInc                jailLessFunc | ||||||
| 	SwapuseDec				lessFunc | 	SwapuseDec                jailLessFunc | ||||||
| 	Sync_stateInc			lessFunc | 	Sync_stateInc             jailLessFunc | ||||||
| 	Sync_stateDec			lessFunc | 	Sync_stateDec             jailLessFunc | ||||||
| 	Sync_targetInc			lessFunc | 	Sync_targetInc            jailLessFunc | ||||||
| 	Sync_targetDec			lessFunc | 	Sync_targetDec            jailLessFunc | ||||||
| 	Sync_tgt_zpoolInc		lessFunc | 	Sync_tgt_zpoolInc         jailLessFunc | ||||||
| 	Sync_tgt_zpoolDec		lessFunc | 	Sync_tgt_zpoolDec         jailLessFunc | ||||||
| 	SysvmsgInc				lessFunc | 	SysvmsgInc                jailLessFunc | ||||||
| 	SysvmsgDec				lessFunc | 	SysvmsgDec                jailLessFunc | ||||||
| 	SysvsemInc				lessFunc	 | 	SysvsemInc                jailLessFunc | ||||||
| 	SysvsemDec				lessFunc	 | 	SysvsemDec                jailLessFunc | ||||||
| 	SysvshmInc				lessFunc	 | 	SysvshmInc                jailLessFunc | ||||||
| 	SysvshmDec				lessFunc	 | 	SysvshmDec                jailLessFunc | ||||||
| 	TemplateInc				lessFunc | 	TemplateInc               jailLessFunc | ||||||
| 	TemplateDec				lessFunc | 	TemplateDec               jailLessFunc | ||||||
| 	JailtypeInc				lessFunc | 	JailtypeInc               jailLessFunc | ||||||
| 	JailtypeDec				lessFunc | 	JailtypeDec               jailLessFunc | ||||||
| 	UsedInc					lessFunc | 	UsedInc                   jailLessFunc | ||||||
| 	UsedDec					lessFunc | 	UsedDec                   jailLessFunc | ||||||
| 	VmemoryuseInc			lessFunc | 	VmemoryuseInc             jailLessFunc | ||||||
| 	VmemoryuseDec			lessFunc | 	VmemoryuseDec             jailLessFunc | ||||||
| 	VnetInc					lessFunc | 	VnetInc                   jailLessFunc | ||||||
| 	VnetDec					lessFunc | 	VnetDec                   jailLessFunc | ||||||
| 	Vnet0_macInc			lessFunc | 	Vnet0_macInc              jailLessFunc | ||||||
| 	Vnet0_macDec			lessFunc | 	Vnet0_macDec              jailLessFunc | ||||||
| 	Vnet1_macInc			lessFunc | 	Vnet1_macInc              jailLessFunc | ||||||
| 	Vnet1_macDec			lessFunc | 	Vnet1_macDec              jailLessFunc | ||||||
| 	Vnet2_macInc			lessFunc | 	Vnet2_macInc              jailLessFunc | ||||||
| 	Vnet2_macDec			lessFunc | 	Vnet2_macDec              jailLessFunc | ||||||
| 	Vnet3_macInc			lessFunc | 	Vnet3_macInc              jailLessFunc | ||||||
| 	Vnet3_macDec			lessFunc | 	Vnet3_macDec              jailLessFunc | ||||||
| 	Vnet_default_interfaceInc	lessFunc | 	Vnet_default_interfaceInc jailLessFunc | ||||||
| 	Vnet_default_interfaceDec	lessFunc | 	Vnet_default_interfaceDec jailLessFunc | ||||||
| 	Vnet_interfacesInc		lessFunc | 	Vnet_interfacesInc        jailLessFunc | ||||||
| 	Vnet_interfacesDec		lessFunc | 	Vnet_interfacesDec        jailLessFunc | ||||||
| 	WallclockInc			lessFunc | 	WallclockInc              jailLessFunc | ||||||
| 	WallclockDec			lessFunc | 	WallclockDec              jailLessFunc | ||||||
| 	WritebpsInc				lessFunc | 	WritebpsInc               jailLessFunc | ||||||
| 	WritebpsDec				lessFunc | 	WritebpsDec               jailLessFunc | ||||||
| 	WriteiopsInc			lessFunc | 	WriteiopsInc              jailLessFunc | ||||||
| 	WriteiopsDec			lessFunc | 	WriteiopsDec              jailLessFunc | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type SnapshotSort struct { | ||||||
|  | 	NameInc       snapshotLessFunc | ||||||
|  | 	NameDec       snapshotLessFunc | ||||||
|  | 	DatastoreInc  snapshotLessFunc | ||||||
|  | 	DatastoreDec  snapshotLessFunc | ||||||
|  | 	JailnameInc   snapshotLessFunc | ||||||
|  | 	JailnameDec   snapshotLessFunc | ||||||
|  | 	MountpointInc snapshotLessFunc | ||||||
|  | 	MountpointDec snapshotLessFunc | ||||||
|  | 	UsedInc       snapshotLessFunc | ||||||
|  | 	UsedDec       snapshotLessFunc | ||||||
|  | 	ReferencedInc snapshotLessFunc | ||||||
|  | 	ReferencedDec snapshotLessFunc | ||||||
|  | 	CreationInc   snapshotLessFunc | ||||||
|  | 	CreationDec   snapshotLessFunc | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DatastoreSort struct { | ||||||
|  | 	NameInc       datastoreLessFunc | ||||||
|  | 	NameDec       datastoreLessFunc | ||||||
|  | 	MountpointInc datastoreLessFunc | ||||||
|  | 	MountpointDec datastoreLessFunc | ||||||
|  | 	ZFSDatasetInc datastoreLessFunc | ||||||
|  | 	ZFSDatasetDec datastoreLessFunc | ||||||
|  | 	UsedInc       datastoreLessFunc | ||||||
|  | 	UsedDec       datastoreLessFunc | ||||||
|  | 	ReferencedInc datastoreLessFunc | ||||||
|  | 	ReferencedDec datastoreLessFunc | ||||||
|  | 	AvailableInc  datastoreLessFunc | ||||||
|  | 	AvailableDec  datastoreLessFunc | ||||||
|  | } | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										93
									
								
								cmd/update.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								cmd/update.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"fmt" | ||||||
|  | 	//"log" | ||||||
|  | 	"time" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Internal usage only | ||||||
|  | func updateJail(jail *Jail) error { | ||||||
|  | 	// Create default config as temporary file | ||||||
|  | 	cfgFile, err := os.CreateTemp("", "gocage-jail-update-") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfgFile.Write([]byte(fbsdUpdateConfig)) | ||||||
|  |  | ||||||
|  | 	defer cfgFile.Close() | ||||||
|  | 	defer os.Remove(cfgFile.Name()) | ||||||
|  |  | ||||||
|  | 	// Folder containing update/upgrade temporary files. Common so we save bandwith when upgrading multiple jails | ||||||
|  | 	// TODO: Variabilize /iocage/freebsd-update | ||||||
|  | 	_, err = os.Stat("/iocage/freebsd-update") | ||||||
|  | 	if os.IsNotExist(err) { | ||||||
|  | 		if err := os.Mkdir("/iocage/freebsd-update", 0755); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s fetch", | ||||||
|  | 					   cfgFile.Name(), jail.RootPath, jail.Config.Release) | ||||||
|  | 	err = executeCommandWithOutputToStdout(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd = fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s install", | ||||||
|  | 			   cfgFile.Name(), jail.RootPath, jail.Config.Release) | ||||||
|  | 	err = executeCommandWithOutputToStdout(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get and write new release into config.json | ||||||
|  | 	updateVersion(jail) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func UpdateJail(args []string) { | ||||||
|  | 	// Current jail were stopping | ||||||
|  | 	var cj *Jail | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	for _, a := range args { | ||||||
|  | 		// Check if jail exist and is distinctly named | ||||||
|  | 		cj, err = getJailFromArray(a, []string{""}, gJails) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting jail: %s\n", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// We cant update basejail as system is readonly | ||||||
|  | 		if strings.EqualFold(cj.Config.Jailtype, "basejail") { | ||||||
|  | 			fmt.Printf("%s is a basejail using %s system files. Please update %s!\n", cj.Name, cj.Config.Origin, cj.Config.Origin) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Printf("  > Snapshot jail %s\n", cj.Name) | ||||||
|  | 		// Set snapshot name | ||||||
|  | 		dt := time.Now() | ||||||
|  | 		curDate := fmt.Sprintf("%s", dt.Format("2006-01-02_15-04-05")) | ||||||
|  | 		gSnapshotName = fmt.Sprintf("goc_update_%s_%s", cj.Config.Release, curDate) | ||||||
|  | 		err := createJailSnapshot(*cj) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("  > Snapshot jail %s: ERROR: %s\n", cj.Name, err.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("  > Snapshot jail %s: OK\n", cj.Name) | ||||||
|  | 		 | ||||||
|  | 		fmt.Printf("  > Update jail %s\n", cj.Name) | ||||||
|  | 		err = updateJail(cj) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
|  | 		} else { | ||||||
|  | 			fmt.Printf("  > Update jail %s: OK\n", cj.Name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										125
									
								
								cmd/upgrade.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								cmd/upgrade.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | |||||||
|  | package cmd | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"fmt" | ||||||
|  | 	//"log" | ||||||
|  | 	"time" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Internal usage only | ||||||
|  | func upgradeJail(jail *Jail, version string) error { | ||||||
|  | 	// Create default config as temporary file | ||||||
|  | 	cfgFile, err := os.CreateTemp("", "gocage-jail-upgrade-") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfgFile.Write([]byte(fbsdUpdateConfig)) | ||||||
|  |  | ||||||
|  | 	defer cfgFile.Close() | ||||||
|  | 	defer os.Remove(cfgFile.Name()) | ||||||
|  |  | ||||||
|  | 	// Folder containing update/uipgrade temporary files. Common so we save bandwith when upgrading multiple jails | ||||||
|  | 	// TODO: Variabilize /iocage/freebsd-update | ||||||
|  | 	_, err = os.Stat("/iocage/freebsd-update") | ||||||
|  | 	if os.IsNotExist(err) { | ||||||
|  | 		if err := os.Mkdir("/iocage/freebsd-update", 0755); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get current version. Won't work on stopped jail. | ||||||
|  | 	fbsdvers, err := executeCommandInJail(jail, "/bin/freebsd-version") | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("ERROR executeCommandInJail: %s\n", err.Error()) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	fbsdvers = strings.TrimRight(fbsdvers, "\n") | ||||||
|  | 	//fbsdvers := jail.Config.Release | ||||||
|  |  | ||||||
|  | 	cmd := fmt.Sprintf("/usr/sbin/freebsd-update -f %s -b %s --currently-running %s -r %s upgrade",  | ||||||
|  | 			   cfgFile.Name(), jail.RootPath, fbsdvers, version) | ||||||
|  |  | ||||||
|  | 	//fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd) | ||||||
|  |  | ||||||
|  | 	// Need to give user control, bc there could be merge edit needs | ||||||
|  | 	err = executeCommandWithStdinStdoutStderr(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd = fmt.Sprintf("/usr/sbin/freebsd-update -f %s -b %s --currently-running %s -r %s install",  | ||||||
|  | 			   cfgFile.Name(), jail.RootPath, fbsdvers, version) | ||||||
|  |  | ||||||
|  | 	//fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd) | ||||||
|  |  | ||||||
|  | 	err = executeCommandWithStdinStdoutStderr(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd = fmt.Sprintf("/usr/sbin/freebsd-update -f %s -b %s --currently-running %s -r %s install",  | ||||||
|  | 			  cfgFile.Name(), jail.RootPath, fbsdvers, version) | ||||||
|  |  | ||||||
|  | 	//fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd) | ||||||
|  |  | ||||||
|  | 	err = executeCommandWithStdinStdoutStderr(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd = fmt.Sprintf("/usr/local/sbin/pkg-static -j %d install -q -f -y pkg", jail.JID) | ||||||
|  | 	err = executeCommandWithStdinStdoutStderr(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get and write new release into config.json | ||||||
|  | 	updateVersion(jail) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func UpgradeJail(args []string) { | ||||||
|  | 	// Current jail were stopping | ||||||
|  | 	var cj *Jail | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	for _, a := range args { | ||||||
|  | 		// Check if jail exist and is distinctly named | ||||||
|  | 		cj, err = getJailFromArray(a, []string{""}, gJails) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("Error getting jail: %s\n", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if cj.Running == false { | ||||||
|  | 			fmt.Printf("Error: jail must be running for upgrade.\n") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Printf("  > Snapshot jail %s\n", cj.Name) | ||||||
|  | 		// Set snapshot name | ||||||
|  | 		dt := time.Now() | ||||||
|  | 		curDate := fmt.Sprintf("%s", dt.Format("2006-01-02_15-04-05")) | ||||||
|  | 		gSnapshotName = fmt.Sprintf("goc_upgrade_%s_%s", cj.Config.Release, curDate) | ||||||
|  | 		err := createJailSnapshot(*cj) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("  > Snapshot jail %s: ERROR: %s\n", cj.Name, err.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("  > Snapshot jail %s: OK\n", cj.Name) | ||||||
|  |  | ||||||
|  | 		fmt.Printf("  > Upgrade jail %s to %s\n", cj.Name, gUpgradeRelease) | ||||||
|  | 		err = upgradeJail(cj, gUpgradeRelease) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("ERROR: %s\n", err.Error()) | ||||||
|  | 		} else { | ||||||
|  | 			fmt.Printf("  > Upgrade jail %s: OK\n", cj.Name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										1993
									
								
								cmd/utils.go
									
									
									
									
									
								
							
							
						
						
									
										1993
									
								
								cmd/utils.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @ -3,8 +3,14 @@ module gocage | |||||||
| go 1.17 | go 1.17 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
|  | 	github.com/c-robinson/iplib v1.0.3 | ||||||
|  | 	github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b | ||||||
|  | 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | ||||||
|  | 	github.com/otiai10/copy v1.12.0 | ||||||
|  | 	github.com/sirupsen/logrus v1.8.1 | ||||||
| 	github.com/spf13/cobra v1.2.1 | 	github.com/spf13/cobra v1.2.1 | ||||||
| 	github.com/spf13/viper v1.9.0 | 	github.com/spf13/viper v1.9.0 | ||||||
|  | 	golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| @ -19,7 +25,7 @@ require ( | |||||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||||
| 	github.com/spf13/pflag v1.0.5 // indirect | 	github.com/spf13/pflag v1.0.5 // indirect | ||||||
| 	github.com/subosito/gotenv v1.2.0 // indirect | 	github.com/subosito/gotenv v1.2.0 // indirect | ||||||
| 	golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect | 	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect | ||||||
| 	golang.org/x/text v0.3.6 // indirect | 	golang.org/x/text v0.3.6 // indirect | ||||||
| 	gopkg.in/ini.v1 v1.63.2 // indirect | 	gopkg.in/ini.v1 v1.63.2 // indirect | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||||
|  | |||||||
| @ -1,11 +1,12 @@ | |||||||
| # List datastore root directory (the one containing jails/ and defaults.json) | # List datastore(s) root directory (the one containing jails/ and defaults.json) | ||||||
| datastore: | datastore: | ||||||
|     - /iocage |     - /iocage | ||||||
|  |  | ||||||
|  | # Prefix all commands with sudo | ||||||
| sudo: false | sudo: false | ||||||
|  |  | ||||||
| # Columns to display when "gocage list". Column names are struct fields, see cmd/struct.go | # Columns to display when "gocage list". Column names are struct fields, see cmd/struct.go | ||||||
| outcol: 'JID,Name,Config.Release,Config.Ip4_addr,Running' | outcol: 'JID,Name,Config.Release,Config.Ip4_addr,Running' | ||||||
|  |  | ||||||
| # Do not add line separator between jails | # Do not add line separator between jails | ||||||
| nolinesep: false | nolinesep: false | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								jail/main.go
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								jail/main.go
									
									
									
									
									
								
							| @ -24,6 +24,7 @@ type Jail struct { | |||||||
|     Name          string |     Name          string | ||||||
|     Jid           int |     Jid           int | ||||||
|     Path          string |     Path          string | ||||||
|  |     Devfs_ruleset int | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -33,8 +34,8 @@ func GetJails() ([]Jail, error) { | |||||||
|     var jl Jail |     var jl Jail | ||||||
|     var err error |     var err error | ||||||
|  |  | ||||||
| 	// Make "params" a list of 4 jails parameters | 	// Make "params" a list of 5 jails parameters | ||||||
|     params := make([]C.struct_jailparam, 4) |     params := make([]C.struct_jailparam, 5) | ||||||
|  |  | ||||||
|     // initialize parameter names |     // initialize parameter names | ||||||
|     csname := C.CString("name") |     csname := C.CString("name") | ||||||
| @ -43,27 +44,31 @@ func GetJails() ([]Jail, error) { | |||||||
|     defer C.free(unsafe.Pointer(csjid)) |     defer C.free(unsafe.Pointer(csjid)) | ||||||
|     cspath := C.CString("path") |     cspath := C.CString("path") | ||||||
|     defer C.free(unsafe.Pointer(cspath)) |     defer C.free(unsafe.Pointer(cspath)) | ||||||
|  | 	csdevfsrs := C.CString("devfs_ruleset") | ||||||
|  | 	defer C.free(unsafe.Pointer(csdevfsrs)) | ||||||
|     cslastjid := C.CString("lastjid") |     cslastjid := C.CString("lastjid") | ||||||
|     defer C.free(unsafe.Pointer(cslastjid)) |     defer C.free(unsafe.Pointer(cslastjid)) | ||||||
| 	 | 	 | ||||||
|  |  | ||||||
|     // initialize params struct with parameter names |     // initialize params struct with parameter names | ||||||
|     C.jailparam_init(¶ms[0], csname) |     C.jailparam_init(¶ms[0], csname) | ||||||
|     C.jailparam_init(¶ms[1], csjid) |     C.jailparam_init(¶ms[1], csjid) | ||||||
|     C.jailparam_init(¶ms[2], cspath) |     C.jailparam_init(¶ms[2], cspath) | ||||||
|  | 	C.jailparam_init(¶ms[3], csdevfsrs) | ||||||
|  |  | ||||||
|     // The key to retrieve jail. lastjid = 0 returns first jail and its jid as jailparam_get return value |     // The key to retrieve jail. lastjid = 0 returns first jail and its jid as jailparam_get return value | ||||||
|     C.jailparam_init(¶ms[3], cslastjid) |     C.jailparam_init(¶ms[4], cslastjid) | ||||||
|  |  | ||||||
|     lastjailid := 0 |     lastjailid := 0 | ||||||
|     cslastjidval := C.CString(strconv.Itoa(lastjailid)) |     cslastjidval := C.CString(strconv.Itoa(lastjailid)) | ||||||
|     defer C.free(unsafe.Pointer(cslastjidval)) |     defer C.free(unsafe.Pointer(cslastjidval)) | ||||||
|  |  | ||||||
|     C.jailparam_import(¶ms[3], cslastjidval) |     C.jailparam_import(¶ms[4], cslastjidval) | ||||||
|  |  | ||||||
|     // loop on existing jails |     // loop on existing jails | ||||||
|     for lastjailid >= 0 { |     for lastjailid >= 0 { | ||||||
|         // get parameter values |         // get parameter values | ||||||
|         lastjailid = int(C.jailparam_get(¶ms[0], 4, 0)) |         lastjailid = int(C.jailparam_get(¶ms[0], 5, 0)) | ||||||
|         if lastjailid > 0 { |         if lastjailid > 0 { | ||||||
|             nametmp := C.jailparam_export(¶ms[0]) |             nametmp := C.jailparam_export(¶ms[0]) | ||||||
|             jl.Name = C.GoString(nametmp) |             jl.Name = C.GoString(nametmp) | ||||||
| @ -80,18 +85,23 @@ func GetJails() ([]Jail, error) { | |||||||
|             // Memory mgmt : Non gere par Go |             // Memory mgmt : Non gere par Go | ||||||
|             C.free(unsafe.Pointer(pathtmp)) |             C.free(unsafe.Pointer(pathtmp)) | ||||||
|  |  | ||||||
|  |             drstmp := C.jailparam_export(¶ms[3]) | ||||||
|  |             jl.Devfs_ruleset, _ = strconv.Atoi(C.GoString(drstmp)) | ||||||
|  |             // Memory mgmt : Non gere par Go | ||||||
|  |             C.free(unsafe.Pointer(drstmp)) | ||||||
|  |  | ||||||
|             jls = append(jls, jl) |             jls = append(jls, jl) | ||||||
|             //log.Debug("Got jid " + strconv.Itoa(jl.jid) + " with name " + jl.name) |             //log.Debug("Got jid " + strconv.Itoa(jl.jid) + " with name " + jl.name) | ||||||
|  |  | ||||||
|             // Prepare next loop iteration |             // Prepare next loop iteration | ||||||
|             cslastjidval := C.CString(strconv.Itoa(lastjailid)) |             cslastjidval := C.CString(strconv.Itoa(lastjailid)) | ||||||
|             defer C.free(unsafe.Pointer(cslastjidval)) |             defer C.free(unsafe.Pointer(cslastjidval)) | ||||||
|             C.jailparam_import(¶ms[3], cslastjidval) |             C.jailparam_import(¶ms[4], cslastjidval) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| 	// Free 4 items of params list | 	// Free 5 items of params list | ||||||
|     C.jailparam_free(¶ms[0], 4) |     C.jailparam_free(¶ms[0], 5) | ||||||
|  |  | ||||||
|     return jls, err |     return jls, err | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.go
									
									
									
									
									
								
							| @ -7,4 +7,3 @@ import ( | |||||||
| func main() { | func main() { | ||||||
| 	cmd.Execute() | 	cmd.Execute() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								service/gocage
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										43
									
								
								service/gocage
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | #!/bin/sh | ||||||
|  | # | ||||||
|  | # $FreeBSD$ | ||||||
|  | # | ||||||
|  |  | ||||||
|  | # PROVIDE: gocage | ||||||
|  | # REQUIRE: LOGIN cleanvar | ||||||
|  | # KEYWORD: shutdown | ||||||
|  |  | ||||||
|  | # Add the following lines to /etc/rc.conf to enable : | ||||||
|  | # | ||||||
|  | # gocage_enable="YES" | ||||||
|  | # | ||||||
|  | # gocage_conf="/usr/local/etc/gocage.conf.yml" | ||||||
|  | # | ||||||
|  |  | ||||||
|  | . /etc/rc.subr | ||||||
|  |  | ||||||
|  | name="gocage" | ||||||
|  | rcvar=gocage_enable | ||||||
|  |  | ||||||
|  | # read configuration and set defaults | ||||||
|  | load_rc_config "$name" | ||||||
|  |  | ||||||
|  | : ${gocage_enable:="NO"} | ||||||
|  | : ${gocage_conf="/usr/local/etc/gocage.conf.yml"} | ||||||
|  |  | ||||||
|  | start_cmd=${name}_start | ||||||
|  | stop_cmd=${name}_stop | ||||||
|  |  | ||||||
|  | gocage_start() | ||||||
|  | { | ||||||
|  |     echo "Gocage starting jails... " | ||||||
|  |     /usr/local/bin/gocage -c ${gocage_conf} start | ||||||
|  | } | ||||||
|  |  | ||||||
|  | gocage_stop() | ||||||
|  | { | ||||||
|  |     echo "Gocage stopping jails... " | ||||||
|  |     /usr/local/bin/gocage  -c ${gocage_conf} stop | ||||||
|  | } | ||||||
|  |  | ||||||
|  | run_rc_command "$1" | ||||||
		Reference in New Issue
	
	Block a user