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  }