github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/proxyupdater/proxyupdater.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package proxyupdater 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "path" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/utils" 14 "github.com/juju/utils/exec" 15 "github.com/juju/utils/os" 16 "github.com/juju/utils/packaging/commands" 17 "github.com/juju/utils/packaging/config" 18 proxyutils "github.com/juju/utils/proxy" 19 "github.com/juju/utils/series" 20 21 "github.com/juju/juju/watcher" 22 "github.com/juju/juju/worker" 23 ) 24 25 var ( 26 logger = loggo.GetLogger("juju.worker.proxyupdater") 27 ) 28 29 type Config struct { 30 Directory string 31 RegistryPath string 32 Filename string 33 API API 34 ExternalUpdate func(proxyutils.Settings) error 35 } 36 37 // API is an interface that is provided to New 38 // which can be used to fetch the API host ports 39 type API interface { 40 ProxyConfig() (proxyutils.Settings, proxyutils.Settings, error) 41 WatchForProxyConfigAndAPIHostPortChanges() (watcher.NotifyWatcher, error) 42 } 43 44 // proxyWorker is responsible for monitoring the juju environment 45 // configuration and making changes on the physical (or virtual) machine as 46 // necessary to match the environment changes. Examples of these types of 47 // changes are apt proxy configuration and the juju proxies stored in the juju 48 // proxy file. 49 type proxyWorker struct { 50 aptProxy proxyutils.Settings 51 proxy proxyutils.Settings 52 53 // The whole point of the first value is to make sure that the the files 54 // are written out the first time through, even if they are the same as 55 // "last" time, as the initial value for last time is the zeroed struct. 56 // There is the possibility that the files exist on disk with old 57 // settings, and the environment has been updated to now not have them. We 58 // need to make sure that the disk reflects the environment, so the first 59 // time through, even if the proxies are empty, we write the files to 60 // disk. 61 first bool 62 config Config 63 } 64 65 // NewWorker returns a worker.Worker that updates proxy environment variables for the 66 // process and for the whole machine. 67 var NewWorker = func(config Config) (worker.Worker, error) { 68 envWorker := &proxyWorker{ 69 first: true, 70 config: config, 71 } 72 w, err := watcher.NewNotifyWorker(watcher.NotifyConfig{ 73 Handler: envWorker, 74 }) 75 if err != nil { 76 return nil, errors.Trace(err) 77 } 78 return w, nil 79 } 80 81 func (w *proxyWorker) writeEnvironmentFile() error { 82 // Writing the environment file is handled by executing the script: 83 // 84 // On cloud-instance ubuntu images, the ubuntu user is uid 1000, but in 85 // the situation where the ubuntu user has been created as a part of the 86 // manual provisioning process, the user will exist, and will not have the 87 // same uid/gid as the default cloud image. 88 // 89 // It is easier to shell out to check, and is also the same way that the file 90 // is written in the cloud-init process, so consistency FTW. 91 filePath := path.Join(w.config.Directory, w.config.Filename) 92 result, err := exec.RunCommands(exec.RunParams{ 93 Commands: fmt.Sprintf( 94 `[ -e %s ] && (printf '%%s\n' %s > %s && chown ubuntu:ubuntu %s)`, 95 w.config.Directory, 96 utils.ShQuote(w.proxy.AsScriptEnvironment()), 97 filePath, filePath), 98 WorkingDir: w.config.Directory, 99 }) 100 101 if err != nil { 102 return err 103 } 104 if result.Code != 0 { 105 logger.Errorf("failed writing new proxy values: \n%s\n%s", result.Stdout, result.Stderr) 106 } 107 return nil 108 } 109 110 func (w *proxyWorker) writeEnvironmentToRegistry() error { 111 // On windows we write the proxy settings to the registry. 112 setProxyScript := `$value_path = "%s" 113 $new_proxy = "%s" 114 $proxy_val = Get-ItemProperty -Path $value_path -Name ProxySettings 115 if ($? -eq $false){ New-ItemProperty -Path $value_path -Name ProxySettings -PropertyType String -Value $new_proxy }else{ Set-ItemProperty -Path $value_path -Name ProxySettings -Value $new_proxy } 116 ` 117 118 if w.config.RegistryPath == "" { 119 err := fmt.Errorf("config.RegistryPath is empty") 120 logger.Errorf("writeEnvironmentToRegistry couldn't write proxy settings to registry: %s", err) 121 return err 122 } 123 124 result, err := exec.RunCommands(exec.RunParams{ 125 Commands: fmt.Sprintf( 126 setProxyScript, 127 w.config.RegistryPath, 128 w.proxy.Http), 129 }) 130 if err != nil { 131 return err 132 } 133 if result.Code != 0 { 134 logger.Errorf("failed writing new proxy values: \n%s\n%s", result.Stdout, result.Stderr) 135 } 136 return nil 137 } 138 139 func (w *proxyWorker) writeEnvironment() error { 140 switch os.HostOS() { 141 case os.Windows: 142 return w.writeEnvironmentToRegistry() 143 default: 144 return w.writeEnvironmentFile() 145 } 146 } 147 148 func (w *proxyWorker) handleProxyValues(proxySettings proxyutils.Settings) { 149 proxySettings.SetEnvironmentValues() 150 if proxySettings != w.proxy || w.first { 151 logger.Debugf("new proxy settings %#v", proxySettings) 152 w.proxy = proxySettings 153 if err := w.writeEnvironment(); err != nil { 154 // It isn't really fatal, but we should record it. 155 logger.Errorf("error writing proxy environment file: %v", err) 156 } 157 if externalFunc := w.config.ExternalUpdate; externalFunc != nil { 158 if err := externalFunc(proxySettings); err != nil { 159 // It isn't really fatal, but we should record it. 160 logger.Errorf("%v", err) 161 } 162 } 163 } 164 } 165 166 // getPackageCommander is a helper function which returns the 167 // package commands implementation for the current system. 168 func getPackageCommander() (commands.PackageCommander, error) { 169 return commands.NewPackageCommander(series.HostSeries()) 170 } 171 172 func (w *proxyWorker) handleAptProxyValues(aptSettings proxyutils.Settings) error { 173 if aptSettings != w.aptProxy || w.first { 174 logger.Debugf("new apt proxy settings %#v", aptSettings) 175 paccmder, err := getPackageCommander() 176 if err != nil { 177 return err 178 } 179 w.aptProxy = aptSettings 180 181 // Always finish with a new line. 182 content := paccmder.ProxyConfigContents(w.aptProxy) + "\n" 183 err = ioutil.WriteFile(config.AptProxyConfigFile, []byte(content), 0644) 184 if err != nil { 185 // It isn't really fatal, but we should record it. 186 logger.Errorf("error writing apt proxy config file: %v", err) 187 } 188 } 189 return nil 190 } 191 192 func (w *proxyWorker) onChange() error { 193 proxySettings, APTProxySettings, err := w.config.API.ProxyConfig() 194 if err != nil { 195 return err 196 } 197 198 w.handleProxyValues(proxySettings) 199 return w.handleAptProxyValues(APTProxySettings) 200 } 201 202 // SetUp is defined on the worker.NotifyWatchHandler interface. 203 func (w *proxyWorker) SetUp() (watcher.NotifyWatcher, error) { 204 // We need to set this up initially as the NotifyWorker sucks up the first 205 // event. 206 err := w.onChange() 207 if err != nil { 208 return nil, err 209 } 210 w.first = false 211 return w.config.API.WatchForProxyConfigAndAPIHostPortChanges() 212 } 213 214 // Handle is defined on the worker.NotifyWatchHandler interface. 215 func (w *proxyWorker) Handle(_ <-chan struct{}) error { 216 return w.onChange() 217 } 218 219 // TearDown is defined on the worker.NotifyWatchHandler interface. 220 func (w *proxyWorker) TearDown() error { 221 // Nothing to cleanup, only state is the watcher 222 return nil 223 }