135 Commits
v0.2 ... v0.34

Author SHA1 Message Date
yo
dc4213a8d5 v.0.34 : Jail names can be shortened 2023-07-09 10:38:00 +02:00
yo
5eed121f0b Protect devfs last ruleset acquiring with mutex 2023-06-25 23:32:15 +02:00
yo
812c77790a Add CHANGELOG 2023-06-25 23:09:03 +02:00
yo
7575da794e 0.33c : parallelize start/stop of jails up to gMaxThreads 2023-06-25 23:07:53 +02:00
yo
6f9bb504be Fix forever waiting on services not properly closing pipes at start 2023-06-10 14:12:53 +02:00
yo
7c3e14f0f1 version bump, README update 2023-06-03 11:18:42 +02:00
yo
a4ff9c1d51 datasets now should be specified with zpool 2023-06-03 11:18:40 +02:00
yo
00fd283987 resolver.conf bugfix, datasets now should be specified with zpool 2023-06-03 11:18:37 +02:00
yo
7cf4594f34 Update TODO list with some bugs 2022-11-20 20:20:44 +01:00
yo
37fea55e42 Add update command 2022-11-20 20:20:20 +01:00
yo
c15ee68d2e Add update command 2022-11-20 20:20:06 +01:00
yo
54fd1f8064 FIXME: Update Last_started 2022-11-20 20:19:37 +01:00
yo
89db166040 FIXME: Update release in config file when stopping jail 2022-11-20 20:17:59 +01:00
yo
9c18a83ee8 ExecuteCommandWithOutputToStdout 2022-11-20 20:16:23 +01:00
yo
561ae4386a FIXME 2022-11-20 20:13:47 +01:00
yo
667c73216e gocage fetch finished 2022-11-06 16:34:52 +01:00
yo
9e506145a8 downloaded files goes to download 2022-10-16 15:32:47 +02:00
yo
d636d963ff WIP: fetch command 2022-10-16 15:20:00 +02:00
yo
56b4d8ea84 WIP: fetch command 2022-10-16 15:19:51 +02:00
yo
abaa4a11f9 Check if dataset exist, create dataset 2022-10-16 15:19:03 +02:00
yo
74602dc0df Add arch property to jailhost 2022-10-16 15:18:35 +02:00
yo
be756edea7 rm blank line 2022-10-16 15:17:56 +02:00
yo
546382ded7 Add TODO in readme 2022-10-15 16:38:17 +02:00
yo
07eccffbd1 Add Devfs_ruleset property to reflect generated RS 2022-10-15 16:33:29 +02:00
yo
7809107ea4 Update readme 2022-10-15 15:24:24 +02:00
yo
5ab0a59db4 Update readme 2022-10-15 15:23:58 +02:00
yo
1b27753718 Add service file 2022-10-15 15:16:26 +02:00
yo
c97f5317dd Add start and stop all jails for boot/shutdown, add debug mode 2022-10-15 14:53:43 +02:00
yo
38266af66e Version 0.29f + WIP: gocage destroy 2022-09-25 13:45:40 +02:00
yo
fabe9cc330 Use hostuuid as jail Name 2022-09-25 13:45:12 +02:00
yo
0637686f91 Format 2022-09-25 12:01:16 +02:00
yo
99f7bed6f3 Add ability to delete multiple snapshots separated by comma 2022-07-14 14:00:41 +02:00
yo
9b86d786fe Fix setting Running to false 2022-07-14 12:32:14 +02:00
yo
efd28c03ad Reset Running and JID so we can call start() after stop() 2022-07-14 11:12:13 +02:00
yo
e1786a4d08 Fix poststart 2022-07-14 11:11:23 +02:00
yo
bf1037c1d3 Add gocage restart $jailname 2022-07-14 10:57:57 +02:00
yo
cbdcb039cb Version bump 2022-07-10 21:27:17 +02:00
yo
09ac7464f5 BUGFIX: Was adding 2 Datastore columns 2022-07-10 21:26:54 +02:00
yo
de1a92dec8 Remove Datastore from default columns (it will be added if jails on 2 DS) 2022-07-10 21:26:18 +02:00
yo
d16a2d6789 Execute "login -f root" when gocage console 2022-07-10 21:13:36 +02:00
yo
35b75ee7fb Version bump 2022-07-10 20:56:57 +02:00
yo
4d92e880c9 TODO comments 2022-07-10 20:56:49 +02:00
yo
52916f9ae9 TODO: Browse jailed ZFS dataset to migrate them 2022-07-10 20:56:17 +02:00
yo
31961b8e3a Start rtsold 2022-07-10 20:52:54 +02:00
yo
128e2aa0c4 Cosmetic 2022-07-10 20:48:40 +02:00
yo
0c7293ae66 Generate resolv.conf, copy localtime 2022-07-10 20:48:24 +02:00
yo
e1f6b4f6f9 Add default gateway, execute exec_start 2022-07-10 19:55:13 +02:00
yo
f29aeb2e23 Execute poststop 2022-07-10 19:54:23 +02:00
yo
1cb7c5fec7 Bugfixes, dont stop if fs already mounted in jail 2022-07-10 16:29:26 +02:00
yo
3dae685fc4 Check if jail is unique, else check if long name is used 2022-07-10 14:18:29 +02:00
yo
9b90f9c812 Use getJailFromArray so we can handle same name jails 2022-07-10 14:17:12 +02:00
yo
e13437b79e Fix version display 2022-07-10 14:16:39 +02:00
yo
484e05e8d1 Use getJailFromArray so we can handle same name jails + check pertinence before jailing non existing DS 2022-07-10 14:16:24 +02:00
yo
e7a6bdd376 Use getJailFromArray so we can handle same name jails 2022-07-10 14:15:26 +02:00
yo
170dce31b3 getJailFromArray now support long names 2022-07-10 14:15:02 +02:00
yo
1295fb86f6 Remove trailing \n 2022-07-10 14:14:32 +02:00
yo
d3410c281a use getJailFromArray so we can handle same name jails on different DS 2022-07-10 14:14:12 +02:00
yo
51dc7d1588 BUGFIX: path to root dataset was wrong 2022-07-04 20:47:19 +02:00
yo
92d8beb58f Delete dynamic ruleset, obtained from /var/run/jail.$InternalName.conf 2022-06-26 20:03:08 +02:00
yo
745811c39b Version bump 2022-06-26 20:02:29 +02:00
yo
276d01ed4c VNet configuration, jail and mount ZFS datasets into jail 2022-06-26 20:02:28 +02:00
yo
7266496cac Memo for network configuration 2022-06-26 20:02:28 +02:00
yo
5b0de24508 executeCommand supports quoted arguments, getValueFromRunningConfig 2022-06-26 20:02:28 +02:00
yo
2a8836721c GetInterfaces, GetBridgeMTU 2022-06-26 20:02:22 +02:00
yo
0f6b7b8b80 Hide ifconfig destroy output 2022-06-19 19:45:36 +02:00
yo
1462c383d3 Cosmetic rearrangement 2022-06-19 19:45:07 +02:00
yo
29ce0d9b58 Cosmetic rearrangement 2022-06-19 19:44:40 +02:00
yo
42e1085ad4 Fix disableRcKey bug when key do not exist 2022-06-19 19:14:58 +02:00
yo
0f4f76a9a2 fix Error unmounting display bug, unconditionally unmount /dev & /dev/fd, remove parameter file 2022-06-19 19:14:11 +02:00
yo
e87699e2dc executeScript function 2022-06-19 17:44:00 +02:00
yo
4d8bf6e0d5 executeScript function 2022-06-19 17:43:48 +02:00
yo
d9e1e20afc WIP on start: Build parameter file 2022-06-19 14:48:55 +02:00
yo
fcf7d68d06 Add FreeBSD version to JailHost struct 2022-06-19 13:55:41 +02:00
yo
f919ff2ec3 Initialise gJailHost 2022-06-19 13:55:07 +02:00
yo
1bc248fdcc Add hostname, hostid and version initialisation with NewJailHost() 2022-06-19 13:51:57 +02:00
yo
a7aaa11de6 Fix getJailFromArray: the returned reference was to a copy of the jail 2022-06-19 13:50:49 +02:00
yo
71f345dff4 temporary command to test code snippet 2022-06-18 20:09:32 +02:00
yo
e1410bf209 create dynamic devfs ruleset from configured or default 2022-06-18 20:08:12 +02:00
yo
c585678be9 create dynamic devfs ruleset from configured or default 2022-06-18 20:07:57 +02:00
yo
9218ffafe1 Add datastore to snapshots, force Datastore display when jail exist on multi datastores 2022-06-18 18:24:09 +02:00
yo
1c04f62ed8 Comments 2022-06-18 16:34:06 +02:00
yo
2151034a02 Fix migrate: use datastore.ZFSDataset for replication to dest 2022-06-18 16:24:53 +02:00
yo
e84c43c759 little progress on start/build devfs ruleset 2022-06-18 16:10:10 +02:00
yo
d4f6b9ddc7 Add datastore list, filter and sort 2022-06-18 16:09:38 +02:00
yo
86e08ec0f7 Add datastore list, filter and sort, add snapshot sorting 2022-06-18 16:09:22 +02:00
yo
b4fd7caca7 Update readme 2022-06-18 16:07:29 +02:00
yo
29e8736fbc WIP on gocage start : dynamic devfs rulesets 2022-06-18 11:10:06 +02:00
yo
203c4bff3b Cosmetic 2022-06-18 11:09:40 +02:00
yo
7356c0d3d0 gocage migrate now synchronize destination dataset after stoppoing jail 2022-06-18 11:09:06 +02:00
yo
a446a19a08 gocage console jailname now working 2022-06-18 11:08:03 +02:00
yo
0bf825ee5a WIP migration on running jail with minimized downtime 2022-06-05 18:43:02 +02:00
yo
542d2f96f6 zfsCopyIncremental to send/receive incremental snapshots 2022-06-05 18:42:20 +02:00
yo
6c6cb7edc8 Bugfix when "migrate clean" a jail which was already clean, code cleaning 2022-06-05 17:36:27 +02:00
yo
31fa6904db "gocage migrate jail -d destination_dataset" working for cold migrations 2022-06-05 14:10:07 +02:00
yo
fb3ee07585 "gocage migrate jail -d destination_dataset" working for cold migrations 2022-06-05 14:09:55 +02:00
yo
57c8bba09b Do not support 2 jails with same name, only keep the first seen 2022-06-05 14:09:04 +02:00
yo
249ab19173 getJailFromArray use name instead of internalName 2022-06-05 11:20:16 +02:00
yo
bc92f29900 executeCommand now returns stdout and stderr combined in output 2022-06-04 22:28:38 +02:00
yo
e4fc9c3a6c Some code reorg 2022-04-24 16:55:33 +02:00
yo
43f26d099f WIP on start, go fmt on * 2022-04-24 16:49:54 +02:00
yo
dbd9153513 Get default router and gateways, IPv4 & IPv6 2022-04-18 13:53:18 +02:00
yo
7b5ae7ce6e start : Check if nat doesnt conflict with running jails, get default router 2022-04-18 13:52:44 +02:00
yo
77a2e9dabf cleanAfterRun renamed to WriteConfigToDisk, dont overwrite "auto" values by default 2022-04-18 13:50:20 +02:00
yo
6821b14407 BUGFIX : getJailConfig returns 2 values 2022-04-18 13:37:40 +02:00
yo
f9ce3601df Convert setJailProperty to setStructFieldValue 2022-04-18 13:36:33 +02:00
yo
4f85f2e6ac WIP: checks before starting jail 2022-04-05 22:21:39 +02:00
yo
e0f371693a Raise error if /etc/hostid not readable 2022-04-05 22:21:04 +02:00
yo
0f97270a6a BUGFIX on hostid reading : remove trailing \n 2022-04-05 21:43:11 +02:00
yo
7dbbf8a757 started working on "gocage console" 2022-04-05 20:58:33 +02:00
yo
0053fd6c8b WIP on rollback, started "gocage console", default values for jail properties to stay compatible with iocage 2022-04-05 20:58:11 +02:00
yo
4aa1c81fea Create and delete snapshot OK + version bump to 0.25 2022-04-04 21:00:44 +02:00
yo
966a3d57c1 Correctly display multiple jails snapshots 2022-04-04 20:10:42 +02:00
yo
a12c268be2 BUGFIX on snapshot name 2022-04-04 20:03:08 +02:00
yo
7b34495cf6 displaySnapshotsFields is a copy of displayJailsFields to pretty display jails snapshots 2022-04-04 19:47:15 +02:00
yo
1c40e29eff Cleaning + TODO: PRetty display of snapshot list 2022-04-03 14:30:14 +02:00
yo
ef78245902 Add gocage list snapshot myjail 2022-04-03 14:27:42 +02:00
yo
910be4ea31 Add gocage list snapshot myjail 2022-04-03 14:27:26 +02:00
yo
285229009f Add "gocage get all myjail" 2022-04-03 11:04:01 +02:00
yo
19dd2dfb33 version bump to v0.24 2022-04-03 10:36:23 +02:00
yo
139ea18422 add "gocage get property jail" + "gocage set property jail" now support string, int and bool types 2022-04-03 10:35:48 +02:00
yo
3bedf019dc Move property mgmt functions to properties.go 2022-04-03 10:35:01 +02:00
yo
da74456d6a Set property K for int type + write config to disk 2022-04-02 21:38:54 +02:00
yo
f40db69b9d WIP: Implementing setJailProperty, add recursivity to getStructFieldValue 2022-04-02 21:15:06 +02:00
yo
239bcd4b95 WIP: set jail properties 2022-04-02 17:12:51 +02:00
yo
5a3d26a52c Formatting + add "set" command 2022-04-02 17:11:54 +02:00
yo
bf20c815ce Moved some code to utils.go 2022-04-02 15:50:14 +02:00
yo
f1c4fd960d Add command "list properties" so we can get all internal properties to sort, filter, or set jail properties 2022-04-02 15:40:04 +02:00
yo
8d18bfe55d Bugfix 2022-04-02 15:38:24 +02:00
yo
eacc165481 Mount local FS; get struct pointer so we can modify values 2022-04-02 14:18:50 +02:00
yo
349ea12979 Unmount local FS before stopping jail 2022-04-02 14:17:10 +02:00
yo
30209d2890 Start implementing gocage start 2021-12-21 20:48:15 +01:00
yo
ea25db2f27 Start implementing gocage start 2021-12-21 20:48:07 +01:00
yo
12c0a37617 BUGFIX when no -s 2021-12-21 20:47:46 +01:00
yo
fb94921afd update doc with sort example 2021-12-20 22:23:17 +01:00
yo
74da6909c3 update doc with sort example 2021-12-20 22:16:36 +01:00
23 changed files with 6740 additions and 952 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
gocage
go.sum

