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