engine: resources: cron: Add auto edges from SvcRes

This commit is contained in:
Jonathan Gold
2018-12-14 23:07:45 -05:00
parent 68ef312233
commit da0ffa5e56
3 changed files with 142 additions and 28 deletions

View File

@@ -21,7 +21,8 @@ import (
"bytes"
"context"
"fmt"
"os"
"os/user"
"path"
"strings"
"time"
@@ -141,15 +142,13 @@ func (obj *CronRes) Default() engine.Res {
// validate and initialize the nested file resource and to apply the file state
// in CheckApply.
func (obj *CronRes) makeComposite() (*FileRes, error) {
// root timer
path := fmt.Sprintf("/etc/systemd/system/%s.timer", obj.Name())
if obj.Session {
// user timer
path = fmt.Sprintf("%s/.config/systemd/user/%s.timer", os.Getenv("HOME"), obj.Name())
}
res, err := engine.NewNamedResource("file", path)
p, err := obj.UnitFilePath()
if err != nil {
return nil, err
return nil, errwrap.Wrapf(err, "error generating unit file path")
}
res, err := engine.NewNamedResource("file", p)
if err != nil {
return nil, errwrap.Wrapf(err, "error creating nested file resource")
}
file, ok := res.(*FileRes)
if !ok {
@@ -260,14 +259,12 @@ func (obj *CronRes) Watch() error {
bus.Signal(dbusChan)
defer bus.RemoveSignal(dbusChan) // not needed here, but nice for symmetry
// root timer
path := fmt.Sprintf("/etc/systemd/system/%s.timer", obj.Name())
if obj.Session {
// user timer
path = fmt.Sprintf("%s/.config/systemd/user/%s.timer", os.Getenv("HOME"), obj.Name())
p, err := obj.UnitFilePath()
if err != nil {
return errwrap.Wrapf(err, "error generating unit file path")
}
// recwatcher for the systemd-timer unit file
obj.recWatcher, err = recwatch.NewRecWatcher(path, false)
obj.recWatcher, err = recwatch.NewRecWatcher(p, false)
if err != nil {
return err
}
@@ -453,7 +450,8 @@ type CronUID struct {
// used in the IFF function, is what you see in the struct fields here.
engine.BaseUID
name string // the machine name
unit string // name of target unit
session bool // user session
}
// IFF aka if and only if they are equivalent, return true. If not, false.
@@ -462,17 +460,38 @@ func (obj *CronUID) IFF(uid engine.ResUID) bool {
if !ok {
return false
}
return obj.name == res.name
if obj.unit != res.unit {
return false
}
if obj.session != res.session {
return false
}
return true
}
// AutoEdges returns the AutoEdge interface.
func (obj *CronRes) AutoEdges() (engine.AutoEdge, error) {
return nil, nil
}
// UIDs includes all params to make a unique identification of this object.
// Most resources only return one although some resources can return multiple.
func (obj *CronRes) UIDs() []engine.ResUID {
x := &CronUID{
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
name: obj.Name(), // svc name
unit := fmt.Sprintf("%s.service", obj.Name())
if obj.Unit != "" {
unit = obj.Unit
}
return append([]engine.ResUID{x}, obj.file.UIDs()...)
uids := []engine.ResUID{
&CronUID{
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
unit: unit, // name of target unit
session: obj.Session, // user session
},
}
if file, err := obj.makeComposite(); err == nil {
uids = append(uids, file.UIDs()...) // add the file uid if we can
}
return uids
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
@@ -495,6 +514,23 @@ func (obj *CronRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// UnitFilePath returns the path to the systemd-timer unit file.
func (obj *CronRes) UnitFilePath() (string, error) {
// root timer
if !obj.Session {
return fmt.Sprintf("/etc/systemd/system/%s.timer", obj.Name()), nil
}
// user timer
u, err := user.Current()
if err != nil {
return "", errwrap.Wrapf(err, "error getting current user")
}
if u.HomeDir == "" {
return "", fmt.Errorf("user has no home directory")
}
return path.Join(u.HomeDir, "/.config/systemd/user/", fmt.Sprintf("%s.timer", obj.Name())), nil
}
// unitFileContents returns the contents of the unit file representing the
// CronRes struct.
func (obj *CronRes) unitFileContents() string {

View File

@@ -21,9 +21,12 @@ package resources
import (
"fmt"
"os/user"
"path"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/util"
systemd "github.com/coreos/go-systemd/dbus" // change namespace
@@ -381,6 +384,7 @@ type SvcUID struct {
// used in the IFF function, is what you see in the struct fields here.
engine.BaseUID
name string // the svc name
session bool // user session
}
// IFF aka if and only if they are equivalent, return true. If not, false.
@@ -389,7 +393,13 @@ func (obj *SvcUID) IFF(uid engine.ResUID) bool {
if !ok {
return false
}
return obj.name == res.name
if obj.name != res.name {
return false
}
if obj.session != res.session {
return false
}
return true
}
// SvcResAutoEdges holds the state of the auto edge generator.
@@ -431,13 +441,56 @@ func (obj *SvcResAutoEdges) Test(input []bool) bool {
return true // keep going
}
// AutoEdges returns the AutoEdge interface. In this case the systemd units.
// SvcResAutoEdgesCron holds the state of the svc -> cron auto edge generator.
type SvcResAutoEdgesCron struct {
unit string // target unit
session bool // user session
}
// Next returns the next automatic edge.
func (obj *SvcResAutoEdgesCron) Next() []engine.ResUID {
// XXX: should this be true if SvcRes State == "stopped"?
reversed := false
value := &CronUID{
BaseUID: engine.BaseUID{
Kind: "CronRes",
Reversed: &reversed,
},
unit: obj.unit, // target unit
session: obj.session, // user session
}
return []engine.ResUID{value} // we return one, even though api supports N
}
// Test takes the output of the last call to Next() and outputs true if we
// should continue.
func (obj *SvcResAutoEdgesCron) Test([]bool) bool {
return false // only get one svc -> cron edge
}
// AutoEdges returns the AutoEdge interface. In this case, systemd unit file
// resources and cron (systemd-timer) resources.
func (obj *SvcRes) AutoEdges() (engine.AutoEdge, error) {
var data []engine.ResUID
svcFiles := []string{
var svcFiles []string
svcFiles = []string{
// root svc
fmt.Sprintf("/etc/systemd/system/%s.service", obj.Name()), // takes precedence
fmt.Sprintf("/usr/lib/systemd/system/%s.service", obj.Name()), // pkg default
}
if obj.Session {
// user svc
u, err := user.Current()
if err != nil {
return nil, errwrap.Wrapf(err, "error getting current user")
}
if u.HomeDir == "" {
return nil, fmt.Errorf("user has no home directory")
}
svcFiles = []string{
path.Join(u.HomeDir, "/.config/systemd/user/", fmt.Sprintf("%s.service", obj.Name())),
}
}
for _, x := range svcFiles {
var reversed = true
data = append(data, &FileUID{
@@ -449,11 +502,18 @@ func (obj *SvcRes) AutoEdges() (engine.AutoEdge, error) {
path: x, // what matters
})
}
return &FileResAutoEdges{
fileEdge := &FileResAutoEdges{
data: data,
pointer: 0,
found: false,
}, nil
}
cronEdge := &SvcResAutoEdgesCron{
session: obj.Session,
unit: fmt.Sprintf("%s.service", obj.Name()),
}
return engineUtil.AutoEdgeCombiner(fileEdge, cronEdge)
}
// UIDs includes all params to make a unique identification of this object.
@@ -462,6 +522,7 @@ func (obj *SvcRes) UIDs() []engine.ResUID {
x := &SvcUID{
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
name: obj.Name(), // svc name
session: obj.Session, // user session
}
return []engine.ResUID{x}
}

17
examples/lang/cron4.mcl Normal file
View File

@@ -0,0 +1,17 @@
$home = getenv("HOME")
cron "purpleidea-oneshot" {
state => "absent",
session => true,
trigger => "OnCalendar",
time => "*:*:0",
}
svc "purpleidea-oneshot" {
state => "stopped",
session => true,
}
file printf("%s/.config/systemd/user/purpleidea-oneshot.service", $home) {
state => "absent",
}