14 Commits

8 changed files with 332 additions and 56 deletions

4
CHANGELOG Normal file
View File

@ -0,0 +1,4 @@
v.0.33b : Support jailing datasets on differents pools : jail_zfs_dataset now have to include the pool name
v.0.33c : Parallelize start/stop of jails with same priority
v.0.34 : jail name can be shortened
v.0.35 : One can now "gocage destroy"

View File

@ -6,7 +6,13 @@ 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.
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
----------
@ -115,11 +121,22 @@ gocage list -o JID,Name,Config.Ip4_addr,Config.Priority,Config.Boot,Running -s -
</pre></code>
Stop jails
Stop jails
----------
`gocage stop test`
Update jails
----------
To update jail patch version, use gocage update :
`gocage update test`
Delete jails
----------
`gocage destroy test`
Multi datastore
----------
A datastore is a ZFS dataset mounted. It should be declared in gocage.conf.yml, specifying its ZFS mountpoint :
@ -200,9 +217,7 @@ gocage fetch -r 12.3 -o iocage --from file:/iocage/download
TODO
----------
gocage update
gocage upgrade
gocage create
gocage destroy
gocage init
create default pool with defaults.json

57
cmd/destroy.go Normal file
View File

@ -0,0 +1,57 @@
package cmd
import (
"fmt"
//"log"
//"errors"
"strings"
)
func DestroyJails(args []string) {
for _, a := range args {
cj, err := getJailFromArray(a, gJails)
if err != nil {
fmt.Printf("Error getting jail: %s\n", err)
return
}
if cj.Running == true {
fmt.Printf("Jail %s is running\n", cj.Name)
if gForce == false {
var answer string
fmt.Printf("Stop jail and delete? (y/n) ")
fmt.Scanln(&answer)
if false == strings.EqualFold(answer, "y") {
return
}
}
fmt.Printf("Stopping jail %s\n", cj.Name)
StopJail([]string{fmt.Sprintf("%s/%s", cj.Datastore, cj.Name)})
}
// Get root and config datasets, then destroy
dsRootName, err := zfsGetDatasetByMountpoint(cj.RootPath)
if err != nil {
fmt.Printf("Error getting root dataset: %s\n", err)
return
}
fmt.Printf("DEBUG: Prepare to zfs destroy %s\n", dsRootName)
if err = zfsDestroy(dsRootName); err != nil {
fmt.Printf("Error deleting root dataset: %s\n", err)
return
}
dsConfName, err := zfsGetDatasetByMountpoint(cj.ConfigPath)
if err != nil {
fmt.Printf("Error getting config dataset: %s\n", err)
return
}
fmt.Printf("DEBUG: Prepare to zfs destroy %s\n", dsConfName)
if err = zfsDestroy(dsConfName); err != nil {
fmt.Printf("Error deleting config dataset: %s\n", err)
return
}
//TODO: Delete jail named directory
}
}

View File

