Skip to content

Instantly share code, notes, and snippets.

@purpleidea
Forked from sushlala/sysctl.go
Created May 1, 2019 07:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save purpleidea/5fddc1d633edd7b95371e9d391208222 to your computer and use it in GitHub Desktop.
Save purpleidea/5fddc1d633edd7b95371e9d391208222 to your computer and use it in GitHub Desktop.
// Mgmt
// Copyright (C) 2013-2018+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"fmt"
errwrap "github.com/pkg/errors"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits"
"github.com/purpleidea/mgmt/recwatch"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
func init() {
engine.RegisterResource("sysctl", func() engine.Res { return &FileRes{} })
}
// SysctlRes is a Linux sysctl resource. Ensure a particular kernel parameter has
// the desired value set
type SysctlRes struct {
traits.Base // add the base methods without re-implementation
init *engine.Init
// Parameter is the name of the kernel parameter that is to be set
Parameter string `yaml:"parameter"`
// Value is set to the Parameter key
Value string `yaml:"value"` // override the path dirname
recWatcher *recwatch.RecWatcher
file string
}
// Default returns some sensible defaults for this resource.
func (obj *SysctlRes) Default() engine.Res {
return &SysctlRes{
}
}
const sysctlBase = "/proc/sys/"
// pathFromKey converts the Parameter key to the corresponding file under /proc/sys/
func pathFromKey(key string) string {
return filepath.Join(sysctlBase, strings.Replace(key, ".", "/", -1))
}
// Validate reports any problems with the struct definition.
func (obj *SysctlRes) Validate() error {
if obj.Parameter == "" {
return fmt.Errorf("parameter is empty")
}
obj.file = pathFromKey(obj.Parameter)
if _, err := os.Stat(obj.file); err != nil {
return fmt.Errorf("could not access file %s corresponding to Parameter %s: %s",
obj.file, obj.Parameter, err)
}
return nil
}
// Init runs some startup code for this resource.
func (obj *SysctlRes) Init(init *engine.Init) error {
obj.init = init // save for later
return nil
}
// Close is run by the engine to clean up after the resource is done.
func (obj *SysctlRes) Close() error {
return nil
}
// Watch is the primary listener for this resource and it outputs events.
// This one is a file watcher for files and directories.
// Modify with caution, it is probably important to write some test cases first!
// If the Watch returns an error, it means that something has gone wrong, and it
// must be restarted. On a clean exit it returns nil.
// FIXME: Also watch the source directory when using obj.Source !!!
func (obj *SysctlRes) Watch() error {
var err error
obj.recWatcher, err = recwatch.NewRecWatcher(obj.file, false)
if err != nil {
return err
}
defer obj.recWatcher.Close()
// notify engine that we're running
if err := obj.init.Running(); err != nil {
return err // exit if requested
}
var send = false // send event?
for {
if obj.init.Debug {
obj.init.Logf("Watching: %s", obj.file) // attempting to watch...
}
select {
case event, ok := <-obj.recWatcher.Events():
if !ok { // channel shutdown
return nil
}
if err := event.Error; err != nil {
return errwrap.Wrapf(err, "unknown %s watcher error", obj)
}
if obj.init.Debug { // don't access event.Body if event.Error isn't nil
obj.init.Logf("Event(%s): %v", event.Body.Name, event.Body.Op)
}
send = true
obj.init.Dirty() // dirty
case event, ok := <-obj.init.Events:
if !ok {
return nil
}
if err := obj.init.Read(event); err != nil {
return err
}
}
// do all our event sending all together to avoid duplicate msgs
if send {
send = false
if err := obj.init.Event(); err != nil {
return err // exit if requested
}
}
}
}
// CheckApply checks the resource state and applies the resource if apply is true.
// It returns error info and if the state check passed or not.
func (obj *SysctlRes) CheckApply(apply bool) (checkOK bool, _ error) {
data, err := ioutil.ReadFile(obj.file)
if err != nil {
return false, err
}
if string(data) == obj.Value {
return true, nil
}
if !apply {
return false, nil
}
// apply
err = ioutil.WriteFile(obj.file, []byte(obj.Value), 0644)
if err != nil {
return false, err
}
return false, nil
}
// Cmp compares two resources and returns an error if they are not equivalent.
func (obj *SysctlRes) Cmp(r engine.Res) error {
res, ok := r.(*SysctlRes)
if !ok {
return fmt.Errorf("not a %s", obj.Kind())
}
if res.Parameter != obj.Parameter{
return fmt.Errorf("the Paramater differs")
}
if res.Value != obj.Value{
return fmt.Errorf("the Value differs")
}
return nil
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
func (obj *SysctlRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
def := obj.Default() // get the default
res, ok := def.(*SysctlRes) // put in the right format
if !ok {
return fmt.Errorf("could not convert to SysctlRes")
}
raw := SysctlRes(*res) // convert; the defaults go here
if err := unmarshal(&raw); err != nil {
return err
}
*obj = SysctlRes(raw) // restore from indirection with type conversion!
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment