engine: resources: cron: Add auto edges from SvcRes
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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
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