3
CHANGELOG Normal file
View File

@ -0,0 +1,3 @@
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

130
README.md
View File

@ -4,8 +4,15 @@ GoCage
Jail management tool for FreeBSD, written in Go.
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 can handle multiple datastores, so you can have jails on HDD storage and jails on SSD storage.
At present time, it list and stops jails.
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. :
<code>
Config.Jail_zfs = 1
Config.Jail_zfs_dataset = myzfspool/poudriere
Config.Jail_zfs_mountpoint = none
</code>
List jails
----------
@ -80,7 +87,128 @@ gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot
+-----+----------+---------+-------------+
</pre></code>
Sort jails
----------
Use -s switch followed by sort criteria. Criteria is a field name, prefixed with + or - for sort order (increase/decrease):
<pre><code>
gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot -s +JID
+=====+==========+=========+=============+
| JID | Name | Running | Config.Boot |
+=====+==========+=========+=============+
| 22 | srv-dns1 | true | 1 |
+-----+----------+---------+-------------+
| 29 | bdd-tst | true | 1 |
+-----+----------+---------+-------------+
| 183 | test | true | 1 |
+-----+----------+---------+-------------+
</pre></code>
You can use up to 3 criteria, delimited with comma.
As an example, you want to list boot priorities of automatically starting jails:
<pre><code>
gocage list -o JID,Name,Config.Ip4_addr,Config.Priority,Config.Boot,Running -s -Config.Priority,-Config.Boot -f Running=true
+=====+==============+=======================+=================+=============+=========+
| JID | Name | Config.Ip4_addr | Config.Priority | Config.Boot | Running |
+=====+==============+=======================+=================+=============+=========+
| 1 | srv-dhcp | vnet0|192.168.1.2/24 | 99 | 1 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
| 8 | srv-dns | vnet0|192.168.1.1/24 | 80 | 1 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
| 7 | srv-random | vnet0|192.168.1.12/24 | 20 | 1 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
| 4 | coincoin | vnet0|192.168.1.9/24 | 20 | 0 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
</pre></code>
Stop jails
----------
`gocage stop 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 update
gocage upgrade
gocage create
gocage destroy
gocage init
create default pool with defaults.json

View File

@ -1,4 +1,8 @@
Replicating jails between two servers (use zrepl)
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

46
cmd/console.go Normal file
View File

@ -0,0 +1,46 @@
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], 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, "/bin/csh"}, os.Environ())
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
}

108
cmd/datastore.go Normal file
View 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
}

374
cmd/fetch.go Normal file
View File

@ -0,0 +1,374 @@
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 (
FetchFiles = []string{"base.txz", "lib32.txz", "src.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 := createZfsDataset(downloadDsName, downloadDsMountPoint, "lz4"); err != nil {
return fmt.Errorf("Error creating dataset %s: %v\n", downloadDsName, err)
}
}
// Create download/XX.X-RELEASE dataset if necessary
thisDownloadDsName := fmt.Sprintf("%s/%s-RELEASE", downloadDsName, release)
thisDownloadDsMountPoint := fmt.Sprintf("%s/%s-RELEASE", 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 := createZfsDataset(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-RELEASE", fetchFrom, release)
} else {
fetchUrl = fmt.Sprintf("%s://%s/%s/%s/%s-RELEASE", 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 := createZfsDataset(releaseDsName, releaseDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", releaseDsName, err)
return
}
}
// Create releases/XX.X-RELEASE dataset if necessary
thisReleaseDsName := fmt.Sprintf("%s/%s-RELEASE", releaseDsName, release)
thisReleaseDsMountPoint := fmt.Sprintf("%s/%s-RELEASE", 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 := createZfsDataset(thisReleaseDsName, thisReleaseDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", thisReleaseDsName, err)
return
}
}
// Create releases/XX.X-RELEASE/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 := createZfsDataset(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-RELEASE/root
downloadDsMountPoint := fmt.Sprintf("%s/download", ds.Mountpoint)
downloadDir := fmt.Sprintf("%s/%s-RELEASE", 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
for _, fi := range files {
if false == fi.IsDir() {
if strings.HasSuffix(fi.Name(), ".txz") {
ar := fmt.Sprintf("%s/%s", downloadDir, fi.Name())
fmt.Printf("Extracting file %s... ", ar)
// 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
}

241
cmd/jailhost.go Normal file
View 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
}

View File

@ -1,315 +1,121 @@
package cmd
import (
"os"
"encoding/json"
"fmt"
"gocage/jail"
"io/ioutil"
"log"
"errors"
"os"
"reflect"
"strings"
"io/ioutil"
"gocage/jail"
"encoding/json"
"github.com/spf13/viper"
)
// Recurse into structure, returning reflect.Value of wanted field.
// Nested fields are named with a dot (ex "MyStruct.MyField")
func getStructFieldValue(parentStruct interface{}, fieldName string) (reflect.Value, string, error) {
v := reflect.ValueOf(parentStruct)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if false {
for i := 0 ; i < v.NumField(); i++ {
f := v.Field(i)
if f.Kind() == reflect.String {
fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface())
}
}
}
if strings.Contains(fieldName, ".") {
fs := strings.Split(fieldName, ".")
f := v.FieldByName(fs[0])
if f.Kind() == reflect.Struct {
return getStructFieldValue(f.Interface(), strings.Join(fs[1:], "."))
} else {
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
}
} else {
f := v.FieldByName(fieldName)
if f.IsValid() {
return f, fieldName, nil
} else {
return v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
}
}
return v, fieldName, nil
}
// TODO : Replace by getStructFieldValue
// Recurse into structure, returning reflect.Value of wanted field.
// Nested fields are named with a dot (ex "MyStruct.MyField")
func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) {
v := reflect.ValueOf(parentStruct)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if false {
for i := 0 ; i < v.NumField(); i++ {
f := v.Field(i)
if f.Kind() == reflect.String {
fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface())
}
}
}
if strings.Contains(fieldName, ".") {
fs := strings.Split(fieldName, ".")
f := v.FieldByName(fs[0])
if f.Kind() == reflect.Struct {
return getStructField(f.Interface(), strings.Join(fs[1:], "."))
} else {
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
}
}
return v, fieldName
}
/* Pretty display of jails field
Fields to show are given in a string array parameter
Ex. : displayJails(["Name", "JID", "RootPath"])
*/
func displayStructFields(jails []Jail, valsToDisplay []string) {
/* A line is defined by :
Nr of fields
For each field :
Its name
Its max length
Its value
*/
type Field struct {
Name string
MaxLen int
Value string
}
type Line []Field
type Output []Line
var out Output
//fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails))
// Browse structure to get max length and values
for _, j := range jails {
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
tj := &j
line := make([]Field, len(valsToDisplay))
for i, f := range valsToDisplay {
a, f := getStructField(tj, f)
field := Field {
Name: f,
}
if a.FieldByName(f).IsValid() {
// For now this just contains this item length, will adjust later
// We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr)
if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String {
itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ","))
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr
} else {
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
}
field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface())
} else {
fmt.Printf("Invalid field name: %s\n", f)
}
line[i] = field
}
out = append(out, line)
}
if len(out) == 0 {
fmt.Printf("Nothing to see here!\n")
/********************************************************************************
* List all properties a jail have, with their internal name
* Only print properties name. To get name & values, use GetJailProperties()
*******************************************************************************/
func ListJailsProps(args []string) {
var conf Jail
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
}
// Get real maximum length
maxlen := make([]int, len(valsToDisplay))
for i := 0; i< len(valsToDisplay); i++ {
maxlen[i] = len(valsToDisplay[i])
}
for _, l := range out {
for i, f := range l {
if f.MaxLen > maxlen[i] {
maxlen[i] = f.MaxLen
}
}
}
conf.Config = jailconf
// Align maxlen to the real maximum length
for io, l := range out {
for ii, _ := range l {
// We need to access slice by index if we want to modify content
out[io][ii].MaxLen = maxlen[ii]
}
}
result = getStructFieldNames(conf, result, "")
totalLen := 0
// For each field, add separator and 2 blank spaces
for _, f := range out[0] {
totalLen += f.MaxLen + 3
for _, f := range result {
fmt.Printf("%s\n", f)
}
// Then add starting delimiter
totalLen += 1
Debug := false
if Debug == true {
for _, f := range out[0] {
fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen)
}
}
// Lets draw things on the screen!
// First, headers: 1st separator line
for i, f := range out[0] {
if i == 0 { fmt.Printf("+") }
for i := 0 ; i < f.MaxLen+2 ; i++ { fmt.Printf("=") }
fmt.Printf("+")
}
fmt.Printf("\n")
// Column names
for i, f := range out[0] {
if i == 0 {
fmt.Printf("|")
}
/* Use vlsToDisplay to get real name (with "Config.")
fmt.Printf(" %s", f.Name)
for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */
fmt.Printf(" %s", valsToDisplay[i])
for i := len(valsToDisplay[i])+1 ; i < f.MaxLen+1 ; i++ {
fmt.Printf(" ")
}
fmt.Printf(" |")
}
// Finally separator line
fmt.Printf("\n")
for i, f := range out[0] {
if i == 0 { fmt.Printf("+") }
for i := 0 ; i < f.MaxLen+2 ; i++ { fmt.Printf("=") }
fmt.Printf("+")
}
fmt.Printf("\n")
// Then display data
// Loop through lines
for _, l := range out {
// Loop through fields
// In case we need to add a line for a 2nd IP, or whatever object
var supplines = make(map[string]string)
for i, f := range l {
if i == 0 {
fmt.Printf("|")
}
// Special cases of value displaying
if f.Name == "JID" && f.Value == "0" {
fmt.Printf(" ")
} else if f.Name == "Ip4_addr" {
ia := strings.Split(f.Value, ",")
// If we have more than 1 value we need to finish this line, and store value for writing at the end of line loop
for i, inter := range ia {
if i > 0 {
supplines[f.Name] = inter
} else {
fmt.Printf(" %s", inter)
}
}
//fmt.Printf(" %s", strings.Split(strings.Split(f.Value, "|")[1], "/")[0])
} else {
fmt.Printf(" %s", f.Value)
}
// Complete with spaces to the max length
for i := len(f.Value)+1 ; i < f.MaxLen+1 ; i++ {
fmt.Printf(" ")
}
fmt.Printf(" |")
}
// Draw supplementary lines
if len(supplines) > 0 {
for i, f := range l {
if i == 0 {
fmt.Printf("\n|")
}
// Overwrite value in this scope
v, exists := supplines[f.Name]
if exists {
fmt.Printf(" %s", v)
} else {
// 1st option : Do not redisplay precedent line values
fmt.Printf(" ")
// 2nd option : Redisplay precedenet line values
//fmt.Printf(" %s", f.Value)
}
// Complete with spaces to the max length
for i := len(v)+1 ; i < f.MaxLen+1 ; i++ {
fmt.Printf(" ")
}
fmt.Printf(" |")
}
}
// Draw line separator between jails
if !gNoLineSep {
fmt.Printf("\n")
for i, f := range out[0] {
if i == 0 { fmt.Printf("+") }
for i := 0 ; i < f.MaxLen+2 ; i++ { fmt.Printf("-") }
fmt.Printf("+")
}
}
fmt.Printf("\n")
}
if gNoLineSep {
for i, f := range out[0] {
if i == 0 { fmt.Printf("+") }
for i := 0 ; i < f.MaxLen+2 ; i++ { fmt.Printf("-") }
fmt.Printf("+")
}
}
fmt.Printf("\n")
}
/* Get Jails from datastores. Store config and running metadata into gJails global var */
/********************************************************************************
* Get Jails from datastores. Store config and running metadata
* into gJails global var
*******************************************************************************/
func ListJails(args []string, display bool) {
fields := strings.Split(gDisplayColumns, ",")
for _, d := range viper.GetStringSlice("datastore") {
listJailsFromDatastore(d)
type uniqueJailName struct {
jail string
unique bool
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
var jails []Jail
/************************************************************************************
/***************************************************************
/ Filter jails with "filter" options
/***********************************************************************************/
/**************************************************************/
if len(gFilterJails) > 0 && gFilterJails != "none" {
flts := make(map[string]string)
for _, flt := range strings.Split(gFilterJails, ",") {
@ -340,33 +146,31 @@ func ListJails(args []string, display bool) {
jails = gJails
}
/************************************************************************************
/***************************************************************
/ Filter jails by names given on command line
/***********************************************************************************/
/**************************************************************/
if len(args) > 0 {
var js []Jail
for _, a := range args {
for _, j := range jails {
if j.Name == a {
js = append(js, j)
break
}
}
}
jails = js
}
/************************************************************************************
/***************************************************************
/ Sort jails
/ We support 3 sort criteria max
/***********************************************************************************/
if len(gSortFields) > 0 {
js := initSortStruct()
/**************************************************************/
if len(gSortJailFields) > 0 && gSortJailFields != "none" {
js := initJailSortStruct()
// The way we manage criteria quantity is not very elegant...
var fct1, fct2, fct3 reflect.Value
for i, c := range strings.Split(gSortFields, ",") {
var fct1, fct2, fct3 *reflect.Value
for i, c := range strings.Split(gSortJailFields, ",") {
var fctName string
if strings.HasPrefix(c, "-") {
fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1))
@ -381,50 +185,62 @@ func ListJails(args []string, display bool) {
fmt.Printf("ERROR getting JailSort 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(gSortFields, ",")) {
switch i + 1 {
case 1:
OrderedBy(fct1.Interface().(lessFunc)).Sort(jails)
fct1 = fct
case 2:
OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc)).Sort(jails)
fct2 = fct
case 3:
OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc), fct3.Interface().(lessFunc)).Sort(jails)
fct3 = fct
}
}
/************************************************************************************
switch len(strings.Split(gSortJailFields, ",")) {
case 1:
JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails)
case 2:
JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc)).Sort(jails)
case 3:
JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc), fct3.Interface().(jailLessFunc)).Sort(jails)
}
}
/***************************************************************
/ Finally, display jails
/***********************************************************************************/
/**************************************************************/
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(datastore)
if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", datastore)) }
if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", datastore)) }
fileInfo, err := os.Stat(ds.Mountpoint)
if err != nil {
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", ds.Mountpoint))
}
// 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)
if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir)) }
if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir)) }
if err != nil {
log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir))
}
if fileInfo.IsDir() == false {
log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir))
}
listJailsFromDirectory(jailsDir)
listJailsFromDirectory(jailsDir, ds.Name)
}
func listJailsFromDirectory(dir string) ([]Jail) {
func listJailsFromDirectory(dir string, dsname string) ([]Jail, error) {
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 {
if fi.IsDir() == true {
@ -432,31 +248,39 @@ func listJailsFromDirectory(dir string) ([]Jail) {
// 1. Get conf from config.json
jailConfPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "config.json")
jailConf, err := getJailConfig(jailConfPath)
if err != nil { log.Println("ERROR reading jail config for %s", jailConfPath) }
if err != nil {
log.Println("ERROR reading jail config for %s", jailConfPath)
}
// 2. Build jail object from config
jailRootPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "root")
j := Jail{
Name: jailConf.Host_hostname,
Name: jailConf.Host_hostuuid,
Config: jailConf,
ConfigPath: jailConfPath,
Datastore: dsname,
RootPath: jailRootPath,
Running: false,
}
// 3. Add current running informations
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 {
if rj.Path == j.RootPath {
j.JID = rj.Jid
j.Running = true
// FIXME ??? Shouldn't be ioc-$Name ?
j.InternalName = rj.Name
j.Devfs_ruleset = rj.Devfs_ruleset
break
}
}
/* This op take some 600ms for ~40 jails :^( */
// 4. Get Zpool
/* This op take some 600ms for ~40 jails :^( */
out, err := executeCommand(fmt.Sprintf("zfs list -H -o name %s", j.RootPath))
if err != nil {
fmt.Printf("ERROR getting dataset from %s: %s\n", j.RootPath, err.Error())
@ -464,21 +288,40 @@ func listJailsFromDirectory(dir string) ([]Jail) {
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)
}
}
return gJails
return gJails, nil
}
func getJailConfig(jailConfigPath string) (JailConfig, error) {
content, err := ioutil.ReadFile(jailConfigPath)
if err != nil { log.Fatalln(fmt.Sprintf("Unable to read %s, check path and/or rights", jailConfigPath)) }
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)
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
}

224
cmd/migrate.go Normal file
View 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, 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, 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
}

123
cmd/properties.go Normal file
View File

@ -0,0 +1,123 @@
package cmd
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
func GetJailProperties(args []string) {
var props []string
var jail *Jail
var err error
if len(args) > 0 {
for i, a := range args {
// Last arg is the jail name
if i == len(args)-1 {
jail, err = getJailFromArray(a, gJails)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}
} else {
props = append(props, a)
}
}
}
if len(jail.Name) == 0 || len(args) == 0 {
// TODO : Show help
fmt.Printf("Error\n")
return
}
if isStringInArray(props, "all") {
var result []string
result = getStructFieldNames(*jail, result, "")
props = result
}
for _, p := range props {
v, err := getJailProperty(jail, p)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
} else {
fmt.Printf("%s = %s\n", p, v)
}
}
}
func getJailProperty(jail *Jail, propName string) (string, error) {
val, _, err := getStructFieldValue(jail, propName)
if err != nil {
return "", err
}
switch val.Kind() {
case reflect.String:
return val.String(), nil
case reflect.Int:
return strconv.FormatInt(val.Int(), 10), nil
case reflect.Bool:
return strconv.FormatBool(val.Bool()), nil
default:
return "", errors.New(fmt.Sprintf("Error: Unknown type for property %s: %s\n", propName, val.Kind()))
}
return "", errors.New("Unknown error in getJailProperty")
}
func SetJailProperties(args []string) {
type properties struct {
name string
value string
}
var jail Jail
var props []properties
if len(args) > 0 {
for i, a := range args {
// This is the jail name
if i == len(args)-1 {
jail.Name = a
} else {
kv := strings.Split(a, "=")
if len(kv) != 2 {
// TODO : Show help
fmt.Printf("Error parsing args: %s\n", a)
return
} else {
p := properties{name: kv[0], value: kv[1]}
props = append(props, p)
}
}
}
}
if len(jail.Name) == 0 || len(args) == 0 {
// TODO : Show help
fmt.Printf("Error\n")
return
}
// Get jail by index to modify it
for i, _ := range gJails {
if gJails[i].Name == jail.Name {
for _, p := range props {
err := setStructFieldValue(&gJails[i], p.name, p.value)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
} else {
fmt.Printf("%s: %s set to %s\n", gJails[i].Name, p.name, p.value)
gJails[i].ConfigUpdated = true
}
}
}
}
}

View File

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

330
cmd/snapshots.go Normal file
View 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
}

1591
cmd/start.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,16 @@ package cmd
import (
"os"
"fmt"
// "log"
//"log"
"sync"
"errors"
"regexp"
"os/exec"
// "reflect"
//"reflect"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
)
// TODO : Use SYS_RCTL_GET_RACCT syscall
@ -38,7 +42,6 @@ func removeRctlRules(jail string, rules []string) error {
return nil
}
// TODO: Validate with >1 dataset
func umountAndUnjailZFS(jail *Jail) error {
var ds []string
@ -48,10 +51,10 @@ func umountAndUnjailZFS(jail *Jail) error {
for _, zd := range ds {
// 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)
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)
}
for _, c := range strings.Split(out, "\n") {
@ -69,22 +72,22 @@ func umountAndUnjailZFS(jail *Jail) error {
}
// 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)
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 nil
}
func destroyVNetInterfaces(jail *Jail) error {
for _, i := range strings.Split(jail.Config.Ip4_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 {
@ -95,17 +98,23 @@ func destroyVNetInterfaces(jail *Jail) error {
return nil
}
func deleteDevfsRuleset(jail *Jail) error {
// Jails copy the ruleset referenced as "devfs_ruleset" when starting, getting a new devsf_ruleset ID.
// This new ID can be obtained with 'jls -j $JID devfs_ruleset'
// This is the ID which needs to be removed. Original ID referenced is json should not be deleted
// or else it will require a restart of "devfs" service.
// But, stoppign the jail already removes this >1000 ID.
// So no need to call this function.
func deleteDevfsRuleset(ruleset int) error {
cmd := "devfs rule showsets"
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("ERROR listing rulesets: %s", err.Error()))
}
rs := strconv.Itoa(ruleset)
for _, r := range strings.Split(out, "\n") {
if r == jail.Config.Devfs_ruleset {
cmd := fmt.Sprintf("devfs rule -s %s delset", jail.Config.Devfs_ruleset)
if r == rs {
cmd := fmt.Sprintf("devfs rule -s %d delset", ruleset)
_, err := executeCommand(cmd)
return err
}
@ -114,7 +123,6 @@ func deleteDevfsRuleset(jail *Jail) error {
return nil
}
func umountJailFsFromHost(jail *Jail, mountpoint string) error {
cmd := "mount -p"
out, err := executeCommand(cmd)
@ -130,7 +138,7 @@ func umountJailFsFromHost(jail *Jail, mountpoint string) error {
cmd = fmt.Sprintf("umount %s%s", jail.RootPath, mountpoint)
_, err := executeCommand(cmd)
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: %s", jail.RootPath, mountpoint, err.Error()))
}
return nil
}
@ -140,7 +148,6 @@ func umountJailFsFromHost(jail *Jail, mountpoint string) error {
return nil
}
// Internal usage only
func stopJail(jail *Jail) error {
cmd := "jail -q"
@ -162,6 +169,79 @@ func stopJail(jail *Jail) error {
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:
Remove rctl rules
@ -186,25 +266,41 @@ func stopJail(jail *Jail) error {
func StopJail(args []string) {
// Current jail were stopping
var cj *Jail
var err error
for _, j := range args {
fmt.Printf("> Stopping jail %s\n", j)
for _, rj := range gJails {
if rj.Name == j {
cj = &rj
break
}
}
if cj == nil {
fmt.Printf("Jail not found: %s\n", j)
for _, a := range args {
// Check if jail exist and is distinctly named
cj, err = getJailFromArray(a, gJails)
if err != nil {
fmt.Printf("Error getting jail: %s\n", err)
continue
}
if cj.Running == false {
fmt.Printf("Jail %s is not running!\n", cj.Name)
continue
}
fmt.Printf("> Stopping jail %s\n", a)
// Get current version to update config.json
cvers, err := executeCommandInJail(cj, "/bin/freebsd-version")
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
return
}
cvers = strings.TrimRight(cvers, "\n")
//fmt.Sprintf(cj.Config.Release, cvers)
//cj.Config.Release = cvers
//cj.ConfigUpdated = true
// This is working in this context, but value is not available in WriteConfigToDisk context :/
setStructFieldValue(cj, "Config.Release", cvers)
fmt.Printf("DEBUG: release was set, now is : %s\n", cj.Config.Release)
// We need to get the real Config object, not a copy of it
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
if err == nil && len(out) > 0 {
fmt.Printf(" > Remove RCTL rules:\n")
@ -216,17 +312,17 @@ func StopJail(args []string) {
}
}
if len (cj.Config.Exec_prestop) > 0 {
fmt.Printf(" > Execute prestop:\n")
if len(cj.Config.Exec_prestop) > 0 {
fmt.Printf(" > Execute pre-stop:\n")
_, err := executeCommand(cj.Config.Exec_prestop)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Execute prestop: OK\n")
fmt.Printf(" > Execute pre-stop: OK\n")
}
}
if len (cj.Config.Exec_stop) > 0 {
if len(cj.Config.Exec_stop) > 0 {
fmt.Printf(" > Execute stop:\n")
_, err := executeCommandInJail(cj, cj.Config.Exec_stop)
if err != nil {
@ -256,12 +352,12 @@ func StopJail(args []string) {
}
}
fmt.Printf(" > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset)
err = deleteDevfsRuleset(cj)
fmt.Printf(" > Remove devfs ruleset %d: \n", cj.Devfs_ruleset)
err = deleteDevfsRuleset(cj.Devfs_ruleset)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} 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)
@ -292,15 +388,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")
err := umountJailFsFromHost(cj, "/dev/fd")
err = umountJailFsFromHost(cj, "/dev/fd")
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Umount fdescfs: OK\n")
}
}
//}
if cj.Config.Mount_devfs > 0 {
fmt.Printf(" > Umount devfs:\n")
@ -311,6 +408,65 @@ func StopJail(args []string) {
fmt.Printf(" > Umount devfs: OK\n")
}
}
// Remove local mounts from $JAIL/fstab
fstab := strings.Replace(cj.ConfigPath, "config.json", "fstab", 1)
mounts, err := getFstab(fstab)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
}
if len(mounts) > 0 {
fmt.Printf(" > Umount mountpoints from %s\n", fstab)
errs := 0
for _, m := range mounts {
err = umountJailFsFromHost(cj, m.Mountpoint)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
errs += 1
}
}
if errs == 0 {
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())
}
}
}
fmt.Printf("DEBUG: release = %s\n", cj.Config.Release)
WriteConfigToDisk(cj.Name, false, true)
}
}

View File

@ -1,5 +1,19 @@
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
type Jail struct {
@ -9,10 +23,13 @@ type Jail struct {
Config JailConfig
RootPath string
ConfigPath string
ConfigUpdated bool
Running bool
// No need, Config.Release always represent what is running (plus it know release for non-running jails)
//Release string
Devfs_ruleset int // The effective devfs ruleset generated at runtime
Zpool string
Datastore string
}
// iocage struct as stored in config.json
@ -20,6 +37,7 @@ type Jail struct {
//
// Fields in this struct are acquired by their name using reflection
// So these char are forbidden for field name: -+.
// Array should be forbidden, or else you'll need to rewrite setJailProperty()
//
// To allow sorting, just duplicate fields in JailConfigSort below
type JailConfig struct {
@ -81,8 +99,10 @@ type JailConfig struct {
Host_time int `json:"host_time"`
Hostid string `json:"hostid"`
Hostid_strict_check int `json:"hostid_strict_check"`
// Specify multiple net cards with "vnet0:bridge0,vnet1:bridge1"
Interfaces string `json:"interfaces"`
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_saddrsel string `json:"ip4_saddrsel"`
Ip6 string `json:"ip6"`
@ -162,302 +182,382 @@ type JailConfig struct {
Writeiops string `json:"writeiops"`
}
// This struct hold "sort by jail fields" functions
type lessFunc func(j1 *Jail, j2 *Jail) bool
// Represent an fstab line
type Mount struct {
Device string
Mountpoint string
Type string
Options []string
Fs_Freq 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
}
type FreeBSDVersion struct {
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
// So these char are forbidden for field name: -+.
//
type JailSort struct {
NameInc lessFunc
NameDec lessFunc
InternalNameInc lessFunc
InternalNameDec lessFunc
JIDInc lessFunc
JIDDec lessFunc
RootPathInc lessFunc
RootPathDec lessFunc
ConfigPathInc lessFunc
ConfigPathDec lessFunc
RunningInc lessFunc
RunningDec lessFunc
ZpoolInc lessFunc
ZpoolDec lessFunc
NameInc jailLessFunc
NameDec jailLessFunc
InternalNameInc jailLessFunc
InternalNameDec jailLessFunc
JIDInc jailLessFunc
JIDDec jailLessFunc
RootPathInc jailLessFunc
RootPathDec jailLessFunc
ConfigPathInc jailLessFunc
ConfigPathDec jailLessFunc
RunningInc jailLessFunc
RunningDec jailLessFunc
DatastoreInc jailLessFunc
DatastoreDec jailLessFunc
ZpoolInc jailLessFunc
ZpoolDec jailLessFunc
Devfs_rulesetInc jailLessFunc
Devfs_rulesetDec jailLessFunc
Config JailConfigSort
}
type JailConfigSort struct {
Config_versionInc lessFunc
Config_versionDec lessFunc
Allow_chflagsInc lessFunc
Allow_chflagsDec lessFunc
Allow_mlockInc lessFunc
Allow_mlockDec lessFunc
Allow_mountInc lessFunc
Allow_mountDec lessFunc
Allow_mount_devfsInc lessFunc
Allow_mount_devfsDec lessFunc
Allow_mount_fusefsInc lessFunc
Allow_mount_fusefsDec lessFunc
Allow_mount_nullfsInc lessFunc
Allow_mount_nullfsDec lessFunc
Allow_mount_procfsInc lessFunc
Allow_mount_procfsDec lessFunc
Allow_mount_tmpfsInc lessFunc
Allow_mount_tmpfsDec lessFunc
Allow_mount_zfsInc lessFunc
Allow_mount_zfsDec lessFunc
Allow_quotasInc lessFunc
Allow_quotasDec lessFunc
Allow_raw_socketsInc lessFunc
Allow_raw_socketsDec lessFunc
Allow_set_hostnameInc lessFunc
Allow_set_hostnameDec lessFunc
Allow_socket_afInc lessFunc
Allow_socket_afDec lessFunc
Allow_sysvipcInc lessFunc
Allow_sysvipcDec lessFunc
Allow_tunInc lessFunc
Allow_tunDec lessFunc
Allow_vmmInc lessFunc
Allow_vmmDec lessFunc
Assign_localhostInc lessFunc
Assign_localhostDec lessFunc
AvailableInc lessFunc
AvailableDec lessFunc
BasejailInc lessFunc
BasejailDec lessFunc
BootInc lessFunc
BootDec lessFunc
BpfInc lessFunc
BpfDec lessFunc
Children_maxInc lessFunc
Children_maxDec lessFunc
Cloned_releaseInc lessFunc
Cloned_releaseDec lessFunc
CommentInc lessFunc
CommentDec lessFunc
CompressionInc lessFunc
CompressionDec lessFunc
CompressratioInc lessFunc
CompressratioDec lessFunc
CoredumpsizeInc lessFunc
CoredumpsizeDec lessFunc
CountInc lessFunc
CountDec lessFunc
CpusetInc lessFunc
CpusetDec lessFunc
CputimeInc lessFunc
CputimeDec lessFunc
DatasizeInc lessFunc
DatasizeDec lessFunc
DedupInc lessFunc
DedupDec lessFunc
DefaultrouterInc lessFunc
DefaultrouterDec lessFunc
Defaultrouter6Inc lessFunc
Defaultrouter6Dec lessFunc
DependsInc lessFunc
DependsDec lessFunc
Devfs_rulesetInc lessFunc
Devfs_rulesetDec lessFunc
DhcpInc lessFunc
DhcpDec lessFunc
Enforce_statfsInc lessFunc
Enforce_statfsDec lessFunc
Exec_cleanInc lessFunc
Exec_cleanDec lessFunc
Exec_createdInc lessFunc
Exec_createdDec lessFunc
Exec_fibInc lessFunc
Exec_fibDec lessFunc
Exec_jail_userInc lessFunc
Exec_jail_userDec lessFunc
Exec_poststartInc lessFunc
Exec_poststartDec lessFunc
Exec_poststopInc lessFunc
Exec_poststopDec lessFunc
Exec_prestartInc lessFunc
Exec_prestartDec lessFunc
Exec_prestopInc lessFunc
Exec_prestopDec lessFunc
Exec_startInc lessFunc
Exec_startDec lessFunc
Exec_stopInc lessFunc
Exec_stopDec lessFunc
Exec_system_jail_userInc lessFunc
Exec_system_jail_userDec lessFunc
Exec_system_userInc lessFunc
Exec_system_userDec lessFunc
Exec_timeoutInc lessFunc
Exec_timeoutDec lessFunc
Host_domainnameInc lessFunc
Host_domainnameDec lessFunc
Host_hostnameInc lessFunc
Host_hostnameDec lessFunc
Host_hostuuidInc lessFunc
Host_hostuuidDec lessFunc
Host_timeInc lessFunc
Host_timeDec lessFunc
HostidInc lessFunc
HostidDec lessFunc
Hostid_strict_checkInc lessFunc
Hostid_strict_checkDec lessFunc
InterfacesInc lessFunc
InterfacesDec lessFunc
Ip4Inc lessFunc
Ip4Dec lessFunc
Ip4_addrInc lessFunc
Ip4_addrDec lessFunc
Ip4_saddrselInc lessFunc
Ip4_saddrselDec lessFunc
Ip6Inc lessFunc
Ip6Dec lessFunc
Ip6_addrInc lessFunc
Ip6_addrDec lessFunc
Ip6_saddrselInc lessFunc
Ip6_saddrselDec lessFunc
Ip_hostnameInc lessFunc
Ip_hostnameDec lessFunc
Jail_zfsInc lessFunc
Jail_zfsDec lessFunc
Jail_zfs_datasetInc lessFunc
Jail_zfs_datasetDec lessFunc
Jail_zfs_mountpointInc lessFunc
Jail_zfs_mountpointDec lessFunc
Last_startedInc lessFunc
Last_startedDec lessFunc
Localhost_ipInc lessFunc
Localhost_ipDec lessFunc
Login_flagsInc lessFunc
Login_flagsDec lessFunc
Mac_prefixInc lessFunc
Mac_prefixDec lessFunc
MaxprocInc lessFunc
MaxprocDec lessFunc
MemorylockedInc lessFunc
MemorylockedDec lessFunc
MemoryuseInc lessFunc
MemoryuseDec lessFunc
Min_dyn_devfs_rulesetInc lessFunc
Min_dyn_devfs_rulesetDec lessFunc
Mount_devfsInc lessFunc
Mount_devfsDec lessFunc
Mount_fdescfsInc lessFunc
Mount_fdescfsDec lessFunc
Mount_linprocfsInc lessFunc
Mount_linprocfsDec lessFunc
Mount_procfsInc lessFunc
Mount_procfsDec lessFunc
MountpointInc lessFunc
MountpointDec lessFunc
MsgqqueuedInc lessFunc
MsgqqueuedDec lessFunc
MsgqsizeInc lessFunc
MsgqsizeDec lessFunc
NatInc lessFunc
NatDec lessFunc
Nat_backendInc lessFunc
Nat_backendDec lessFunc
Nat_forwardsInc lessFunc
Nat_forwardsDec lessFunc
Nat_interfaceInc lessFunc
Nat_interfaceDec lessFunc
Nat_prefixInc lessFunc
Nat_prefixDec lessFunc
NmsgqInc lessFunc
NmsgqDec lessFunc
NotesInc lessFunc
NotesDec lessFunc
NsemInc lessFunc
NsemDec lessFunc
NsemopInc lessFunc
NsemopDec lessFunc
NshmInc lessFunc
NshmDec lessFunc
NthrInc lessFunc
NthrDec lessFunc
OpenfilesInc lessFunc
OpenfilesDec lessFunc
OriginInc lessFunc
OriginDec lessFunc
OwnerInc lessFunc
OwnerDec lessFunc
PcpuInc lessFunc
PcpuDec lessFunc
Plugin_nameInc lessFunc
Plugin_nameDec lessFunc
Plugin_repositoryInc lessFunc
Plugin_repositoryDec lessFunc
PriorityInc lessFunc
PriorityDec lessFunc
PseudoterminalsInc lessFunc
PseudoterminalsDec lessFunc
QuotaInc lessFunc
QuotaDec lessFunc
ReadbpsInc lessFunc
ReadbpsDec lessFunc
ReadiopsInc lessFunc
ReadiopsDec lessFunc
ReleaseInc lessFunc
ReleaseDec lessFunc
ReservationInc lessFunc
ReservationDec lessFunc
ResolverInc lessFunc
ResolverDec lessFunc
RlimitsInc lessFunc
RlimitsDec lessFunc
RtsoldInc lessFunc
RtsoldDec lessFunc
SecurelevelInc lessFunc
SecurelevelDec lessFunc
ShmsizeInc lessFunc
ShmsizeDec lessFunc
StacksizeInc lessFunc
StacksizeDec lessFunc
Stop_timeoutInc lessFunc
Stop_timeoutDec lessFunc
SwapuseInc lessFunc
SwapuseDec lessFunc
Sync_stateInc lessFunc
Sync_stateDec lessFunc
Sync_targetInc lessFunc
Sync_targetDec lessFunc
Sync_tgt_zpoolInc lessFunc
Sync_tgt_zpoolDec lessFunc
SysvmsgInc lessFunc
SysvmsgDec lessFunc
SysvsemInc lessFunc
SysvsemDec lessFunc
SysvshmInc lessFunc
SysvshmDec lessFunc
TemplateInc lessFunc
TemplateDec lessFunc
JailtypeInc lessFunc
JailtypeDec lessFunc
UsedInc lessFunc
UsedDec lessFunc
VmemoryuseInc lessFunc
VmemoryuseDec lessFunc
VnetInc lessFunc
VnetDec lessFunc
Vnet0_macInc lessFunc
Vnet0_macDec lessFunc
Vnet1_macInc lessFunc
Vnet1_macDec lessFunc
Vnet2_macInc lessFunc
Vnet2_macDec lessFunc
Vnet3_macInc lessFunc
Vnet3_macDec lessFunc
Vnet_default_interfaceInc lessFunc
Vnet_default_interfaceDec lessFunc
Vnet_interfacesInc lessFunc
Vnet_interfacesDec lessFunc
WallclockInc lessFunc
WallclockDec lessFunc
WritebpsInc lessFunc
WritebpsDec lessFunc
WriteiopsInc lessFunc
WriteiopsDec lessFunc
Config_versionInc jailLessFunc
Config_versionDec jailLessFunc
Allow_chflagsInc jailLessFunc
Allow_chflagsDec jailLessFunc
Allow_mlockInc jailLessFunc
Allow_mlockDec jailLessFunc
Allow_mountInc jailLessFunc
Allow_mountDec jailLessFunc
Allow_mount_devfsInc jailLessFunc
Allow_mount_devfsDec jailLessFunc
Allow_mount_fusefsInc jailLessFunc
Allow_mount_fusefsDec jailLessFunc
Allow_mount_nullfsInc jailLessFunc
Allow_mount_nullfsDec jailLessFunc
Allow_mount_procfsInc jailLessFunc
Allow_mount_procfsDec jailLessFunc
Allow_mount_tmpfsInc jailLessFunc
Allow_mount_tmpfsDec jailLessFunc
Allow_mount_zfsInc jailLessFunc
Allow_mount_zfsDec jailLessFunc
Allow_quotasInc jailLessFunc
Allow_quotasDec jailLessFunc
Allow_raw_socketsInc jailLessFunc
Allow_raw_socketsDec jailLessFunc
Allow_set_hostnameInc jailLessFunc
Allow_set_hostnameDec jailLessFunc
Allow_socket_afInc jailLessFunc
Allow_socket_afDec jailLessFunc
Allow_sysvipcInc jailLessFunc
Allow_sysvipcDec jailLessFunc
Allow_tunInc jailLessFunc
Allow_tunDec jailLessFunc
Allow_vmmInc jailLessFunc
Allow_vmmDec jailLessFunc
Assign_localhostInc jailLessFunc
Assign_localhostDec jailLessFunc
AvailableInc jailLessFunc
AvailableDec jailLessFunc
BasejailInc jailLessFunc
BasejailDec jailLessFunc
BootInc jailLessFunc
BootDec jailLessFunc
BpfInc jailLessFunc
BpfDec jailLessFunc
Children_maxInc jailLessFunc
Children_maxDec jailLessFunc
Cloned_releaseInc jailLessFunc
Cloned_releaseDec jailLessFunc
CommentInc jailLessFunc
CommentDec jailLessFunc
CompressionInc jailLessFunc
CompressionDec jailLessFunc
CompressratioInc jailLessFunc
CompressratioDec jailLessFunc
CoredumpsizeInc jailLessFunc
CoredumpsizeDec jailLessFunc
CountInc jailLessFunc
CountDec jailLessFunc
CpusetInc jailLessFunc
CpusetDec jailLessFunc
CputimeInc jailLessFunc
CputimeDec jailLessFunc
DatasizeInc jailLessFunc
DatasizeDec jailLessFunc
DedupInc jailLessFunc
DedupDec jailLessFunc
DefaultrouterInc jailLessFunc
DefaultrouterDec jailLessFunc
Defaultrouter6Inc jailLessFunc
Defaultrouter6Dec jailLessFunc
DependsInc jailLessFunc
DependsDec jailLessFunc
Devfs_rulesetInc jailLessFunc
Devfs_rulesetDec jailLessFunc
DhcpInc jailLessFunc
DhcpDec jailLessFunc
Enforce_statfsInc jailLessFunc
Enforce_statfsDec jailLessFunc
Exec_cleanInc jailLessFunc
Exec_cleanDec jailLessFunc
Exec_createdInc jailLessFunc
Exec_createdDec jailLessFunc
Exec_fibInc jailLessFunc
Exec_fibDec jailLessFunc
Exec_jail_userInc jailLessFunc
Exec_jail_userDec jailLessFunc
Exec_poststartInc jailLessFunc
Exec_poststartDec jailLessFunc
Exec_poststopInc jailLessFunc
Exec_poststopDec jailLessFunc
Exec_prestartInc jailLessFunc
Exec_prestartDec jailLessFunc
Exec_prestopInc jailLessFunc
Exec_prestopDec jailLessFunc
Exec_startInc jailLessFunc
Exec_startDec jailLessFunc
Exec_stopInc jailLessFunc
Exec_stopDec jailLessFunc
Exec_system_jail_userInc jailLessFunc
Exec_system_jail_userDec jailLessFunc
Exec_system_userInc jailLessFunc
Exec_system_userDec jailLessFunc
Exec_timeoutInc jailLessFunc
Exec_timeoutDec jailLessFunc
Host_domainnameInc jailLessFunc
Host_domainnameDec jailLessFunc
Host_hostnameInc jailLessFunc
Host_hostnameDec jailLessFunc
Host_hostuuidInc jailLessFunc
Host_hostuuidDec jailLessFunc
Host_timeInc jailLessFunc
Host_timeDec jailLessFunc
HostidInc jailLessFunc
HostidDec jailLessFunc
Hostid_strict_checkInc jailLessFunc
Hostid_strict_checkDec jailLessFunc
InterfacesInc jailLessFunc
InterfacesDec jailLessFunc
Ip4Inc jailLessFunc
Ip4Dec jailLessFunc
Ip4_addrInc jailLessFunc
Ip4_addrDec jailLessFunc
Ip4_saddrselInc jailLessFunc
Ip4_saddrselDec jailLessFunc
Ip6Inc jailLessFunc
Ip6Dec jailLessFunc
Ip6_addrInc jailLessFunc
Ip6_addrDec jailLessFunc
Ip6_saddrselInc jailLessFunc
Ip6_saddrselDec jailLessFunc
Ip_hostnameInc jailLessFunc
Ip_hostnameDec jailLessFunc
Jail_zfsInc jailLessFunc
Jail_zfsDec jailLessFunc
Jail_zfs_datasetInc jailLessFunc
Jail_zfs_datasetDec jailLessFunc
Jail_zfs_mountpointInc jailLessFunc
Jail_zfs_mountpointDec jailLessFunc
Last_startedInc jailLessFunc
Last_startedDec jailLessFunc
Localhost_ipInc jailLessFunc
Localhost_ipDec jailLessFunc
Login_flagsInc jailLessFunc
Login_flagsDec jailLessFunc
Mac_prefixInc jailLessFunc
Mac_prefixDec jailLessFunc
MaxprocInc jailLessFunc
MaxprocDec jailLessFunc
MemorylockedInc jailLessFunc
MemorylockedDec jailLessFunc
MemoryuseInc jailLessFunc
MemoryuseDec jailLessFunc
Min_dyn_devfs_rulesetInc jailLessFunc
Min_dyn_devfs_rulesetDec jailLessFunc
Mount_devfsInc jailLessFunc
Mount_devfsDec jailLessFunc
Mount_fdescfsInc jailLessFunc
Mount_fdescfsDec jailLessFunc
Mount_linprocfsInc jailLessFunc
Mount_linprocfsDec jailLessFunc
Mount_procfsInc jailLessFunc
Mount_procfsDec jailLessFunc
MountpointInc jailLessFunc
MountpointDec jailLessFunc
MsgqqueuedInc jailLessFunc
MsgqqueuedDec jailLessFunc
MsgqsizeInc jailLessFunc
MsgqsizeDec jailLessFunc
NatInc jailLessFunc
NatDec jailLessFunc
Nat_backendInc jailLessFunc
Nat_backendDec jailLessFunc
Nat_forwardsInc jailLessFunc
Nat_forwardsDec jailLessFunc
Nat_interfaceInc jailLessFunc
Nat_interfaceDec jailLessFunc
Nat_prefixInc jailLessFunc
Nat_prefixDec jailLessFunc
NmsgqInc jailLessFunc
NmsgqDec jailLessFunc
NotesInc jailLessFunc
NotesDec jailLessFunc
NsemInc jailLessFunc
NsemDec jailLessFunc
NsemopInc jailLessFunc
NsemopDec jailLessFunc
NshmInc jailLessFunc
NshmDec jailLessFunc
NthrInc jailLessFunc
NthrDec jailLessFunc
OpenfilesInc jailLessFunc
OpenfilesDec jailLessFunc
OriginInc jailLessFunc
OriginDec jailLessFunc
OwnerInc jailLessFunc
OwnerDec jailLessFunc
PcpuInc jailLessFunc
PcpuDec jailLessFunc
Plugin_nameInc jailLessFunc
Plugin_nameDec jailLessFunc
Plugin_repositoryInc jailLessFunc
Plugin_repositoryDec jailLessFunc
PriorityInc jailLessFunc
PriorityDec jailLessFunc
PseudoterminalsInc jailLessFunc
PseudoterminalsDec jailLessFunc
QuotaInc jailLessFunc
QuotaDec jailLessFunc
ReadbpsInc jailLessFunc
ReadbpsDec jailLessFunc
ReadiopsInc jailLessFunc
ReadiopsDec jailLessFunc
ReleaseInc jailLessFunc
ReleaseDec jailLessFunc
ReservationInc jailLessFunc
ReservationDec jailLessFunc
ResolverInc jailLessFunc
ResolverDec jailLessFunc
RlimitsInc jailLessFunc
RlimitsDec jailLessFunc
RtsoldInc jailLessFunc
RtsoldDec jailLessFunc
SecurelevelInc jailLessFunc
SecurelevelDec jailLessFunc
ShmsizeInc jailLessFunc
ShmsizeDec jailLessFunc
StacksizeInc jailLessFunc
StacksizeDec jailLessFunc
Stop_timeoutInc jailLessFunc
Stop_timeoutDec jailLessFunc
SwapuseInc jailLessFunc
SwapuseDec jailLessFunc
Sync_stateInc jailLessFunc
Sync_stateDec jailLessFunc
Sync_targetInc jailLessFunc
Sync_targetDec jailLessFunc
Sync_tgt_zpoolInc jailLessFunc
Sync_tgt_zpoolDec jailLessFunc
SysvmsgInc jailLessFunc
SysvmsgDec jailLessFunc
SysvsemInc jailLessFunc
SysvsemDec jailLessFunc
SysvshmInc jailLessFunc
SysvshmDec jailLessFunc
TemplateInc jailLessFunc
TemplateDec jailLessFunc
JailtypeInc jailLessFunc
JailtypeDec jailLessFunc
UsedInc jailLessFunc
UsedDec jailLessFunc
VmemoryuseInc jailLessFunc
VmemoryuseDec jailLessFunc
VnetInc jailLessFunc
VnetDec jailLessFunc
Vnet0_macInc jailLessFunc
Vnet0_macDec jailLessFunc
Vnet1_macInc jailLessFunc
Vnet1_macDec jailLessFunc
Vnet2_macInc jailLessFunc
Vnet2_macDec jailLessFunc
Vnet3_macInc jailLessFunc
Vnet3_macDec jailLessFunc
Vnet_default_interfaceInc jailLessFunc
Vnet_default_interfaceDec jailLessFunc
Vnet_interfacesInc jailLessFunc
Vnet_interfacesDec jailLessFunc
WallclockInc jailLessFunc
WallclockDec jailLessFunc
WritebpsInc jailLessFunc
WritebpsDec jailLessFunc
WriteiopsInc jailLessFunc
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
}

157
cmd/update.go Normal file
View File

@ -0,0 +1,157 @@
package cmd
import (
"os"
"fmt"
//"log"
"time"
)
const (
fbsdUpdateConfig = `
# $FreeBSD: releng/12.2/usr.sbin/freebsd-update/freebsd-update.conf 337338 2018-08-04 22:25:41Z brd $
# 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 /var/db/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 no
# 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 yes
# 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 yes
`
)
// 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())
cmd := fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s fetch install",
cfgFile.Name(), jail.RootPath, jail.Config.Release)
fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd)
err = executeCommandWithOutputToStdout(cmd)
if err != nil {
return err
}
// Get and write new release into config.json
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, gJails)
if err != nil {
fmt.Printf("Error getting jail: %s\n", err)
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)
}
}
}

File diff suppressed because it is too large Load Diff

5
go.mod
View File

@ -3,8 +3,13 @@ module gocage
go 1.17
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/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.9.0
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420
)
require (

View File

@ -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:
- /iocage
# Prefix all commands with sudo
sudo: false
# Columns to display when "gocage list". Column names are struct fields, see cmd/struct.go
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

View File

@ -24,6 +24,7 @@ type Jail struct {
Name string
Jid int
Path string
Devfs_ruleset int
}
@ -33,8 +34,8 @@ func GetJails() ([]Jail, error) {
var jl Jail
var err error
// Make "params" a list of 4 jails parameters
params := make([]C.struct_jailparam, 4)
// Make "params" a list of 5 jails parameters
params := make([]C.struct_jailparam, 5)
// initialize parameter names
csname := C.CString("name")
@ -43,27 +44,31 @@ func GetJails() ([]Jail, error) {
defer C.free(unsafe.Pointer(csjid))
cspath := C.CString("path")
defer C.free(unsafe.Pointer(cspath))
csdevfsrs := C.CString("devfs_ruleset")
defer C.free(unsafe.Pointer(csdevfsrs))
cslastjid := C.CString("lastjid")
defer C.free(unsafe.Pointer(cslastjid))
// initialize params struct with parameter names
C.jailparam_init(&params[0], csname)
C.jailparam_init(&params[1], csjid)
C.jailparam_init(&params[2], cspath)
C.jailparam_init(&params[3], csdevfsrs)
// The key to retrieve jail. lastjid = 0 returns first jail and its jid as jailparam_get return value
C.jailparam_init(&params[3], cslastjid)
C.jailparam_init(&params[4], cslastjid)
lastjailid := 0
cslastjidval := C.CString(strconv.Itoa(lastjailid))
defer C.free(unsafe.Pointer(cslastjidval))
C.jailparam_import(&params[3], cslastjidval)
C.jailparam_import(&params[4], cslastjidval)
// loop on existing jails
for lastjailid >= 0 {
// get parameter values
lastjailid = int(C.jailparam_get(&params[0], 4, 0))
lastjailid = int(C.jailparam_get(&params[0], 5, 0))
if lastjailid > 0 {
nametmp := C.jailparam_export(&params[0])
jl.Name = C.GoString(nametmp)
@ -80,18 +85,23 @@ func GetJails() ([]Jail, error) {
// Memory mgmt : Non gere par Go
C.free(unsafe.Pointer(pathtmp))
drstmp := C.jailparam_export(&params[3])
jl.Devfs_ruleset, _ = strconv.Atoi(C.GoString(drstmp))
// Memory mgmt : Non gere par Go
C.free(unsafe.Pointer(drstmp))
jls = append(jls, jl)
//log.Debug("Got jid " + strconv.Itoa(jl.jid) + " with name " + jl.name)
// Prepare next loop iteration
cslastjidval := C.CString(strconv.Itoa(lastjailid))
defer C.free(unsafe.Pointer(cslastjidval))
C.jailparam_import(&params[3], cslastjidval)
C.jailparam_import(&params[4], cslastjidval)
}
}
// Free 4 items of params list
C.jailparam_free(&params[0], 4)
// Free 5 items of params list
C.jailparam_free(&params[0], 5)
return jls, err
}

View File

@ -4,7 +4,6 @@ import (
"gocage/cmd"
)
func main () {
func main() {
cmd.Execute()
}

43
service/gocage Executable file
View 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"