bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/conf/rule/modify.go (about)

     1  package rule
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"bosun.org/cmd/bosun/conf"
     8  	"bosun.org/cmd/bosun/conf/rule/parse"
     9  	"github.com/pmezard/go-difflib/difflib"
    10  )
    11  
    12  // SaveRawText saves a new configuration file. The contextual diff of the change is provided
    13  // to verify that no other changes have happened since the save request is issue. User, message, and
    14  // args are passed to an optionally configured save hook. If the config file is not valid the file
    15  // will not be saved. If the savehook fails to run or returns an error thaen the orginal config
    16  // will be restored and the reload will not take place.
    17  func (c *Conf) SaveRawText(rawConfig, diff, user, message string, args ...string) error {
    18  	newConf, err := NewConf(c.Name, c.backends, c.sysVars, rawConfig)
    19  	if err != nil {
    20  		return err
    21  	}
    22  	currentDiff, err := c.RawDiff(rawConfig)
    23  	if err != nil {
    24  		return fmt.Errorf("couldn't save config because failed to generate a diff: %v", err)
    25  	}
    26  	if currentDiff != diff {
    27  		return fmt.Errorf("couldn't save config file because the change and supplied diff do not match the current diff")
    28  	}
    29  	if err = c.SaveConf(newConf); err != nil {
    30  		return fmt.Errorf("couldn't save config file: %v", err)
    31  	}
    32  	if c.saveHook != nil {
    33  		err := c.callSaveHook(c.Name, user, message, args...)
    34  		if err != nil {
    35  			sErr := c.SaveConf(c)
    36  			restore := "successful"
    37  			if sErr != nil {
    38  				restore = sErr.Error()
    39  			}
    40  			return fmt.Errorf("failed to call save hook: %v. Restoring config: %v", err, restore)
    41  		}
    42  	}
    43  	err = c.reload()
    44  	if err != nil {
    45  		return err
    46  	}
    47  	return nil
    48  }
    49  
    50  // BulkEdit applies sequental edits to the configuration file. Each individual edit
    51  // must generate a valid configuration or the edit request will fail.
    52  func (c *Conf) BulkEdit(edits conf.BulkEditRequest) error {
    53  	select {
    54  	case c.writeLock <- true:
    55  		// Got Write Lock
    56  	default:
    57  		return fmt.Errorf("cannot write alert, write in progress")
    58  	}
    59  	defer func() {
    60  		<-c.writeLock
    61  	}()
    62  	newConf := c
    63  	var err error
    64  	for _, edit := range edits {
    65  		var l Location
    66  		switch edit.Type {
    67  		case "alert":
    68  			a := newConf.GetAlert(edit.Name)
    69  			if a != nil {
    70  				l = a.Locator.(Location)
    71  			}
    72  		case "template":
    73  			t := newConf.GetTemplate(edit.Name)
    74  			if t != nil {
    75  				l = t.Locator.(Location)
    76  			}
    77  		case "notification":
    78  			n := newConf.GetNotification(edit.Name)
    79  			if n != nil {
    80  				l = n.Locator.(Location)
    81  			}
    82  		case "lookup":
    83  			look := newConf.GetLookup(edit.Name)
    84  			if look != nil {
    85  				l = look.Locator.(Location)
    86  			}
    87  		case "macro":
    88  			m := newConf.GetMacro(edit.Name)
    89  			if m != nil {
    90  				l = m.Locator.(Location)
    91  			}
    92  		default:
    93  			return fmt.Errorf("%v is an unsuported type for bulk edit. must be alert, template, notification, lookup or macro", edit.Type)
    94  		}
    95  		var rawConf string
    96  		if edit.Delete {
    97  			if l == nil {
    98  				return fmt.Errorf("could not delete %v:%v - not found", edit.Type, edit.Name)
    99  			}
   100  			rawConf = removeSection(l, newConf.RawText)
   101  		} else {
   102  			rawConf = writeSection(l, newConf.RawText, edit.Text)
   103  		}
   104  		newConf, err = NewConf(c.Name, c.backends, c.sysVars, rawConf)
   105  		if err != nil {
   106  			return fmt.Errorf("could not create new conf: failed on step %v:%v : %v", edit.Type, edit.Name, err)
   107  		}
   108  	}
   109  	if err := c.SaveConf(newConf); err != nil {
   110  		return fmt.Errorf("couldn't save config file: %v", err)
   111  	}
   112  	err = c.reload()
   113  	if err != nil {
   114  		return err
   115  	}
   116  	return nil
   117  }
   118  
   119  // Location stores the start byte position and end byte position of
   120  // an object in the raw configuration
   121  type Location []int
   122  
   123  func writeSection(l Location, orginalRaw, newText string) string {
   124  	var newRawConf bytes.Buffer
   125  	if l == nil {
   126  		newRawConf.WriteString(orginalRaw)
   127  		newRawConf.WriteString("\n")
   128  		newRawConf.WriteString(newText)
   129  		newRawConf.WriteString("\n")
   130  		return newRawConf.String()
   131  	}
   132  	newRawConf.WriteString(orginalRaw[:getLocationStart(l)])
   133  	newRawConf.WriteString(newText)
   134  	newRawConf.WriteString(orginalRaw[getLocationEnd(l):])
   135  	return newRawConf.String()
   136  }
   137  
   138  func removeSection(l Location, orginalRaw string) string {
   139  	var newRawConf bytes.Buffer
   140  	newRawConf.WriteString(orginalRaw[:getLocationStart(l)])
   141  	newRawConf.WriteString(orginalRaw[getLocationEnd(l):])
   142  	return newRawConf.String()
   143  }
   144  
   145  func newSectionLocator(s *parse.SectionNode) Location {
   146  	start := int(s.Position())
   147  	end := int(s.Position()) + len(s.RawText)
   148  	return Location{start, end}
   149  }
   150  
   151  func getLocationStart(l Location) int {
   152  	return l[0]
   153  }
   154  
   155  func getLocationEnd(l Location) int {
   156  	return l[1]
   157  }
   158  
   159  // RawDiff returns a contextual diff of the running rule configuration
   160  // against the provided configuration. This contextual diff library
   161  // does not guarantee that the generated unified diff can be applied
   162  // so this is only used for human consumption and verifying that the diff
   163  // has not change since an edit request was issued
   164  func (c *Conf) RawDiff(rawConf string) (string, error) {
   165  	diff := difflib.UnifiedDiff{
   166  		A:        difflib.SplitLines(c.RawText),
   167  		B:        difflib.SplitLines(rawConf),
   168  		FromFile: c.Name,
   169  		ToFile:   c.Name,
   170  		Context:  3,
   171  	}
   172  	return difflib.GetUnifiedDiffString(diff)
   173  }