github.com/jenkins-x/test-infra@v0.0.7/prow/config/agent.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"os"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/sirupsen/logrus"
    25  )
    26  
    27  // Agent watches a path and automatically loads the config stored
    28  // therein.
    29  type Agent struct {
    30  	sync.Mutex
    31  	c             *Config
    32  	subscriptions []chan<- Delta
    33  }
    34  
    35  // Start will begin polling the config file at the path. If the first load
    36  // fails, Start will return the error and abort. Future load failures will log
    37  // the failure message but continue attempting to load.
    38  func (ca *Agent) Start(prowConfig, jobConfig string) error {
    39  	c, err := Load(prowConfig, jobConfig)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	ca.Set(c)
    44  	go func() {
    45  		var lastModTime time.Time
    46  		// Rarely, if two changes happen in the same second, mtime will
    47  		// be the same for the second change, and an mtime-based check would
    48  		// fail. Reload periodically just in case.
    49  		skips := 0
    50  		for range time.Tick(1 * time.Second) {
    51  			if skips < 600 {
    52  				// Check if the file changed to see if it needs to be re-read.
    53  				// os.Stat follows symbolic links, which is how ConfigMaps work.
    54  				prowStat, err := os.Stat(prowConfig)
    55  				if err != nil {
    56  					logrus.WithField("prowConfig", prowConfig).WithError(err).Error("Error loading prow config.")
    57  					continue
    58  				}
    59  
    60  				recentModTime := prowStat.ModTime()
    61  
    62  				// TODO(krzyzacy): allow empty jobConfig till fully migrate config to subdirs
    63  				if jobConfig != "" {
    64  					jobConfigStat, err := os.Stat(jobConfig)
    65  					if err != nil {
    66  						logrus.WithField("jobConfig", jobConfig).WithError(err).Error("Error loading job configs.")
    67  						continue
    68  					}
    69  
    70  					if jobConfigStat.ModTime().After(recentModTime) {
    71  						recentModTime = jobConfigStat.ModTime()
    72  					}
    73  				}
    74  
    75  				if !recentModTime.After(lastModTime) {
    76  					skips++
    77  					continue // file hasn't been modified
    78  				}
    79  				lastModTime = recentModTime
    80  			}
    81  			if c, err := Load(prowConfig, jobConfig); err != nil {
    82  				logrus.WithField("prowConfig", prowConfig).
    83  					WithField("jobConfig", jobConfig).
    84  					WithError(err).Error("Error loading config.")
    85  			} else {
    86  				skips = 0
    87  				ca.Set(c)
    88  			}
    89  		}
    90  	}()
    91  	return nil
    92  }
    93  
    94  // Delta represents the before and after states of a Config change detected by the Agent.
    95  type Delta struct {
    96  	Before, After Config
    97  }
    98  
    99  // Subscribe registers the channel for messages on config reload.
   100  // The caller can expect a copy of the previous and current config
   101  // to be sent down the subscribed channel when a new configuration
   102  // is loaded.
   103  func (ca *Agent) Subscribe(subscription chan<- Delta) {
   104  	ca.Lock()
   105  	defer ca.Unlock()
   106  	ca.subscriptions = append(ca.subscriptions, subscription)
   107  }
   108  
   109  // Config returns the latest config. Do not modify the config.
   110  func (ca *Agent) Config() *Config {
   111  	ca.Lock()
   112  	defer ca.Unlock()
   113  	return ca.c
   114  }
   115  
   116  // Set sets the config. Useful for testing.
   117  func (ca *Agent) Set(c *Config) {
   118  	ca.Lock()
   119  	defer ca.Unlock()
   120  	var oldConfig Config
   121  	if ca.c != nil {
   122  		oldConfig = *ca.c
   123  	}
   124  	delta := Delta{oldConfig, *c}
   125  	for _, subscription := range ca.subscriptions {
   126  		// we can't let unbuffered channels for subscriptions lock us up
   127  		// here, so we will send events best-effort into the channels we have
   128  		go func(out chan<- Delta) { out <- delta }(subscription)
   129  	}
   130  	ca.c = c
   131  }