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  }