13 Commits

13 changed files with 358 additions and 350 deletions

111
README.md
View File

@ -8,30 +8,36 @@ Gocage can handle multiple datastores, so you can have jails on HDD storage and
From v0.33b, due to multi ZFS pool support, gocage is no longer 100% compatible with iocage. 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. : Zfs datasets now should be specified with the ZFS pool. e.g. :
<pre><code>Config.Jail_zfs = 1 <pre><code>
Config.Jail_zfs = 1
Config.Jail_zfs_dataset = myzfspool/poudriere Config.Jail_zfs_dataset = myzfspool/poudriere
Config.Jail_zfs_mountpoint = none Config.Jail_zfs_mountpoint = none
</code></pre> </code></pre>
Create jails Create jails
------------ ------------
You need to specify release, and optional configuration: For now, we can't pass config at creation time. We have to define config after creation:
<pre><code>gocage create jail1 -r 13.2-RELEASE -p "Config.Ip4_addr='vnet0|192.168.1.91/24',Config.Ip6=none,Config.Boot=1" <pre><code>
gocage create jail1 -r 13.2-RELEASE
gocage set Config.Ip4_addr="vnet0|192.168.1.91/24" Config.Vnet=1 jail1
</code></pre> </code></pre>
Create basejail. A basejail is a jail based on a release: system will be nullfs read-only mounted from the release directory. Main advantage is that release updates will immediately apply to jails based on this release. Another advantage is that jail system is mounted read-only, a plus from a security perspective: Create basejail (jail based on a release, system will be nullfs read-only mounted from the release directory):
<pre><code>gocage create -b -r 14.0-RELEASE basejail1 <pre><code>
gocage create -b -r 14.0-RELEASE basejail1
</code></pre> </code></pre>
List jails List jails
---------- ----------
<pre><code>gocage list</code></pre> Nothing fancy, just use
`gocage list`
### Specify fields to display ### Specify fields to display
Use -o to specify which fields you want to display: Use -o to specify which fields you want to display:
<pre><code>gocage list -o JID,Name,Running,Config.Boot,Config.Comment <pre><code>
gocage list -o JID,Name,Running,Config.Boot,Config.Comment
+=====+==========+=========+=============+================+ +=====+==========+=========+=============+================+
| JID | Name | Running | Config.Boot | Config.Comment | | JID | Name | Running | Config.Boot | Config.Comment |
+=====+==========+=========+=============+================+ +=====+==========+=========+=============+================+
@ -45,7 +51,7 @@ Use -o to specify which fields you want to display:
+-----+----------+---------+-------------+----------------+ +-----+----------+---------+-------------+----------------+
</code></pre> </code></pre>
Use `gocage properties`to list available fields. See [cmd/struct.go](https://git.nosd.in/yo/gocage/src/branch/master/cmd/struct.go) for field names.
Filter jails Filter jails
@ -53,7 +59,8 @@ Filter jails
### By name ### By name
Just add name on gocage list command : Just add name on gocage list command :
<pre><code>gocage list srv-bdd srv-web <pre><code>
gocage list srv-bdd srv-web
+=====+=========+=================+=======================+=========+ +=====+=========+=================+=======================+=========+
| JID | Name | Config.Release | Config.Ip4_addr | Running | | JID | Name | Config.Release | Config.Ip4_addr | Running |
+=====+=========+=================+=======================+=========+ +=====+=========+=================+=======================+=========+
@ -61,11 +68,12 @@ Just add name on gocage list command :
+-----+---------+-----------------+-----------------------+---------+ +-----+---------+-----------------+-----------------------+---------+
| 41 | srv-web | 13.0-RELEASE-p4 | vnet0|192.168.1.26/24 | true | | 41 | srv-web | 13.0-RELEASE-p4 | vnet0|192.168.1.26/24 | true |
+-----+---------+-----------------+-----------------------+---------+ +-----+---------+-----------------+-----------------------+---------+
</code></pre> </pre></code>
### By field value ### By field value
You can filter jails with -f option, followed by key=value. Suppose you want to see only active at boot jails: You can filter jails with -f option, followed by key=value. Suppose you want to see only active at boot jails:
<pre><code>gocage list -f Config.Boot=1 -o JID,Name,Running,Config.Boot,Config.Comment <pre><code>
gocage list -f Config.Boot=1 -o JID,Name,Running,Config.Boot,Config.Comment
+=====+==========+=========+=============+================+ +=====+==========+=========+=============+================+
| JID | Name | Running | Config.Boot | Config.Comment | | JID | Name | Running | Config.Boot | Config.Comment |
+=====+==========+=========+=============+================+ +=====+==========+=========+=============+================+
@ -77,10 +85,11 @@ You can filter jails with -f option, followed by key=value. Suppose you want to
+-----+----------+---------+-------------+----------------+ +-----+----------+---------+-------------+----------------+
| 22 | srv-dns1 | true | 1 | | | 22 | srv-dns1 | true | 1 | |
+-----+----------+---------+-------------+----------------+ +-----+----------+---------+-------------+----------------+
</code></pre> </pre></code>
Now, only active at boot and running : Now, only active at boot and running :
<pre><code>gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot <pre><code>
gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot
+=====+==========+=========+=============+ +=====+==========+=========+=============+
| JID | Name | Running | Config.Boot | | JID | Name | Running | Config.Boot |
+=====+==========+=========+=============+ +=====+==========+=========+=============+
@ -90,12 +99,13 @@ Now, only active at boot and running :
+-----+----------+---------+-------------+ +-----+----------+---------+-------------+
| 22 | srv-dns1 | true | 1 | | 22 | srv-dns1 | true | 1 |
+-----+----------+---------+-------------+ +-----+----------+---------+-------------+
</code></pre> </pre></code>
Sort jails Sort jails
---------- ----------
Use -s switch followed by sort criteria. Criteria is a field name, prefixed with + or - for sort order (increase/decrease): 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 <pre><code>
gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot -s +JID
+=====+==========+=========+=============+ +=====+==========+=========+=============+
| JID | Name | Running | Config.Boot | | JID | Name | Running | Config.Boot |
+=====+==========+=========+=============+ +=====+==========+=========+=============+
@ -105,11 +115,12 @@ Use -s switch followed by sort criteria. Criteria is a field name, prefixed with
+-----+----------+---------+-------------+ +-----+----------+---------+-------------+
| 183 | test | true | 1 | | 183 | test | true | 1 |
+-----+----------+---------+-------------+ +-----+----------+---------+-------------+
</code></pre> </pre></code>
You can use up to 3 criteria, delimited with comma. You can use up to 3 criteria, delimited with comma.
As an example, you want to list boot priorities of automatically starting jails: 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 <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 | | JID | Name | Config.Ip4_addr | Config.Priority | Config.Boot | Running |
+=====+==============+=======================+=================+=============+=========+ +=====+==============+=======================+=================+=============+=========+
@ -121,62 +132,47 @@ As an example, you want to list boot priorities of automatically starting jails:
+-----+--------------+-----------------------+-----------------+-------------+---------+ +-----+--------------+-----------------------+-----------------+-------------+---------+
| 4 | coincoin | vnet0|192.168.1.9/24 | 20 | 0 | true | | 4 | coincoin | vnet0|192.168.1.9/24 | 20 | 0 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+ +-----+--------------+-----------------------+-----------------+-------------+---------+
</code></pre> </pre></code>
Stop jails Stop jails
---------- ----------
<pre><code>gocage stop test</code></pre> `gocage stop test`
Update jails Update jails
---------- ----------
To update jail patch version, use gocage update : To update jail patch version, use gocage update :
<pre><code>gocage update test</code></pre> `gocage update test`
Update basejails/releases
----------
To update basejails, you need to update the release they are based on. Specify release with -r, and the datastore storing concerned release with -d :
<pre><code>gocage update -d fastgocage -r 14.1-RELEASE</code></pre>
Upgrade jails Upgrade jails
---------- ----------
To upgrade jail to newer release, use gocage upgrade : To upgrade jail to newer release, use gocage upgrade :
<pre><code>gocage upgrade -r 13.2-RELEASE test</code></pre> `gocage upgrade -r 13.2-RELEASE test`
A pre-upgrade snapshot wil be made so you can rollback if needed. A pre-upgrade snapshot wil be made so we can rollback if needed.
Upgrading basejail/release
----------
Upgrading basejails currently needs to be done manually, for each jail.
The idea is to stop the jail, change the content of its fstab file to point to the new release, then start jail.
If one change the fstab while the jail is running, its system directories won't be unmounted at stop time and this will provoke stop errors.
To minimize downtime, the change could be scripted:
<pre><code>gocage stop jail1
sed -i .bak 's/14.0-RELEASE/14.1-RELEASE/' /iocage/jails/jail1/fstab
# Avoid race-condition by waiting for the update in fstab
until grep -q 14.1-RELEASE /iocage/jails/jail1/fstab; do sleep 0.2; done
gocage start jail1
</code></pre>
You can now update ports.
Delete jails Delete jails
---------- ----------
<pre><code>gocage destroy test</code></pre> `gocage destroy test`
Multi datastore Multi datastore
---------- ----------
A datastore is a ZFS dataset mounted. It should be declared in gocage.conf.yml, specifying its ZFS mountpoint : A datastore is a ZFS dataset mounted. It should be declared in gocage.conf.yml, specifying its ZFS mountpoint :
<pre><code>datastore: <pre><code>
datastore:
- /iocage - /iocage
- /fastiocage - /fastiocage
</code></pre> </pre></code>
In gocage commands, datastore name is the mountpoint without its "/" prefix. In gocage commands, datastore name is the mountpoint without its "/" prefix.
### List datastores ### List datastores
<pre><code>gocage datastore list <pre><code>
gocage datastore list
+============+=============+============+===========+==========+============+ +============+=============+============+===========+==========+============+
| Name | Mountpoint | ZFSDataset | Available | Used | Referenced | | Name | Mountpoint | ZFSDataset | Available | Used | Referenced |
+============+=============+============+===========+==========+============+ +============+=============+============+===========+==========+============+
@ -184,21 +180,23 @@ In gocage commands, datastore name is the mountpoint without its "/" prefix.
+------------+-------------+------------+-----------+----------+------------+ +------------+-------------+------------+-----------+----------+------------+
| fastiocage | /fastiocage | ssd/iocage | 1.5 TB | 65.3 KB | 34.6 KB | | fastiocage | /fastiocage | ssd/iocage | 1.5 TB | 65.3 KB | 34.6 KB |
+------------+-------------+------------+-----------+----------+------------+ +------------+-------------+------------+-----------+----------+------------+
</code></pre> </pre></code>
### Filter datastores ### Filter datastores
As with jails and snapshots, you can filter by name: As with jails and snapshots, you can filter by name:
<pre><code>gocage datastore list iocage <pre><code>
gocage datastore list iocage
+============+=============+============+===========+==========+============+ +============+=============+============+===========+==========+============+
| Name | Mountpoint | ZFSDataset | Available | Used | Referenced | | Name | Mountpoint | ZFSDataset | Available | Used | Referenced |
+============+=============+============+===========+==========+============+ +============+=============+============+===========+==========+============+
| iocage | /iocage | hdd/iocage | 1.6 TB | 414.9 GB | 27.5 KB | | iocage | /iocage | hdd/iocage | 1.6 TB | 414.9 GB | 27.5 KB |
+------------+-------------+------------+-----------+----------+------------+ +------------+-------------+------------+-----------+----------+------------+
</code></pre> </pre></code>
### Sort datastores ### Sort datastores
You can sort datastores: You can sort datastores:
<pre><code>gocage datastore list -s -Available <pre><code>
gocage datastore list -s -Available
+============+=============+============+===========+==========+============+ +============+=============+============+===========+==========+============+
| Name | Mountpoint | ZFSDataset | Available | Used | Referenced | | Name | Mountpoint | ZFSDataset | Available | Used | Referenced |
+============+=============+============+===========+==========+============+ +============+=============+============+===========+==========+============+
@ -206,9 +204,9 @@ You can sort datastores:
+------------+-------------+------------+-----------+----------+------------+ +------------+-------------+------------+-----------+----------+------------+
| fastiocage | /fastiocage | ssd/iocage | 1.5 TB | 65.3 KB | 34.6 KB | | fastiocage | /fastiocage | ssd/iocage | 1.5 TB | 65.3 KB | 34.6 KB |
+------------+-------------+------------+-----------+----------+------------+ +------------+-------------+------------+-----------+----------+------------+
</code></pre> </pre></code>
Use `gocage properties`to list available fields. See [cmd/struct.go](https://git.nosd.in/yo/gocage/src/branch/master/cmd/struct.go) for field names.
Migrating jails Migrating jails
---------- ----------
@ -221,23 +219,26 @@ Be aware the moment you migrate a jail to another datastore than /iocage default
Then you need to disable iocage service, and enable gocage so the jails will start automatically at boot. 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. 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 <pre><code>
gocage migrate -d fastiocage srv-random
Snapshot data/iocage/jails/srv-random: Done Snapshot data/iocage/jails/srv-random: Done
Snapshot data/iocage/jails/srv-random/root: Done Snapshot data/iocage/jails/srv-random/root: Done
Migrate jail config dataset to fastdata/iocage/jails/srv-random: Done Migrate jail config dataset to fastdata/iocage/jails/srv-random: Done
Migrate jail filesystem dataset to fastdata/iocage/jails/srv-random/root: Done Migrate jail filesystem dataset to fastdata/iocage/jails/srv-random/root: Done
</code></pre> </pre></code>
Fetch Fetch
---------- ----------
Files can be fetched from custom repository, or from local directory with "from" option. 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: 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 -d iocage -f file:/iocage/download <pre><code>
</code></pre> gocage fetch -r 12.3 -o iocage --from file:/iocage/download
</pre></code>
TODO TODO
---------- ----------
gocage create from templates gocage create from templates
gocage init
create default pool with defaults.json

96
cmd/api.go Normal file
View File

@ -0,0 +1,96 @@
package cmd
import (
//"io"
"fmt"
"strings"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gin-contrib/cors"
)
func printJails(jails []string, fields []string) ([]byte, error) {
var res []byte
res = append(res, []byte("[")...)
// Build a filtered jail list
var filteredJails []Jail
if (len(jails) >= 1 && len(jails[0]) > 0) {
for _, j := range gJails {
if isStringInArray(jails, j.Name) {
filteredJails = append(filteredJails, j)
}
}
} else {
filteredJails = gJails
}
for i, j := range filteredJails {
out, err := j.PrintJSON(fields)
if err != nil {
return res, err
}
res = append(res, out...)
if i < len(filteredJails) - 1 {
res = append(res, []byte(",")...)
}
}
res = append(res, []byte("]")...)
return res, nil
}
func startApi(address string, port int) {
// Initialize gJails.
// FIXME: How to update this at running time without doubling the same jails?
// core will do this for us in add and delete functions
ListJails([]string{""}, false)
r := gin.Default()
// Cross Origin Request Support
config := cors.DefaultConfig()
// DANGER !
config.AllowOrigins = []string{"*"}
r.Use(cors.New(config))
r.GET("/jail/list", func(c *gin.Context) {
jailstr := c.Query("jail")
fields := c.Query("fields")
if len(fields) == 0 {
// TODO : Put this default value in (user?) configuration
fields = "Name,Running,Config.Release,Config.Ip4_addr"
}
jsout, err := printJails(strings.Split(jailstr, ","), strings.Split(fields, ","))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": 1, "error": err.Error()})
} else {
c.Data(http.StatusOK, "application/json", jsout)
}
})
r.POST("/jail/start/:jail", func(c *gin.Context) {
jailstr := c.Params.ByName("jail")
// TODO : Capture or redirect output so we can know if jail was started or not
StartJail(strings.Split(jailstr, ","))
c.JSON(http.StatusOK, gin.H{"status": 0, "error": ""})
})
r.POST("/jail/stop/:jail", func(c *gin.Context) {
jailstr := c.Params.ByName("jail")
// TODO : Capture or redirect output so we can know if jail was started or not
StopJail(strings.Split(jailstr, ","))
c.JSON(http.StatusOK, gin.H{"status": 0, "error": ""})
})
r.GET("/jail/properties", func(c *gin.Context) {
props, err := ListJailsProps("json")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": 1, "error": err.Error()})
} else {
c.Data(http.StatusOK, "application/json", []byte(props))
}
})
r.Run(fmt.Sprintf("%s:%d", address, port))
}

