github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imagebuilder/builder/load.go (about) 1 package builder 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "path/filepath" 10 "sort" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/Cloud-Foundations/Dominator/imageserver/client" 16 "github.com/Cloud-Foundations/Dominator/lib/configwatch" 17 "github.com/Cloud-Foundations/Dominator/lib/filter" 18 libjson "github.com/Cloud-Foundations/Dominator/lib/json" 19 "github.com/Cloud-Foundations/Dominator/lib/log" 20 "github.com/Cloud-Foundations/Dominator/lib/slavedriver" 21 "github.com/Cloud-Foundations/Dominator/lib/srpc" 22 "github.com/Cloud-Foundations/Dominator/lib/triggers" 23 "github.com/Cloud-Foundations/Dominator/lib/url/urlutil" 24 ) 25 26 func imageStreamsDecoder(reader io.Reader) (interface{}, error) { 27 var config imageStreamsConfigurationType 28 decoder := json.NewDecoder(bufio.NewReader(reader)) 29 if err := decoder.Decode(&config); err != nil { 30 return nil, fmt.Errorf("error reading image streams: %s", err) 31 } 32 return &config, nil 33 } 34 35 func load(confUrl, variablesFile, stateDir, imageServerAddress string, 36 imageRebuildInterval time.Duration, slaveDriver *slavedriver.SlaveDriver, 37 logger log.DebugLogger) (*Builder, error) { 38 err := syscall.Mount("none", "/", "", syscall.MS_REC|syscall.MS_PRIVATE, "") 39 if err != nil { 40 return nil, fmt.Errorf("error making mounts private: %s", err) 41 } 42 masterConfiguration, err := masterConfiguration(confUrl) 43 if err != nil { 44 return nil, fmt.Errorf("error getting master configuration: %s", err) 45 } 46 imageStreamsToAutoRebuild := make([]string, 0) 47 for name := range masterConfiguration.BootstrapStreams { 48 imageStreamsToAutoRebuild = append(imageStreamsToAutoRebuild, name) 49 } 50 sort.Strings(imageStreamsToAutoRebuild) 51 for _, name := range masterConfiguration.ImageStreamsToAutoRebuild { 52 imageStreamsToAutoRebuild = append(imageStreamsToAutoRebuild, name) 53 } 54 var variables map[string]string 55 if variablesFile != "" { 56 if err := libjson.ReadFromFile(variablesFile, &variables); err != nil { 57 return nil, err 58 } 59 } 60 if variables == nil { 61 variables = make(map[string]string) 62 } 63 b := &Builder{ 64 bindMounts: masterConfiguration.BindMounts, 65 stateDir: stateDir, 66 imageServerAddress: imageServerAddress, 67 logger: logger, 68 imageStreamsUrl: masterConfiguration.ImageStreamsUrl, 69 bootstrapStreams: masterConfiguration.BootstrapStreams, 70 imageStreamsToAutoRebuild: imageStreamsToAutoRebuild, 71 slaveDriver: slaveDriver, 72 currentBuildLogs: make(map[string]*bytes.Buffer), 73 lastBuildResults: make(map[string]buildResultType), 74 packagerTypes: masterConfiguration.PackagerTypes, 75 variables: variables, 76 } 77 for name, stream := range b.bootstrapStreams { 78 stream.builder = b 79 stream.name = name 80 } 81 imageStreamsConfigChannel, err := configwatch.WatchWithCache( 82 masterConfiguration.ImageStreamsUrl, 83 time.Second*time.Duration( 84 masterConfiguration.ImageStreamsCheckInterval), imageStreamsDecoder, 85 filepath.Join(stateDir, "image-streams.json"), 86 time.Second*5, logger) 87 if err != nil { 88 return nil, err 89 } 90 go b.watchConfigLoop(imageStreamsConfigChannel) 91 go b.rebuildImages(imageRebuildInterval) 92 return b, nil 93 } 94 95 func loadImageStreams(url string) (*imageStreamsConfigurationType, error) { 96 if url == "" { 97 return &imageStreamsConfigurationType{}, nil 98 } 99 file, err := urlutil.Open(url) 100 if err != nil { 101 return nil, err 102 } 103 defer file.Close() 104 var configuration imageStreamsConfigurationType 105 decoder := json.NewDecoder(bufio.NewReader(file)) 106 if err := decoder.Decode(&configuration); err != nil { 107 return nil, fmt.Errorf("error decoding image streams from: %s: %s", 108 url, err) 109 } 110 return &configuration, nil 111 } 112 113 func masterConfiguration(url string) (*masterConfigurationType, error) { 114 file, err := urlutil.Open(url) 115 if err != nil { 116 return nil, err 117 } 118 defer file.Close() 119 var configuration masterConfigurationType 120 decoder := json.NewDecoder(bufio.NewReader(file)) 121 if err := decoder.Decode(&configuration); err != nil { 122 return nil, fmt.Errorf("error reading configuration from: %s: %s", 123 url, err) 124 } 125 for _, stream := range configuration.BootstrapStreams { 126 if _, ok := configuration.PackagerTypes[stream.PackagerType]; !ok { 127 return nil, fmt.Errorf("packager type: \"%s\" unknown", 128 stream.PackagerType) 129 } 130 if stream.Filter != nil { 131 if err := stream.Filter.Compile(); err != nil { 132 return nil, err 133 } 134 } 135 if stream.ImageFilterUrl != "" { 136 filterFile, err := urlutil.Open(stream.ImageFilterUrl) 137 if err != nil { 138 return nil, err 139 } 140 defer filterFile.Close() 141 stream.imageFilter, err = filter.Read(filterFile) 142 if err != nil { 143 return nil, err 144 } 145 } 146 if stream.ImageTriggersUrl != "" { 147 triggersFile, err := urlutil.Open(stream.ImageTriggersUrl) 148 if err != nil { 149 return nil, err 150 } 151 defer triggersFile.Close() 152 stream.imageTriggers, err = triggers.Read(triggersFile) 153 if err != nil { 154 return nil, err 155 } 156 } 157 } 158 return &configuration, nil 159 } 160 161 func (b *Builder) delayMakeRequiredDirectories(abortNotifier <-chan struct{}) { 162 timer := time.NewTimer(time.Second * 5) 163 select { 164 case <-abortNotifier: 165 if !timer.Stop() { 166 <-timer.C 167 } 168 case <-timer.C: 169 b.makeRequiredDirectories() 170 } 171 } 172 173 func (b *Builder) makeRequiredDirectories() error { 174 imageServer, err := srpc.DialHTTP("tcp", b.imageServerAddress, 0) 175 if err != nil { 176 b.logger.Printf("%s: %s\n", b.imageServerAddress, err) 177 return nil 178 } 179 defer imageServer.Close() 180 directoryList, err := client.ListDirectories(imageServer) 181 if err != nil { 182 b.logger.Println(err) 183 return nil 184 } 185 directories := make(map[string]struct{}, len(directoryList)) 186 for _, directory := range directoryList { 187 directories[directory.Name] = struct{}{} 188 } 189 streamNames := b.listAllStreamNames() 190 for _, streamName := range streamNames { 191 if _, ok := directories[streamName]; ok { 192 continue 193 } 194 pathComponents := strings.Split(streamName, "/") 195 for index := range pathComponents { 196 partPath := strings.Join(pathComponents[0:index+1], "/") 197 if _, ok := directories[partPath]; ok { 198 continue 199 } 200 if err := client.MakeDirectory(imageServer, partPath); err != nil { 201 return err 202 } 203 b.logger.Printf("Created missing directory: %s\n", partPath) 204 directories[partPath] = struct{}{} 205 } 206 } 207 return nil 208 } 209 210 func (b *Builder) reloadNormalStreamsConfiguration() error { 211 imageStreamsConfiguration, err := loadImageStreams(b.imageStreamsUrl) 212 if err != nil { 213 return err 214 } 215 b.logger.Println("Reloaded streams streams configuration") 216 return b.updateImageStreams(imageStreamsConfiguration) 217 } 218 219 func (b *Builder) updateImageStreams( 220 imageStreamsConfiguration *imageStreamsConfigurationType) error { 221 for name, stream := range imageStreamsConfiguration.Streams { 222 stream.builder = b 223 stream.name = name 224 } 225 b.streamsLock.Lock() 226 b.imageStreams = imageStreamsConfiguration.Streams 227 b.streamsLock.Unlock() 228 return b.makeRequiredDirectories() 229 } 230 231 func (b *Builder) watchConfigLoop(configChannel <-chan interface{}) { 232 firstLoadNotifier := make(chan struct{}) 233 go b.delayMakeRequiredDirectories(firstLoadNotifier) 234 for rawConfig := range configChannel { 235 imageStreamsConfig, ok := rawConfig.(*imageStreamsConfigurationType) 236 if !ok { 237 b.logger.Printf("received unknown type over config channel") 238 continue 239 } 240 if firstLoadNotifier != nil { 241 firstLoadNotifier <- struct{}{} 242 close(firstLoadNotifier) 243 firstLoadNotifier = nil 244 } 245 b.logger.Println("received new image streams configuration") 246 b.updateImageStreams(imageStreamsConfig) 247 } 248 }