diff --git a/.drone.yml b/.drone.yml
index ebc2747..88d1c02 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -30,7 +30,7 @@ build:
# TODO: make all tests work in drone!
# Missing: checklists, checks, memstatus, netstatus
#- godep go test ./...
- - godep go test . ./chkutil ./dockerstatus ./errutil ./fsstatus ./netstatus ./systemdstatus ./tabular ./usrstatus
+ - godep go test . ./chkutil ./dockerstatus ./errutil ./fsstatus ./netstatus ./systemdstatus ./tabular
- godep go install .
- distributive --verbosity=info -d "./samples"
# - distributive -d ./samples/
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 3aa1bc0..1c54806 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -10,15 +10,44 @@
"Comment": "v0.8.7",
"Rev": "418b41d23a1bf978c06faea5313ba194650ac088"
},
+ {
+ "ImportPath": "github.com/aelsabbahy/GOnetstat",
+ "Rev": "2907f74398ebea717cab8187513bee184b1fdd26"
+ },
+ {
+ "ImportPath": "github.com/aelsabbahy/goss/system",
+ "Comment": "v0.1.3-3-gd28f3cc",
+ "Rev": "d28f3cc6d708fb012ea614acf712eb56712a7de3"
+ },
+ {
+ "ImportPath": "github.com/aelsabbahy/goss/util",
+ "Comment": "v0.1.3-3-gd28f3cc",
+ "Rev": "d28f3cc6d708fb012ea614acf712eb56712a7de3"
+ },
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0",
"Rev": "565493f259bf868adb54d45d5f4c68d405117adf"
},
+ {
+ "ImportPath": "github.com/coreos/go-systemd/dbus",
+ "Comment": "v5",
+ "Rev": "7b2428fec40033549c68f54e26e89e7ca9a9ce31"
+ },
+ {
+ "ImportPath": "github.com/coreos/go-systemd/util",
+ "Comment": "v5",
+ "Rev": "7b2428fec40033549c68f54e26e89e7ca9a9ce31"
+ },
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "c9ad0ce23f68428421adfc6ced9e6123f54788a5"
},
+ {
+ "ImportPath": "github.com/godbus/dbus",
+ "Comment": "v3-20-ge2cf281",
+ "Rev": "e2cf28118e66a6a63db46cf6088a35d2054d3bb0"
+ },
{
"ImportPath": "github.com/mitchellh/go-ps",
"Rev": "e6c6068076470196af082b1ff896e24a51a87b2a"
@@ -31,6 +60,11 @@
"ImportPath": "github.com/mitchellh/panicwrap",
"Rev": "1655d88c8ff7495ae9d2c19fd8f445f4657e22b0"
},
+ {
+ "ImportPath": "github.com/opencontainers/runc/libcontainer/user",
+ "Comment": "v0.0.9-82-g519529f",
+ "Rev": "519529febe2e0acb14e39cf54112c292ccb2eabe"
+ },
{
"ImportPath": "golang.org/x/crypto/sha3",
"Rev": "c8b9e6388ef638d5a8a9d865c634befdc46a6784"
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp.go
new file mode 100644
index 0000000..c762a88
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/aelsabbahy/GOnetstat"
+)
+
+/* Get TCP information and show like netstat.
+ Information like 'user' and 'name' of some processes will not show if you
+ don't have root permissions */
+
+func main() {
+ d := GOnetstat.Tcp(false)
+
+ // format header
+ fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress",
+ "State", "Pid/Program")
+
+ for _, p := range d {
+
+ // Check STATE to show only Listening connections
+ if p.State == "LISTEN" {
+ // format data like netstat output
+ ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port)
+ fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort)
+ pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name)
+
+ fmt.Printf("tcp %16v %20v %16v %20v\n", ip_port, fip_port,
+ p.State, pid_program)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp_json.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp_json.go
new file mode 100644
index 0000000..dc3a1ce
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp_json.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "fmt"
+ "encoding/json"
+ "github.com/drael/GOnetstat"
+)
+
+/* Get TCP information and output in json.
+ Information like 'user' and 'name' of some processes will not show if you
+ don't have root permissions */
+
+func main () {
+ d := GOnetstat.Tcp()
+
+ // Marshal in prety print way
+ output, err := json.MarshalIndent(d, "", " ")
+ if err != nil {
+ fmt.Println(err)
+ }
+
+ fmt.Println(string(output))
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/udp.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/udp.go
new file mode 100644
index 0000000..dec5843
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/udp.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ "fmt"
+ "github.com/drael/GOnetstat"
+)
+
+/* Get Udp information and show like netstat.
+ Information like 'user' and 'name' of some processes will not show if you
+ don't have root permissions */
+
+
+func main() {
+ // Get Udp data, you can use GOnetstat.Tcp() to get TCP data
+ d := GOnetstat.Udp()
+
+ // format header
+ fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress",
+ "State", "Pid/Program")
+
+ for _, p := range(d) {
+ // format data like netstat output
+ ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port)
+ fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort)
+ pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name)
+
+ fmt.Printf("udp %16v %20v %16v %20v\n", ip_port, fip_port,
+ p.State, pid_program)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/LICENSE.md b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/LICENSE.md
new file mode 100644
index 0000000..07a2573
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Rafael Santos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/README.md b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/README.md
new file mode 100644
index 0000000..8bc25f4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/README.md
@@ -0,0 +1,41 @@
+# GOnetstat
+
+Netstat implementation in Golang.
+
+This Package get data from /proc/net/tcp|6 and /proc/net/udp|6 and parse
+/proc/[0-9]*/fd/[0-9]* to match the correct inode.
+
+## Usage
+
+TCP/UDP
+```go
+tcp_data := GOnetstat.Tcp()
+udp_data := GOnetstat.Udp()
+```
+
+This will return a array of a Process struct like this
+
+```go
+type Process struct {
+ User string
+ Name string
+ Pid string
+ Exe string
+ State string
+ Ip string
+ Port int64
+ ForeignIp string
+ ForeignPort int64
+}
+```
+So you can loop through data output and format the output of your program
+in whatever way you want it.
+See the Examples folder!
+
+TCP6/UDP6
+```go
+tcp6_data := GOnetstat.Tcp6()
+udp6_data := GOnetstat.Udp6()
+```
+The return will be a array of a Process struct like mentioned above.
+Still need to create a way to compress the ipv6 because is too long.
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/gonetstat.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/gonetstat.go
new file mode 100644
index 0000000..739b03d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/gonetstat.go
@@ -0,0 +1,281 @@
+/*
+ Simple Netstat implementation.
+ Get data from /proc/net/tcp and /proc/net/udp and
+ and parse /proc/[0-9]/fd/[0-9].
+
+ Author: Rafael Santos
+*/
+
+package GOnetstat
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/opencontainers/runc/libcontainer/user"
+)
+
+const (
+ PROC_TCP = "/proc/net/tcp"
+ PROC_UDP = "/proc/net/udp"
+ PROC_TCP6 = "/proc/net/tcp6"
+ PROC_UDP6 = "/proc/net/udp6"
+)
+
+var STATE = map[string]string{
+ "01": "ESTABLISHED",
+ "02": "SYN_SENT",
+ "03": "SYN_RECV",
+ "04": "FIN_WAIT1",
+ "05": "FIN_WAIT2",
+ "06": "TIME_WAIT",
+ "07": "CLOSE",
+ "08": "CLOSE_WAIT",
+ "09": "LAST_ACK",
+ "0A": "LISTEN",
+ "0B": "CLOSING",
+}
+
+type Process struct {
+ User string
+ Name string
+ Pid string
+ Exe string
+ State string
+ Ip string
+ Port int64
+ ForeignIp string
+ ForeignPort int64
+}
+
+func getData(t string) ([]string, error) {
+ // Get data from tcp or udp file.
+
+ var proc_t string
+
+ if t == "tcp" {
+ proc_t = PROC_TCP
+ } else if t == "udp" {
+ proc_t = PROC_UDP
+ } else if t == "tcp6" {
+ proc_t = PROC_TCP6
+ } else if t == "udp6" {
+ proc_t = PROC_UDP6
+ } else {
+ fmt.Printf("%s is a invalid type, tcp and udp only!\n", t)
+ os.Exit(1)
+ }
+
+ data, err := ioutil.ReadFile(proc_t)
+ if err != nil {
+ return []string{}, err
+ }
+ lines := strings.Split(string(data), "\n")
+
+ // Return lines without Header line and blank line on the end
+ return lines[1 : len(lines)-1], nil
+
+}
+
+func hexToDec(h string) int64 {
+ // convert hexadecimal to decimal.
+ d, err := strconv.ParseInt(h, 16, 32)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ return d
+}
+
+func convertIp(ip string) string {
+ // Convert the ipv4 to decimal. Have to rearrange the ip because the
+ // default value is in little Endian order.
+
+ var out string
+
+ // Check ip size if greater than 8 is a ipv6 type
+ if len(ip) > 8 {
+ i := []string{ip[30:32],
+ ip[28:30],
+ ip[26:28],
+ ip[24:26],
+ ip[22:24],
+ ip[20:22],
+ ip[18:20],
+ ip[16:18],
+ ip[14:16],
+ ip[12:14],
+ ip[10:12],
+ ip[8:10],
+ ip[6:8],
+ ip[4:6],
+ ip[2:4],
+ ip[0:2]}
+ out = fmt.Sprintf("%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v",
+ i[14], i[15], i[13], i[12],
+ i[10], i[11], i[8], i[9],
+ i[6], i[7], i[4], i[5],
+ i[2], i[3], i[0], i[1])
+
+ } else {
+ i := []int64{hexToDec(ip[6:8]),
+ hexToDec(ip[4:6]),
+ hexToDec(ip[2:4]),
+ hexToDec(ip[0:2])}
+
+ out = fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[3])
+ }
+ return out
+}
+
+func findPid(inode string) string {
+ // Loop through all fd dirs of process on /proc to compare the inode and
+ // get the pid.
+
+ pid := "-"
+
+ d, err := filepath.Glob("/proc/[0-9]*/fd/[0-9]*")
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ re := regexp.MustCompile(inode)
+ for _, item := range d {
+ path, _ := os.Readlink(item)
+ out := re.FindString(path)
+ if len(out) != 0 {
+ pid = strings.Split(item, "/")[2]
+ }
+ }
+ return pid
+}
+
+func getProcessExe(pid string) string {
+ exe := fmt.Sprintf("/proc/%s/exe", pid)
+ path, _ := os.Readlink(exe)
+ return path
+}
+
+func getProcessName(exe string) string {
+ n := strings.Split(exe, "/")
+ name := n[len(n)-1]
+ return strings.Title(name)
+}
+
+func getUser(uid int) string {
+ u, _ := user.LookupUid(uid)
+ return u.Name
+}
+
+func removeEmpty(array []string) []string {
+ // remove empty data from line
+ var new_array []string
+ for _, i := range array {
+ if i != "" {
+ new_array = append(new_array, i)
+ }
+ }
+ return new_array
+}
+
+func getInode2pid() map[string]string {
+ // Loop through all fd dirs of process on /proc to compare the inode and
+ // get the pid.
+
+ inode2pid := make(map[string]string)
+ pid := "-"
+
+ d, err := filepath.Glob("/proc/[0-9]*/fd/[0-9]*")
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ var inode string
+ for _, item := range d {
+ path, _ := os.Readlink(item)
+ if strings.Contains(path, "socket:[") {
+ inode = path[8 : len(path)-1]
+ pid = strings.Split(item, "/")[2]
+ inode2pid[inode] = pid
+ }
+ }
+ return inode2pid
+}
+
+func netstat(t string, lookupPids bool) ([]Process, error) {
+ // Return a array of Process with Name, Ip, Port, State .. etc
+ // Require Root acess to get information about some processes.
+
+ var Processes []Process
+
+ data, err := getData(t)
+ if err != nil {
+ return Processes, nil
+ }
+ var inode2pid map[string]string
+ if lookupPids {
+ inode2pid = getInode2pid()
+ }
+
+ for _, line := range data {
+
+ // local ip and port
+ line_array := removeEmpty(strings.Split(strings.TrimSpace(line), " "))
+ ip_port := strings.Split(line_array[1], ":")
+ ip := convertIp(ip_port[0])
+ port := hexToDec(ip_port[1])
+
+ // foreign ip and port
+ fip_port := strings.Split(line_array[2], ":")
+ fip := convertIp(fip_port[0])
+ fport := hexToDec(fip_port[1])
+
+ state := STATE[line_array[3]]
+ uid, err := strconv.Atoi(line_array[7])
+ if err != nil {
+ return Processes, err
+ }
+ userName := getUser(uid)
+ var pid, exe, name string
+ if lookupPids {
+ pid = inode2pid[line_array[9]]
+ exe = getProcessExe(pid)
+ name = getProcessName(exe)
+ }
+
+ p := Process{userName, name, pid, exe, state, ip, port, fip, fport}
+
+ Processes = append(Processes, p)
+
+ }
+
+ return Processes, nil
+}
+
+func Tcp(lookupPids bool) ([]Process, error) {
+ // Get a slice of Process type with TCP data
+ return netstat("tcp", lookupPids)
+}
+
+func Udp(lookupPids bool) ([]Process, error) {
+ // Get a slice of Process type with UDP data
+ return netstat("udp", lookupPids)
+}
+
+func Tcp6(lookupPids bool) ([]Process, error) {
+ // Get a slice of Process type with TCP6 data
+ return netstat("tcp6", lookupPids)
+}
+
+func Udp6(lookupPids bool) ([]Process, error) {
+ // Get a slice of Process type with UDP6 data
+ return netstat("udp6", lookupPids)
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/addr.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/addr.go
new file mode 100644
index 0000000..2d6fd0b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/addr.go
@@ -0,0 +1,61 @@
+package system
+
+import (
+ "net"
+ "strings"
+ "time"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type Addr interface {
+ Address() string
+ Exists() (bool, error)
+ Reachable() (bool, error)
+}
+
+type DefAddr struct {
+ address string
+ Timeout int
+}
+
+func NewDefAddr(address string, system *System, config util.Config) Addr {
+ addr := normalizeAddress(address)
+ return &DefAddr{
+ address: addr,
+ Timeout: config.Timeout,
+ }
+}
+
+func (a *DefAddr) ID() string {
+ return a.address
+}
+func (a *DefAddr) Address() string {
+ return a.address
+}
+func (a *DefAddr) Exists() (bool, error) { return a.Reachable() }
+
+func (a *DefAddr) Reachable() (bool, error) {
+ network, address := splitAddress(a.address)
+
+ conn, err := net.DialTimeout(network, address, time.Duration(a.Timeout)*time.Millisecond)
+ if err != nil {
+ return false, nil
+ }
+ conn.Close()
+ return true, nil
+}
+
+func splitAddress(fulladdress string) (network, address string) {
+ split := strings.SplitN(fulladdress, "://", 2)
+ if len(split) == 2 {
+ return split[0], split[1]
+ }
+ return "tcp", fulladdress
+
+}
+
+func normalizeAddress(fulladdress string) string {
+ net, addr := splitAddress(fulladdress)
+ return net + "://" + addr
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/command.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/command.go
new file mode 100644
index 0000000..802b9a0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/command.go
@@ -0,0 +1,104 @@
+package system
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os/exec"
+ "time"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type Command interface {
+ Command() string
+ Exists() (bool, error)
+ ExitStatus() (int, error)
+ Stdout() (io.Reader, error)
+ Stderr() (io.Reader, error)
+}
+
+type DefCommand struct {
+ command string
+ exitStatus int
+ stdout io.Reader
+ stderr io.Reader
+ loaded bool
+ Timeout int
+ err error
+}
+
+func NewDefCommand(command string, system *System, config util.Config) Command {
+ return &DefCommand{
+ command: command,
+ Timeout: config.Timeout,
+ }
+}
+
+func (c *DefCommand) setup() error {
+ if c.loaded {
+ return c.err
+ }
+ c.loaded = true
+
+ cmd := util.NewCommand("sh", "-c", c.command)
+ err := runCommand(cmd, c.Timeout)
+
+ // We don't care about ExitError since it's covered by status
+ if _, ok := err.(*exec.ExitError); !ok {
+ c.err = err
+ }
+ c.exitStatus = cmd.Status
+ c.stdout = bytes.NewReader(cmd.Stdout.Bytes())
+ c.stderr = bytes.NewReader(cmd.Stderr.Bytes())
+
+ return c.err
+}
+
+func (c *DefCommand) Command() string {
+ return c.command
+}
+
+func (c *DefCommand) ExitStatus() (int, error) {
+ err := c.setup()
+
+ return c.exitStatus, err
+}
+
+func (c *DefCommand) Stdout() (io.Reader, error) {
+ err := c.setup()
+
+ return c.stdout, err
+}
+
+func (c *DefCommand) Stderr() (io.Reader, error) {
+ err := c.setup()
+
+ return c.stderr, err
+}
+
+// Stub out
+func (c *DefCommand) Exists() (bool, error) {
+ return false, nil
+}
+
+func runCommand(cmd *util.Command, timeout int) error {
+ c1 := make(chan bool, 1)
+ e1 := make(chan error, 1)
+ timeoutD := time.Duration(timeout) * time.Millisecond
+ go func() {
+ err := cmd.Run()
+ if err != nil {
+ e1 <- err
+ }
+ c1 <- true
+ }()
+ select {
+ case <-c1:
+ return nil
+ case err := <-e1:
+ return err
+ case <-time.After(timeoutD):
+ return fmt.Errorf("Command execution timed out (%s)", timeoutD)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/dns.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/dns.go
new file mode 100644
index 0000000..38551a2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/dns.go
@@ -0,0 +1,98 @@
+package system
+
+import (
+ "fmt"
+ "net"
+ "sort"
+ "time"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type DNS interface {
+ Host() string
+ Addrs() ([]string, error)
+ Resolveable() (bool, error)
+ Exists() (bool, error)
+}
+
+type DefDNS struct {
+ host string
+ resolveable bool
+ addrs []string
+ Timeout int
+ loaded bool
+ err error
+}
+
+func NewDefDNS(host string, system *System, config util.Config) DNS {
+ return &DefDNS{
+ host: host,
+ Timeout: config.Timeout,
+ }
+}
+
+func (d *DefDNS) Host() string {
+ return d.host
+}
+
+func (d *DefDNS) setup() error {
+ if d.loaded {
+ return d.err
+ }
+ d.loaded = true
+
+ addrs, err := lookupHost(d.host, d.Timeout)
+ if err != nil || len(addrs) == 0 {
+ d.resolveable = false
+ d.addrs = []string{}
+ // DNSError is resolvable == false, ignore error
+ if _, ok := err.(*net.DNSError); ok {
+ return nil
+ }
+ d.err = err
+ return d.err
+ }
+ sort.Strings(addrs)
+ d.resolveable = true
+ d.addrs = addrs
+ return nil
+}
+
+func (d *DefDNS) Addrs() ([]string, error) {
+ err := d.setup()
+
+ return d.addrs, err
+}
+
+func (d *DefDNS) Resolveable() (bool, error) {
+ err := d.setup()
+
+ return d.resolveable, err
+}
+
+// Stub out
+func (d *DefDNS) Exists() (bool, error) {
+ return false, nil
+}
+
+func lookupHost(host string, timeout int) ([]string, error) {
+ c1 := make(chan []string, 1)
+ e1 := make(chan error, 1)
+ timeoutD := time.Duration(timeout) * time.Millisecond
+ go func() {
+ addrs, err := net.LookupHost(host)
+ if err != nil {
+ e1 <- err
+ }
+ c1 <- addrs
+ }()
+ select {
+ case res := <-c1:
+ return res, nil
+ case err := <-e1:
+ return nil, err
+ case <-time.After(timeoutD):
+ return nil, fmt.Errorf("DNS lookup timed out (%s)", timeoutD)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/file.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/file.go
new file mode 100644
index 0000000..adc3852
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/file.go
@@ -0,0 +1,125 @@
+package system
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strconv"
+ "syscall"
+
+ "github.com/aelsabbahy/goss/util"
+ "github.com/opencontainers/runc/libcontainer/user"
+)
+
+type File interface {
+ Path() string
+ Exists() (bool, error)
+ Contains() (io.Reader, error)
+ Mode() (string, error)
+ Filetype() (string, error)
+ Owner() (string, error)
+ Group() (string, error)
+ LinkedTo() (string, error)
+}
+
+type DefFile struct {
+ path string
+}
+
+func NewDefFile(path string, system *System, config util.Config) File {
+ absPath, _ := filepath.Abs(path)
+ return &DefFile{path: absPath}
+}
+
+func (f *DefFile) Path() string {
+ return f.path
+}
+
+func (f *DefFile) Exists() (bool, error) {
+ if _, err := os.Stat(f.path); os.IsNotExist(err) {
+ return false, nil
+ }
+ return true, nil
+}
+
+func (f *DefFile) Contains() (io.Reader, error) {
+ fh, err := os.Open(f.path)
+ if err != nil {
+ return nil, err
+ }
+ return fh, nil
+}
+
+func (f *DefFile) Mode() (string, error) {
+ fi, err := os.Lstat(f.path)
+ if err != nil {
+ return "", err
+ }
+
+ return fmt.Sprintf("%#o", fi.Mode().Perm()), nil
+}
+
+func (f *DefFile) Filetype() (string, error) {
+ fi, err := os.Lstat(f.path)
+ if err != nil {
+ return "", err
+ }
+
+ switch {
+ case fi.Mode()&os.ModeSymlink == os.ModeSymlink:
+ return "symlink", nil
+ case fi.IsDir():
+ return "directory", nil
+ case fi.Mode().IsRegular():
+ return "file", nil
+ }
+ // FIXME: file as a catchall?
+ return "file", nil
+}
+
+func (f *DefFile) Owner() (string, error) {
+ fi, err := os.Lstat(f.path)
+ if err != nil {
+ return "", err
+ }
+
+ uidS := fmt.Sprint(fi.Sys().(*syscall.Stat_t).Uid)
+ uid, err := strconv.Atoi(uidS)
+ if err != nil {
+ return "", err
+ }
+ user, err := user.LookupUid(uid)
+ if err != nil {
+ return "", err
+ }
+
+ return user.Name, nil
+}
+
+func (f *DefFile) Group() (string, error) {
+ fi, err := os.Lstat(f.path)
+ if err != nil {
+ return "", err
+ }
+
+ gidS := fmt.Sprint(fi.Sys().(*syscall.Stat_t).Gid)
+ gid, err := strconv.Atoi(gidS)
+ if err != nil {
+ return "", err
+ }
+ group, err := user.LookupGid(gid)
+ if err != nil {
+ return "", err
+ }
+
+ return group.Name, nil
+}
+
+func (f *DefFile) LinkedTo() (string, error) {
+ dst, err := os.Readlink(f.path)
+ if err != nil {
+ return "", err
+ }
+ return dst, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/gossfile.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/gossfile.go
new file mode 100644
index 0000000..137d5a7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/gossfile.go
@@ -0,0 +1,25 @@
+package system
+
+import "github.com/aelsabbahy/goss/util"
+
+type Gossfile interface {
+ Path() string
+ Exists() (bool, error)
+}
+
+type DefGossfile struct {
+ path string
+}
+
+func (g *DefGossfile) Path() string {
+ return g.path
+}
+
+// Stub out
+func (g *DefGossfile) Exists() (bool, error) {
+ return false, nil
+}
+
+func NewDefGossfile(path string, system *System, config util.Config) Gossfile {
+ return &DefGossfile{path: path}
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/group.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/group.go
new file mode 100644
index 0000000..9bf4817
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/group.go
@@ -0,0 +1,41 @@
+package system
+
+import (
+ "github.com/aelsabbahy/goss/util"
+ "github.com/opencontainers/runc/libcontainer/user"
+)
+
+type Group interface {
+ Groupname() string
+ Exists() (bool, error)
+ GID() (int, error)
+}
+
+type DefGroup struct {
+ groupname string
+}
+
+func NewDefGroup(groupname string, system *System, config util.Config) Group {
+ return &DefGroup{groupname: groupname}
+}
+
+func (u *DefGroup) Groupname() string {
+ return u.groupname
+}
+
+func (u *DefGroup) Exists() (bool, error) {
+ _, err := user.LookupGroup(u.groupname)
+ if err != nil {
+ return false, nil
+ }
+ return true, nil
+}
+
+func (u *DefGroup) GID() (int, error) {
+ group, err := user.LookupGroup(u.groupname)
+ if err != nil {
+ return 0, err
+ }
+
+ return group.Gid, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package.go
new file mode 100644
index 0000000..e9c666d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package.go
@@ -0,0 +1,36 @@
+package system
+
+import (
+ "errors"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type Package interface {
+ Name() string
+ Exists() (bool, error)
+ Installed() (bool, error)
+ Versions() ([]string, error)
+}
+
+var ErrNullPackage = errors.New("Could not detect Package type on this system, please use --package flag to explicity set it")
+
+type NullPackage struct {
+ name string
+}
+
+func NewNullPackage(name string, system *System, config util.Config) Package {
+ return &NullPackage{name: name}
+}
+
+func (p *NullPackage) Name() string { return p.name }
+
+func (p *NullPackage) Exists() (bool, error) { return p.Installed() }
+
+func (p *NullPackage) Installed() (bool, error) {
+ return false, ErrNullPackage
+}
+
+func (p *NullPackage) Versions() ([]string, error) {
+ return nil, ErrNullPackage
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_alpine.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_alpine.go
new file mode 100644
index 0000000..49ea090
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_alpine.go
@@ -0,0 +1,61 @@
+package system
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type AlpinePackage struct {
+ name string
+ versions []string
+ loaded bool
+ installed bool
+}
+
+func NewAlpinePackage(name string, system *System, config util.Config) Package {
+ return &AlpinePackage{name: name}
+}
+
+func (p *AlpinePackage) setup() {
+ if p.loaded {
+ return
+ }
+ p.loaded = true
+ cmd := util.NewCommand("apk", "version", p.name)
+ if err := cmd.Run(); err != nil {
+ return
+ }
+ for _, l := range strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") {
+ if strings.HasPrefix(l, "Installed:") || strings.HasPrefix(l, "WARNING") {
+ continue
+ }
+ ver := strings.TrimPrefix(strings.Fields(l)[0], p.name+"-")
+ p.versions = append(p.versions, ver)
+ }
+
+ if len(p.versions) > 0 {
+ p.installed = true
+ }
+}
+
+func (p *AlpinePackage) Name() string {
+ return p.name
+}
+
+func (p *AlpinePackage) Exists() (bool, error) { return p.Installed() }
+
+func (p *AlpinePackage) Installed() (bool, error) {
+ p.setup()
+
+ return p.installed, nil
+}
+
+func (p *AlpinePackage) Versions() ([]string, error) {
+ p.setup()
+ if len(p.versions) == 0 {
+ return p.versions, errors.New("Package version not found")
+ }
+ return p.versions, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_deb.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_deb.go
new file mode 100644
index 0000000..b222d5d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_deb.go
@@ -0,0 +1,61 @@
+package system
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type DebPackage struct {
+ name string
+ versions []string
+ loaded bool
+ installed bool
+}
+
+func NewDebPackage(name string, system *System, config util.Config) Package {
+ return &DebPackage{name: name}
+}
+
+func (p *DebPackage) setup() {
+ if p.loaded {
+ return
+ }
+ p.loaded = true
+ cmd := util.NewCommand("dpkg-query", "-f", "${Status} ${Version}\n", "-W", p.name)
+ if err := cmd.Run(); err != nil {
+ return
+ }
+ for _, l := range strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") {
+ if !(strings.HasPrefix(l, "install ok installed") || strings.HasPrefix(l, "hold ok installed")) {
+ continue
+ }
+ ver := strings.Fields(l)[3]
+ p.versions = append(p.versions, ver)
+ }
+
+ if len(p.versions) > 0 {
+ p.installed = true
+ }
+}
+
+func (p *DebPackage) Name() string {
+ return p.name
+}
+
+func (p *DebPackage) Exists() (bool, error) { return p.Installed() }
+
+func (p *DebPackage) Installed() (bool, error) {
+ p.setup()
+
+ return p.installed, nil
+}
+
+func (p *DebPackage) Versions() ([]string, error) {
+ p.setup()
+ if len(p.versions) == 0 {
+ return p.versions, errors.New("Package version not found")
+ }
+ return p.versions, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_rpm.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_rpm.go
new file mode 100644
index 0000000..4537c33
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_rpm.go
@@ -0,0 +1,52 @@
+package system
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type RpmPackage struct {
+ name string
+ versions []string
+ loaded bool
+ installed bool
+}
+
+func NewRpmPackage(name string, system *System, config util.Config) Package {
+ return &RpmPackage{name: name}
+}
+
+func (p *RpmPackage) setup() {
+ if p.loaded {
+ return
+ }
+ p.loaded = true
+ cmd := util.NewCommand("rpm", "-q", "--nosignature", "--nohdrchk", "--nodigest", "--qf", "%{VERSION}\n", p.name)
+ if err := cmd.Run(); err != nil {
+ return
+ }
+ p.installed = true
+ p.versions = strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n")
+}
+
+func (p *RpmPackage) Name() string {
+ return p.name
+}
+
+func (p *RpmPackage) Exists() (bool, error) { return p.Installed() }
+
+func (p *RpmPackage) Installed() (bool, error) {
+ p.setup()
+
+ return p.installed, nil
+}
+
+func (p *RpmPackage) Versions() ([]string, error) {
+ p.setup()
+ if len(p.versions) == 0 {
+ return p.versions, errors.New("Package version not found")
+ }
+ return p.versions, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/port.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/port.go
new file mode 100644
index 0000000..0753be7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/port.go
@@ -0,0 +1,107 @@
+package system
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/aelsabbahy/GOnetstat"
+ "github.com/aelsabbahy/goss/util"
+)
+
+type Port interface {
+ Port() string
+ Exists() (bool, error)
+ Listening() (bool, error)
+ IP() ([]string, error)
+}
+
+type DefPort struct {
+ port string
+ sysPorts map[string][]GOnetstat.Process
+}
+
+func NewDefPort(port string, system *System, config util.Config) Port {
+ p := normalizePort(port)
+ return &DefPort{
+ port: p,
+ sysPorts: system.Ports(),
+ }
+}
+
+func splitPort(fullport string) (network, port string) {
+ split := strings.SplitN(fullport, ":", 2)
+ if len(split) == 2 {
+ return split[0], split[1]
+ }
+ return "tcp", fullport
+
+}
+
+func normalizePort(fullport string) string {
+ net, addr := splitPort(fullport)
+ return net + ":" + addr
+}
+
+func (p *DefPort) Port() string {
+ return p.port
+}
+
+func (p *DefPort) Exists() (bool, error) { return p.Listening() }
+
+func (p *DefPort) Listening() (bool, error) {
+ if _, ok := p.sysPorts[p.port]; ok {
+ return true, nil
+ }
+ return false, nil
+}
+
+func (p *DefPort) IP() ([]string, error) {
+ var ips []string
+ for _, entry := range p.sysPorts[p.port] {
+ ips = append(ips, entry.Ip)
+ }
+ return ips, nil
+}
+
+// FIXME: Is there a better way to do this rather than ignoring errors?
+func GetPorts(lookupPids bool) map[string][]GOnetstat.Process {
+ ports := make(map[string][]GOnetstat.Process)
+ netstat, _ := GOnetstat.Tcp(lookupPids)
+ var net string
+ //netPorts := make(map[string]GOnetstat.Process)
+ //ports["tcp"] = netPorts
+ net = "tcp"
+ for _, entry := range netstat {
+ if entry.State == "LISTEN" {
+ port := strconv.FormatInt(entry.Port, 10)
+ ports[net+":"+port] = append(ports[net+":"+port], entry)
+ }
+ }
+ netstat, _ = GOnetstat.Tcp6(lookupPids)
+ //netPorts = make(map[string]GOnetstat.Process)
+ //ports["tcp6"] = netPorts
+ net = "tcp6"
+ for _, entry := range netstat {
+ if entry.State == "LISTEN" {
+ port := strconv.FormatInt(entry.Port, 10)
+ ports[net+":"+port] = append(ports[net+":"+port], entry)
+ }
+ }
+ netstat, _ = GOnetstat.Udp(lookupPids)
+ //netPorts = make(map[string]GOnetstat.Process)
+ //ports["udp"] = netPorts
+ net = "udp"
+ for _, entry := range netstat {
+ port := strconv.FormatInt(entry.Port, 10)
+ ports[net+":"+port] = append(ports[net+":"+port], entry)
+ }
+ netstat, _ = GOnetstat.Udp6(lookupPids)
+ //netPorts = make(map[string]GOnetstat.Process)
+ //ports["udp6"] = netPorts
+ net = "udp6"
+ for _, entry := range netstat {
+ port := strconv.FormatInt(entry.Port, 10)
+ ports[net+":"+port] = append(ports[net+":"+port], entry)
+ }
+ return ports
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/process.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/process.go
new file mode 100644
index 0000000..cdb3b31
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/process.go
@@ -0,0 +1,63 @@
+package system
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/aelsabbahy/goss/util"
+ "github.com/mitchellh/go-ps"
+)
+
+type Process interface {
+ Executable() string
+ Exists() (bool, error)
+ Running() (bool, error)
+ Pids() ([]int, error)
+}
+
+type DefProcess struct {
+ executable string
+ procMap map[string][]ps.Process
+}
+
+func NewDefProcess(executable string, system *System, config util.Config) Process {
+ return &DefProcess{
+ executable: executable,
+ procMap: system.ProcMap(),
+ }
+}
+
+func (p *DefProcess) Executable() string {
+ return p.executable
+}
+
+func (p *DefProcess) Exists() (bool, error) { return p.Running() }
+
+func (p *DefProcess) Pids() ([]int, error) {
+ var pids []int
+ for _, proc := range p.procMap[p.executable] {
+ pids = append(pids, proc.Pid())
+ }
+ return pids, nil
+}
+
+func (p *DefProcess) Running() (bool, error) {
+ if _, ok := p.procMap[p.executable]; ok {
+ return true, nil
+ }
+ return false, nil
+}
+
+func GetProcs() map[string][]ps.Process {
+ pmap := make(map[string][]ps.Process)
+ processes, err := ps.Processes()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ for _, p := range processes {
+ pmap[p.Executable()] = append(pmap[p.Executable()], p)
+ }
+
+ return pmap
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service.go
new file mode 100644
index 0000000..3a4ac97
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service.go
@@ -0,0 +1,8 @@
+package system
+
+type Service interface {
+ Service() string
+ Exists() (bool, error)
+ Enabled() (bool, error)
+ Running() (bool, error)
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_dbus.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_dbus.go
new file mode 100644
index 0000000..be21c19
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_dbus.go
@@ -0,0 +1,72 @@
+package system
+
+import (
+ "strings"
+
+ "github.com/aelsabbahy/goss/util"
+ "github.com/coreos/go-systemd/dbus"
+)
+
+type ServiceDbus struct {
+ service string
+ dbus *dbus.Conn
+}
+
+func NewServiceDbus(service string, system *System, config util.Config) Service {
+ return &ServiceDbus{
+ service: service,
+ dbus: system.Dbus(),
+ }
+}
+
+func (s *ServiceDbus) Service() string {
+ return s.service
+}
+
+func (s *ServiceDbus) Exists() (bool, error) {
+ units, err := s.dbus.ListUnits()
+ if err != nil {
+ return false, err
+ }
+ for _, u := range units {
+ if u.Name == s.service+".service" {
+ return true, nil
+ }
+ }
+ return false, err
+}
+
+func (s *ServiceDbus) Enabled() (bool, error) {
+ stateRaw, err := s.dbus.GetUnitProperty(s.service+".service", "UnitFileState")
+ if err != nil {
+ return false, err
+ }
+ state := stateRaw.Value.String()
+ state = strings.Trim(state, "\"")
+
+ if state == "enabled" {
+ return true, nil
+ }
+
+ // Fall back on initv
+ if en, _ := initServiceEnabled(s.service, 3); en {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func (s *ServiceDbus) Running() (bool, error) {
+ stateRaw, err := s.dbus.GetUnitProperty(s.service+".service", "ActiveState")
+ if err != nil {
+ return false, err
+ }
+ state := stateRaw.Value.String()
+ state = strings.Trim(state, "\"")
+
+ if state == "active" {
+ return true, nil
+ }
+
+ return false, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_init.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_init.go
new file mode 100644
index 0000000..336a108
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_init.go
@@ -0,0 +1,66 @@
+package system
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type ServiceInit struct {
+ service string
+ alpine bool
+}
+
+func NewServiceInit(service string, system *System, config util.Config) Service {
+ return &ServiceInit{service: service}
+}
+
+func NewAlpineServiceInit(service string, system *System, config util.Config) Service {
+ return &ServiceInit{service: service, alpine: true}
+}
+
+func (s *ServiceInit) Service() string {
+ return s.service
+}
+
+func (s *ServiceInit) Exists() (bool, error) {
+ if _, err := os.Stat(fmt.Sprintf("/etc/init.d/%s", s.service)); err == nil {
+ return true, err
+ }
+ return false, nil
+}
+
+func (s *ServiceInit) Enabled() (bool, error) {
+ if s.alpine {
+ return alpineInitServiceEnabled(s.service, "sysinit")
+ } else {
+ return initServiceEnabled(s.service, 3)
+ }
+}
+
+func (s *ServiceInit) Running() (bool, error) {
+ cmd := util.NewCommand("service", s.service, "status")
+ cmd.Run()
+ if cmd.Status == 0 {
+ return true, nil
+ }
+ return false, nil
+}
+
+func initServiceEnabled(service string, level int) (bool, error) {
+ matches, err := filepath.Glob(fmt.Sprintf("/etc/rc%d.d/S[0-9][0-9]%s", level, service))
+ if err == nil && matches != nil {
+ return true, nil
+ }
+ return false, err
+}
+
+func alpineInitServiceEnabled(service string, level string) (bool, error) {
+ matches, err := filepath.Glob(fmt.Sprintf("/etc/runlevels/%s/%s", level, service))
+ if err == nil && matches != nil {
+ return true, nil
+ }
+ return false, err
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_upstart.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_upstart.go
new file mode 100644
index 0000000..4febd31
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_upstart.go
@@ -0,0 +1,67 @@
+package system
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "regexp"
+ "strings"
+
+ "github.com/aelsabbahy/goss/util"
+)
+
+type ServiceUpstart struct {
+ service string
+}
+
+var upstartEnabled = regexp.MustCompile(`^\s*start on`)
+
+func NewServiceUpstart(service string, system *System, config util.Config) Service {
+ return &ServiceUpstart{service: service}
+}
+
+func (s *ServiceUpstart) Service() string {
+ return s.service
+}
+
+func (s *ServiceUpstart) Exists() (bool, error) {
+ // upstart
+ if _, err := os.Stat(fmt.Sprintf("/etc/init/%s.conf", s.service)); err == nil {
+ return true, err
+ }
+
+ // initv
+ if _, err := os.Stat(fmt.Sprintf("/etc/init.d/%s", s.service)); err == nil {
+ return true, err
+ }
+ return false, nil
+}
+
+func (s *ServiceUpstart) Enabled() (bool, error) {
+ if fh, err := os.Open(fmt.Sprintf("/etc/init/%s.conf", s.service)); err == nil {
+ scanner := bufio.NewScanner(fh)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if upstartEnabled.MatchString(line) {
+ return true, nil
+ }
+ }
+ }
+
+ // Fall back on initv
+ if en, _ := initServiceEnabled(s.service, 3); en {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func (s *ServiceUpstart) Running() (bool, error) {
+ cmd := util.NewCommand("service", s.service, "status")
+ cmd.Run()
+ out := cmd.Stdout.String()
+ if cmd.Status == 0 && (strings.Contains(out, "running") || strings.Contains(out, "online")) {
+ return true, nil
+ }
+ return false, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/system.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/system.go
new file mode 100644
index 0000000..f947bc2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/system.go
@@ -0,0 +1,184 @@
+package system
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "sync"
+
+ "github.com/aelsabbahy/GOnetstat"
+ // This needs a better name
+ util2 "github.com/aelsabbahy/goss/util"
+ "github.com/codegangsta/cli"
+ "github.com/coreos/go-systemd/dbus"
+ "github.com/coreos/go-systemd/util"
+ "github.com/mitchellh/go-ps"
+)
+
+type Resource interface {
+ Exists() (bool, error)
+}
+
+type System struct {
+ NewPackage func(string, *System, util2.Config) Package
+ NewFile func(string, *System, util2.Config) File
+ NewAddr func(string, *System, util2.Config) Addr
+ NewPort func(string, *System, util2.Config) Port
+ NewService func(string, *System, util2.Config) Service
+ NewUser func(string, *System, util2.Config) User
+ NewGroup func(string, *System, util2.Config) Group
+ NewCommand func(string, *System, util2.Config) Command
+ NewDNS func(string, *System, util2.Config) DNS
+ NewProcess func(string, *System, util2.Config) Process
+ NewGossfile func(string, *System, util2.Config) Gossfile
+ dbus *dbus.Conn
+ ports map[string][]GOnetstat.Process
+ dbusOnce sync.Once
+ portsOnce sync.Once
+ procOnce sync.Once
+ procMap map[string][]ps.Process
+}
+
+func (s *System) Ports() map[string][]GOnetstat.Process {
+ s.portsOnce.Do(func() {
+ s.ports = GetPorts(false)
+ })
+ return s.ports
+}
+
+func (s *System) Dbus() *dbus.Conn {
+ s.dbusOnce.Do(func() {
+ dbus, err := dbus.New()
+ if err != nil {
+ fmt.Println(err)
+ // FIXME: Do we really want to exit here?
+ os.Exit(1)
+ }
+ s.dbus = dbus
+ })
+ return s.dbus
+}
+
+func (s *System) ProcMap() map[string][]ps.Process {
+ s.procOnce.Do(func() {
+ s.procMap = GetProcs()
+ })
+ return s.procMap
+}
+
+func New(c *cli.Context) *System {
+ sys := &System{
+ NewFile: NewDefFile,
+ NewAddr: NewDefAddr,
+ NewPort: NewDefPort,
+ NewUser: NewDefUser,
+ NewGroup: NewDefGroup,
+ NewCommand: NewDefCommand,
+ NewDNS: NewDefDNS,
+ NewProcess: NewDefProcess,
+ NewGossfile: NewDefGossfile,
+ }
+ // FIXME: Detect-os needs to be refactored in a consistent way
+ // Also, cache should be its own object
+ sys.detectService()
+
+ p := c.GlobalString("package")
+ switch p {
+ case "rpm":
+ sys.NewPackage = NewRpmPackage
+ case "deb":
+ sys.NewPackage = NewDebPackage
+ case "alpine":
+ sys.NewPackage = NewAlpinePackage
+ default:
+ sys.NewPackage = detectPackage()
+ }
+
+ return sys
+}
+
+func detectPackage() func(string, *System, util2.Config) Package {
+ switch {
+ case isRpm():
+ return NewRpmPackage
+ case isDeb():
+ return NewDebPackage
+ case isAlpine():
+ return NewAlpinePackage
+ default:
+ return NewNullPackage
+ }
+}
+
+func (s *System) detectService() {
+ switch {
+ case util.IsRunningSystemd():
+ s.NewService = NewServiceDbus
+ case isUbuntu():
+ s.NewService = NewServiceUpstart
+ case isAlpine():
+ s.NewService = NewAlpineServiceInit
+ default:
+ s.NewService = NewServiceInit
+ }
+}
+
+func isUbuntu() bool {
+ if b, err := ioutil.ReadFile("/etc/lsb-release"); err == nil {
+ if bytes.Contains(b, []byte("Ubuntu")) {
+ return true
+ }
+ }
+ return false
+
+}
+func isDeb() bool {
+ if _, err := os.Stat("/etc/debian_version"); err == nil {
+ return true
+ }
+
+ // See if it has only one of the package managers
+ if hasCommand("dpkg") && !hasCommand("rpm") && !hasCommand("apk") {
+ return true
+ }
+
+ return false
+}
+
+func isRpm() bool {
+ if _, err := os.Stat("/etc/redhat-release"); err == nil {
+ return true
+ }
+
+ if _, err := os.Stat("/etc/system-release"); err == nil {
+ return true
+ }
+
+ // See if it has only one of the package managers
+ if hasCommand("rpm") && !hasCommand("dpkg") && !hasCommand("apk") {
+ return true
+ }
+ return false
+}
+
+func isAlpine() bool {
+ if _, err := os.Stat("/etc/alpine-release"); err == nil {
+ return true
+ }
+
+ // See if it has only one of the package managers
+ if !hasCommand("dpkg") && !hasCommand("rpm") && hasCommand("apk") {
+ return true
+ }
+
+ return false
+}
+
+func hasCommand(cmd string) bool {
+ if _, err := exec.LookPath(cmd); err == nil {
+ return true
+ }
+ return false
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/user.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/user.go
new file mode 100644
index 0000000..c489203
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/user.go
@@ -0,0 +1,116 @@
+package system
+
+import (
+ "fmt"
+ "sort"
+
+ "github.com/aelsabbahy/goss/util"
+ "github.com/opencontainers/runc/libcontainer/user"
+)
+
+type User interface {
+ Username() string
+ Exists() (bool, error)
+ UID() (int, error)
+ GID() (int, error)
+ Groups() ([]string, error)
+ Home() (string, error)
+}
+
+type DefUser struct {
+ username string
+}
+
+func NewDefUser(username string, system *System, config util.Config) User {
+ return &DefUser{username: username}
+}
+
+func (u *DefUser) Username() string {
+ return u.username
+}
+
+func (u *DefUser) Exists() (bool, error) {
+ _, err := user.LookupUser(u.username)
+ if err != nil {
+ return false, nil
+ }
+ return true, nil
+}
+
+func (u *DefUser) UID() (int, error) {
+ user, err := user.LookupUser(u.username)
+ if err != nil {
+ return 0, err
+ }
+
+ return user.Uid, nil
+}
+
+func (u *DefUser) GID() (int, error) {
+ user, err := user.LookupUser(u.username)
+ if err != nil {
+ return 0, err
+ }
+
+ return user.Gid, nil
+}
+
+func (u *DefUser) Home() (string, error) {
+ user, err := user.LookupUser(u.username)
+ if err != nil {
+ return "", err
+ }
+
+ return user.Home, nil
+}
+
+func (u *DefUser) Groups() ([]string, error) {
+ user, err := user.LookupUser(u.username)
+ if err != nil {
+ return nil, err
+ }
+
+ var groupList []string
+ groups, err := lookupUserGroups(user)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, g := range groups {
+ groupList = append(groupList, g.Name)
+ }
+
+ sort.Strings(groupList)
+ return groupList, nil
+}
+
+func lookupUserGroups(userS user.User) ([]user.Group, error) {
+ // Get operating system-specific group reader-closer.
+ group, err := user.GetGroup()
+ if err != nil {
+ return []user.Group{user.Group{}}, err
+ }
+ defer group.Close()
+
+ groups, err := user.ParseGroupFilter(group, func(g user.Group) bool {
+ // Primary group
+ if g.Gid == userS.Gid {
+ return true
+ }
+
+ // Check if user is a member.
+ for _, u := range g.List {
+ if u == userS.Name {
+ return true
+ }
+ }
+
+ return false
+ })
+
+ if err != nil {
+ return []user.Group{user.Group{}}, fmt.Errorf("Unable to find groups for user %v: %v", userS, err)
+ }
+
+ return groups, nil
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/command.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/command.go
new file mode 100644
index 0000000..4d47666
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/command.go
@@ -0,0 +1,45 @@
+package util
+
+import (
+ "bytes"
+ //"fmt"
+ "os/exec"
+ "syscall"
+)
+
+type Command struct {
+ Cmd *exec.Cmd
+ Stdout, Stderr bytes.Buffer
+ Err error
+ Status int
+}
+
+func NewCommand(name string, arg ...string) *Command {
+ //fmt.Println(arg)
+ command := new(Command)
+ command.Cmd = exec.Command(name, arg...)
+ return command
+}
+
+func (c *Command) Run() error {
+ c.Cmd.Stdout = &c.Stdout
+ c.Cmd.Stderr = &c.Stderr
+
+ if err := c.Cmd.Start(); err != nil {
+ c.Err = err
+ //log.Fatalf("Cmd.Start: %v")
+ }
+
+ if err := c.Cmd.Wait(); err != nil {
+ c.Err = err
+ if exiterr, ok := err.(*exec.ExitError); ok {
+ if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
+ c.Status = status.ExitStatus()
+ //log.Printf("Exit Status: %d", status.ExitStatus())
+ }
+ }
+ } else {
+ c.Status = 0
+ }
+ return c.Err
+}
diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/config.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/config.go
new file mode 100644
index 0000000..0765e79
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/config.go
@@ -0,0 +1,6 @@
+package util
+
+type Config struct {
+ IgnoreList []string
+ Timeout int
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go
new file mode 100644
index 0000000..9404334
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go
@@ -0,0 +1,198 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
+package dbus
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/godbus/dbus"
+)
+
+const (
+ alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
+ num = `0123456789`
+ alphanum = alpha + num
+ signalBuffer = 100
+)
+
+// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
+func needsEscape(i int, b byte) bool {
+ // Escape everything that is not a-z-A-Z-0-9
+ // Also escape 0-9 if it's the first character
+ return strings.IndexByte(alphanum, b) == -1 ||
+ (i == 0 && strings.IndexByte(num, b) != -1)
+}
+
+// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
+// rules that systemd uses for serializing special characters.
+func PathBusEscape(path string) string {
+ // Special case the empty string
+ if len(path) == 0 {
+ return "_"
+ }
+ n := []byte{}
+ for i := 0; i < len(path); i++ {
+ c := path[i]
+ if needsEscape(i, c) {
+ e := fmt.Sprintf("_%x", c)
+ n = append(n, []byte(e)...)
+ } else {
+ n = append(n, c)
+ }
+ }
+ return string(n)
+}
+
+// Conn is a connection to systemd's dbus endpoint.
+type Conn struct {
+ // sysconn/sysobj are only used to call dbus methods
+ sysconn *dbus.Conn
+ sysobj dbus.BusObject
+
+ // sigconn/sigobj are only used to receive dbus signals
+ sigconn *dbus.Conn
+ sigobj dbus.BusObject
+
+ jobListener struct {
+ jobs map[dbus.ObjectPath]chan<- string
+ sync.Mutex
+ }
+ subscriber struct {
+ updateCh chan<- *SubStateUpdate
+ errCh chan<- error
+ sync.Mutex
+ ignore map[dbus.ObjectPath]int64
+ cleanIgnore int64
+ }
+}
+
+// New establishes a connection to the system bus and authenticates.
+// Callers should call Close() when done with the connection.
+func New() (*Conn, error) {
+ return newConnection(func() (*dbus.Conn, error) {
+ return dbusAuthHelloConnection(dbus.SystemBusPrivate)
+ })
+}
+
+// NewUserConnection establishes a connection to the session bus and
+// authenticates. This can be used to connect to systemd user instances.
+// Callers should call Close() when done with the connection.
+func NewUserConnection() (*Conn, error) {
+ return newConnection(func() (*dbus.Conn, error) {
+ return dbusAuthHelloConnection(dbus.SessionBusPrivate)
+ })
+}
+
+// NewSystemdConnection establishes a private, direct connection to systemd.
+// This can be used for communicating with systemd without a dbus daemon.
+// Callers should call Close() when done with the connection.
+func NewSystemdConnection() (*Conn, error) {
+ return newConnection(func() (*dbus.Conn, error) {
+ // We skip Hello when talking directly to systemd.
+ return dbusAuthConnection(func() (*dbus.Conn, error) {
+ return dbus.Dial("unix:path=/run/systemd/private")
+ })
+ })
+}
+
+// Close closes an established connection
+func (c *Conn) Close() {
+ c.sysconn.Close()
+ c.sigconn.Close()
+}
+
+func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
+ sysconn, err := createBus()
+ if err != nil {
+ return nil, err
+ }
+
+ sigconn, err := createBus()
+ if err != nil {
+ sysconn.Close()
+ return nil, err
+ }
+
+ c := &Conn{
+ sysconn: sysconn,
+ sysobj: systemdObject(sysconn),
+ sigconn: sigconn,
+ sigobj: systemdObject(sigconn),
+ }
+
+ c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
+ c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
+
+ // Setup the listeners on jobs so that we can get completions
+ c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
+ "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
+
+ c.dispatch()
+ return c, nil
+}
+
+// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager
+// interface. The value is returned in its string representation, as defined at
+// https://developer.gnome.org/glib/unstable/gvariant-text.html
+func (c *Conn) GetManagerProperty(prop string) (string, error) {
+ variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop)
+ if err != nil {
+ return "", err
+ }
+ return variant.String(), nil
+}
+
+func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
+ conn, err := createBus()
+ if err != nil {
+ return nil, err
+ }
+
+ // Only use EXTERNAL method, and hardcode the uid (not username)
+ // to avoid a username lookup (which requires a dynamically linked
+ // libc)
+ methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
+
+ err = conn.Auth(methods)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
+ conn, err := dbusAuthConnection(createBus)
+ if err != nil {
+ return nil, err
+ }
+
+ if err = conn.Hello(); err != nil {
+ conn.Close()
+ return nil, err
+ }
+
+ return conn, nil
+}
+
+func systemdObject(conn *dbus.Conn) dbus.BusObject {
+ return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go
new file mode 100644
index 0000000..f9552a3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go
@@ -0,0 +1,442 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dbus
+
+import (
+ "errors"
+ "path"
+ "strconv"
+
+ "github.com/godbus/dbus"
+)
+
+func (c *Conn) jobComplete(signal *dbus.Signal) {
+ var id uint32
+ var job dbus.ObjectPath
+ var unit string
+ var result string
+ dbus.Store(signal.Body, &id, &job, &unit, &result)
+ c.jobListener.Lock()
+ out, ok := c.jobListener.jobs[job]
+ if ok {
+ out <- result
+ delete(c.jobListener.jobs, job)
+ }
+ c.jobListener.Unlock()
+}
+
+func (c *Conn) startJob(ch chan<- string, job string, args ...interface{}) (int, error) {
+ if ch != nil {
+ c.jobListener.Lock()
+ defer c.jobListener.Unlock()
+ }
+
+ var p dbus.ObjectPath
+ err := c.sysobj.Call(job, 0, args...).Store(&p)
+ if err != nil {
+ return 0, err
+ }
+
+ if ch != nil {
+ c.jobListener.jobs[p] = ch
+ }
+
+ // ignore error since 0 is fine if conversion fails
+ jobID, _ := strconv.Atoi(path.Base(string(p)))
+
+ return jobID, nil
+}
+
+// StartUnit enqueues a start job and depending jobs, if any (unless otherwise
+// specified by the mode string).
+//
+// Takes the unit to activate, plus a mode string. The mode needs to be one of
+// replace, fail, isolate, ignore-dependencies, ignore-requirements. If
+// "replace" the call will start the unit and its dependencies, possibly
+// replacing already queued jobs that conflict with this. If "fail" the call
+// will start the unit and its dependencies, but will fail if this would change
+// an already queued job. If "isolate" the call will start the unit in question
+// and terminate all units that aren't dependencies of it. If
+// "ignore-dependencies" it will start a unit but ignore all its dependencies.
+// If "ignore-requirements" it will start a unit but only ignore the
+// requirement dependencies. It is not recommended to make use of the latter
+// two options.
+//
+// If the provided channel is non-nil, a result string will be sent to it upon
+// job completion: one of done, canceled, timeout, failed, dependency, skipped.
+// done indicates successful execution of a job. canceled indicates that a job
+// has been canceled before it finished execution. timeout indicates that the
+// job timeout was reached. failed indicates that the job failed. dependency
+// indicates that a job this job has been depending on failed and the job hence
+// has been removed too. skipped indicates that a job was skipped because it
+// didn't apply to the units current state.
+//
+// If no error occurs, the ID of the underlying systemd job will be returned. There
+// does exist the possibility for no error to be returned, but for the returned job
+// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint
+// should not be considered authoritative.
+//
+// If an error does occur, it will be returned to the user alongside a job ID of 0.
+func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode)
+}
+
+// StopUnit is similar to StartUnit but stops the specified unit rather
+// than starting it.
+func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode)
+}
+
+// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
+func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
+}
+
+// RestartUnit restarts a service. If a service is restarted that isn't
+// running it will be started.
+func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
+}
+
+// TryRestartUnit is like RestartUnit, except that a service that isn't running
+// is not affected by the restart.
+func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
+}
+
+// ReloadOrRestart attempts a reload if the unit supports it and use a restart
+// otherwise.
+func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
+}
+
+// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
+// flavored restart otherwise.
+func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
+}
+
+// StartTransientUnit() may be used to create and start a transient unit, which
+// will be released as soon as it is not running or referenced anymore or the
+// system is rebooted. name is the unit name including suffix, and must be
+// unique. mode is the same as in StartUnit(), properties contains properties
+// of the unit.
+func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) {
+ return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
+}
+
+// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
+// processes are killed.
+func (c *Conn) KillUnit(name string, signal int32) {
+ c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
+}
+
+// ResetFailedUnit resets the "failed" state of a specific unit.
+func (c *Conn) ResetFailedUnit(name string) error {
+ return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
+}
+
+// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
+func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
+ var err error
+ var props map[string]dbus.Variant
+
+ path := unitPath(unit)
+ if !path.IsValid() {
+ return nil, errors.New("invalid unit name: " + unit)
+ }
+
+ obj := c.sysconn.Object("org.freedesktop.systemd1", path)
+ err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
+ if err != nil {
+ return nil, err
+ }
+
+ out := make(map[string]interface{}, len(props))
+ for k, v := range props {
+ out[k] = v.Value()
+ }
+
+ return out, nil
+}
+
+// GetUnitProperties takes the unit name and returns all of its dbus object properties.
+func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
+ return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
+}
+
+func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
+ var err error
+ var prop dbus.Variant
+
+ path := unitPath(unit)
+ if !path.IsValid() {
+ return nil, errors.New("invalid unit name: " + unit)
+ }
+
+ obj := c.sysconn.Object("org.freedesktop.systemd1", path)
+ err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Property{Name: propertyName, Value: prop}, nil
+}
+
+func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
+ return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
+}
+
+// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
+// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
+// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
+func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
+ return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
+}
+
+// SetUnitProperties() may be used to modify certain unit properties at runtime.
+// Not all properties may be changed at runtime, but many resource management
+// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
+// instantly, and stored on disk for future boots, unless runtime is true, in which
+// case the settings only apply until the next reboot. name is the name of the unit
+// to modify. properties are the settings to set, encoded as an array of property
+// name and value pairs.
+func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
+ return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store()
+}
+
+func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
+ return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
+}
+
+type UnitStatus struct {
+ Name string // The primary unit name as string
+ Description string // The human readable description string
+ LoadState string // The load state (i.e. whether the unit file has been loaded successfully)
+ ActiveState string // The active state (i.e. whether the unit is currently started or not)
+ SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not)
+ Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
+ Path dbus.ObjectPath // The unit object path
+ JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise
+ JobType string // The job type as string
+ JobPath dbus.ObjectPath // The job object path
+}
+
+// ListUnits returns an array with all currently loaded units. Note that
+// units may be known by multiple names at the same time, and hence there might
+// be more unit names loaded than actual units behind them.
+func (c *Conn) ListUnits() ([]UnitStatus, error) {
+ result := make([][]interface{}, 0)
+ err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ resultInterface := make([]interface{}, len(result))
+ for i := range result {
+ resultInterface[i] = result[i]
+ }
+
+ status := make([]UnitStatus, len(result))
+ statusInterface := make([]interface{}, len(status))
+ for i := range status {
+ statusInterface[i] = &status[i]
+ }
+
+ err = dbus.Store(resultInterface, statusInterface...)
+ if err != nil {
+ return nil, err
+ }
+
+ return status, nil
+}
+
+type UnitFile struct {
+ Path string
+ Type string
+}
+
+// ListUnitFiles returns an array of all available units on disk.
+func (c *Conn) ListUnitFiles() ([]UnitFile, error) {
+ result := make([][]interface{}, 0)
+ err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ resultInterface := make([]interface{}, len(result))
+ for i := range result {
+ resultInterface[i] = result[i]
+ }
+
+ files := make([]UnitFile, len(result))
+ fileInterface := make([]interface{}, len(files))
+ for i := range files {
+ fileInterface[i] = &files[i]
+ }
+
+ err = dbus.Store(resultInterface, fileInterface...)
+ if err != nil {
+ return nil, err
+ }
+
+ return files, nil
+}
+
+type LinkUnitFileChange EnableUnitFileChange
+
+// LinkUnitFiles() links unit files (that are located outside of the
+// usual unit search paths) into the unit search path.
+//
+// It takes a list of absolute paths to unit files to link and two
+// booleans. The first boolean controls whether the unit shall be
+// enabled for runtime only (true, /run), or persistently (false,
+// /etc).
+// The second controls whether symlinks pointing to other units shall
+// be replaced if necessary.
+//
+// This call returns a list of the changes made. The list consists of
+// structures with three strings: the type of the change (one of symlink
+// or unlink), the file name of the symlink and the destination of the
+// symlink.
+func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) {
+ result := make([][]interface{}, 0)
+ err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ resultInterface := make([]interface{}, len(result))
+ for i := range result {
+ resultInterface[i] = result[i]
+ }
+
+ changes := make([]LinkUnitFileChange, len(result))
+ changesInterface := make([]interface{}, len(changes))
+ for i := range changes {
+ changesInterface[i] = &changes[i]
+ }
+
+ err = dbus.Store(resultInterface, changesInterface...)
+ if err != nil {
+ return nil, err
+ }
+
+ return changes, nil
+}
+
+// EnableUnitFiles() may be used to enable one or more units in the system (by
+// creating symlinks to them in /etc or /run).
+//
+// It takes a list of unit files to enable (either just file names or full
+// absolute paths if the unit files are residing outside the usual unit
+// search paths), and two booleans: the first controls whether the unit shall
+// be enabled for runtime only (true, /run), or persistently (false, /etc).
+// The second one controls whether symlinks pointing to other units shall
+// be replaced if necessary.
+//
+// This call returns one boolean and an array with the changes made. The
+// boolean signals whether the unit files contained any enablement
+// information (i.e. an [Install]) section. The changes list consists of
+// structures with three strings: the type of the change (one of symlink
+// or unlink), the file name of the symlink and the destination of the
+// symlink.
+func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
+ var carries_install_info bool
+
+ result := make([][]interface{}, 0)
+ err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
+ if err != nil {
+ return false, nil, err
+ }
+
+ resultInterface := make([]interface{}, len(result))
+ for i := range result {
+ resultInterface[i] = result[i]
+ }
+
+ changes := make([]EnableUnitFileChange, len(result))
+ changesInterface := make([]interface{}, len(changes))
+ for i := range changes {
+ changesInterface[i] = &changes[i]
+ }
+
+ err = dbus.Store(resultInterface, changesInterface...)
+ if err != nil {
+ return false, nil, err
+ }
+
+ return carries_install_info, changes, nil
+}
+
+type EnableUnitFileChange struct {
+ Type string // Type of the change (one of symlink or unlink)
+ Filename string // File name of the symlink
+ Destination string // Destination of the symlink
+}
+
+// DisableUnitFiles() may be used to disable one or more units in the system (by
+// removing symlinks to them from /etc or /run).
+//
+// It takes a list of unit files to disable (either just file names or full
+// absolute paths if the unit files are residing outside the usual unit
+// search paths), and one boolean: whether the unit was enabled for runtime
+// only (true, /run), or persistently (false, /etc).
+//
+// This call returns an array with the changes made. The changes list
+// consists of structures with three strings: the type of the change (one of
+// symlink or unlink), the file name of the symlink and the destination of the
+// symlink.
+func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
+ result := make([][]interface{}, 0)
+ err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ resultInterface := make([]interface{}, len(result))
+ for i := range result {
+ resultInterface[i] = result[i]
+ }
+
+ changes := make([]DisableUnitFileChange, len(result))
+ changesInterface := make([]interface{}, len(changes))
+ for i := range changes {
+ changesInterface[i] = &changes[i]
+ }
+
+ err = dbus.Store(resultInterface, changesInterface...)
+ if err != nil {
+ return nil, err
+ }
+
+ return changes, nil
+}
+
+type DisableUnitFileChange struct {
+ Type string // Type of the change (one of symlink or unlink)
+ Filename string // File name of the symlink
+ Destination string // Destination of the symlink
+}
+
+// Reload instructs systemd to scan for and reload unit files. This is
+// equivalent to a 'systemctl daemon-reload'.
+func (c *Conn) Reload() error {
+ return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
+}
+
+func unitPath(name string) dbus.ObjectPath {
+ return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go
new file mode 100644
index 0000000..7520011
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go
@@ -0,0 +1,218 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dbus
+
+import (
+ "github.com/godbus/dbus"
+)
+
+// From the systemd docs:
+//
+// The properties array of StartTransientUnit() may take many of the settings
+// that may also be configured in unit files. Not all parameters are currently
+// accepted though, but we plan to cover more properties with future release.
+// Currently you may set the Description, Slice and all dependency types of
+// units, as well as RemainAfterExit, ExecStart for service units,
+// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares,
+// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth,
+// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit,
+// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map
+// directly to their counterparts in unit files and as normal D-Bus object
+// properties. The exception here is the PIDs field of scope units which is
+// used for construction of the scope only and specifies the initial PIDs to
+// add to the scope object.
+
+type Property struct {
+ Name string
+ Value dbus.Variant
+}
+
+type PropertyCollection struct {
+ Name string
+ Properties []Property
+}
+
+type execStart struct {
+ Path string // the binary path to execute
+ Args []string // an array with all arguments to pass to the executed command, starting with argument 0
+ UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly
+}
+
+// PropExecStart sets the ExecStart service property. The first argument is a
+// slice with the binary path to execute followed by the arguments to pass to
+// the executed command. See
+// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=
+func PropExecStart(command []string, uncleanIsFailure bool) Property {
+ execStarts := []execStart{
+ execStart{
+ Path: command[0],
+ Args: command,
+ UncleanIsFailure: uncleanIsFailure,
+ },
+ }
+
+ return Property{
+ Name: "ExecStart",
+ Value: dbus.MakeVariant(execStarts),
+ }
+}
+
+// PropRemainAfterExit sets the RemainAfterExit service property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit=
+func PropRemainAfterExit(b bool) Property {
+ return Property{
+ Name: "RemainAfterExit",
+ Value: dbus.MakeVariant(b),
+ }
+}
+
+// PropDescription sets the Description unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description=
+func PropDescription(desc string) Property {
+ return Property{
+ Name: "Description",
+ Value: dbus.MakeVariant(desc),
+ }
+}
+
+func propDependency(name string, units []string) Property {
+ return Property{
+ Name: name,
+ Value: dbus.MakeVariant(units),
+ }
+}
+
+// PropRequires sets the Requires unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=
+func PropRequires(units ...string) Property {
+ return propDependency("Requires", units)
+}
+
+// PropRequiresOverridable sets the RequiresOverridable unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable=
+func PropRequiresOverridable(units ...string) Property {
+ return propDependency("RequiresOverridable", units)
+}
+
+// PropRequisite sets the Requisite unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite=
+func PropRequisite(units ...string) Property {
+ return propDependency("Requisite", units)
+}
+
+// PropRequisiteOverridable sets the RequisiteOverridable unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable=
+func PropRequisiteOverridable(units ...string) Property {
+ return propDependency("RequisiteOverridable", units)
+}
+
+// PropWants sets the Wants unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=
+func PropWants(units ...string) Property {
+ return propDependency("Wants", units)
+}
+
+// PropBindsTo sets the BindsTo unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo=
+func PropBindsTo(units ...string) Property {
+ return propDependency("BindsTo", units)
+}
+
+// PropRequiredBy sets the RequiredBy unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy=
+func PropRequiredBy(units ...string) Property {
+ return propDependency("RequiredBy", units)
+}
+
+// PropRequiredByOverridable sets the RequiredByOverridable unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable=
+func PropRequiredByOverridable(units ...string) Property {
+ return propDependency("RequiredByOverridable", units)
+}
+
+// PropWantedBy sets the WantedBy unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy=
+func PropWantedBy(units ...string) Property {
+ return propDependency("WantedBy", units)
+}
+
+// PropBoundBy sets the BoundBy unit property. See
+// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy=
+func PropBoundBy(units ...string) Property {
+ return propDependency("BoundBy", units)
+}
+
+// PropConflicts sets the Conflicts unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=
+func PropConflicts(units ...string) Property {
+ return propDependency("Conflicts", units)
+}
+
+// PropConflictedBy sets the ConflictedBy unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy=
+func PropConflictedBy(units ...string) Property {
+ return propDependency("ConflictedBy", units)
+}
+
+// PropBefore sets the Before unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
+func PropBefore(units ...string) Property {
+ return propDependency("Before", units)
+}
+
+// PropAfter sets the After unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After=
+func PropAfter(units ...string) Property {
+ return propDependency("After", units)
+}
+
+// PropOnFailure sets the OnFailure unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure=
+func PropOnFailure(units ...string) Property {
+ return propDependency("OnFailure", units)
+}
+
+// PropTriggers sets the Triggers unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers=
+func PropTriggers(units ...string) Property {
+ return propDependency("Triggers", units)
+}
+
+// PropTriggeredBy sets the TriggeredBy unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy=
+func PropTriggeredBy(units ...string) Property {
+ return propDependency("TriggeredBy", units)
+}
+
+// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo=
+func PropPropagatesReloadTo(units ...string) Property {
+ return propDependency("PropagatesReloadTo", units)
+}
+
+// PropRequiresMountsFor sets the RequiresMountsFor unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor=
+func PropRequiresMountsFor(units ...string) Property {
+ return propDependency("RequiresMountsFor", units)
+}
+
+// PropSlice sets the Slice unit property. See
+// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice=
+func PropSlice(slice string) Property {
+ return Property{
+ Name: "Slice",
+ Value: dbus.MakeVariant(slice),
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go
new file mode 100644
index 0000000..f92e6fb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go
@@ -0,0 +1,47 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dbus
+
+type set struct {
+ data map[string]bool
+}
+
+func (s *set) Add(value string) {
+ s.data[value] = true
+}
+
+func (s *set) Remove(value string) {
+ delete(s.data, value)
+}
+
+func (s *set) Contains(value string) (exists bool) {
+ _, exists = s.data[value]
+ return
+}
+
+func (s *set) Length() int {
+ return len(s.data)
+}
+
+func (s *set) Values() (values []string) {
+ for val, _ := range s.data {
+ values = append(values, val)
+ }
+ return
+}
+
+func newSet() *set {
+ return &set{make(map[string]bool)}
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go
new file mode 100644
index 0000000..9964514
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go
@@ -0,0 +1,250 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dbus
+
+import (
+ "errors"
+ "time"
+
+ "github.com/godbus/dbus"
+)
+
+const (
+ cleanIgnoreInterval = int64(10 * time.Second)
+ ignoreInterval = int64(30 * time.Millisecond)
+)
+
+// Subscribe sets up this connection to subscribe to all systemd dbus events.
+// This is required before calling SubscribeUnits. When the connection closes
+// systemd will automatically stop sending signals so there is no need to
+// explicitly call Unsubscribe().
+func (c *Conn) Subscribe() error {
+ c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
+ "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
+ c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
+ "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
+
+ err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Unsubscribe this connection from systemd dbus events.
+func (c *Conn) Unsubscribe() error {
+ err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (c *Conn) dispatch() {
+ ch := make(chan *dbus.Signal, signalBuffer)
+
+ c.sigconn.Signal(ch)
+
+ go func() {
+ for {
+ signal, ok := <-ch
+ if !ok {
+ return
+ }
+
+ if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" {
+ c.jobComplete(signal)
+ }
+
+ if c.subscriber.updateCh == nil {
+ continue
+ }
+
+ var unitPath dbus.ObjectPath
+ switch signal.Name {
+ case "org.freedesktop.systemd1.Manager.JobRemoved":
+ unitName := signal.Body[2].(string)
+ c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
+ case "org.freedesktop.systemd1.Manager.UnitNew":
+ unitPath = signal.Body[1].(dbus.ObjectPath)
+ case "org.freedesktop.DBus.Properties.PropertiesChanged":
+ if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
+ unitPath = signal.Path
+ }
+ }
+
+ if unitPath == dbus.ObjectPath("") {
+ continue
+ }
+
+ c.sendSubStateUpdate(unitPath)
+ }
+ }()
+}
+
+// Returns two unbuffered channels which will receive all changed units every
+// interval. Deleted units are sent as nil.
+func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
+ return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
+}
+
+// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
+// size of the channels, the comparison function for detecting changes and a filter
+// function for cutting down on the noise that your channel receives.
+func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
+ old := make(map[string]*UnitStatus)
+ statusChan := make(chan map[string]*UnitStatus, buffer)
+ errChan := make(chan error, buffer)
+
+ go func() {
+ for {
+ timerChan := time.After(interval)
+
+ units, err := c.ListUnits()
+ if err == nil {
+ cur := make(map[string]*UnitStatus)
+ for i := range units {
+ if filterUnit != nil && filterUnit(units[i].Name) {
+ continue
+ }
+ cur[units[i].Name] = &units[i]
+ }
+
+ // add all new or changed units
+ changed := make(map[string]*UnitStatus)
+ for n, u := range cur {
+ if oldU, ok := old[n]; !ok || isChanged(oldU, u) {
+ changed[n] = u
+ }
+ delete(old, n)
+ }
+
+ // add all deleted units
+ for oldN := range old {
+ changed[oldN] = nil
+ }
+
+ old = cur
+
+ if len(changed) != 0 {
+ statusChan <- changed
+ }
+ } else {
+ errChan <- err
+ }
+
+ <-timerChan
+ }
+ }()
+
+ return statusChan, errChan
+}
+
+type SubStateUpdate struct {
+ UnitName string
+ SubState string
+}
+
+// SetSubStateSubscriber writes to updateCh when any unit's substate changes.
+// Although this writes to updateCh on every state change, the reported state
+// may be more recent than the change that generated it (due to an unavoidable
+// race in the systemd dbus interface). That is, this method provides a good
+// way to keep a current view of all units' states, but is not guaranteed to
+// show every state transition they go through. Furthermore, state changes
+// will only be written to the channel with non-blocking writes. If updateCh
+// is full, it attempts to write an error to errCh; if errCh is full, the error
+// passes silently.
+func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
+ c.subscriber.Lock()
+ defer c.subscriber.Unlock()
+ c.subscriber.updateCh = updateCh
+ c.subscriber.errCh = errCh
+}
+
+func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
+ c.subscriber.Lock()
+ defer c.subscriber.Unlock()
+
+ if c.shouldIgnore(path) {
+ return
+ }
+
+ info, err := c.GetUnitProperties(string(path))
+ if err != nil {
+ select {
+ case c.subscriber.errCh <- err:
+ default:
+ }
+ }
+
+ name := info["Id"].(string)
+ substate := info["SubState"].(string)
+
+ update := &SubStateUpdate{name, substate}
+ select {
+ case c.subscriber.updateCh <- update:
+ default:
+ select {
+ case c.subscriber.errCh <- errors.New("update channel full!"):
+ default:
+ }
+ }
+
+ c.updateIgnore(path, info)
+}
+
+// The ignore functions work around a wart in the systemd dbus interface.
+// Requesting the properties of an unloaded unit will cause systemd to send a
+// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's
+// properties on UnitNew (as that's the only indication of a new unit coming up
+// for the first time), we would enter an infinite loop if we did not attempt
+// to detect and ignore these spurious signals. The signal themselves are
+// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an
+// unloaded unit's signals for a short time after requesting its properties.
+// This means that we will miss e.g. a transient unit being restarted
+// *immediately* upon failure and also a transient unit being started
+// immediately after requesting its status (with systemctl status, for example,
+// because this causes a UnitNew signal to be sent which then causes us to fetch
+// the properties).
+
+func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
+ t, ok := c.subscriber.ignore[path]
+ return ok && t >= time.Now().UnixNano()
+}
+
+func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
+ c.cleanIgnore()
+
+ // unit is unloaded - it will trigger bad systemd dbus behavior
+ if info["LoadState"].(string) == "not-found" {
+ c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
+ }
+}
+
+// without this, ignore would grow unboundedly over time
+func (c *Conn) cleanIgnore() {
+ now := time.Now().UnixNano()
+ if c.subscriber.cleanIgnore < now {
+ c.subscriber.cleanIgnore = now + cleanIgnoreInterval
+
+ for p, t := range c.subscriber.ignore {
+ if t < now {
+ delete(c.subscriber.ignore, p)
+ }
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go
new file mode 100644
index 0000000..5b408d5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go
@@ -0,0 +1,57 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dbus
+
+import (
+ "time"
+)
+
+// SubscriptionSet returns a subscription set which is like conn.Subscribe but
+// can filter to only return events for a set of units.
+type SubscriptionSet struct {
+ *set
+ conn *Conn
+}
+
+func (s *SubscriptionSet) filter(unit string) bool {
+ return !s.Contains(unit)
+}
+
+// Subscribe starts listening for dbus events for all of the units in the set.
+// Returns channels identical to conn.SubscribeUnits.
+func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) {
+ // TODO: Make fully evented by using systemd 209 with properties changed values
+ return s.conn.SubscribeUnitsCustom(time.Second, 0,
+ mismatchUnitStatus,
+ func(unit string) bool { return s.filter(unit) },
+ )
+}
+
+// NewSubscriptionSet returns a new subscription set.
+func (conn *Conn) NewSubscriptionSet() *SubscriptionSet {
+ return &SubscriptionSet{newSet(), conn}
+}
+
+// mismatchUnitStatus returns true if the provided UnitStatus objects
+// are not equivalent. false is returned if the objects are equivalent.
+// Only the Name, Description and state-related fields are used in
+// the comparison.
+func mismatchUnitStatus(u1, u2 *UnitStatus) bool {
+ return u1.Name != u2.Name ||
+ u1.Description != u2.Description ||
+ u1.LoadState != u2.LoadState ||
+ u1.ActiveState != u2.ActiveState ||
+ u1.SubState != u2.SubState
+}
diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/util/util.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/util/util.go
new file mode 100644
index 0000000..f9f0b2a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/util/util.go
@@ -0,0 +1,270 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package util contains utility functions related to systemd that applications
+// can use to check things like whether systemd is running. Note that some of
+// these functions attempt to manually load systemd libraries at runtime rather
+// than linking against them.
+package util
+
+// #cgo LDFLAGS: -ldl
+// #include
+// #include
+// #include
+// #include
+//
+// int
+// my_sd_pid_get_owner_uid(void *f, pid_t pid, uid_t *uid)
+// {
+// int (*sd_pid_get_owner_uid)(pid_t, uid_t *);
+//
+// sd_pid_get_owner_uid = (int (*)(pid_t, uid_t *))f;
+// return sd_pid_get_owner_uid(pid, uid);
+// }
+//
+// int
+// my_sd_pid_get_unit(void *f, pid_t pid, char **unit)
+// {
+// int (*sd_pid_get_unit)(pid_t, char **);
+//
+// sd_pid_get_unit = (int (*)(pid_t, char **))f;
+// return sd_pid_get_unit(pid, unit);
+// }
+//
+// int
+// my_sd_pid_get_slice(void *f, pid_t pid, char **slice)
+// {
+// int (*sd_pid_get_slice)(pid_t, char **);
+//
+// sd_pid_get_slice = (int (*)(pid_t, char **))f;
+// return sd_pid_get_slice(pid, slice);
+// }
+//
+// int
+// am_session_leader()
+// {
+// return (getsid(0) == getpid());
+// }
+import "C"
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+var ErrSoNotFound = errors.New("unable to open a handle to libsystemd")
+
+// libHandle represents an open handle to the systemd C library
+type libHandle struct {
+ handle unsafe.Pointer
+ libname string
+}
+
+func (h *libHandle) Close() error {
+ if r := C.dlclose(h.handle); r != 0 {
+ return fmt.Errorf("error closing %v: %d", h.libname, r)
+ }
+ return nil
+}
+
+// getHandle tries to get a handle to a systemd library (.so), attempting to
+// access it by several different names and returning the first that is
+// successfully opened. Callers are responsible for closing the handler.
+// If no library can be successfully opened, an error is returned.
+func getHandle() (*libHandle, error) {
+ for _, name := range []string{
+ // systemd < 209
+ "libsystemd-login.so",
+ "libsystemd-login.so.0",
+
+ // systemd >= 209 merged libsystemd-login into libsystemd proper
+ "libsystemd.so",
+ "libsystemd.so.0",
+ } {
+ libname := C.CString(name)
+ defer C.free(unsafe.Pointer(libname))
+ handle := C.dlopen(libname, C.RTLD_LAZY)
+ if handle != nil {
+ h := &libHandle{
+ handle: handle,
+ libname: name,
+ }
+ return h, nil
+ }
+ }
+ return nil, ErrSoNotFound
+}
+
+// GetRunningSlice attempts to retrieve the name of the systemd slice in which
+// the current process is running.
+// This function is a wrapper around the libsystemd C library; if it cannot be
+// opened, an error is returned.
+func GetRunningSlice() (slice string, err error) {
+ var h *libHandle
+ h, err = getHandle()
+ if err != nil {
+ return
+ }
+ defer func() {
+ if err1 := h.Close(); err1 != nil {
+ err = err1
+ }
+ }()
+
+ sym := C.CString("sd_pid_get_slice")
+ defer C.free(unsafe.Pointer(sym))
+ sd_pid_get_slice := C.dlsym(h.handle, sym)
+ if sd_pid_get_slice == nil {
+ err = fmt.Errorf("error resolving sd_pid_get_slice function")
+ return
+ }
+
+ var s string
+ sl := C.CString(s)
+ defer C.free(unsafe.Pointer(sl))
+
+ ret := C.my_sd_pid_get_slice(sd_pid_get_slice, 0, &sl)
+ if ret < 0 {
+ err = fmt.Errorf("error calling sd_pid_get_slice: %v", syscall.Errno(-ret))
+ return
+ }
+
+ return C.GoString(sl), nil
+}
+
+// RunningFromSystemService tries to detect whether the current process has
+// been invoked from a system service. The condition for this is whether the
+// process is _not_ a user process. User processes are those running in session
+// scopes or under per-user `systemd --user` instances.
+//
+// To avoid false positives on systems without `pam_systemd` (which is
+// responsible for creating user sessions), this function also uses a heuristic
+// to detect whether it's being invoked from a session leader process. This is
+// the case if the current process is executed directly from a service file
+// (e.g. with `ExecStart=/this/cmd`). Note that this heuristic will fail if the
+// command is instead launched in a subshell or similar so that it is not
+// session leader (e.g. `ExecStart=/bin/bash -c "/this/cmd"`)
+//
+// This function is a wrapper around the libsystemd C library; if this is
+// unable to successfully open a handle to the library for any reason (e.g. it
+// cannot be found), an errr will be returned
+func RunningFromSystemService() (ret bool, err error) {
+ var h *libHandle
+ h, err = getHandle()
+ if err != nil {
+ return
+ }
+ defer func() {
+ if err1 := h.Close(); err1 != nil {
+ err = err1
+ }
+ }()
+
+ sym := C.CString("sd_pid_get_owner_uid")
+ defer C.free(unsafe.Pointer(sym))
+ sd_pid_get_owner_uid := C.dlsym(h.handle, sym)
+ if sd_pid_get_owner_uid == nil {
+ err = fmt.Errorf("error resolving sd_pid_get_owner_uid function")
+ return
+ }
+
+ var uid C.uid_t
+ errno := C.my_sd_pid_get_owner_uid(sd_pid_get_owner_uid, 0, &uid)
+ serrno := syscall.Errno(-errno)
+ // when we're running from a unit file, sd_pid_get_owner_uid returns
+ // ENOENT (systemd <220) or ENXIO (systemd >=220)
+ switch {
+ case errno >= 0:
+ ret = false
+ case serrno == syscall.ENOENT, serrno == syscall.ENXIO:
+ // Since the implementation of sessions in systemd relies on
+ // the `pam_systemd` module, using the sd_pid_get_owner_uid
+ // heuristic alone can result in false positives if that module
+ // (or PAM itself) is not present or properly configured on the
+ // system. As such, we also check if we're the session leader,
+ // which should be the case if we're invoked from a unit file,
+ // but not if e.g. we're invoked from the command line from a
+ // user's login session
+ ret = C.am_session_leader() == 1
+ default:
+ err = fmt.Errorf("error calling sd_pid_get_owner_uid: %v", syscall.Errno(-errno))
+ }
+ return
+}
+
+// CurrentUnitName attempts to retrieve the name of the systemd system unit
+// from which the calling process has been invoked. It wraps the systemd
+// `sd_pid_get_unit` call, with the same caveat: for processes not part of a
+// systemd system unit, this function will return an error.
+func CurrentUnitName() (unit string, err error) {
+ var h *libHandle
+ h, err = getHandle()
+ if err != nil {
+ return
+ }
+ defer func() {
+ if err1 := h.Close(); err1 != nil {
+ err = err1
+ }
+ }()
+
+ sym := C.CString("sd_pid_get_unit")
+ defer C.free(unsafe.Pointer(sym))
+ sd_pid_get_unit := C.dlsym(h.handle, sym)
+ if sd_pid_get_unit == nil {
+ err = fmt.Errorf("error resolving sd_pid_get_unit function")
+ return
+ }
+
+ var s string
+ u := C.CString(s)
+ defer C.free(unsafe.Pointer(u))
+
+ ret := C.my_sd_pid_get_unit(sd_pid_get_unit, 0, &u)
+ if ret < 0 {
+ err = fmt.Errorf("error calling sd_pid_get_unit: %v", syscall.Errno(-ret))
+ return
+ }
+
+ unit = C.GoString(u)
+ return
+}
+
+// IsRunningSystemd checks whether the host was booted with systemd as its init
+// system. This functions similarly to systemd's `sd_booted(3)`: internally, it
+// checks whether /run/systemd/system/ exists and is a directory.
+// http://www.freedesktop.org/software/systemd/man/sd_booted.html
+func IsRunningSystemd() bool {
+ fi, err := os.Lstat("/run/systemd/system")
+ if err != nil {
+ return false
+ }
+ return fi.IsDir()
+}
+
+// GetMachineID returns a host's 128-bit machine ID as a string. This functions
+// similarly to systemd's `sd_id128_get_machine`: internally, it simply reads
+// the contents of /etc/machine-id
+// http://www.freedesktop.org/software/systemd/man/sd_id128_get_machine.html
+func GetMachineID() (string, error) {
+ machineID, err := ioutil.ReadFile("/etc/machine-id")
+ if err != nil {
+ return "", fmt.Errorf("failed to read /etc/machine-id: %v", err)
+ }
+ return strings.TrimSpace(string(machineID)), nil
+}
diff --git a/Godeps/_workspace/src/github.com/godbus/dbus/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/godbus/dbus/CONTRIBUTING.md
new file mode 100644
index 0000000..c88f9b2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/godbus/dbus/CONTRIBUTING.md
@@ -0,0 +1,50 @@
+# How to Contribute
+
+## Getting Started
+
+- Fork the repository on GitHub
+- Read the [README](README.markdown) for build and test instructions
+- Play with the project, submit bugs, submit patches!
+
+## Contribution Flow
+
+This is a rough outline of what a contributor's workflow looks like:
+
+- Create a topic branch from where you want to base your work (usually master).
+- Make commits of logical units.
+- Make sure your commit messages are in the proper format (see below).
+- Push your changes to a topic branch in your fork of the repository.
+- Make sure the tests pass, and add any new tests as appropriate.
+- Submit a pull request to the original repository.
+
+Thanks for your contributions!
+
+### Format of the Commit Message
+
+We follow a rough convention for commit messages that is designed to answer two
+questions: what changed and why. The subject line should feature the what and
+the body of the commit should describe the why.
+
+```
+scripts: add the test-cluster command
+
+this uses tmux to setup a test cluster that you can easily kill and
+start for debugging.
+
+Fixes #38
+```
+
+The format can be described more formally as follows:
+
+```
+:
+
+
+
+