github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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" 9 "io/ioutil" 10 stdexec "os/exec" 11 12 "github.com/juju/errors" 13 "github.com/juju/os" 14 "github.com/juju/os/series" 15 "github.com/juju/packaging/commands" 16 "github.com/juju/packaging/config" 17 "github.com/juju/proxy" 18 "github.com/juju/utils/exec" 19 "gopkg.in/juju/worker.v1" 20 21 "github.com/juju/juju/api/proxyupdater" 22 "github.com/juju/juju/core/watcher" 23 ) 24 25 type Config struct { 26 RegistryPath string 27 EnvFiles []string 28 SystemdFiles []string 29 API API 30 ExternalUpdate func(proxy.Settings) error 31 InProcessUpdate func(proxy.Settings) error 32 RunFunc func(string, string, ...string) (string, error) 33 Logger Logger 34 } 35 36 // Validate ensures that all the required fields have values. 37 func (c *Config) Validate() error { 38 if c.API == nil { 39 return errors.NotValidf("missing API") 40 } 41 if c.InProcessUpdate == nil { 42 return errors.NotValidf("missing InProcessUpdate") 43 } 44 if c.Logger == nil { 45 return errors.NotValidf("missing Logger") 46 } 47 return nil 48 } 49 50 // API is an interface that is provided to New 51 // which can be used to fetch the API host ports 52 type API interface { 53 ProxyConfig() (proxyupdater.ProxyConfiguration, error) 54 WatchForProxyConfigAndAPIHostPortChanges() (watcher.NotifyWatcher, error) 55 } 56 57 // proxyWorker is responsible for monitoring the juju environment 58 // configuration and making changes on the physical (or virtual) machine as 59 // necessary to match the environment changes. Examples of these types of 60 // changes are apt proxy configuration and the juju proxies stored in the juju 61 // proxy file. 62 type proxyWorker struct { 63 aptProxy proxy.Settings 64 proxy proxy.Settings 65 66 snapProxy proxy.Settings 67 snapStoreProxy string 68 snapStoreAssertions string 69 70 // The whole point of the first value is to make sure that the the files 71 // are written out the first time through, even if they are the same as 72 // "last" time, as the initial value for last time is the zeroed struct. 73 // There is the possibility that the files exist on disk with old 74 // settings, and the environment has been updated to now not have them. We 75 // need to make sure that the disk reflects the environment, so the first 76 // time through, even if the proxies are empty, we write the files to 77 // disk. 78 first bool 79 config Config 80 } 81 82 // NewWorker returns a worker.Worker that updates proxy environment variables for the 83 // process and for the whole machine. 84 var NewWorker = func(config Config) (worker.Worker, error) { 85 if err := config.Validate(); err != nil { 86 return nil, err 87 } 88 envWorker := &proxyWorker{ 89 first: true, 90 config: config, 91 } 92 w, err := watcher.NewNotifyWorker(watcher.NotifyConfig{ 93 Handler: envWorker, 94 }) 95 if err != nil { 96 return nil, errors.Trace(err) 97 } 98 return w, nil 99 } 100 101 func (w *proxyWorker) saveProxySettingsToFiles() error { 102 // The proxy settings are (usually) stored in three files: 103 // - /etc/juju-proxy.conf - in 'env' format 104 // - /etc/systemd/system.conf.d/juju-proxy.conf 105 // - /etc/systemd/user.conf.d/juju-proxy.conf - both in 'systemd' format 106 for _, file := range w.config.EnvFiles { 107 err := ioutil.WriteFile(file, []byte(w.proxy.AsScriptEnvironment()), 0644) 108 if err != nil { 109 w.config.Logger.Errorf("Error updating environment file %s - %v", file, err) 110 } 111 } 112 for _, file := range w.config.SystemdFiles { 113 err := ioutil.WriteFile(file, []byte(w.proxy.AsSystemdDefaultEnv()), 0644) 114 if err != nil { 115 w.config.Logger.Errorf("Error updating systemd file - %v", err) 116 } 117 } 118 return nil 119 } 120 121 func (w *proxyWorker) saveProxySettingsToRegistry() error { 122 // On windows we write the proxy settings to the registry. 123 setProxyScript := `$value_path = "%s" 124 $new_proxy = "%s" 125 $proxy_val = Get-ItemProperty -Path $value_path -Name ProxySettings 126 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 } 127 ` 128 129 if w.config.RegistryPath == "" { 130 err := fmt.Errorf("config.RegistryPath is empty") 131 w.config.Logger.Errorf("saveProxySettingsToRegistry couldn't write proxy settings to registry: %s", err) 132 return err 133 } 134 135 result, err := exec.RunCommands(exec.RunParams{ 136 Commands: fmt.Sprintf( 137 setProxyScript, 138 w.config.RegistryPath, 139 w.proxy.Http), 140 }) 141 if err != nil { 142 return err 143 } 144 if result.Code != 0 { 145 w.config.Logger.Errorf("failed writing new proxy values: \n%s\n%s", result.Stdout, result.Stderr) 146 } 147 return nil 148 } 149 150 func (w *proxyWorker) saveProxySettings() error { 151 switch os.HostOS() { 152 case os.Windows: 153 return w.saveProxySettingsToRegistry() 154 default: 155 return w.saveProxySettingsToFiles() 156 } 157 } 158 159 func (w *proxyWorker) handleProxyValues(legacyProxySettings, jujuProxySettings proxy.Settings) { 160 // Legacy proxy settings update the environment, and also call the 161 // InProcessUpdate, which installs the proxy into the default HTTP 162 // transport. The same occurs for jujuProxySettings. 163 settings := jujuProxySettings 164 if jujuProxySettings.HasProxySet() { 165 w.config.Logger.Debugf("applying in-process juju proxy settings %#v", jujuProxySettings) 166 } else { 167 settings = legacyProxySettings 168 w.config.Logger.Debugf("applying in-process legacy proxy settings %#v", legacyProxySettings) 169 } 170 171 settings.SetEnvironmentValues() 172 if err := w.config.InProcessUpdate(settings); err != nil { 173 w.config.Logger.Errorf("error updating in-process proxy settings: %v", err) 174 } 175 176 // If the external update function is passed in, it is to update the LXD 177 // proxies. We want to set this to the proxy specified regardless of whether 178 // it was set with the legacy fields or the new juju fields. 179 if externalFunc := w.config.ExternalUpdate; externalFunc != nil { 180 if err := externalFunc(settings); err != nil { 181 // It isn't really fatal, but we should record it. 182 w.config.Logger.Errorf("%v", err) 183 } 184 } 185 186 // Here we write files to disk. This is done only for legacyProxySettings. 187 if legacyProxySettings != w.proxy || w.first { 188 w.config.Logger.Debugf("saving new legacy proxy settings %#v", legacyProxySettings) 189 w.proxy = legacyProxySettings 190 if err := w.saveProxySettings(); err != nil { 191 // It isn't really fatal, but we should record it. 192 w.config.Logger.Errorf("error saving proxy settings: %v", err) 193 } 194 } 195 } 196 197 // getPackageCommander is a helper function which returns the 198 // package commands implementation for the current system. 199 func getPackageCommander() (commands.PackageCommander, error) { 200 hostSeries, err := series.HostSeries() 201 if err != nil { 202 return nil, errors.Trace(err) 203 } 204 return commands.NewPackageCommander(hostSeries) 205 } 206 207 func (w *proxyWorker) handleSnapProxyValues(proxy proxy.Settings, storeID, storeAssertions string) { 208 if os.HostOS() == os.Windows { 209 w.config.Logger.Tracef("no snap proxies on windows") 210 return 211 } 212 if w.config.RunFunc == nil { 213 w.config.Logger.Tracef("snap proxies not updated by unit agents") 214 return 215 } 216 w.config.Logger.Tracef("setting snap proxy values: %#v, %q, %q", proxy, storeID, storeAssertions) 217 218 var snapSettings []string 219 maybeAddSettings := func(setting, value, saved string) { 220 if value != saved || w.first { 221 snapSettings = append(snapSettings, setting+"="+value) 222 } 223 } 224 maybeAddSettings("proxy.http", proxy.Http, w.snapProxy.Http) 225 maybeAddSettings("proxy.https", proxy.Https, w.snapProxy.Https) 226 maybeAddSettings("proxy.store", storeID, w.snapStoreProxy) 227 if len(snapSettings) > 0 { 228 args := append([]string{"set", "core"}, snapSettings...) 229 output, err := w.config.RunFunc(noStdIn, "snap", args...) 230 if err != nil { 231 w.config.Logger.Warningf("unable to set snap core settings %v: %v, output: %q", snapSettings, err, output) 232 } else { 233 w.config.Logger.Debugf("snap core settings %v updated, output: %q", snapSettings, output) 234 w.snapProxy = proxy 235 w.snapStoreProxy = storeID 236 } 237 } 238 239 if (storeAssertions != w.snapStoreAssertions || w.first) && storeAssertions != "" { 240 output, err := w.config.RunFunc(storeAssertions, "snap", "ack", "/dev/stdin") 241 if err != nil { 242 w.config.Logger.Warningf("unable to acknowledge assertions: %v, output: %q", err, output) 243 } else { 244 w.config.Logger.Debugf("snap store assertions acked, output: %q", output) 245 } 246 w.snapStoreAssertions = storeAssertions 247 } 248 } 249 250 func (w *proxyWorker) handleAptProxyValues(aptSettings proxy.Settings) error { 251 if aptSettings != w.aptProxy || w.first { 252 w.config.Logger.Debugf("new apt proxy settings %#v", aptSettings) 253 paccmder, err := getPackageCommander() 254 if err != nil { 255 return err 256 } 257 w.aptProxy = aptSettings 258 259 // Always finish with a new line. 260 content := paccmder.ProxyConfigContents(w.aptProxy) + "\n" 261 err = ioutil.WriteFile(config.AptProxyConfigFile, []byte(content), 0644) 262 if err != nil { 263 // It isn't really fatal, but we should record it. 264 w.config.Logger.Errorf("error writing apt proxy config file: %v", err) 265 } 266 } 267 return nil 268 } 269 270 func (w *proxyWorker) onChange() error { 271 config, err := w.config.API.ProxyConfig() 272 if err != nil { 273 return err 274 } 275 276 w.handleProxyValues(config.LegacyProxy, config.JujuProxy) 277 w.handleSnapProxyValues(config.SnapProxy, config.SnapStoreProxyId, config.SnapStoreProxyAssertions) 278 return w.handleAptProxyValues(config.APTProxy) 279 } 280 281 // SetUp is defined on the worker.NotifyWatchHandler interface. 282 func (w *proxyWorker) SetUp() (watcher.NotifyWatcher, error) { 283 // We need to set this up initially as the NotifyWorker sucks up the first 284 // event. 285 err := w.onChange() 286 if err != nil { 287 return nil, err 288 } 289 w.first = false 290 return w.config.API.WatchForProxyConfigAndAPIHostPortChanges() 291 } 292 293 // Handle is defined on the worker.NotifyWatchHandler interface. 294 func (w *proxyWorker) Handle(_ <-chan struct{}) error { 295 return w.onChange() 296 } 297 298 // TearDown is defined on the worker.NotifyWatchHandler interface. 299 func (w *proxyWorker) TearDown() error { 300 // Nothing to cleanup, only state is the watcher 301 return nil 302 } 303 304 const noStdIn = "" 305 306 // Execute the command specified with the args with optional stdin. 307 func RunWithStdIn(input string, command string, args ...string) (string, error) { 308 cmd := stdexec.Command(command, args...) 309 310 if input != "" { 311 stdin, err := cmd.StdinPipe() 312 if err != nil { 313 return "", errors.Annotate(err, "getting stdin pipe") 314 } 315 316 go func() { 317 defer stdin.Close() 318 io.WriteString(stdin, input) 319 }() 320 } 321 322 out, err := cmd.CombinedOutput() 323 output := string(out) 324 return output, err 325 }