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  }