github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/proxyupdater/proxyupdater_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package proxyupdater_test
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/loggo"
    18  	jujuos "github.com/juju/os/v2"
    19  	"github.com/juju/packaging/v3/commands"
    20  	pacconfig "github.com/juju/packaging/v3/config"
    21  	"github.com/juju/proxy"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/worker/v3"
    24  	"github.com/juju/worker/v3/workertest"
    25  	gc "gopkg.in/check.v1"
    26  
    27  	proxyupdaterapi "github.com/juju/juju/api/agent/proxyupdater"
    28  	"github.com/juju/juju/core/watcher"
    29  	coretesting "github.com/juju/juju/testing"
    30  	"github.com/juju/juju/worker/proxyupdater"
    31  )
    32  
    33  type ProxyUpdaterSuite struct {
    34  	coretesting.BaseSuite
    35  
    36  	api              *fakeAPI
    37  	proxyEnvFile     string
    38  	proxySystemdFile string
    39  	detectedSettings proxy.Settings
    40  	inProcSettings   chan proxy.Settings
    41  	config           proxyupdater.Config
    42  }
    43  
    44  var _ = gc.Suite(&ProxyUpdaterSuite{})
    45  
    46  func newNotAWatcher() notAWatcher {
    47  	return notAWatcher{workertest.NewFakeWatcher(2, 2)}
    48  }
    49  
    50  type notAWatcher struct {
    51  	workertest.NotAWatcher
    52  }
    53  
    54  func (w notAWatcher) Changes() watcher.NotifyChannel {
    55  	return w.NotAWatcher.Changes()
    56  }
    57  
    58  type fakeAPI struct {
    59  	proxies proxyupdaterapi.ProxyConfiguration
    60  	Err     error
    61  	Watcher *notAWatcher
    62  }
    63  
    64  func NewFakeAPI() *fakeAPI {
    65  	f := &fakeAPI{}
    66  	return f
    67  }
    68  
    69  func (api fakeAPI) ProxyConfig() (proxyupdaterapi.ProxyConfiguration, error) {
    70  	return api.proxies, api.Err
    71  }
    72  
    73  func (api *fakeAPI) WatchForProxyConfigAndAPIHostPortChanges() (watcher.NotifyWatcher, error) {
    74  	if api.Watcher == nil {
    75  		w := newNotAWatcher()
    76  		api.Watcher = &w
    77  	}
    78  	return api.Watcher, nil
    79  }
    80  
    81  func (s *ProxyUpdaterSuite) SetUpTest(c *gc.C) {
    82  	s.BaseSuite.SetUpTest(c)
    83  	s.api = NewFakeAPI()
    84  
    85  	// Make buffer large for tests that never look at the settings.
    86  	s.inProcSettings = make(chan proxy.Settings, 1000)
    87  
    88  	directory := c.MkDir()
    89  	s.proxySystemdFile = filepath.Join(directory, "systemd.file")
    90  	s.proxyEnvFile = filepath.Join(directory, "env.file")
    91  	logger := loggo.GetLogger("test.proxyupdater")
    92  	logger.SetLogLevel(loggo.TRACE)
    93  	s.config = proxyupdater.Config{
    94  		SupportLegacyValues: true,
    95  		SystemdFiles:        []string{s.proxySystemdFile},
    96  		EnvFiles:            []string{s.proxyEnvFile},
    97  		API:                 s.api,
    98  		InProcessUpdate: func(newSettings proxy.Settings) error {
    99  			select {
   100  			case s.inProcSettings <- newSettings:
   101  			case <-time.After(coretesting.LongWait):
   102  				panic("couldn't send settings on inProcSettings channel")
   103  			}
   104  			return nil
   105  		},
   106  		Logger: logger,
   107  	}
   108  	s.PatchValue(&pacconfig.AptProxyConfigFile, path.Join(directory, "juju-apt-proxy"))
   109  }
   110  
   111  func (s *ProxyUpdaterSuite) TearDownTest(c *gc.C) {
   112  	s.BaseSuite.TearDownTest(c)
   113  	if s.api.Watcher != nil {
   114  		s.api.Watcher.Close()
   115  	}
   116  }
   117  
   118  func (s *ProxyUpdaterSuite) waitProxySettings(c *gc.C, expected proxy.Settings) {
   119  	maxWait := time.After(coretesting.LongWait)
   120  	var (
   121  		inProcSettings, envSettings proxy.Settings
   122  		gotInProc, gotEnv           bool
   123  	)
   124  	for {
   125  		select {
   126  		case <-maxWait:
   127  			c.Fatalf("timeout while waiting for proxy settings to change")
   128  			return
   129  		case inProcSettings = <-s.inProcSettings:
   130  			if c.Check(inProcSettings, gc.Equals, expected) {
   131  				gotInProc = true
   132  			}
   133  		case <-time.After(coretesting.ShortWait):
   134  			envSettings = proxy.DetectProxies()
   135  			if envSettings == expected {
   136  				gotEnv = true
   137  			} else {
   138  				if envSettings != s.detectedSettings {
   139  					c.Logf("proxy settings are \n%#v, should be \n%#v, still waiting", envSettings, expected)
   140  				}
   141  				s.detectedSettings = envSettings
   142  			}
   143  		}
   144  		if gotEnv && gotInProc {
   145  			break
   146  		}
   147  	}
   148  }
   149  
   150  func (s *ProxyUpdaterSuite) waitForFile(c *gc.C, filename, expected string) {
   151  	maxWait := time.After(coretesting.LongWait)
   152  	for {
   153  		select {
   154  		case <-maxWait:
   155  			c.Fatalf("timeout while waiting for proxy settings to change")
   156  			return
   157  		case <-time.After(10 * time.Millisecond):
   158  			fileContent, err := os.ReadFile(filename)
   159  			if os.IsNotExist(err) {
   160  				continue
   161  			}
   162  			c.Assert(err, jc.ErrorIsNil)
   163  			if string(fileContent) != expected {
   164  				c.Logf("file content not matching, still waiting")
   165  				continue
   166  			}
   167  			return
   168  		}
   169  	}
   170  }
   171  
   172  func (s *ProxyUpdaterSuite) TestRunStop(c *gc.C) {
   173  	updater, err := proxyupdater.NewWorker(s.config)
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	workertest.CleanKill(c, updater)
   176  }
   177  
   178  func (s *ProxyUpdaterSuite) useLegacyConfig(c *gc.C) (proxy.Settings, proxy.Settings) {
   179  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   180  		LegacyProxy: proxy.Settings{
   181  			Http:    "http legacy proxy",
   182  			Https:   "https legacy proxy",
   183  			Ftp:     "ftp legacy proxy",
   184  			NoProxy: "localhost,no legacy proxy",
   185  		},
   186  		APTProxy: proxy.Settings{
   187  			Http:  "http://apt.http.proxy",
   188  			Https: "https://apt.https.proxy",
   189  			Ftp:   "ftp://apt.ftp.proxy",
   190  		},
   191  	}
   192  
   193  	return s.api.proxies.LegacyProxy, s.api.proxies.APTProxy
   194  }
   195  
   196  func (s *ProxyUpdaterSuite) useJujuConfig(c *gc.C) (proxy.Settings, proxy.Settings) {
   197  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   198  		JujuProxy: proxy.Settings{
   199  			Http:    "http juju proxy",
   200  			Https:   "https juju proxy",
   201  			Ftp:     "ftp juju proxy",
   202  			NoProxy: "localhost,no juju proxy",
   203  		},
   204  		APTProxy: proxy.Settings{
   205  			Http:  "http://apt.http.proxy",
   206  			Https: "https://apt.https.proxy",
   207  			Ftp:   "ftp://apt.ftp.proxy",
   208  		},
   209  	}
   210  
   211  	return s.api.proxies.JujuProxy, s.api.proxies.APTProxy
   212  }
   213  
   214  func (s *ProxyUpdaterSuite) TestInitialStateLegacyProxy(c *gc.C) {
   215  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   216  		c.Skip(fmt.Sprintf("apt settings not handled on %s", host.String()))
   217  	}
   218  
   219  	proxySettings, aptProxySettings := s.useLegacyConfig(c)
   220  
   221  	updater, err := proxyupdater.NewWorker(s.config)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	defer worker.Stop(updater)
   224  
   225  	s.waitProxySettings(c, proxySettings)
   226  	s.waitForFile(c, s.proxyEnvFile, proxySettings.AsScriptEnvironment())
   227  	s.waitForFile(c, s.proxySystemdFile, proxySettings.AsSystemdDefaultEnv())
   228  
   229  	paccmder := commands.NewAptPackageCommander()
   230  	s.waitForFile(c, pacconfig.AptProxyConfigFile, paccmder.ProxyConfigContents(aptProxySettings)+"\n")
   231  }
   232  
   233  func (s *ProxyUpdaterSuite) TestInitialStateJujuProxy(c *gc.C) {
   234  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   235  		c.Skip(fmt.Sprintf("apt settings not handled on %s", host.String()))
   236  	}
   237  
   238  	proxySettings, aptProxySettings := s.useJujuConfig(c)
   239  
   240  	updater, err := proxyupdater.NewWorker(s.config)
   241  	c.Assert(err, jc.ErrorIsNil)
   242  	defer worker.Stop(updater)
   243  
   244  	s.waitProxySettings(c, proxySettings)
   245  	var empty proxy.Settings
   246  	// The environment files are written, but with empty content.
   247  	// This keeps the symlinks working.
   248  	s.waitForFile(c, s.proxyEnvFile, empty.AsScriptEnvironment())
   249  	s.waitForFile(c, s.proxySystemdFile, empty.AsSystemdDefaultEnv())
   250  
   251  	paccmder := commands.NewAptPackageCommander()
   252  	s.waitForFile(c, pacconfig.AptProxyConfigFile, paccmder.ProxyConfigContents(aptProxySettings)+"\n")
   253  }
   254  
   255  func (s *ProxyUpdaterSuite) TestEnvironmentVariablesLegacyProxy(c *gc.C) {
   256  	setenv := func(proxy, value string) {
   257  		os.Setenv(proxy, value)
   258  		os.Setenv(strings.ToUpper(proxy), value)
   259  	}
   260  	setenv("http_proxy", "foo")
   261  	setenv("https_proxy", "foo")
   262  	setenv("ftp_proxy", "foo")
   263  	setenv("no_proxy", "foo")
   264  
   265  	proxySettings, _ := s.useLegacyConfig(c)
   266  	updater, err := proxyupdater.NewWorker(s.config)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	defer worker.Stop(updater)
   269  	s.waitProxySettings(c, proxySettings)
   270  
   271  	assertEnv := func(proxy, value string) {
   272  		c.Assert(os.Getenv(proxy), gc.Equals, value)
   273  		c.Assert(os.Getenv(strings.ToUpper(proxy)), gc.Equals, value)
   274  	}
   275  	assertEnv("http_proxy", proxySettings.Http)
   276  	assertEnv("https_proxy", proxySettings.Https)
   277  	assertEnv("ftp_proxy", proxySettings.Ftp)
   278  	assertEnv("no_proxy", proxySettings.NoProxy)
   279  }
   280  
   281  func (s *ProxyUpdaterSuite) TestEnvironmentVariablesJujuProxy(c *gc.C) {
   282  	setenv := func(proxy, value string) {
   283  		os.Setenv(proxy, value)
   284  		os.Setenv(strings.ToUpper(proxy), value)
   285  	}
   286  	setenv("http_proxy", "foo")
   287  	setenv("https_proxy", "foo")
   288  	setenv("ftp_proxy", "foo")
   289  	setenv("no_proxy", "foo")
   290  
   291  	proxySettings, _ := s.useJujuConfig(c)
   292  	updater, err := proxyupdater.NewWorker(s.config)
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	defer worker.Stop(updater)
   295  	s.waitProxySettings(c, proxySettings)
   296  
   297  	assertEnv := func(proxy, value string) {
   298  		c.Assert(os.Getenv(proxy), gc.Equals, value)
   299  		c.Assert(os.Getenv(strings.ToUpper(proxy)), gc.Equals, value)
   300  	}
   301  	assertEnv("http_proxy", proxySettings.Http)
   302  	assertEnv("https_proxy", proxySettings.Https)
   303  	assertEnv("ftp_proxy", proxySettings.Ftp)
   304  	assertEnv("no_proxy", proxySettings.NoProxy)
   305  }
   306  
   307  func (s *ProxyUpdaterSuite) TestExternalFuncCalled(c *gc.C) {
   308  
   309  	// Called for both legacy and juju proxy values
   310  	externalProxySet := func() proxy.Settings {
   311  		updated := make(chan proxy.Settings)
   312  		done := make(chan struct{})
   313  		s.config.ExternalUpdate = func(values proxy.Settings) error {
   314  			select {
   315  			case updated <- values:
   316  			case <-done:
   317  			}
   318  			return nil
   319  		}
   320  		updater, err := proxyupdater.NewWorker(s.config)
   321  		c.Assert(err, jc.ErrorIsNil)
   322  		defer worker.Stop(updater)
   323  		// We need to close done before stopping the worker, so the
   324  		// defer comes after the worker stop.
   325  		defer close(done)
   326  
   327  		select {
   328  		case <-time.After(time.Second):
   329  			c.Fatal("function not called")
   330  		case externalSettings := <-updated:
   331  			return externalSettings
   332  		}
   333  		return proxy.Settings{}
   334  	}
   335  
   336  	proxySettings, _ := s.useLegacyConfig(c)
   337  	externalSettings := externalProxySet()
   338  	c.Assert(externalSettings, jc.DeepEquals, proxySettings)
   339  
   340  	proxySettings, _ = s.useJujuConfig(c)
   341  	externalSettings = externalProxySet()
   342  	c.Assert(externalSettings, jc.DeepEquals, proxySettings)
   343  }
   344  
   345  func (s *ProxyUpdaterSuite) TestErrorSettingInProcessLogs(c *gc.C) {
   346  	proxySettings, _ := s.useJujuConfig(c)
   347  
   348  	s.config.InProcessUpdate = func(newSettings proxy.Settings) error {
   349  		select {
   350  		case s.inProcSettings <- newSettings:
   351  		case <-time.After(coretesting.LongWait):
   352  			panic("couldn't send settings on inProcSettings channel")
   353  		}
   354  		return errors.New("gone daddy gone")
   355  	}
   356  
   357  	var logWriter loggo.TestWriter
   358  	c.Assert(loggo.RegisterWriter("proxyupdater-tests", &logWriter), jc.ErrorIsNil)
   359  	defer func() {
   360  		loggo.RemoveWriter("proxyupdater-tests")
   361  		logWriter.Clear()
   362  	}()
   363  
   364  	updater, err := proxyupdater.NewWorker(s.config)
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	s.waitProxySettings(c, proxySettings)
   367  	workertest.CleanKill(c, updater)
   368  
   369  	var foundMessage bool
   370  	expectedMessage := "error updating in-process proxy settings: gone daddy gone"
   371  	for _, entry := range logWriter.Log() {
   372  		if entry.Level == loggo.ERROR && strings.Contains(entry.Message, expectedMessage) {
   373  			foundMessage = true
   374  			break
   375  		}
   376  	}
   377  	c.Assert(foundMessage, jc.IsTrue)
   378  }
   379  
   380  func nextCall(c *gc.C, calls <-chan []string) []string {
   381  	select {
   382  	case call := <-calls:
   383  		return call
   384  	case <-time.After(coretesting.LongWait):
   385  		c.Fatalf("run func not called")
   386  	}
   387  	panic("unreachable")
   388  }
   389  
   390  func (s *ProxyUpdaterSuite) TestSnapProxySetNoneSet(c *gc.C) {
   391  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   392  		c.Skip(fmt.Sprintf("snap settings not handled on %s", host.String()))
   393  	}
   394  
   395  	logger := s.config.Logger
   396  	calls := make(chan []string)
   397  	s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) {
   398  		logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args)
   399  		calls <- append([]string{in, cmd}, args...)
   400  		return "", nil
   401  	}
   402  
   403  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{}
   404  
   405  	updater, err := proxyupdater.NewWorker(s.config)
   406  	c.Assert(err, jc.ErrorIsNil)
   407  	defer workertest.CleanKill(c, updater)
   408  
   409  	// The worker doesn't precheck any of the snap proxy values, as it is expected
   410  	// that the set call is cheap. Every time the worker starts, we call set for the current
   411  	// values.
   412  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "system",
   413  		"proxy.http=",
   414  		"proxy.https=",
   415  		"proxy.store=",
   416  	})
   417  }
   418  
   419  func (s *ProxyUpdaterSuite) TestSnapProxySet(c *gc.C) {
   420  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   421  		c.Skip(fmt.Sprintf("snap settings not handled on %s", host.String()))
   422  	}
   423  
   424  	logger := s.config.Logger
   425  	calls := make(chan []string)
   426  	s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) {
   427  		logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args)
   428  		calls <- append([]string{in, cmd}, args...)
   429  		return "", nil
   430  	}
   431  
   432  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   433  		SnapProxy: proxy.Settings{
   434  			Http:  "http://snap-proxy",
   435  			Https: "https://snap-proxy",
   436  		},
   437  	}
   438  
   439  	updater, err := proxyupdater.NewWorker(s.config)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	defer workertest.CleanKill(c, updater)
   442  
   443  	// The snap store is set to the empty string because as the agent is starting
   444  	// and it doesn't check to see what the store was set to, so to be sure, it just
   445  	// calls the set value.
   446  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "system",
   447  		"proxy.http=http://snap-proxy",
   448  		"proxy.https=https://snap-proxy",
   449  		"proxy.store=",
   450  	})
   451  }
   452  
   453  func (s *ProxyUpdaterSuite) TestSnapStoreProxy(c *gc.C) {
   454  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   455  		c.Skip(fmt.Sprintf("snap settings not handled on %s", host.String()))
   456  	}
   457  
   458  	logger := s.config.Logger
   459  	calls := make(chan []string)
   460  	s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) {
   461  		logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args)
   462  		calls <- append([]string{in, cmd}, args...)
   463  		return "", nil
   464  	}
   465  
   466  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   467  		SnapStoreProxyId:         "42",
   468  		SnapStoreProxyAssertions: "please trust us",
   469  	}
   470  
   471  	updater, err := proxyupdater.NewWorker(s.config)
   472  	c.Assert(err, jc.ErrorIsNil)
   473  	defer workertest.CleanKill(c, updater)
   474  
   475  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"please trust us", "snap", "ack", "/dev/stdin"})
   476  
   477  	// The http and https proxy values are set to be empty as it is the first pass through.
   478  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "system",
   479  		"proxy.http=",
   480  		"proxy.https=",
   481  		"proxy.store=42",
   482  	})
   483  }
   484  
   485  func (s *ProxyUpdaterSuite) TestSnapStoreProxyURL(c *gc.C) {
   486  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   487  		c.Skip(fmt.Sprintf("snap settings not handled on %s", host.String()))
   488  	}
   489  
   490  	logger := s.config.Logger
   491  	calls := make(chan []string)
   492  	s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) {
   493  		logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args)
   494  		calls <- append([]string{in, cmd}, args...)
   495  		return "", nil
   496  	}
   497  
   498  	var (
   499  		srv *httptest.Server
   500  
   501  		proxyRes = `
   502  type: store
   503  authority-id: canonical
   504  store: WhatDoesTheBigRedButtonDo
   505  operator-id: 0123456789067OdMqoW9YLp3e0EgakQf
   506  timestamp: 2019-08-27T12:20:45.166790Z
   507  url: $url
   508  sign-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul
   509  
   510  DATA...
   511  DATA...
   512  `
   513  	)
   514  
   515  	srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   516  		_, _ = w.Write([]byte(proxyRes))
   517  	}))
   518  	proxyRes = strings.Replace(proxyRes, "$url", srv.URL, -1)
   519  	defer srv.Close()
   520  
   521  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   522  		SnapStoreProxyURL: srv.URL,
   523  	}
   524  
   525  	updater, err := proxyupdater.NewWorker(s.config)
   526  	c.Assert(err, jc.ErrorIsNil)
   527  	defer workertest.CleanKill(c, updater)
   528  
   529  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{proxyRes, "snap", "ack", "/dev/stdin"})
   530  
   531  	// The http and https proxy values are set to be empty as it is the first pass through.
   532  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "system",
   533  		"proxy.http=",
   534  		"proxy.https=",
   535  		"proxy.store=WhatDoesTheBigRedButtonDo",
   536  	})
   537  }
   538  
   539  func (s *ProxyUpdaterSuite) TestSnapStoreProxyURLOverridesManualAssertion(c *gc.C) {
   540  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   541  		c.Skip(fmt.Sprintf("snap settings not handled on %s", host.String()))
   542  	}
   543  
   544  	logger := s.config.Logger
   545  	calls := make(chan []string)
   546  	s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) {
   547  		logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args)
   548  		calls <- append([]string{in, cmd}, args...)
   549  		return "", nil
   550  	}
   551  
   552  	var (
   553  		srv *httptest.Server
   554  
   555  		proxyRes = `
   556  type: store
   557  authority-id: canonical
   558  store: WhatDoesTheBigRedButtonDo
   559  operator-id: 0123456789067OdMqoW9YLp3e0EgakQf
   560  timestamp: 2019-08-27T12:20:45.166790Z
   561  url: $url
   562  sign-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul
   563  
   564  DATA...
   565  DATA...
   566  `
   567  	)
   568  
   569  	srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   570  		_, _ = w.Write([]byte(proxyRes))
   571  	}))
   572  	proxyRes = strings.Replace(proxyRes, "$url", srv.URL, -1)
   573  	defer srv.Close()
   574  
   575  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   576  		SnapStoreProxyId:         "42",
   577  		SnapStoreProxyAssertions: "please trust us",
   578  		SnapStoreProxyURL:        srv.URL,
   579  	}
   580  
   581  	updater, err := proxyupdater.NewWorker(s.config)
   582  	c.Assert(err, jc.ErrorIsNil)
   583  	defer workertest.CleanKill(c, updater)
   584  
   585  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{proxyRes, "snap", "ack", "/dev/stdin"})
   586  
   587  	// The http and https proxy values are set to be empty as it is the first pass through.
   588  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "system",
   589  		"proxy.http=",
   590  		"proxy.https=",
   591  		"proxy.store=WhatDoesTheBigRedButtonDo",
   592  	})
   593  }
   594  
   595  func (s *ProxyUpdaterSuite) TestAptMirror(c *gc.C) {
   596  	if host := jujuos.HostOS(); host == jujuos.CentOS {
   597  		c.Skip(fmt.Sprintf("apt mirror not supported on %s", host.String()))
   598  	}
   599  
   600  	logger := s.config.Logger
   601  	calls := make(chan []string)
   602  	s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) {
   603  		logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args)
   604  		calls <- append([]string{in, cmd}, args...)
   605  		return "", nil
   606  	}
   607  
   608  	s.api.proxies = proxyupdaterapi.ProxyConfiguration{
   609  		AptMirror: "http://mirror",
   610  	}
   611  
   612  	updater, err := proxyupdater.NewWorker(s.config)
   613  	c.Assert(err, jc.ErrorIsNil)
   614  	defer workertest.CleanKill(c, updater)
   615  
   616  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "system",
   617  		"proxy.http=",
   618  		"proxy.https=",
   619  		"proxy.store=",
   620  	})
   621  	c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "/bin/bash", "-c", `
   622  #!/bin/bash
   623  set -e
   624  (
   625  old_archive_mirror=$(awk "/^deb .* $(awk -F= '/DISTRIB_CODENAME=/ {gsub(/"/,""); print $2}' /etc/lsb-release) .*main.*\$/{print \$2;exit}" /etc/apt/sources.list)
   626  new_archive_mirror=http://mirror
   627  sed -i s,$old_archive_mirror,$new_archive_mirror, /etc/apt/sources.list
   628  old_prefix=/var/lib/apt/lists/$(echo $old_archive_mirror | sed 's,.*://,,' | sed 's,/$,,' | tr / _)
   629  new_prefix=/var/lib/apt/lists/$(echo $new_archive_mirror | sed 's,.*://,,' | sed 's,/$,,' | tr / _)
   630  [ "$old_prefix" != "$new_prefix" ] &&
   631  for old in ${old_prefix}_*; do
   632      new=$(echo $old | sed s,^$old_prefix,$new_prefix,)
   633      if [ -f $old ]; then
   634        mv $old $new
   635      fi
   636  done
   637  old_security_mirror=$(awk "/^deb .* $(awk -F= '/DISTRIB_CODENAME=/ {gsub(/"/,""); print $2}' /etc/lsb-release)-security .*main.*\$/{print \$2;exit}" /etc/apt/sources.list)
   638  new_security_mirror=http://mirror
   639  sed -i s,$old_security_mirror,$new_security_mirror, /etc/apt/sources.list
   640  old_prefix=/var/lib/apt/lists/$(echo $old_security_mirror | sed 's,.*://,,' | sed 's,/$,,' | tr / _)
   641  new_prefix=/var/lib/apt/lists/$(echo $new_security_mirror | sed 's,.*://,,' | sed 's,/$,,' | tr / _)
   642  [ "$old_prefix" != "$new_prefix" ] &&
   643  for old in ${old_prefix}_*; do
   644      new=$(echo $old | sed s,^$old_prefix,$new_prefix,)
   645      if [ -f $old ]; then
   646        mv $old $new
   647      fi
   648  done
   649  )`[1:],
   650  	})
   651  }