resource/systemd/unit/systemd_linux.go
// Copyright © 2016 Asteris, LLC
//
// 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.
// +build linux
package unit
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/coreos/go-systemd/dbus"
"github.com/pkg/errors"
)
// LinuxExecutor provides a command executor for interacting with systemd on Linux
type LinuxExecutor struct {
dbusConn SystemdConnection
}
// ListUnits will use dbus to get a list of all units
func (l LinuxExecutor) ListUnits() ([]*Unit, error) {
var units []*Unit
unitStatuses, err := l.dbusConn.ListUnits()
if err != nil {
return units, err
}
for _, status := range unitStatuses {
unit, err := unitFromStatus(l.dbusConn, &status)
if err != nil {
return units, err
}
units = append(units, unit)
}
return units, nil
}
// QueryUnit will use dbus to get the unit status by name
func (l LinuxExecutor) QueryUnit(unitName string, verify bool) (*Unit, error) {
units, err := l.ListUnits()
var toReturn *Unit
if err != nil {
return nil, errors.Wrap(err, "Cannot query unit by name")
}
for _, u := range units {
if u.Name == unitName {
toReturn = u
}
}
if toReturn == nil {
if verify {
return nil, fmt.Errorf("%s: no such unit known", unitName)
}
toReturn = &Unit{ActiveState: "unknown"}
}
return toReturn, nil
}
// StartUnit will use dbus to start a unit
func (l LinuxExecutor) StartUnit(u *Unit) error {
return runDbusCommand(l.dbusConn.StartUnit, u.Name, "replace", "starting")
}
// StopUnit will use dbus to stop a unit
func (l LinuxExecutor) StopUnit(u *Unit) error {
return runDbusCommand(l.dbusConn.StopUnit, u.Name, "replace", "stopping")
}
// RestartUnit will restart a unit
func (l LinuxExecutor) RestartUnit(u *Unit) error {
return runDbusCommand(l.dbusConn.RestartUnit, u.Name, "replace", "restarting")
}
// ReloadUnit will use dbus to reload a unit
func (l LinuxExecutor) ReloadUnit(u *Unit) error {
return runDbusCommand(l.dbusConn.ReloadUnit, u.Name, "replace", "reloading")
}
// SendSignal will send a signal
func (l LinuxExecutor) SendSignal(u *Unit, signal Signal) {
l.dbusConn.KillUnit(u.Name, int32(signal))
}
func runDbusCommand(f func(string, string, chan<- string) (int, error), name, mode, operation string) error {
ch := make(chan string)
defer close(ch)
_, err := f(name, mode, ch)
if err != nil {
log.WithFields(log.Fields{
"operation": operation,
"unit name": name,
"mode": mode,
}).Errorf("operation returned an error: %v", err)
return err
}
msg := <-ch
switch msg {
case "done":
return nil
case "canceled":
return fmt.Errorf("operation was cancelled while %s: %s", operation, name)
case "timeout":
return fmt.Errorf("operation timed out while %s: %s", operation, name)
case "failed":
return fmt.Errorf("operation failed while %s: %s", operation, name)
case "dependency":
return fmt.Errorf("operation depends on a failed unit when %s: %s", operation, name)
case "skipped":
return nil
}
log.WithFields(log.Fields{
"operation": operation,
"unit name": name,
"message": msg,
}).Errorf("systemd returned an unknown message")
return fmt.Errorf("unknown systemd status: %s", msg)
}
func realExecutor() (SystemdExecutor, error) {
conn, err := dbus.New()
if err != nil {
return nil, err
}
return LinuxExecutor{conn}, nil
}
// Close will close a connection
func (l LinuxExecutor) Close() {
l.dbusConn.Close()
}
// NewSystemExecutor will generate a new real executor
func NewSystemExecutor() SystemdExecutor {
executor, err := realExecutor()
if err != nil {
panic(err)
}
return executor
}
func unitFromStatus(conn SystemdConnection, status *dbus.UnitStatus) (*Unit, error) {
u := newFromStatus(status)
properties, err := conn.GetUnitProperties(status.Name)
if err != nil {
return nil, err
}
u.SetProperties(properties)
if u.Type.HasProperties() {
typeProperties, err := conn.GetUnitTypeProperties(status.Name, u.Type.UnitTypeString())
if err != nil {
return nil, err
}
u.SetTypedProperties(typeProperties)
}
return u, nil
}
func (l LinuxExecutor) unitExists(unitName string) (bool, error) {
units, err := l.ListUnits()
if err != nil {
return false, err
}
for _, u := range units {
if u.Name == unitName {
return true, nil
}
}
return false, nil
}