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" "bytes"
"context" "context"
"fmt" "fmt"
"os" "os/user"
"path"
"strings" "strings"
"time" "time"
@@ -141,15 +142,13 @@ func (obj *CronRes) Default() engine.Res {
// validate and initialize the nested file resource and to apply the file state // validate and initialize the nested file resource and to apply the file state
// in CheckApply. // in CheckApply.
func (obj *CronRes) makeComposite() (*FileRes, error) { func (obj *CronRes) makeComposite() (*FileRes, error) {
// root timer p, err := obj.UnitFilePath()
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)
if err != nil { 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) file, ok := res.(*FileRes)
if !ok { if !ok {
@@ -260,14 +259,12 @@ func (obj *CronRes) Watch() error {
bus.Signal(dbusChan) bus.Signal(dbusChan)
defer bus.RemoveSignal(dbusChan) // not needed here, but nice for symmetry defer bus.RemoveSignal(dbusChan) // not needed here, but nice for symmetry
// root timer p, err := obj.UnitFilePath()
path := fmt.Sprintf("/etc/systemd/system/%s.timer", obj.Name()) if err != nil {
if obj.Session { return errwrap.Wrapf(err, "error generating unit file path")
// user timer
path = fmt.Sprintf("%s/.config/systemd/user/%s.timer", os.Getenv("HOME"), obj.Name())
} }
// recwatcher for the systemd-timer unit file // recwatcher for the systemd-timer unit file
obj.recWatcher, err = recwatch.NewRecWatcher(path, false) obj.recWatcher, err = recwatch.NewRecWatcher(p, false)
if err != nil { if err != nil {
return err return err
} }
@@ -453,7 +450,8 @@ type CronUID struct {
// used in the IFF function, is what you see in the struct fields here. // used in the IFF function, is what you see in the struct fields here.
engine.BaseUID 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. // 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 { if !ok {
return false 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. // UIDs includes all params to make a unique identification of this object.
// Most resources only return one although some resources can return multiple. // Most resources only return one although some resources can return multiple.
func (obj *CronRes) UIDs() []engine.ResUID { func (obj *CronRes) UIDs() []engine.ResUID {
x := &CronUID{ unit := fmt.Sprintf("%s.service", obj.Name())
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()}, if obj.Unit != "" {
name: obj.Name(), // svc name 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. // UnmarshalYAML is the custom unmarshal handler for this struct.
@@ -495,6 +514,23 @@ func (obj *CronRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil 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 // unitFileContents returns the contents of the unit file representing the
// CronRes struct. // CronRes struct.
func (obj *CronRes) unitFileContents() string { func (obj *CronRes) unitFileContents() string {

View File

@@ -21,9 +21,12 @@ package resources
import ( import (
"fmt" "fmt"
"os/user"
"path"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits" "github.com/purpleidea/mgmt/engine/traits"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
systemd "github.com/coreos/go-systemd/dbus" // change namespace systemd "github.com/coreos/go-systemd/dbus" // change namespace
@@ -380,7 +383,8 @@ type SvcUID struct {
// information about the resource we're matching. That data which is // information about the resource we're matching. That data which is
// used in the IFF function, is what you see in the struct fields here. // used in the IFF function, is what you see in the struct fields here.
engine.BaseUID engine.BaseUID
name string // the svc name name string // the svc name
session bool // user session
} }
// IFF aka if and only if they are equivalent, return true. If not, false. // 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 { if !ok {
return false 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. // SvcResAutoEdges holds the state of the auto edge generator.
@@ -431,13 +441,56 @@ func (obj *SvcResAutoEdges) Test(input []bool) bool {
return true // keep going 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) { func (obj *SvcRes) AutoEdges() (engine.AutoEdge, error) {
var data []engine.ResUID 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("/etc/systemd/system/%s.service", obj.Name()), // takes precedence
fmt.Sprintf("/usr/lib/systemd/system/%s.service", obj.Name()), // pkg default 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 { for _, x := range svcFiles {
var reversed = true var reversed = true
data = append(data, &FileUID{ data = append(data, &FileUID{
@@ -449,11 +502,18 @@ func (obj *SvcRes) AutoEdges() (engine.AutoEdge, error) {
path: x, // what matters path: x, // what matters
}) })
} }
return &FileResAutoEdges{
fileEdge := &FileResAutoEdges{
data: data, data: data,
pointer: 0, pointer: 0,
found: false, 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. // UIDs includes all params to make a unique identification of this object.
@@ -461,7 +521,8 @@ func (obj *SvcRes) AutoEdges() (engine.AutoEdge, error) {
func (obj *SvcRes) UIDs() []engine.ResUID { func (obj *SvcRes) UIDs() []engine.ResUID {
x := &SvcUID{ x := &SvcUID{
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()}, BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
name: obj.Name(), // svc name name: obj.Name(), // svc name
session: obj.Session, // user session
} }
return []engine.ResUID{x} 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",
}