View File

@ -44,7 +44,7 @@ func CreateJail(args []string) {
var ds *Datastore var ds *Datastore
if len(gCreateArgs.Datastore) > 0 { if len(gCreateArgs.Datastore) > 0 {
log.Debugf("Use %s datastore\n", gCreateArgs.Datastore) fmt.Printf("DEBUG: Use %s datastore\n", gCreateArgs.Datastore)
ds, err = getDatastoreFromArray(gCreateArgs.Datastore, gDatastores) ds, err = getDatastoreFromArray(gCreateArgs.Datastore, gDatastores)
if err != nil { if err != nil {
fmt.Printf("ERROR Getting datastore: %s\n", gCreateArgs.Datastore, err.Error()) fmt.Printf("ERROR Getting datastore: %s\n", gCreateArgs.Datastore, err.Error())
@ -272,6 +272,7 @@ func CreateJail(args []string) {
j.Config.Host_hostname = jname j.Config.Host_hostname = jname
j.Config.Host_hostuuid = jname j.Config.Host_hostuuid = jname
j.Config.Jailtype = "jail" j.Config.Jailtype = "jail"
j.WriteConfigToDisk(false) j.WriteConfigToDisk(false)
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
@ -284,17 +285,5 @@ func CreateJail(args []string) {
defer fstabHandle.Close() defer fstabHandle.Close()
fmt.Printf(" > Jail created!\n") fmt.Printf(" > Jail created!\n")
} }
var cmdline []string
for _, props := range strings.Split(gCreateArgs.Properties, ",") {
cmdline = append(cmdline, props)
}
// Reload jail list so SetJailProperties will see it
ListJails(nil, false)
cmdline = append(cmdline, jname)
log.Debugf("cmdline: \"%v\"", cmdline)
SetJailProperties(cmdline)
} }
} }

View File

@ -1,153 +0,0 @@
package cmd
import (
"os"
"fmt"
"strings"
"github.com/spf13/viper"
log "github.com/sirupsen/logrus"
)
/********************************************************************************
* Initialize datastore(s) /iocage, /iocage/jails
* Put defaults.json,
* Update it with hostid, interfaces, and maybe other necessary fields
* Initialize bridge
*******************************************************************************/
func InitGoCage(args []string) {
// Create datastores
for _, dstore := range viper.GetStringSlice("datastore") {
log.Debugf("Ranging over %v\n", dstore)
dset, err := zfsGetDatasetByMountpoint(dstore)
if err != nil && strings.HasSuffix(err.Error(), "No such file or directory\"") {
if len(gZPool) == 0 {
log.Errorf("Datastore mountpoint \"%s\" does not exist. Specify a pool if you want to create it.", dstore)
return
}
// Create dataset /iocage
rootDSName := fmt.Sprintf("%s%s", gZPool, dstore)
log.Debugf("Creating dataset %s mounted on %s\n", rootDSName, dstore)
if err = zfsCreateDataset(rootDSName, dstore, ""); err != nil {
log.Errorf("Error creating dataset %s: %v\n", rootDSName, err)
return
}
// Create /iocage/jail, releases, templates
for _, l := range []string{"jails","releases","templates"} {
cds := fmt.Sprintf("%s/%s", rootDSName, l)
cmp := fmt.Sprintf("%s/%s", dstore, l)
log.Debugf("Creating dataset %s mounted on %s\n", cds, cmp)
if err = zfsCreateDataset(cds, cmp, ""); err != nil {
log.Errorf("Error creating dataset %s: %v\n", cds, err)
return
}
}
// Create /iocage/defaults.json
exists, err := doFileExist(fmt.Sprintf("%s/defaults.json", dstore))
if err != nil {
log.Errorf("Error checking defaults.json: %v\n", err)
return
}
if !exists {
if err = createDefaultsJson(dstore, gBridge); err != nil {
log.Errorf("%v\n", err)
}
}
} else if err != nil {
log.Errorf("Error checking datastore existence: %v\n", err)
return
} else {
log.Debugf("Datastore dataset exist: %s\n", dset)
}
}
// Check and create bridge
// FIXME: What if bridge name is invalid, as we already wrote it in defaults.json in dstore loop?
if len(gBridge) > 0 && len(gInterface) > 0 {
if err := initBridge(); err != nil {
log.Errorf("%v\n", err)
}
}
}
func createDefaultsJson(rootDirectory string, bridge string) error {
hostid, err := os.ReadFile("/etc/hostid")
if err != nil {
log.Fatalf("Unable to read /etc/hostid: %v\n", err)
}
json := strings.Replace(gDefaultsJson, "TO-BE-REPLACED-WITH-HOSTID", strings.Trim(string(hostid), "\n"), 1)
json = strings.Replace(json, "TO-BE-REPLACED-WITH-BRIDGE", bridge, 1)
if err := os.WriteFile(fmt.Sprintf("%s/defaults.json", rootDirectory), []byte(json), 0640); err != nil {
log.Fatal(err)
}
return nil
}
func createInterface(iface string) error {
log.Debugf("creating interface \"%s\"\n", iface)
cmd := fmt.Sprintf("/sbin/ifconfig %s create", iface)
_, err := executeCommand(cmd)
if err != nil {
return err
}
return nil
}
func bringUpInterface(iface string) error {
log.Debugf("bringing up interface \"%s\"\n", iface)
cmd := fmt.Sprintf("/sbin/ifconfig %s up", iface)
_, err := executeCommand(cmd)
if err != nil {
return err
}
return nil
}
func addMemberToBridge(bridge string, iface string) error {
log.Debugf("adding member interface \"%s\" to bridge \"%s\"\n", iface, bridge)
cmd := fmt.Sprintf("/sbin/ifconfig %s addm %s", bridge, iface)
_, err := executeCommand(cmd)
if err != nil {
return err
}
return nil
}
func initBridge() error {
hostInt, err := gJailHost.GetInterfaces()
if err != nil {
return fmt.Errorf("Error listing interfaces: %v\n", err)
}
if !isStringInArray(hostInt, gInterface) {
return fmt.Errorf("Interface not found: %s\n", gInterface)
}
if !isStringInArray(hostInt, gBridge) {
if err := createInterface(gBridge); err != nil {
return fmt.Errorf("Error creating bridge: %v\n", err)
}
if err := bringUpInterface(gBridge); err != nil {
return fmt.Errorf("Error bringing up bridge: %v\n", err)
}
log.Infof("bridge was created, but it won't persist reboot. Configure rc.conf to persist. See https://docs.freebsd.org/en/books/handbook/advanced-networking/#network-bridging\n")
log.Infof("It is strongly suggested you move interface %s IP to bridge %s\n", gInterface, gBridge)
}
// FIXME: Need to check if not already member
members, err := getBridgeMembers(gBridge)
if err != nil {
return fmt.Errorf("Error getting bridge members: %v\n", err)
}
// Return if interface already member of the bridge
for _, m := range members {
log.Debugf("Bridge member: %s\n", m)
if strings.EqualFold(m, gInterface) {
return nil
}
}
if err := addMemberToBridge(gBridge, gInterface); err != nil {
return fmt.Errorf("Error adding interface to bridge: %v\n", err)
}
return nil
}

View File

@ -15,23 +15,37 @@ import (
* List all properties a jail have, with their internal name * List all properties a jail have, with their internal name
* Only print properties name. To get name & values, use GetJailProperties() * Only print properties name. To get name & values, use GetJailProperties()
*******************************************************************************/ *******************************************************************************/
func ListJailsProps(args []string) { func ListJailsProps(format string) (string, error) {
var conf Jail var conf Jail
var result []string var props []string
var out string
// Mandatory constructor to init default values // Mandatory constructor to init default values
jailconf, err := NewJailConfig() jailconf, err := NewJailConfig()
if err != nil { if err != nil {
fmt.Printf("Error allocating JailConfig: %s\n", err.Error()) fmt.Printf("Error allocating JailConfig: %s\n", err.Error())
return return out, err
} }
conf.Config = jailconf conf.Config = jailconf
result = getStructFieldNames(conf, result, "") props = getStructFieldNames(conf, props, "")
for _, f := range result { if format == "json" {
fmt.Printf("%s\n", f) out = "{\"properties\":["
for i, p := range props {
out += fmt.Sprintf("\"%s\"", p)
if i < len(props) - 1 {
out += ","
}
}
out += "]}"
} else {
for _, p := range props {
out += fmt.Sprintf("%s\n", p)
}
} }
return out, nil
} }
/******************************************************************************** /********************************************************************************

View File

@ -14,7 +14,7 @@ import (
) )
const ( const (
gVersion = "0.42d" gVersion = "0.42b"
// TODO : Get from $jail_zpool/defaults.json // TODO : Get from $jail_zpool/defaults.json
MIN_DYN_DEVFS_RULESET = 1000 MIN_DYN_DEVFS_RULESET = 1000
@ -25,10 +25,12 @@ type createArgs struct {
BaseJail bool BaseJail bool
Datastore string Datastore string
JailType string JailType string
Properties string
} }
var ( var (
gApiAddress string
gApiPort int
gJailHost JailHost gJailHost JailHost
gJails []Jail gJails []Jail
gDatastores []Datastore gDatastores []Datastore
@ -68,8 +70,6 @@ var (
gFetchIntoDS string gFetchIntoDS string
gFetchFrom string gFetchFrom string
gUpgradeRelease string gUpgradeRelease string
gUpdateRelease string
gUpdateReleaseDS string
// For a based jail, these are directories binded to basejail // For a based jail, these are directories binded to basejail
gBaseDirs = []string{"bin", "boot", "lib", "libexec", "rescue", "sbin", "usr/bin", "usr/include", gBaseDirs = []string{"bin", "boot", "lib", "libexec", "rescue", "sbin", "usr/bin", "usr/include",
@ -127,7 +127,12 @@ ex: gocage list srv-db srv-web`,
Short: "Print jails properties", Short: "Print jails properties",
Long: "Display jails properties. You can use properties to filter, get or set them.", Long: "Display jails properties. You can use properties to filter, get or set them.",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ListJailsProps(args) out, err := ListJailsProps("text")
if err != nil {
fmt.Printf("Error listing properties : %v\n", err)
} else {
fmt.Printf("%s\n", out)
}
}, },
} }
@ -357,6 +362,14 @@ You can specify multiple datastores.`,
}, },
} }
apiCmd = &cobra.Command{
Use: "startapi",
Short: "Run docage as a daemon, exposing API",
Run: func(cmd *cobra.Command, args []string) {
startApi(gApiAddress, gApiPort)
},
}
testCmd = &cobra.Command{ testCmd = &cobra.Command{
Use: "test", Use: "test",
Short: "temporary command to test some code snippet", Short: "temporary command to test some code snippet",
@ -382,7 +395,6 @@ func init() {
initCmd.Flags().StringVarP(&gZPool, "pool", "p", "", "ZFS pool to create datastore on") initCmd.Flags().StringVarP(&gZPool, "pool", "p", "", "ZFS pool to create datastore on")
initCmd.Flags().StringVarP(&gBridge, "bridge", "b", "", "bridge to create for jails networking") initCmd.Flags().StringVarP(&gBridge, "bridge", "b", "", "bridge to create for jails networking")
initCmd.Flags().StringVarP(&gInterface, "interface", "i", "", "interface to add as bridge member. This should be your main interface") initCmd.Flags().StringVarP(&gInterface, "interface", "i", "", "interface to add as bridge member. This should be your main interface")
initCmd.MarkFlagRequired("bridge")
initCmd.MarkFlagsRequiredTogether("bridge", "interface") initCmd.MarkFlagsRequiredTogether("bridge", "interface")
// We reuse these flags in "gocage snapshot list myjail" and 'gocage datastore list" commands // We reuse these flags in "gocage snapshot list myjail" and 'gocage datastore list" commands
@ -416,28 +428,26 @@ func init() {
migrateCmd.MarkFlagRequired("datastore") migrateCmd.MarkFlagRequired("datastore")
fetchCmd.Flags().StringVarP(&gFetchRelease, "release", "r", "", "Release to fetch (e.g.: \"13.1-RELEASE\"") fetchCmd.Flags().StringVarP(&gFetchRelease, "release", "r", "", "Release to fetch (e.g.: \"13.1-RELEASE\"")
fetchCmd.Flags().StringVarP(&gFetchIntoDS, "datastore", "d", "", "Datastore release will be saved to") fetchCmd.Flags().StringVarP(&gFetchIntoDS, "datastore", "o", "", "Datastore release will be saved to")
fetchCmd.Flags().StringVarP(&gFetchFrom, "from", "f", "", "Repository to download from. Should contain XY.Z-RELEASE. File protocol supported") fetchCmd.Flags().StringVarP(&gFetchFrom, "from", "d", "", "Repository to download from. Should contain XY.Z-RELEASE. File protocol supported")
fetchCmd.MarkFlagRequired("release") fetchCmd.MarkFlagRequired("release")
fetchCmd.MarkFlagRequired("datastore") fetchCmd.MarkFlagRequired("datastore")
upgradeCmd.Flags().StringVarP(&gUpgradeRelease, "release", "r", "", "Release to upgrade to (e.g.: \"13.1-RELEASE\"") upgradeCmd.Flags().StringVarP(&gUpgradeRelease, "release", "r", "", "Release to upgrade to (e.g.: \"13.1-RELEASE\"")
upgradeCmd.MarkFlagRequired("release") upgradeCmd.MarkFlagRequired("release")
updateCmd.Flags().StringVarP(&gUpdateRelease, "release", "r", "", "Release to update (e.g.: \"13.1-RELEASE\"")
updateCmd.Flags().StringVarP(&gUpdateReleaseDS, "datastore", "d", "", "Datastore release is stored on")
updateCmd.MarkFlagsRequiredTogether("release", "datastore")
createCmd.Flags().StringVarP(&gCreateArgs.Release, "release", "r", "", "Release for the jail (e.g.: \"13.1-RELEASE\"") createCmd.Flags().StringVarP(&gCreateArgs.Release, "release", "r", "", "Release for the jail (e.g.: \"13.1-RELEASE\"")
createCmd.Flags().BoolVarP(&gCreateArgs.BaseJail, "basejail", "b", false, "Basejail. This will create a jail mounted read only from a release, so every up(date|grade) made to this release will immediately propagate to new jail.\n") createCmd.Flags().BoolVarP(&gCreateArgs.BaseJail, "basejail", "b", false, "Basejail. This will create a jail mounted read only from a release, so every up(date|grade) made to this release will immediately propagate to new jail.\n")
createCmd.Flags().StringVarP(&gCreateArgs.Datastore, "datastore", "d", "", "Datastore to create the jail on. Defaults to first declared in config.") createCmd.Flags().StringVarP(&gCreateArgs.Datastore, "datastore", "d", "", "Datastore to create the jail on. Defaults to first declared in config.")
createCmd.Flags().StringVarP(&gCreateArgs.Properties, "configuration", "p", "", "Configuration properties with format k1=v1,k2=v2 (Ex: \"Config.Ip4_addr=vnet0|192.168.1.2,Config.Ip6=none\")")
apiCmd.Flags().StringVarP(&gApiAddress, "listen-addr", "l", "127.0.0.1", "API listening address")
apiCmd.Flags().IntVarP(&gApiPort, "listen-port", "p", 1234, "API listening port")
// Now declare commands // Now declare commands
rootCmd.AddCommand(initCmd) rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(listCmd) rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(listPropsCmd) listCmd.AddCommand(listPropsCmd)
rootCmd.AddCommand(stopCmd) rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(startCmd) rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(restartCmd) rootCmd.AddCommand(restartCmd)
@ -452,6 +462,7 @@ func init() {
rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(updateCmd)
rootCmd.AddCommand(upgradeCmd) rootCmd.AddCommand(upgradeCmd)
rootCmd.AddCommand(createCmd) rootCmd.AddCommand(createCmd)
rootCmd.AddCommand(apiCmd)
rootCmd.AddCommand(testCmd) rootCmd.AddCommand(testCmd)
snapshotCmd.AddCommand(snapshotListCmd) snapshotCmd.AddCommand(snapshotListCmd)

View File

@ -1421,6 +1421,25 @@ func StartJail(args []string) {
} }
} }
// Get a reference to current jail, to update its properties in RAM so API will get fresh data
for i, j := range gJails {
if strings.EqualFold(j.Name, cj.Name) && strings.EqualFold(j.Datastore, cj.Datastore) {
if err = setStructFieldValue(&gJails[i], "Running", "true"); err != nil {
fmt.Printf("ERROR: setting Running property to true: %s\n", err.Error())
}
if err = setStructFieldValue(&gJails[i], "JID", fmt.Sprintf("%d", cj.JID)); err != nil {
fmt.Printf("ERROR: setting JID property to %d: %s\n", cj.JID, err.Error())
}
if err = setStructFieldValue(&gJails[i], "InternalName", cj.InternalName); err != nil {
fmt.Printf("ERROR: Setting InternalName property: %s\n", err.Error())
}
// FIXME: this value of devfs_ruleset should not go in Config, it should reside in Jail root properties as it is volatile (only valid while jail is running)
/*if err = setStructFieldValue(&gJails[i], "Devfs_ruleset", "0"); err != nil {
fmt.Printf("ERROR: setting Devfs_ruleset property to 0: %s\n", err.Error())
}*/
}
}
hostInt, err := gJailHost.GetInterfaces() hostInt, err := gJailHost.GetInterfaces()
if err != nil { if err != nil {
fmt.Printf("Error listing jail host interfaces: %v\n", err) fmt.Printf("Error listing jail host interfaces: %v\n", err)

View File

@ -7,8 +7,8 @@ import (
"sync" "sync"
"errors" "errors"
"regexp" "regexp"
"slices"
"os/exec" "os/exec"
//"reflect"
"strconv" "strconv"
"strings" "strings"
@ -83,26 +83,39 @@ func umountAndUnjailZFS(jail *Jail) error {
} }
func destroyVNetInterfaces(jail *Jail) error { func destroyVNetInterfaces(jail *Jail) error {
if !strings.EqualFold(jail.Config.Ip4_addr, "none") { // Wherever ipv6/ipv4 is enabled, if interface exist we destroy it
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") { var vnetnames []string
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") {
if len(strings.Split(i, "|")) == 2 {
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID) iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
fmt.Printf("%s: ", iname) if !slices.Contains(vnetnames, iname) {
_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname)) vnetnames = append(vnetnames, iname)
//_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", iname))
if err != nil {
return err
} else {
fmt.Printf("OK\n")
} }
} }
} }
if !strings.EqualFold(jail.Config.Ip6_addr, "none") { for _, i := range strings.Split(jail.Config.Ip6_addr, ",") {
for _, i := range strings.Split(jail.Config.Ip6_addr, ",") { if len(strings.Split(i, "|")) == 2 {
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID) iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
fmt.Printf("%s: ", iname) if !slices.Contains(vnetnames, iname) {
vnetnames = append(vnetnames, iname)
}
}
}
for _, iname := range vnetnames {
fmt.Printf(" >%s: ", iname)
_, err := executeCommand(fmt.Sprintf("ifconfig %s", iname))
if err != nil {
if strings.Contains(err.Error(), "does not exist") {
fmt.Printf("OK\n")
} else {
fmt.Printf("ERR: %v\n", err)
return err
}
} else {
_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname)) _, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname))
//_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", iname))
if err != nil { if err != nil {
fmt.Printf("ERR: %v\n", err)
return err return err
} else { } else {
fmt.Printf("OK\n") fmt.Printf("OK\n")

View File

@ -1,7 +1,10 @@
package cmd package cmd
import ( import (
"fmt"
"sort"
"time" "time"
"strings"
) )
const ( const (
@ -561,3 +564,90 @@ type DatastoreSort struct {
AvailableDec datastoreLessFunc AvailableDec datastoreLessFunc
} }
type Field struct {
Name string
MaxLen int
Value string
}
func (j *Jail) PrintJSON(fields []string) ([]byte, error) {
return j.PrintJSONWithUpper("", fields)
}
func (j *Jail) PrintJSONWithUpper(upper string, fields []string) ([]byte, error) {
var res []byte
if len(fields) < 1 {
return res, nil
}
if len(upper) > 0 {
res = append(res, []byte(fmt.Sprintf("\"%s\": {", upper))...)
} else {
res = append(res, []byte("{")...)
}
// Reorder fields so we got fields of a sub-structure adjacents
sort.Strings(fields)
var ffname string
offset := 0
for i, _ := range fields {
if i + offset >= len(fields) {
break
}
// Is this struct in struct?
if strings.Contains(fields[i+offset], ".") {
// Count items in this struct
var subfields []string
var newUpper string
for _, sf := range fields[i+offset:] {
if strings.Split(sf, ".")[0] == strings.Split(fields[i+offset], ".")[0] {
newUpper = strings.Split(sf, ".")[0]
sub := strings.Join(strings.Split(string(sf), ".")[1:], ".")
subfields = append(subfields, sub)
}
}
out, err := j.PrintJSONWithUpper(newUpper, subfields)
if err != nil {
return []byte{}, err
}
res = append(res, out...)
offset += len(subfields)-1
if i + offset < len(fields) - 1 {
res = append(res, []byte(",")...)
}
continue
}
if len(upper) > 0 {
ffname = fmt.Sprintf("%s.%s", upper, fields[i+offset])
} else {
ffname = fields[i+offset]
}
val, _, err := getStructFieldValue(j, ffname)
if err != nil {
return []byte{}, fmt.Errorf("Error accessing field \"%s\" : %v\n", fields[i+offset], err)
}
res = append(res, []byte(fmt.Sprintf("\"%s\": ", fields[i+offset]))...)
switch val.Interface().(type) {
case string:
res = append(res, []byte(fmt.Sprintf("\"%s\"", val.Interface().(string)))...)
case int:
res = append(res, []byte(fmt.Sprintf("%d", val.Interface().(int)))...)
case bool:
res = append(res, []byte(fmt.Sprintf("%t", val.Interface().(bool)))...)
default:
fmt.Printf("ERREUR dans Jail.PrintJSONWithUpper() : Type inconnu : %T\n", val.Interface())
}
if i + offset < len(fields) - 1 {
res = append(res, []byte(",")...)
}
}
res = append(res, []byte("}")...)
return res, nil
}

View File

@ -6,36 +6,33 @@ import (
//"log" //"log"
"time" "time"
"strings" "strings"
"github.com/spf13/viper"
) )
// Internal usage only // Internal usage only
func updateJail(jail *Jail, doUpdateVersion bool) error { func updateJail(jail *Jail) error {
// Create default config as temporary file // Create default config as temporary file
cfgFile, err := os.CreateTemp("", "gocage-jail-update-") cfgFile, err := os.CreateTemp("", "gocage-jail-update-")
if err != nil { if err != nil {
return err return err
} }
// Folder containing update/upgrade temporary files. Mutualized so we save bandwith when upgrading multiple jails cfgFile.Write([]byte(fbsdUpdateConfig))
uwd := viper.GetString("updateWorkDir")
if len(uwd) == 0 {
return fmt.Errorf("updateWorkDir not set in configuration")
}
_, err = os.Stat(uwd)
if os.IsNotExist(err) {
if err := os.Mkdir(uwd, 0755); err != nil {
return err
}
}
cfgFile.Write([]byte(strings.Replace(fbsdUpdateConfig, "TO-BE-REPLACED-WITH-UPDATEWORKDIR", uwd, 1)))
defer cfgFile.Close() defer cfgFile.Close()
defer os.Remove(cfgFile.Name()) defer os.Remove(cfgFile.Name())
// Folder containing update/upgrade temporary files. Common so we save bandwith when upgrading multiple jails
// TODO: Variabilize /iocage/freebsd-update
_, err = os.Stat("/iocage/freebsd-update")
if os.IsNotExist(err) {
if err := os.Mkdir("/iocage/freebsd-update", 0755); err != nil {
return err
}
}
cmd := fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s fetch", cmd := fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s fetch",
cfgFile.Name(), jail.RootPath, jail.Config.Release) cfgFile.Name(), jail.RootPath, jail.Config.Release)
err = executeCommandWithOutputToStdout(cmd) err = executeCommandWithOutputToStdout(cmd)
if err != nil { if err != nil {
return err return err
@ -48,10 +45,8 @@ func updateJail(jail *Jail, doUpdateVersion bool) error {
return err return err
} }
// Get and write new release into config.json. Don't do that for fake jail (aka release updating) // Get and write new release into config.json
if doUpdateVersion { updateVersion(jail)
updateVersion(jail)
}
return nil return nil
} }
@ -61,45 +56,6 @@ func UpdateJail(args []string) {
var cj *Jail var cj *Jail
var err error var err error
// User is updateing a release, fake a jail
if len(gUpdateRelease) > 0 {
// get datastore mountpoing from datastore name
ds, err := getDatastoreFromArray(gUpdateReleaseDS, gDatastores)
if err != nil {
fmt.Printf("Error getting datastore %s: %v\n", gUpdateReleaseDS, err)
return
}
rp := fmt.Sprintf("%s/releases/%s/root", ds.Mountpoint, gUpdateRelease)
fakeJail := Jail{RootPath: rp}
v, err := getVersion(&fakeJail)
if err != nil {
fmt.Printf("Error getting version of release %s: %v\n", gUpdateRelease, err)
return
}
fakeJail.Config.Release = v
// Snapshot before updating
dt := time.Now()
curDate := fmt.Sprintf("%s", dt.Format("2006-01-02_15-04-05"))
snapshotName := fmt.Sprintf("gocage_update_%s_%s", v, curDate)
err = zfsSnapshot(fmt.Sprintf("%s/releases/%s", ds.ZFSDataset, fakeJail.Config.Release), snapshotName)
if err != nil {
fmt.Printf("Error snapshoting release %s: %v\n", gUpdateRelease, err)
return
}
err = zfsSnapshot(fmt.Sprintf("%s/releases/%s/root", ds.ZFSDataset, fakeJail.Config.Release), snapshotName)
if err != nil {
fmt.Printf("Error snapshoting release %s: %v\n", gUpdateRelease, err)
} else {
fmt.Printf("Release %s was snapshoted with success: %s\n", gUpdateRelease, snapshotName)
}
if err = updateJail(&fakeJail, false); err != nil {
fmt.Printf("Error updating release %s: %v\n", gUpdateRelease, err)
}
return
}
for _, a := range args { for _, a := range args {
// Check if jail exist and is distinctly named // Check if jail exist and is distinctly named
cj, err = getJailFromArray(a, []string{""}, gJails) cj, err = getJailFromArray(a, []string{""}, gJails)
@ -118,7 +74,7 @@ func UpdateJail(args []string) {
// Set snapshot name // Set snapshot name
dt := time.Now() dt := time.Now()
curDate := fmt.Sprintf("%s", dt.Format("2006-01-02_15-04-05")) curDate := fmt.Sprintf("%s", dt.Format("2006-01-02_15-04-05"))
gSnapshotName = fmt.Sprintf("gocage_update_%s_%s", cj.Config.Release, curDate) gSnapshotName = fmt.Sprintf("goc_update_%s_%s", cj.Config.Release, curDate)
err := createJailSnapshot(*cj) err := createJailSnapshot(*cj)
if err != nil { if err != nil {
fmt.Printf(" > Snapshot jail %s: ERROR: %s\n", cj.Name, err.Error()) fmt.Printf(" > Snapshot jail %s: ERROR: %s\n", cj.Name, err.Error())
@ -127,7 +83,7 @@ func UpdateJail(args []string) {
fmt.Printf(" > Snapshot jail %s: OK\n", cj.Name) fmt.Printf(" > Snapshot jail %s: OK\n", cj.Name)
fmt.Printf(" > Update jail %s\n", cj.Name) fmt.Printf(" > Update jail %s\n", cj.Name)
err = updateJail(cj, true) err = updateJail(cj)
if err != nil { if err != nil {
fmt.Printf("ERROR: %s\n", err.Error()) fmt.Printf("ERROR: %s\n", err.Error())
} else { } else {

View File

@ -68,7 +68,7 @@ MergeChanges /etc/
# Directory in which to store downloaded updates and temporary # Directory in which to store downloaded updates and temporary
# files used by FreeBSD Update. # files used by FreeBSD Update.
WorkDir TO-BE-REPLACED-WITH-UPDATEWORKDIR WorkDir /iocage/freebsd-update
# Destination to send output of "freebsd-update cron" if an error # Destination to send output of "freebsd-update cron" if an error
# occurs or updates have been downloaded. # occurs or updates have been downloaded.
@ -228,7 +228,7 @@ CreateBootEnv no
"type": "jail", "type": "jail",
"used": "readonly", "used": "readonly",
"vmemoryuse": "off", "vmemoryuse": "off",
"vnet": 1, "vnet": 0,
"vnet0_mac": "none", "vnet0_mac": "none",
"vnet1_mac": "none", "vnet1_mac": "none",
"vnet2_mac": "none", "vnet2_mac": "none",
@ -709,10 +709,8 @@ func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
// else // else
word = word + string(c) word = word + string(c)
} }
if gDebug { log.Debugf("executeCommandInJail: will execute \"%s\"\n", strings.Join(cmd, " "))
fmt.Printf("DEBUG: executeCommandInJail: prepare to execute \"%s\"\n", cmd)
}
out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput() out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
@ -751,30 +749,6 @@ func executeScript(script string) (string, error) {
return string(out), err return string(out), err
} }
/*****************************************************************************
*
* Network related operations
*
*****************************************************************************/
func getBridgeMembers(bridge string) ([]string, error) {
var members []string
cmd := fmt.Sprintf("/sbin/ifconfig %s", bridge)
out, err := executeCommand(cmd)
if err != nil {
return members, errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
}
for _, line := range strings.Split(out, "\n") {
if strings.HasPrefix(strings.TrimLeft(line, " \t"), "member:") {
m := strings.Split(strings.TrimLeft(strings.Split(line, ":")[1], " "), " ")[0]
log.Debugf("%s is member of %s\n", m, bridge)
members = append(members, m)
}
}
return members, nil
}
/***************************************************************************** /*****************************************************************************
* *
* ZFS datasets/pools operations * ZFS datasets/pools operations
@ -1057,7 +1031,6 @@ func addRcKeyValue(rcconfpath string, key string, value string) error {
} }
return nil return nil
} }
/***************************************************************************** /*****************************************************************************
* Parse an fstab file, returning an array of Mount * Parse an fstab file, returning an array of Mount
*****************************************************************************/ *****************************************************************************/

4
go.mod
View File

@ -1,10 +1,12 @@
module gocage module gocage
go 1.17 go 1.21
require ( require (
github.com/c-robinson/iplib v1.0.3 github.com/c-robinson/iplib v1.0.3
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/otiai10/copy v1.12.0 github.com/otiai10/copy v1.12.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1

View File

@ -5,9 +5,6 @@ datastore:
# Prefix all commands with sudo # Prefix all commands with sudo
sudo: false sudo: false
# Directory used to store update temporary files. Mutualized so we save bandwith
updateWorkDir: /iocage/freebsd-updates
# Columns to display when "gocage list". Column names are struct fields, see cmd/struct.go # Columns to display when "gocage list". Column names are struct fields, see cmd/struct.go
outcol: 'JID,Name,Config.Release,Config.Ip4_addr,Running' outcol: 'JID,Name,Config.Release,Config.Ip4_addr,Running'