github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/load.go (about) 1 package builder 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/Cloud-Foundations/Dominator/imagebuilder/logarchiver" 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 "github.com/Cloud-Foundations/Dominator/lib/format" 19 "github.com/Cloud-Foundations/Dominator/lib/json" 20 "github.com/Cloud-Foundations/Dominator/lib/srpc" 21 "github.com/Cloud-Foundations/Dominator/lib/stringutil" 22 "github.com/Cloud-Foundations/Dominator/lib/triggers" 23 "github.com/Cloud-Foundations/Dominator/lib/url/urlutil" 24 ) 25 26 func getNamespace() (string, error) { 27 pathname := fmt.Sprintf("/proc/%d/ns/mnt", syscall.Gettid()) 28 namespace, err := os.Readlink(pathname) 29 if err != nil { 30 return "", fmt.Errorf("error discovering namespace: %s", err) 31 } 32 return namespace, nil 33 } 34 35 func imageStreamsDecoder(reader io.Reader) (interface{}, error) { 36 return imageStreamsRealDecoder(reader) 37 } 38 39 func imageStreamsRealDecoder(reader io.Reader) ( 40 *imageStreamsConfigurationType, error) { 41 var config imageStreamsConfigurationType 42 if err := json.Read(reader, &config); err != nil { 43 return nil, err 44 } 45 for _, stream := range config.Streams { 46 stream.builderUsers = stringutil.ConvertListToMap(stream.BuilderUsers, 47 false) 48 } 49 return &config, nil 50 } 51 52 func load(options BuilderOptions, params BuilderParams) (*Builder, error) { 53 if options.CreateSlaveTimeout <= 0 { 54 options.CreateSlaveTimeout = time.Hour 55 if options.ImageRebuildInterval > 0 && 56 options.CreateSlaveTimeout > options.ImageRebuildInterval { 57 options.CreateSlaveTimeout = options.ImageRebuildInterval 58 } 59 } 60 if options.MaximumExpirationDuration < 1 { 61 options.MaximumExpirationDuration = 24 * time.Hour 62 } 63 if options.MaximumExpirationDurationPrivileged < 1 { 64 options.MaximumExpirationDurationPrivileged = 730 * time.Hour 65 } 66 if options.MaximumExpirationDurationPrivileged < 67 options.MaximumExpirationDuration { 68 options.MaximumExpirationDurationPrivileged = 69 options.MaximumExpirationDuration 70 } 71 if options.MinimumExpirationDuration <= 0 { 72 options.MinimumExpirationDuration = 15 * time.Minute 73 } else if options.MinimumExpirationDuration < 15*time.Second { 74 options.MinimumExpirationDuration = 5 * time.Minute 75 } 76 if options.PresentationImageServerAddress == "" { 77 options.PresentationImageServerAddress = options.ImageServerAddress 78 } 79 ctimeResolution, err := getCtimeResolution() 80 if err != nil { 81 return nil, err 82 } 83 if params.BuildLogArchiver == nil { 84 params.BuildLogArchiver = logarchiver.NewNullLogger() 85 } 86 params.Logger.Printf("Inode Ctime resolution: %s\n", 87 format.Duration(ctimeResolution)) 88 initialNamespace, err := getNamespace() 89 if err != nil { 90 return nil, err 91 } 92 params.Logger.Printf("Initial namespace: %s\n", initialNamespace) 93 err = syscall.Mount("none", "/", "", syscall.MS_REC|syscall.MS_PRIVATE, "") 94 if err != nil { 95 return nil, fmt.Errorf("error making mounts private: %s", err) 96 } 97 masterConfiguration, err := loadMasterConfiguration( 98 options.ConfigurationURL) 99 if err != nil { 100 return nil, fmt.Errorf("error getting master configuration: %s", err) 101 } 102 if len(masterConfiguration.BootstrapStreams) < 1 { 103 params.Logger.Println( 104 "No bootstrap streams configured: some operations degraded") 105 } 106 var mtimesCopyFilter *filter.Filter 107 if len(masterConfiguration.MtimesCopyFilterLines) > 0 { 108 mtimesCopyFilter, err = filter.New( 109 masterConfiguration.MtimesCopyFilterLines) 110 if err != nil { 111 return nil, err 112 } 113 } 114 imageStreamsToAutoRebuild := make([]string, 0) 115 for name := range masterConfiguration.BootstrapStreams { 116 imageStreamsToAutoRebuild = append(imageStreamsToAutoRebuild, name) 117 } 118 sort.Strings(imageStreamsToAutoRebuild) 119 for _, name := range masterConfiguration.ImageStreamsToAutoRebuild { 120 imageStreamsToAutoRebuild = append(imageStreamsToAutoRebuild, name) 121 } 122 var variables map[string]string 123 if options.VariablesFile != "" { 124 err := json.ReadFromFile(options.VariablesFile, &variables) 125 if err != nil { 126 return nil, err 127 } 128 } 129 if variables == nil { 130 variables = make(map[string]string) 131 } 132 generateDependencyTrigger := make(chan chan<- struct{}, 1) 133 streamsLoadedChannel := make(chan struct{}) 134 b := &Builder{ 135 buildLogArchiver: params.BuildLogArchiver, 136 bindMounts: masterConfiguration.BindMounts, 137 mtimesCopyFilter: mtimesCopyFilter, 138 createSlaveTimeout: options.CreateSlaveTimeout, 139 generateDependencyTrigger: generateDependencyTrigger, 140 stateDir: options.StateDirectory, 141 imageRebuildInterval: options.ImageRebuildInterval, 142 imageServerAddress: options.ImageServerAddress, 143 linksImageServerAddress: options.PresentationImageServerAddress, 144 logger: params.Logger, 145 imageStreamsUrl: masterConfiguration.ImageStreamsUrl, 146 initialNamespace: initialNamespace, 147 maximumExpiration: options.MaximumExpirationDuration, 148 maximumExpirationPrivileged: options.MaximumExpirationDurationPrivileged, 149 minimumExpiration: options.MinimumExpirationDuration, 150 streamsLoadedChannel: streamsLoadedChannel, 151 bootstrapStreams: masterConfiguration.BootstrapStreams, 152 imageStreamsToAutoRebuild: imageStreamsToAutoRebuild, 153 slaveDriver: params.SlaveDriver, 154 currentBuildInfos: make(map[string]*currentBuildInfo), 155 lastBuildResults: make(map[string]buildResultType), 156 packagerTypes: masterConfiguration.PackagerTypes, 157 relationshipsQuickLinks: masterConfiguration.RelationshipsQuickLinks, 158 variables: variables, 159 } 160 for name, stream := range b.bootstrapStreams { 161 stream.builder = b 162 stream.name = name 163 } 164 imageStreamsConfigChannel, err := configwatch.WatchWithCache( 165 masterConfiguration.ImageStreamsUrl, 166 time.Second*time.Duration( 167 masterConfiguration.ImageStreamsCheckInterval), imageStreamsDecoder, 168 filepath.Join(options.StateDirectory, "image-streams.json"), 169 time.Second*5, params.Logger) 170 if err != nil { 171 return nil, err 172 } 173 go b.dependencyGeneratorLoop(generateDependencyTrigger) 174 go b.watchConfigLoop(imageStreamsConfigChannel, streamsLoadedChannel) 175 go b.rebuildImages(options.ImageRebuildInterval) 176 return b, nil 177 } 178 179 func loadImageStreams(url string) (*imageStreamsConfigurationType, error) { 180 if url == "" { 181 return &imageStreamsConfigurationType{}, nil 182 } 183 file, err := urlutil.Open(url) 184 if err != nil { 185 return nil, err 186 } 187 defer file.Close() 188 configuration, err := imageStreamsRealDecoder(file) 189 if err != nil { 190 return nil, fmt.Errorf("error decoding image streams from: %s: %s", 191 url, err) 192 } 193 return configuration, nil 194 } 195 196 func loadMasterConfiguration(url string) (*masterConfigurationType, error) { 197 file, err := urlutil.Open(url) 198 if err != nil { 199 return nil, err 200 } 201 defer file.Close() 202 var configuration masterConfigurationType 203 if err := json.Read(file, &configuration); err != nil { 204 return nil, fmt.Errorf("error reading configuration from: %s: %s", 205 url, err) 206 } 207 for _, stream := range configuration.BootstrapStreams { 208 if _, ok := configuration.PackagerTypes[stream.PackagerType]; !ok { 209 return nil, fmt.Errorf("packager type: \"%s\" unknown", 210 stream.PackagerType) 211 } 212 if err := stream.loadFiles(); err != nil { 213 return nil, err 214 } 215 } 216 return &configuration, nil 217 } 218 219 func (stream *bootstrapStream) loadFiles() error { 220 if stream.Filter != nil { 221 if err := stream.Filter.Compile(); err != nil { 222 return err 223 } 224 } 225 if stream.ImageFilterUrl != "" { 226 filterFile, err := urlutil.Open(stream.ImageFilterUrl) 227 if err != nil { 228 return err 229 } 230 defer filterFile.Close() 231 stream.imageFilter, err = filter.Read(filterFile) 232 if err != nil { 233 return err 234 } 235 } 236 if stream.ImageTagsUrl != "" { 237 tagsFile, err := urlutil.Open(stream.ImageTagsUrl) 238 if err != nil { 239 return err 240 } 241 defer tagsFile.Close() 242 reader := bufio.NewReader(tagsFile) 243 if err := json.Read(reader, &stream.imageTags); err != nil { 244 return err 245 } 246 } 247 if stream.ImageTriggersUrl != "" { 248 triggersFile, err := urlutil.Open(stream.ImageTriggersUrl) 249 if err != nil { 250 return err 251 } 252 defer triggersFile.Close() 253 stream.imageTriggers, err = triggers.Read(triggersFile) 254 if err != nil { 255 return err 256 } 257 } 258 return nil 259 } 260 261 func (b *Builder) delayMakeRequiredDirectories(abortNotifier <-chan struct{}) { 262 timer := time.NewTimer(time.Second * 5) 263 select { 264 case <-abortNotifier: 265 if !timer.Stop() { 266 <-timer.C 267 } 268 case <-timer.C: 269 b.makeRequiredDirectories() 270 } 271 } 272 273 func (b *Builder) makeRequiredDirectories() error { 274 imageServer, err := srpc.DialHTTP("tcp", b.imageServerAddress, 0) 275 if err != nil { 276 b.logger.Printf("%s: %s\n", b.imageServerAddress, err) 277 return nil 278 } 279 defer imageServer.Close() 280 directoryList, err := client.ListDirectories(imageServer) 281 if err != nil { 282 b.logger.Println(err) 283 return nil 284 } 285 directories := make(map[string]struct{}, len(directoryList)) 286 for _, directory := range directoryList { 287 directories[directory.Name] = struct{}{} 288 } 289 streamNames := b.listAllStreamNames() 290 for _, streamName := range streamNames { 291 if _, ok := directories[streamName]; ok { 292 continue 293 } 294 pathComponents := strings.Split(streamName, "/") 295 for index := range pathComponents { 296 partPath := strings.Join(pathComponents[0:index+1], "/") 297 if _, ok := directories[partPath]; ok { 298 continue 299 } 300 if err := client.MakeDirectory(imageServer, partPath); err != nil { 301 return err 302 } 303 b.logger.Printf("Created missing directory: %s\n", partPath) 304 directories[partPath] = struct{}{} 305 } 306 } 307 return nil 308 } 309 310 func (b *Builder) reloadNormalStreamsConfiguration() error { 311 imageStreamsConfiguration, err := loadImageStreams(b.imageStreamsUrl) 312 if err != nil { 313 return err 314 } 315 b.logger.Println("Reloaded streams streams configuration") 316 return b.updateImageStreams(imageStreamsConfiguration) 317 } 318 319 func (b *Builder) updateImageStreams( 320 imageStreamsConfiguration *imageStreamsConfigurationType) error { 321 for name, stream := range imageStreamsConfiguration.Streams { 322 stream.builder = b 323 stream.name = name 324 } 325 b.streamsLock.Lock() 326 b.imageStreams = imageStreamsConfiguration.Streams 327 b.streamsLock.Unlock() 328 b.triggerDependencyDataGeneration() 329 return b.makeRequiredDirectories() 330 } 331 332 func (b *Builder) waitForStreamsLoaded(timeout time.Duration) error { 333 if timeout < 0 { 334 timeout = time.Hour 335 } else if timeout < time.Second { 336 timeout = time.Second 337 } 338 timer := time.NewTimer(timeout) 339 select { 340 case <-b.streamsLoadedChannel: 341 if !timer.Stop() { 342 <-timer.C 343 } 344 return nil 345 case <-timer.C: 346 return fmt.Errorf("timed out waiting for streams list to load") 347 } 348 } 349 350 func (b *Builder) watchConfigLoop(configChannel <-chan interface{}, 351 streamsLoadedChannel chan<- struct{}) { 352 firstLoadNotifier := make(chan struct{}) 353 go b.delayMakeRequiredDirectories(firstLoadNotifier) 354 for rawConfig := range configChannel { 355 imageStreamsConfig, ok := rawConfig.(*imageStreamsConfigurationType) 356 if !ok { 357 b.logger.Printf("received unknown type over config channel") 358 continue 359 } 360 if firstLoadNotifier != nil { 361 firstLoadNotifier <- struct{}{} 362 close(firstLoadNotifier) 363 firstLoadNotifier = nil 364 } 365 if streamsLoadedChannel != nil { 366 close(streamsLoadedChannel) 367 streamsLoadedChannel = nil 368 } 369 b.logger.Println("received new image streams configuration") 370 b.updateImageStreams(imageStreamsConfig) 371 } 372 }