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 }