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 }