github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/registration/file_content.go (about) 1 // Copyright (c) 2021, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package registration 6 7 import ( 8 "context" 9 "encoding/json" 10 "fmt" 11 "math/rand" 12 "os" 13 "sync" 14 "time" 15 16 "github.com/choria-io/go-choria/config" 17 "github.com/choria-io/go-choria/internal/util" 18 "github.com/choria-io/go-choria/server/data" 19 20 "github.com/sirupsen/logrus" 21 ) 22 23 // FileContent is a fully managed registration plugin for the choria server instance 24 // it reads a file and publishing it to the collective regularly 25 type FileContent struct { 26 dataFile string 27 c *config.Config 28 log *logrus.Entry 29 30 prevMtime int64 31 } 32 33 // FileContentMessage contains message being published 34 type FileContentMessage struct { 35 Mtime int64 `json:"mtime"` 36 File string `json:"file"` 37 Updated bool `json:"updated"` 38 Protocol string `json:"protocol"` 39 Content []byte `json:"content,omitempty"` 40 ZContent []byte `json:"zcontent,omitempty"` 41 } 42 43 // NewFileContent creates a new fully managed registration plugin instance 44 func NewFileContent(c *config.Config, _ ServerInfoSource, logger *logrus.Entry) (*FileContent, error) { 45 if c.Choria.FileContentRegistrationData == "" { 46 return nil, fmt.Errorf("file fontent registration is enabled but no source data is configured, please set plugin.choria.registration.file_content.data") 47 } 48 49 reg := &FileContent{} 50 reg.Init(c, logger) 51 52 return reg, nil 53 } 54 55 // Init sets up the plugin 56 func (fc *FileContent) Init(c *config.Config, logger *logrus.Entry) { 57 fc.c = c 58 fc.dataFile = c.Choria.FileContentRegistrationData 59 fc.log = logger.WithFields(logrus.Fields{"registration": "file_content", "source": fc.dataFile}) 60 61 fc.log.Infof("Configured File Content Registration with source '%s' and target '%s'", fc.dataFile, c.Choria.FileContentRegistrationTarget) 62 } 63 64 // StartRegistration starts stats a publishing loop 65 func (fc *FileContent) StartRegistration(ctx context.Context, wg *sync.WaitGroup, interval int, output chan *data.RegistrationItem) { 66 defer wg.Done() 67 68 delay := time.Duration(rand.Intn(4)+1) * time.Second 69 fc.log.Infof("Sleeping %v before first registration publish", delay) 70 err := util.InterruptibleSleep(ctx, delay) 71 if err != nil { 72 return 73 } 74 75 err = fc.publish(output) 76 if err != nil { 77 fc.log.Errorf("Could not create registration data: %s", err) 78 } 79 80 ticker := time.NewTicker(time.Duration(interval) * time.Second) 81 82 for { 83 select { 84 case <-ticker.C: 85 err = fc.publish(output) 86 if err != nil { 87 fc.log.Errorf("Could not create registration data: %s", err) 88 } 89 90 case <-ctx.Done(): 91 return 92 } 93 } 94 } 95 96 func (fc *FileContent) publish(output chan *data.RegistrationItem) error { 97 fc.log.Infof("Starting file_content registration poll") 98 99 fstat, err := os.Stat(fc.dataFile) 100 if os.IsNotExist(err) { 101 return fmt.Errorf("could not find data file %s", fc.dataFile) 102 } 103 104 if fstat.Size() == 0 { 105 return fmt.Errorf("data file %s is empty", fc.dataFile) 106 } 107 108 fstat, err = os.Stat(fc.dataFile) 109 if err != nil { 110 return fmt.Errorf("could not obtain file times: %s", err) 111 } 112 113 dat, err := os.ReadFile(fc.dataFile) 114 if err != nil { 115 return fmt.Errorf("could not read file registration source %s: %s", fc.dataFile, err) 116 } 117 118 msg := &FileContentMessage{ 119 Protocol: "choria:registration:filecontent:1", 120 File: fc.dataFile, 121 Mtime: fstat.ModTime().Unix(), 122 } 123 124 // the first time it starts we just have no idea, so we set it to whatever 125 // it is now which would also avoid setting updated=true, we do not want a 126 // large fleet restart to mass trigger a needless full site replication 127 if fc.prevMtime == 0 { 128 fc.prevMtime = msg.Mtime 129 } 130 131 if msg.Mtime > fc.prevMtime { 132 msg.Updated = true 133 fc.prevMtime = msg.Mtime 134 } 135 136 if fc.c.Choria.FileContentCompression { 137 zdat, err := compress(dat) 138 if err != nil { 139 fc.log.Warnf("Could not compress file registration data: %s", err) 140 } else { 141 msg.ZContent = zdat 142 } 143 } 144 145 if msg.ZContent == nil { 146 msg.Content = dat 147 } 148 149 jdat, err := json.Marshal(msg) 150 if err != nil { 151 return fmt.Errorf("could not json marshal registration message: %s", err) 152 } 153 154 item := &data.RegistrationItem{ 155 Data: jdat, 156 Destination: fc.c.Choria.FileContentRegistrationTarget, 157 } 158 159 if item.Destination == "" { 160 item.TargetAgent = "registration" 161 } 162 163 output <- item 164 165 return nil 166 }