@ -61,7 +61,7 @@ func fetchRelease(release string, proto string, arch string, datastore string, f
}
if false == exist {
// Then create dataset
if err := createZfsDataset(downloadDsName, downloadDsMountPoint, "lz4"); err != nil {
if err := zfsCreateDataset(downloadDsName, downloadDsMountPoint, "lz4"); err != nil {
return fmt.Errorf("Error creating dataset %s: %v\n", downloadDsName, err)
}
}
@ -75,7 +75,7 @@ func fetchRelease(release string, proto string, arch string, datastore string, f
}
if false == exist {
// Then create dataset
if err := createZfsDataset(thisDownloadDsName, thisDownloadDsMountPoint, "lz4"); err != nil {
if err := zfsCreateDataset(thisDownloadDsName, thisDownloadDsMountPoint, "lz4"); err != nil {
return fmt.Errorf("Error creating dataset %s: %v\n", thisDownloadDsName, err)
}
}
@ -147,7 +147,7 @@ func extractRelease(release string, datastore string) {
}
if false == exist {
// Then create dataset
if err := createZfsDataset(releaseDsName, releaseDsMountPoint, "lz4"); err != nil {
if err := zfsCreateDataset(releaseDsName, releaseDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", releaseDsName, err)
return
}
@ -163,7 +163,7 @@ func extractRelease(release string, datastore string) {
}
if false == exist {
// Then create dataset
if err := createZfsDataset(thisReleaseDsName, thisReleaseDsMountPoint, "lz4"); err != nil {
if err := zfsCreateDataset(thisReleaseDsName, thisReleaseDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", thisReleaseDsName, err)
return
}
@ -179,7 +179,7 @@ func extractRelease(release string, datastore string) {
}
if false == exist {
// Then create dataset
if err := createZfsDataset(thisReleaseRootDsName, thisReleaseRootDsMountPoint, "lz4"); err != nil {
if err := zfsCreateDataset(thisReleaseRootDsName, thisReleaseRootDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", thisReleaseRootDsName, err)
return
}

View File

@ -1,11 +1,12 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"fmt"
"sync"
"strings"
"io/ioutil"
"encoding/json"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -14,7 +15,7 @@ import (
)
const (
gVersion = "0.33a"
gVersion = "0.35"
// TODO : Get from $jail_zpool/defaults.json
MIN_DYN_DEVFS_RULESET = 1000
@ -55,6 +56,7 @@ var (
gFetchIntoDS string
gFetchFrom string
gMdevfs sync.Mutex
rootCmd = &cobra.Command{
Use: "gocage",
@ -97,8 +99,8 @@ ex: gocage list srv-db srv-web`,
ListJailsProps(args)
},
}
/* destroyCmd = &cobra.Command{
destroyCmd = &cobra.Command{
Use: "destroy",
Short: "destroy jails",
Long: `Destroy jail filesystem, snapshots and configuration file.`,
@ -107,7 +109,7 @@ ex: gocage list srv-db srv-web`,
DestroyJails(args)
},
}
*/
stopCmd = &cobra.Command{
Use: "stop",
Short: "stop jail",
@ -341,7 +343,7 @@ func init() {
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")
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")
@ -379,7 +381,7 @@ func init() {
rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(restartCmd)
// rootCmd.AddCommand(destroyCmd)
rootCmd.AddCommand(destroyCmd)
rootCmd.AddCommand(shellCmd)
rootCmd.AddCommand(getCmd)
rootCmd.AddCommand(setCmd)
@ -518,10 +520,8 @@ func WriteConfigToDisk(jailName string, changeauto bool, forceWrite bool) {
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
}
fmt.Printf("DEBUG: Will write config to disk, with content:\n")
fmt.Printf(string(marshaled))
//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)

View File

@ -4,6 +4,7 @@ import (
"os"
"fmt"
"net"
"sync"
"time"
"errors"
"regexp"
@ -190,24 +191,26 @@ func prepareJailedZfsDatasets(jail *Jail) error {
}
for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") {
// Check if dataset exist, create if necessary
cmd := fmt.Sprintf("zfs get -H creation %s/%s", jail.Zpool, d)
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd := fmt.Sprintf("zfs get -H creation %s", d)
out, err := executeCommand(cmd)
if err != nil {
if strings.HasSuffix(out, "dataset does not exist") {
cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s/%s", jail.Zpool, d)
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s", d)
_, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error creating dataset %s/%s: %s", jail.Zpool, d, err.Error()))
return errors.New(fmt.Sprintf("Error creating dataset %s: %s", d, err.Error()))
}
} else {
return errors.New(fmt.Sprintf("Error getting zfs dataset %s: %s", d, err.Error()))
}
}
cmd = fmt.Sprintf("zfs set jailed=on %s/%s", jail.Zpool, d)
cmd = fmt.Sprintf("zfs set jailed=on %s", d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s/%s\": %s", jail.Zpool, d, err.Error()))
return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s\": %s", d, err.Error()))
}
}
}
@ -218,27 +221,27 @@ func jailZfsDatasets(jail *Jail) error {
if jail.Config.Jail_zfs > 0 {
for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") {
// Jail dataset
cmd := fmt.Sprintf("zfs jail %d %s/%s", jail.JID, jail.Zpool, d)
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd := fmt.Sprintf("zfs jail %d %s", jail.JID, d)
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error jailling zfs dataset %s: %v: out", d, err, out))
}
// Mount from inside jail if mountpoint is set
cmd = fmt.Sprintf("zfs get -H -o value mountpoint %s/%s", jail.Zpool, d)
cmd = fmt.Sprintf("zfs get -H -o value mountpoint %s", d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error getting zfs dataset %s/%s mountpoint: %v: %s", jail.Zpool, d, err, out))
return errors.New(fmt.Sprintf("Error getting zfs dataset %s mountpoint: %v: %s", d, err, out))
}
if len(out) > 0 && out != "-" && (false == strings.EqualFold(out, "none")) {
//cmd = fmt.Sprintf("zfs mount %s/%s", jail.Zpool, d)
cmd = fmt.Sprintf("zfs mount -a")
// Should we "mount -a" ? cmd = fmt.Sprintf("zfs mount -a")
cmd = fmt.Sprintf("zfs mount %s", d)
out, err = executeCommandInJail(jail, cmd)
if err != nil {
// If already mounted, continue processing
if ! strings.HasSuffix(out, "filesystem already mounted\n") {
//return errors.New(fmt.Sprintf("Error mounting zfs dataset %s/%s: %v: %s", jail.Zpool, d, err, out))
return errors.New(fmt.Sprintf("Error executing \"zfs mount -a\" from inside jail: %v: %s", err, out))
return errors.New(fmt.Sprintf("Error mounting zfs dataset %s from inside jail: %v: %s", d, err, out))
}
}
}
@ -473,12 +476,17 @@ func genNatIpv4(jail *Jail) ([]string, error) {
return ippair, nil
}
func buildDevfsRuleSet(jail *Jail) (error, int) {
// FIXME : Must lock this function so parallel start do not
func buildDevfsRuleSet(jail *Jail, m *sync.Mutex) (error, int) {
rulesets := []int{}
m.Lock()
//defer m.Unlock()
// Get known rulesets
out, err := executeCommand("devfs rule showsets")
if err != nil {
m.Unlock()
return errors.New(fmt.Sprintf("Error executing command \"devfs rule showsets\": %v; command returned: %s\n", err, out)), 0
}
srs := strings.Split(out, "\n")
@ -506,19 +514,23 @@ func buildDevfsRuleSet(jail *Jail) (error, int) {
// UPDATE: We don't need this as every jail have a default Devfs_ruleset value
/*ds, err := getDatastoreFromArray(jail.Datastore, gDatastores)
if err != nil {
m.Unlock()
return errors.New(fmt.Sprintf("Error getting datastore %s for jail %s", jail.Datastore, jail.Name)), 0
}
defaultrs, err := strconv.ParseInt(ds.DefaultJailConfig.Devfs_ruleset, 10, 64)
if err != nil {
m.Unlock()
return errors.New(fmt.Sprintf("Error parsing default devfs_ruleset for datastore %s", jail.Datastore)), 0
}*/
// Clone configured devfs_ruleset to a dynamic ruleset
if false == isStringInArray(srs, jail.Config.Devfs_ruleset) {
m.Unlock()
return errors.New(fmt.Sprintf("Unknown ruleset: %s", jail.Config.Devfs_ruleset)), 0
}
rs, _ := strconv.Atoi(jail.Config.Devfs_ruleset)
err = copyDevfsRuleset(ruleset, rs)
m.Unlock()
if err != nil {
return err, 0
}
@ -1011,7 +1023,7 @@ func generateResolvConf(jail *Jail) error {
for _, l := range strings.Split(jail.Config.Resolver, ";") {
f.WriteString(fmt.Sprintf("%s\n", l))
}
} else if jail.Config.Resolver == "none" {
} else if jail.Config.Resolver == "none" || jail.Config.Resolver == "/etc/resolv.conf" {
read, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return fmt.Errorf("Error opening /etc/resolv.conf: %v", err)
@ -1053,6 +1065,9 @@ func cleanAfterStartCrash() {
// Start all jails with boot=true, in priority order
func StartJailsAtBoot() {
var startList []Jail
var wg *sync.WaitGroup
var curThNb int
var curPri int
// Get boot enabled jails
for _, j := range gJails {
@ -1070,11 +1085,51 @@ func StartJailsAtBoot() {
}
JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(startList)
for _, j := range startList {
wg = new(sync.WaitGroup)
curThNb = 0
for i, j := range startList {
jFullName := fmt.Sprintf("%s/%s", j.Datastore, j.Name)
log.Debugf("Starting %s with priority %s\n", jFullName, j.Config.Priority)
StartJail([]string{jFullName})
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()
StartJail([]string{jailFullName})
}(jFullName)
} else {
if (curPri == jailPri) {
wg.Add(1)
curThNb++
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
} else {
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
}
}
}
wg.Wait()
}
@ -1121,7 +1176,7 @@ func StartJail(args []string) {
continue
}
fmt.Printf("> Starting jail %s\n", a)
fmt.Printf("> Starting jail %s\n", cj.Name)
// Set InternalName as it is used by some of these
cj.InternalName = fmt.Sprintf("ioc-%s", cj.Name)
@ -1297,7 +1352,7 @@ func StartJail(args []string) {
net = append(net, strings.Split(cj.Config.Vnet_interfaces, " ")...)
}
err, dynrs := buildDevfsRuleSet(cj)
err, dynrs := buildDevfsRuleSet(cj, &gMdevfs)
if err != nil {
fmt.Printf("%s\n", err.Error())
return
@ -1421,9 +1476,9 @@ func StartJail(args []string) {
if len(cj.Config.Exec_start) > 0 {
fmt.Printf(" > Start services:\n")
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d %s", cj.Config.Exec_fib, cj.JID, cj.Config.Exec_start)
out, err := executeCommand(cmd)
err := executeCommandNonBlocking(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf(" > Start services: OK\n")
}

View File

@ -4,6 +4,7 @@ import (
"os"
"fmt"
//"log"
"sync"
"errors"
"regexp"
"os/exec"
@ -50,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") {
@ -71,10 +72,10 @@ 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
}
@ -169,8 +170,13 @@ func stopJail(jail *Jail) error {
}
// 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 {
@ -187,12 +193,53 @@ func StopAllRunningJails() {
return
}
JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(stopList)
for _, j := range 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)
StopJail([]string{jFullName})
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()
}
/*
@ -243,8 +290,16 @@ func StopJail(args []string) {
return
}
cvers = strings.TrimRight(cvers, "\n")
fmt.Sprintf(cj.Config.Release, cvers)
cj.ConfigUpdated = true
//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 {
@ -410,6 +465,7 @@ func StopJail(args []string) {
}
}
fmt.Printf("DEBUG: release = %s\n", cj.Config.Release)
WriteConfigToDisk(cj.Name, false, true)
}

View File

@ -21,6 +21,8 @@ import (
const (
ipv4re = `[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}`
ifconfigipv4re = `inet[[:space:]](` + ipv4re + `)`
// Maximum thread qty for start/stop
gMaxThreads = 4
)
/*****************************************************************************
@ -215,7 +217,7 @@ func executeCommand(cmdline string) (string, error) {
// else
word = word + string(c)
}
if len(cmd) > 1 {
out, err = exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
} else {
@ -225,6 +227,72 @@ func executeCommand(cmdline string) (string, error) {
return string(out), err
}
/* From iocage:
* # Courtesy of @william-gr
* # service(8) and some rc.d scripts have the bad h*abit of
* # exec'ing and never closing stdout/stderr. This makes
* # sure we read only enough until the command exits and do
* # not wait on the pipe to close on the other end.
* So this function executes process without waiting after completion
*/
func executeCommandNonBlocking(cmdline string) (error) {
var cmd []string
var oCmd *exec.Cmd
var err error
if gUseSudo {
cmd = append(cmd, "sudo")
}
var word string
var in_escaped bool
// Split by words, or " enclosed words
for i, c := range (cmdline) {
if string(c) == "\"" {
if in_escaped {
// This is the closing "
cmd = append(cmd, word)
in_escaped = false
} else {
in_escaped = true
}
continue
}
if string(c) == " " {
if in_escaped {
word = word + string(c)
continue
} else {
cmd = append(cmd, word)
word = ""
continue
}
}
if i == (len(cmdline) - 1) {
word = word + string(c)
cmd = append(cmd, word)
break
}
// else
word = word + string(c)
}
if len(cmd) > 1 {
oCmd = exec.Command(cmd[0], cmd[1:]...)
} else {
oCmd = exec.Command(cmd[0])
}
if err = oCmd.Start(); err != nil {
return err
}
err = oCmd.Wait()
return err
}
// Executed command outputs to stdout in realtime
func executeCommandWithOutputToStdout(cmdline string) (error) {
var cmd []string
@ -500,7 +568,7 @@ func doZfsDatasetExist(dataset string) (bool, error) {
}
// Create ZFS dataset. mountpoint can be "none", then the dataset won't be mounted
func createZfsDataset(dataset, mountpoint, compression string) error {
func zfsCreateDataset(dataset, mountpoint, compression string) error {
cmd := fmt.Sprintf("zfs create -o mountpoint=%s -o compression=%s %s", mountpoint, compression, dataset)
out, err := executeCommand(cmd)
if err != nil {
@ -508,6 +576,26 @@ func createZfsDataset(dataset, mountpoint, compression string) error {
}
return nil
}
// Return dataset name for a given mountpoint
func zfsGetDatasetByMountpoint(mountpoint string) (string, error) {
cmd := fmt.Sprintf("zfs list -p -r -H -o name %s", mountpoint)
out, err := executeCommand(cmd)
if err != nil {
return "", errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
}
return strings.TrimSuffix(out, "\n"), nil
}
// Delete a ZFS Dataset by name
func zfsDestroy(dataset string) error {
cmd := fmt.Sprintf("zfs destroy -r %s", dataset)
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
}
return nil
}
/*****************************************************************************
*
@ -616,7 +704,7 @@ func copyDevfsRuleset(ruleset int, srcrs int) error {
/********************************************************************************
* Returns value of parameter as read in /var/run/jail.$InternalName.conf
* Directoves without value will return "true" if found
* Directives without value will return "true" if found
* Returns an error if parameter not found in file
*******************************************************************************/
func getValueFromRunningConfig(jname string, param string) (string, error) {
@ -704,7 +792,8 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
}
for i, j := range jarray {
if jail == j.Name {
//if jail == j.Name {
if strings.HasPrefix(j.Name, jail) {
if len(ds) > 0 {
if strings.EqualFold(ds, j.Datastore) {
return &jarray[i], nil
@ -718,7 +807,7 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
}
if len(jails) > 0 {
if len(jails) > 1 {
return &Jail{}, errors.New("More than one jail found with this name, please use datastore/jail format")
return &Jail{}, errors.New("More than one jail matching, please use datastore/jail format or full name")
} else {
return &jails[0], nil
}