engine: resources: cron: Add auto edges from SvcRes
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
17
examples/lang/cron4.mcl
Normal 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",
|
||||
}
|
||||
Reference in New Issue
Block a user