github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cloudconfig/userdatacfg_test.go (about)

     1  // Copyright 2012, 2013, 2015 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package cloudconfig_test
     6  
     7  import (
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"path"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/juju/collections/set"
    19  	"github.com/juju/loggo"
    20  	pacconf "github.com/juju/packaging/config"
    21  	"github.com/juju/proxy"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/version"
    24  	gc "gopkg.in/check.v1"
    25  	"gopkg.in/juju/names.v2"
    26  	goyaml "gopkg.in/yaml.v2"
    27  
    28  	"github.com/juju/juju/agent"
    29  	"github.com/juju/juju/api"
    30  	"github.com/juju/juju/apiserver/params"
    31  	"github.com/juju/juju/cloudconfig"
    32  	"github.com/juju/juju/cloudconfig/cloudinit"
    33  	"github.com/juju/juju/cloudconfig/instancecfg"
    34  	"github.com/juju/juju/core/constraints"
    35  	"github.com/juju/juju/environs/config"
    36  	"github.com/juju/juju/environs/imagemetadata"
    37  	"github.com/juju/juju/juju/paths"
    38  	jujutesting "github.com/juju/juju/juju/testing"
    39  	"github.com/juju/juju/mongo"
    40  	"github.com/juju/juju/state/multiwatcher"
    41  	"github.com/juju/juju/testing"
    42  	"github.com/juju/juju/tools"
    43  )
    44  
    45  type cloudinitSuite struct {
    46  	testing.BaseSuite
    47  }
    48  
    49  var _ = gc.Suite(&cloudinitSuite{})
    50  
    51  var (
    52  	envConstraints       = constraints.MustParse("mem=2G")
    53  	bootstrapConstraints = constraints.MustParse("mem=4G")
    54  
    55  	allMachineJobs = []multiwatcher.MachineJob{
    56  		multiwatcher.JobManageModel,
    57  		multiwatcher.JobHostUnits,
    58  	}
    59  	normalMachineJobs = []multiwatcher.MachineJob{
    60  		multiwatcher.JobHostUnits,
    61  	}
    62  )
    63  
    64  func jujuLogDir(series string) string {
    65  	return path.Join(must(paths.LogDir(series)), "juju")
    66  }
    67  
    68  func jujuDataDir(series string) string {
    69  	return must(paths.DataDir(series))
    70  }
    71  
    72  func cloudInitOutputLog(logDir string) string {
    73  	return path.Join(logDir, "cloud-init-output.log")
    74  }
    75  
    76  func metricsSpoolDir(series string) string {
    77  	return must(paths.MetricsSpoolDir(series))
    78  }
    79  
    80  // TODO: add this to the utils package
    81  func must(s string, err error) string {
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  	return s
    86  }
    87  
    88  var stateServingInfo = params.StateServingInfo{
    89  	Cert:         string(serverCert),
    90  	PrivateKey:   string(serverKey),
    91  	CAPrivateKey: "ca-private-key",
    92  	StatePort:    37017,
    93  	APIPort:      17070,
    94  }
    95  
    96  // testcfg wraps InstanceConfig and provides helpers to modify it as
    97  // needed for specific test cases before using it. Most methods return
    98  // the method receiver (cfg) after (possibly) modifying it to allow
    99  // chaining calls.
   100  type testInstanceConfig instancecfg.InstanceConfig
   101  
   102  // makeTestConfig returns a minimal instance config for a non state
   103  // server machine (unless bootstrap is true) for the given series.
   104  func makeTestConfig(series string, bootstrap bool) *testInstanceConfig {
   105  	const defaultMachineID = "99"
   106  
   107  	cfg := new(testInstanceConfig)
   108  	cfg.ControllerTag = testing.ControllerTag
   109  	cfg.AuthorizedKeys = "sshkey1"
   110  	cfg.AgentEnvironment = map[string]string{
   111  		agent.ProviderType: "dummy",
   112  	}
   113  	cfg.MachineNonce = "FAKE_NONCE"
   114  	cfg.Jobs = normalMachineJobs
   115  	cfg.MetricsSpoolDir = metricsSpoolDir(series)
   116  	// APIInfo (sans Tag) must be initialized before calling setMachineID().
   117  	cfg.APIInfo = &api.Info{
   118  		Addrs:    []string{"state-addr.testing.invalid:54321"},
   119  		Password: "bletch",
   120  		CACert:   "CA CERT\n" + testing.CACert,
   121  		ModelTag: testing.ModelTag,
   122  	}
   123  	cfg.setMachineID(defaultMachineID)
   124  	cfg.setSeries(series)
   125  	if bootstrap {
   126  		return cfg.setController()
   127  	} else {
   128  		// Non-controller machines fetch their tools from
   129  		// the controller.
   130  		icfg := (*instancecfg.InstanceConfig)(cfg)
   131  		toolsList := icfg.ToolsList()
   132  		for i, tools := range toolsList {
   133  			tools.URL = fmt.Sprintf(
   134  				"https://%s/%s/tools/%s",
   135  				cfg.APIInfo.Addrs[0],
   136  				testing.ModelTag.Id(),
   137  				tools.Version,
   138  			)
   139  			toolsList[i] = tools
   140  		}
   141  		if err := icfg.SetTools(toolsList); err != nil {
   142  			panic(err)
   143  		}
   144  	}
   145  
   146  	return cfg
   147  }
   148  
   149  // makeBootstrapConfig is a shortcut to call makeTestConfig(series, true).
   150  func makeBootstrapConfig(series string) *testInstanceConfig {
   151  	return makeTestConfig(series, true)
   152  }
   153  
   154  // makeNormalConfig is a shortcut to call makeTestConfig(series,
   155  // false).
   156  func makeNormalConfig(series string) *testInstanceConfig {
   157  	return makeTestConfig(series, false)
   158  }
   159  
   160  // setMachineID updates MachineId, MachineAgentServiceName,
   161  // MongoInfo.Tag, and APIInfo.Tag to match the given machine ID. If
   162  // MongoInfo or APIInfo are nil, they're not changed.
   163  func (cfg *testInstanceConfig) setMachineID(id string) *testInstanceConfig {
   164  	cfg.MachineId = id
   165  	cfg.MachineAgentServiceName = fmt.Sprintf("jujud-%s", names.NewMachineTag(id).String())
   166  	if cfg.APIInfo != nil {
   167  		cfg.APIInfo.Tag = names.NewMachineTag(id)
   168  	}
   169  	return cfg
   170  }
   171  
   172  // setGUI populates the configuration with the Juju GUI tools.
   173  func (cfg *testInstanceConfig) setGUI(url string) *testInstanceConfig {
   174  	cfg.Bootstrap.GUI = &tools.GUIArchive{
   175  		URL:     url,
   176  		Version: version.MustParse("1.2.3"),
   177  		Size:    42,
   178  		SHA256:  "1234",
   179  	}
   180  	return cfg
   181  }
   182  
   183  // maybeSetModelConfig sets the Config field to the given envConfig, if not
   184  // nil, and the instance config is for a bootstrap machine.
   185  func (cfg *testInstanceConfig) maybeSetModelConfig(envConfig *config.Config) *testInstanceConfig {
   186  	if envConfig != nil && cfg.Bootstrap != nil {
   187  		cfg.Bootstrap.ControllerModelConfig = envConfig
   188  		cfg.Bootstrap.HostedModelConfig = map[string]interface{}{"name": "hosted-model"}
   189  	}
   190  	return cfg
   191  }
   192  
   193  // setEnableOSUpdateAndUpgrade sets EnableOSRefreshUpdate and EnableOSUpgrade
   194  // fields to the given values.
   195  func (cfg *testInstanceConfig) setEnableOSUpdateAndUpgrade(updateEnabled, upgradeEnabled bool) *testInstanceConfig {
   196  	cfg.EnableOSRefreshUpdate = updateEnabled
   197  	cfg.EnableOSUpgrade = upgradeEnabled
   198  	return cfg
   199  }
   200  
   201  // setSeries sets the series-specific fields (Tools, Series, DataDir,
   202  // LogDir, and CloudInitOutputLog) to match the given series.
   203  func (cfg *testInstanceConfig) setSeries(series string) *testInstanceConfig {
   204  	err := ((*instancecfg.InstanceConfig)(cfg)).SetTools(tools.List{
   205  		newSimpleTools(fmt.Sprintf("1.2.3-%s-amd64", series)),
   206  	})
   207  	if err != nil {
   208  		panic(err)
   209  	}
   210  	cfg.Series = series
   211  	cfg.DataDir = jujuDataDir(series)
   212  	cfg.LogDir = jujuLogDir(series)
   213  	cfg.CloudInitOutputLog = cloudInitOutputLog(series)
   214  	return cfg
   215  }
   216  
   217  // setController updates the config to be suitable for bootstrapping
   218  // a controller instance.
   219  func (cfg *testInstanceConfig) setController() *testInstanceConfig {
   220  	cfg.setMachineID("0")
   221  	cfg.Controller = &instancecfg.ControllerConfig{
   222  		MongoInfo: &mongo.MongoInfo{
   223  			Password: "arble",
   224  			Info: mongo.Info{
   225  				Addrs:  []string{"state-addr.testing.invalid:12345"},
   226  				CACert: "CA CERT\n" + testing.CACert,
   227  			},
   228  		},
   229  	}
   230  	cfg.Bootstrap = &instancecfg.BootstrapConfig{
   231  		StateInitializationParams: instancecfg.StateInitializationParams{
   232  			BootstrapMachineInstanceId:  "i-bootstrap",
   233  			BootstrapMachineConstraints: bootstrapConstraints,
   234  			ModelConstraints:            envConstraints,
   235  		},
   236  		StateServingInfo: stateServingInfo,
   237  		Timeout:          time.Minute * 10,
   238  	}
   239  	cfg.Jobs = allMachineJobs
   240  	cfg.APIInfo.Tag = nil
   241  	return cfg.setEnableOSUpdateAndUpgrade(true, false)
   242  }
   243  
   244  // mutate calls mutator passing cfg to it, and returns the (possibly)
   245  // modified cfg.
   246  func (cfg *testInstanceConfig) mutate(mutator func(*testInstanceConfig)) *testInstanceConfig {
   247  	if mutator == nil {
   248  		panic("mutator is nil!")
   249  	}
   250  	mutator(cfg)
   251  	return cfg
   252  }
   253  
   254  // render returns the config as InstanceConfig.
   255  func (cfg *testInstanceConfig) render() instancecfg.InstanceConfig {
   256  	return instancecfg.InstanceConfig(*cfg)
   257  }
   258  
   259  type cloudinitTest struct {
   260  	cfg           *testInstanceConfig
   261  	setEnvConfig  bool
   262  	expectScripts string
   263  	// inexactMatch signifies whether we allow extra lines
   264  	// in the actual scripts found. If it's true, the lines
   265  	// mentioned in expectScripts must appear in that
   266  	// order, but they can be arbitrarily interleaved with other
   267  	// script lines.
   268  	inexactMatch bool
   269  }
   270  
   271  func minimalModelConfig(c *gc.C) *config.Config {
   272  	cfg, err := config.New(config.NoDefaults, testing.FakeConfig().Delete("authorized-keys"))
   273  	c.Assert(err, jc.ErrorIsNil)
   274  	c.Assert(cfg, gc.NotNil)
   275  	return cfg
   276  }
   277  
   278  // Each test gives a cloudinit config - we check the
   279  // output to see if it looks correct.
   280  var cloudinitTests = []cloudinitTest{
   281  	// Test that cloudinit respects update/upgrade settings.
   282  	{
   283  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(false, false),
   284  		inexactMatch: true,
   285  		// We're just checking for apt-flags. We don't much care if
   286  		// the script matches.
   287  		expectScripts: "",
   288  		setEnvConfig:  true,
   289  	},
   290  
   291  	// Test that cloudinit respects update/upgrade settings.
   292  	{
   293  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(true, false),
   294  		inexactMatch: true,
   295  		// We're just checking for apt-flags. We don't much care if
   296  		// the script matches.
   297  		expectScripts: "",
   298  		setEnvConfig:  true,
   299  	},
   300  
   301  	// Test that cloudinit respects update/upgrade settings.
   302  	{
   303  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(false, true),
   304  		inexactMatch: true,
   305  		// We're just checking for apt-flags. We don't much care if
   306  		// the script matches.
   307  		expectScripts: "",
   308  		setEnvConfig:  true,
   309  	},
   310  
   311  	// Test that cloudinit respects update/upgrade settings.
   312  	{
   313  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(true, true),
   314  		inexactMatch: true,
   315  		// We're just checking for apt-flags. We don't much care if
   316  		// the script matches.
   317  		expectScripts: "",
   318  		setEnvConfig:  true,
   319  	},
   320  
   321  	// precise controller
   322  	{
   323  		cfg:          makeBootstrapConfig("precise"),
   324  		setEnvConfig: true,
   325  		expectScripts: `
   326  install -D -m 644 /dev/null '/etc/apt/preferences\.d/50-cloud-tools'
   327  printf '%s\\n' '.*' > '/etc/apt/preferences\.d/50-cloud-tools'
   328  set -xe
   329  install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf'
   330  printf '%s\\n' '.*"Stop all network interfaces.*' > '/etc/init/juju-clean-shutdown\.conf'
   331  install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'
   332  printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt'
   333  test -n "\$JUJU_PROGRESS_FD" \|\| \(exec \{JUJU_PROGRESS_FD\}>&2\) 2>/dev/null && exec \{JUJU_PROGRESS_FD\}>&2 \|\| JUJU_PROGRESS_FD=2
   334  \[ -e /etc/profile.d/juju-proxy.sh \] \|\| printf .* >> /etc/profile.d/juju-proxy.sh
   335  mkdir -p /var/lib/juju/locks
   336  \(id ubuntu &> /dev/null\) && chown ubuntu:ubuntu /var/lib/juju/locks
   337  mkdir -p /var/log/juju
   338  chown syslog:adm /var/log/juju
   339  bin='/var/lib/juju/tools/1\.2\.3-precise-amd64'
   340  mkdir -p \$bin
   341  echo 'Fetching Juju agent version.*
   342  curl .* '.*' --retry 10 -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/released/juju1\.2\.3-precise-amd64\.tgz'
   343  sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-precise-amd64\.sha256
   344  grep '1234' \$bin/juju1\.2\.3-precise-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\)
   345  tar zxf \$bin/tools.tar.gz -C \$bin
   346  printf %s '{"version":"1\.2\.3-precise-amd64","url":"http://foo\.com/tools/released/juju1\.2\.3-precise-amd64\.tgz","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt
   347  mkdir -p '/var/lib/juju/agents/machine-0'
   348  cat > '/var/lib/juju/agents/machine-0/agent\.conf' << 'EOF'\\n.*\\nEOF
   349  chmod 0600 '/var/lib/juju/agents/machine-0/agent\.conf'
   350  install -D -m 600 /dev/null '/var/lib/juju/bootstrap-params'
   351  printf '%s\\n' '.*' > '/var/lib/juju/bootstrap-params'
   352  echo 'Installing Juju machine agent'.*
   353  /var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug '/var/lib/juju/bootstrap-params'
   354  ln -s 1\.2\.3-precise-amd64 '/var/lib/juju/tools/machine-0'
   355  echo 'Starting Juju machine agent \(service jujud-machine-0\)'.*
   356  cat > /etc/init/jujud-machine-0\.conf << 'EOF'\\ndescription "juju agent for machine-0"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit .*\\n\\nscript\\n\\n\\n  # Ensure log files are properly protected\\n  touch /var/log/juju/machine-0\.log\\n  chown syslog:syslog /var/log/juju/machine-0\.log\\n  chmod 0600 /var/log/juju/machine-0\.log\\n\\n  exec '/var/lib/juju/tools/machine-0/jujud' machine --data-dir '/var/lib/juju' --machine-id 0 --debug >> /var/log/juju/machine-0\.log 2>&1\\nend script\\nEOF\\n
   357  start jujud-machine-0
   358  rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-precise-amd64\.sha256
   359  `,
   360  	},
   361  
   362  	// raring controller - we just test the raring-specific parts of the output.
   363  	{
   364  		cfg:          makeBootstrapConfig("raring"),
   365  		setEnvConfig: true,
   366  		inexactMatch: true,
   367  		expectScripts: `
   368  bin='/var/lib/juju/tools/1\.2\.3-raring-amd64'
   369  curl .* '.*' --retry 10 -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/released/juju1\.2\.3-raring-amd64\.tgz'
   370  sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-raring-amd64\.sha256
   371  grep '1234' \$bin/juju1\.2\.3-raring-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\)
   372  printf %s '{"version":"1\.2\.3-raring-amd64","url":"http://foo\.com/tools/released/juju1\.2\.3-raring-amd64\.tgz","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt
   373  install -D -m 600 /dev/null '/var/lib/juju/bootstrap-params'
   374  printf '%s\\n' '.*' > '/var/lib/juju/bootstrap-params'
   375  /var/lib/juju/tools/1\.2\.3-raring-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug '/var/lib/juju/bootstrap-params'
   376  ln -s 1\.2\.3-raring-amd64 '/var/lib/juju/tools/machine-0'
   377  rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-raring-amd64\.sha256
   378  `,
   379  	},
   380  
   381  	// quantal non controller.
   382  	{
   383  		cfg: makeNormalConfig("quantal"),
   384  		expectScripts: `
   385  set -xe
   386  install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf'
   387  printf '%s\\n' '.*"Stop all network interfaces on shutdown".*' > '/etc/init/juju-clean-shutdown\.conf'
   388  install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'
   389  printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt'
   390  test -n "\$JUJU_PROGRESS_FD" \|\| \(exec \{JUJU_PROGRESS_FD\}>&2\) 2>/dev/null && exec \{JUJU_PROGRESS_FD\}>&2 \|\| JUJU_PROGRESS_FD=2
   391  \[ -e /etc/profile.d/juju-proxy.sh \] \|\| printf .* >> /etc/profile.d/juju-proxy.sh
   392  mkdir -p /var/lib/juju/locks
   393  \(id ubuntu &> /dev/null\) && chown ubuntu:ubuntu /var/lib/juju/locks
   394  mkdir -p /var/log/juju
   395  chown syslog:adm /var/log/juju
   396  bin='/var/lib/juju/tools/1\.2\.3-quantal-amd64'
   397  mkdir -p \$bin
   398  echo 'Fetching Juju agent version.*
   399  curl -sSfw '.*' --connect-timeout 20 --noproxy "\*" --insecure -o \$bin/tools\.tar\.gz 'https://state-addr\.testing\.invalid:54321/deadbeef-0bad-400d-8000-4b1d0d06f00d/tools/1\.2\.3-quantal-amd64'
   400  sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-quantal-amd64\.sha256
   401  grep '1234' \$bin/juju1\.2\.3-quantal-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\)
   402  tar zxf \$bin/tools.tar.gz -C \$bin
   403  printf %s '{"version":"1\.2\.3-quantal-amd64","url":"https://state-addr\.testing\.invalid:54321/deadbeef-0bad-400d-8000-4b1d0d06f00d/tools/1\.2\.3-quantal-amd64","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt
   404  mkdir -p '/var/lib/juju/agents/machine-99'
   405  cat > '/var/lib/juju/agents/machine-99/agent\.conf' << 'EOF'\\n.*\\nEOF
   406  chmod 0600 '/var/lib/juju/agents/machine-99/agent\.conf'
   407  ln -s 1\.2\.3-quantal-amd64 '/var/lib/juju/tools/machine-99'
   408  echo 'Starting Juju machine agent \(service jujud-machine-99\)'.*
   409  cat > /etc/init/jujud-machine-99\.conf << 'EOF'\\ndescription "juju agent for machine-99"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit .*\\n\\nscript\\n\\n\\n  # Ensure log files are properly protected\\n  touch /var/log/juju/machine-99\.log\\n  chown syslog:syslog /var/log/juju/machine-99\.log\\n  chmod 0600 /var/log/juju/machine-99\.log\\n\\n  exec '/var/lib/juju/tools/machine-99/jujud' machine --data-dir '/var/lib/juju' --machine-id 99 --debug >> /var/log/juju/machine-99\.log 2>&1\\nend script\\nEOF\\n
   410  start jujud-machine-99
   411  rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-quantal-amd64\.sha256
   412  `,
   413  	},
   414  
   415  	// non controller with systemd (vivid)
   416  	{
   417  		cfg:          makeNormalConfig("vivid"),
   418  		inexactMatch: true,
   419  		expectScripts: `
   420  set -xe
   421  install -D -m 644 /dev/null '/etc/systemd/system/juju-clean-shutdown\.service'
   422  printf '%s\\n' '\\n\[Unit\]\\n.*Stop all network interfaces.*WantedBy=final\.target\\n' > '/etc/systemd.*'
   423  /bin/systemctl enable '/etc/systemd/system/juju-clean-shutdown\.service'
   424  install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'
   425  printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt'
   426  .*
   427  `,
   428  	},
   429  
   430  	// CentOS non controller with systemd
   431  	{
   432  		cfg:          makeNormalConfig("centos7"),
   433  		inexactMatch: true,
   434  		expectScripts: `
   435  systemctl is-enabled firewalld &> /dev/null && systemctl mask firewalld || true
   436  systemctl is-active firewalld &> /dev/null && systemctl stop firewalld || true
   437  sed -i "s/\^\.\*requiretty/#Defaults requiretty/" /etc/sudoers
   438  `,
   439  	},
   440  	// OpenSUSE non controller with systemd
   441  	{
   442  		cfg:          makeNormalConfig("opensuseleap"),
   443  		inexactMatch: true,
   444  		expectScripts: `
   445  systemctl is-enabled firewalld &> /dev/null && systemctl mask firewalld || true
   446  systemctl is-active firewalld &> /dev/null && systemctl stop firewalld || true
   447  sed -i "s/\^\.\*requiretty/#Defaults requiretty/" /etc/sudoers
   448  `,
   449  	},
   450  
   451  	// check that it works ok with compound machine ids.
   452  	{
   453  		cfg: makeNormalConfig("quantal").mutate(func(cfg *testInstanceConfig) {
   454  			cfg.MachineContainerType = "lxd"
   455  		}).setMachineID("2/lxd/1"),
   456  		inexactMatch: true,
   457  		expectScripts: `
   458  mkdir -p '/var/lib/juju/agents/machine-2-lxd-1'
   459  cat > '/var/lib/juju/agents/machine-2-lxd-1/agent\.conf' << 'EOF'\\n.*\\nEOF
   460  chmod 0600 '/var/lib/juju/agents/machine-2-lxd-1/agent\.conf'
   461  ln -s 1\.2\.3-quantal-amd64 '/var/lib/juju/tools/machine-2-lxd-1'
   462  cat > /etc/init/jujud-machine-2-lxd-1\.conf << 'EOF'\\ndescription "juju agent for machine-2-lxd-1"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit .*\\n\\nscript\\n\\n\\n  # Ensure log files are properly protected\\n  touch /var/log/juju/machine-2-lxd-1\.log\\n  chown syslog:syslog /var/log/juju/machine-2-lxd-1\.log\\n  chmod 0600 /var/log/juju/machine-2-lxd-1\.log\\n\\n  exec '/var/lib/juju/tools/machine-2-lxd-1/jujud' machine --data-dir '/var/lib/juju' --machine-id 2/lxd/1 --debug >> /var/log/juju/machine-2-lxd-1\.log 2>&1\\nend script\\nEOF\\n
   463  start jujud-machine-2-lxd-1
   464  `,
   465  	},
   466  
   467  	// hostname verification disabled.
   468  	{
   469  		cfg: makeNormalConfig("quantal").mutate(func(cfg *testInstanceConfig) {
   470  			cfg.DisableSSLHostnameVerification = true
   471  		}),
   472  		inexactMatch: true,
   473  		expectScripts: `
   474  curl .* --noproxy "\*" --insecure -o \$bin/tools\.tar\.gz 'https://state-addr\.testing\.invalid:54321/deadbeef-0bad-400d-8000-4b1d0d06f00d/tools/1\.2\.3-quantal-amd64'
   475  `,
   476  	},
   477  
   478  	// empty bootstrap contraints.
   479  	{
   480  		cfg: makeBootstrapConfig("precise").mutate(func(cfg *testInstanceConfig) {
   481  			cfg.Bootstrap.BootstrapMachineConstraints = constraints.Value{}
   482  		}),
   483  		setEnvConfig: true,
   484  		inexactMatch: true,
   485  		expectScripts: `
   486  printf '%s\\n' '.*bootstrap-machine-constraints: {}.*' > '/var/lib/juju/bootstrap-params'
   487  `,
   488  	},
   489  
   490  	// empty environ contraints.
   491  	{
   492  		cfg: makeBootstrapConfig("precise").mutate(func(cfg *testInstanceConfig) {
   493  			cfg.Bootstrap.ModelConstraints = constraints.Value{}
   494  		}),
   495  		setEnvConfig: true,
   496  		inexactMatch: true,
   497  		expectScripts: `
   498  printf '%s\\n' '.*model-constraints: {}.*' > '/var/lib/juju/bootstrap-params'
   499  `,
   500  	},
   501  
   502  	// custom image metadata (at bootstrap).
   503  	{
   504  		cfg: makeBootstrapConfig("trusty").mutate(func(cfg *testInstanceConfig) {
   505  			cfg.Bootstrap.CustomImageMetadata = []*imagemetadata.ImageMetadata{{
   506  				Id:         "image-id",
   507  				Storage:    "ebs",
   508  				VirtType:   "pv",
   509  				Arch:       "amd64",
   510  				Version:    "14.04",
   511  				RegionName: "us-east1",
   512  			}}
   513  		}),
   514  		setEnvConfig: true,
   515  		inexactMatch: true,
   516  		expectScripts: `
   517  printf '%s\\n' '.*custom-image-metadata:.*us-east1.*.*' > '/var/lib/juju/bootstrap-params'
   518  `,
   519  	},
   520  
   521  	// custom image metadata signing key.
   522  	{
   523  		cfg: makeBootstrapConfig("trusty").mutate(func(cfg *testInstanceConfig) {
   524  			cfg.Controller.PublicImageSigningKey = "publickey"
   525  		}),
   526  		setEnvConfig: true,
   527  		inexactMatch: true,
   528  		expectScripts: `
   529  install -D -m 644 /dev/null '.*publicsimplestreamskey'
   530  printf '%s\\n' 'publickey' > '.*publicsimplestreamskey'
   531  `,
   532  	},
   533  }
   534  
   535  func newSimpleTools(vers string) *tools.Tools {
   536  	return &tools.Tools{
   537  		URL:     "http://foo.com/tools/released/juju" + vers + ".tgz",
   538  		Version: version.MustParseBinary(vers),
   539  		Size:    10,
   540  		SHA256:  "1234",
   541  	}
   542  }
   543  
   544  func getAgentConfig(c *gc.C, tag string, scripts []string) (cfg string) {
   545  	c.Assert(scripts, gc.Not(gc.HasLen), 0)
   546  	re := regexp.MustCompile(`cat > .*agents/` + regexp.QuoteMeta(tag) + `/agent\.conf' << 'EOF'\n((\n|.)+)\nEOF`)
   547  	found := false
   548  	for _, s := range scripts {
   549  		m := re.FindStringSubmatch(s)
   550  		if m == nil {
   551  			continue
   552  		}
   553  		cfg = m[1]
   554  		found = true
   555  	}
   556  	c.Assert(found, jc.IsTrue)
   557  	return cfg
   558  }
   559  
   560  // check that any --model-config $base64 is valid and matches t.cfg.Config
   561  func checkEnvConfig(c *gc.C, cfg *config.Config, scripts []string) {
   562  	args := getStateInitializationParams(c, scripts)
   563  	c.Assert(cfg.AllAttrs(), jc.DeepEquals, args.ControllerModelConfig.AllAttrs())
   564  }
   565  
   566  func getStateInitializationParams(c *gc.C, scripts []string) instancecfg.StateInitializationParams {
   567  	var args instancecfg.StateInitializationParams
   568  	c.Assert(scripts, gc.Not(gc.HasLen), 0)
   569  	re := regexp.MustCompile(`printf '%s\\n' '(?s:(.+))' > '/var/lib/juju/bootstrap-params'`)
   570  	for _, s := range scripts {
   571  		m := re.FindStringSubmatch(s)
   572  		if m == nil {
   573  			continue
   574  		}
   575  		str := strings.Replace(m[1], "'\"'\"'", "'", -1)
   576  		err := args.Unmarshal([]byte(str))
   577  		c.Assert(err, jc.ErrorIsNil)
   578  		return args
   579  	}
   580  	c.Fatal("could not find state initialization params")
   581  	panic("unreachable")
   582  }
   583  
   584  // TestCloudInit checks that the output from the various tests
   585  // in cloudinitTests is well formed.
   586  func (*cloudinitSuite) TestCloudInit(c *gc.C) {
   587  	for i, test := range cloudinitTests {
   588  
   589  		c.Logf("test %d", i)
   590  		var envConfig *config.Config
   591  		if test.setEnvConfig {
   592  			envConfig = minimalModelConfig(c)
   593  		}
   594  		testConfig := test.cfg.maybeSetModelConfig(envConfig).render()
   595  		ci, err := cloudinit.New(testConfig.Series)
   596  		c.Assert(err, jc.ErrorIsNil)
   597  		udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci)
   598  		c.Assert(err, jc.ErrorIsNil)
   599  		err = udata.Configure()
   600  
   601  		c.Assert(err, jc.ErrorIsNil)
   602  		c.Check(ci, gc.NotNil)
   603  		// render the cloudinit config to bytes, and then
   604  		// back to a map so we can introspect it without
   605  		// worrying about internal details of the cloudinit
   606  		// package.
   607  		data, err := ci.RenderYAML()
   608  		c.Assert(err, jc.ErrorIsNil)
   609  
   610  		configKeyValues := make(map[interface{}]interface{})
   611  		err = goyaml.Unmarshal(data, &configKeyValues)
   612  		c.Assert(err, jc.ErrorIsNil)
   613  
   614  		if testConfig.EnableOSRefreshUpdate {
   615  			c.Check(configKeyValues["package_update"], jc.IsTrue)
   616  		} else {
   617  			c.Check(configKeyValues["package_update"], jc.IsFalse)
   618  		}
   619  
   620  		if testConfig.EnableOSUpgrade {
   621  			c.Check(configKeyValues["package_upgrade"], jc.IsTrue)
   622  		} else {
   623  			c.Check(configKeyValues["package_upgrade"], jc.IsFalse)
   624  		}
   625  
   626  		scripts := getScripts(configKeyValues)
   627  		assertScriptMatch(c, scripts, test.expectScripts, !test.inexactMatch)
   628  		if testConfig.Bootstrap != nil {
   629  			checkEnvConfig(c, testConfig.Bootstrap.ControllerModelConfig, scripts)
   630  		}
   631  
   632  		// curl should always be installed, since it's required by jujud.
   633  		checkPackage(c, configKeyValues, "curl", true)
   634  
   635  		tag := names.NewMachineTag(testConfig.MachineId).String()
   636  		acfg := getAgentConfig(c, tag, scripts)
   637  		c.Assert(acfg, jc.Contains, "AGENT_SERVICE_NAME: jujud-"+tag)
   638  		c.Assert(acfg, jc.Contains, "upgradedToVersion: 1.2.3\n")
   639  		source := "deb http://ubuntu-cloud.archive.canonical.com/ubuntu precise-updates/cloud-tools main"
   640  		needCloudArchive := testConfig.Series == "precise"
   641  		checkAptSource(c, configKeyValues, source, pacconf.UbuntuCloudArchiveSigningKey, needCloudArchive)
   642  	}
   643  }
   644  
   645  func (*cloudinitSuite) TestCloudInitWithLocalGUI(c *gc.C) {
   646  	guiPath := path.Join(c.MkDir(), "gui.tar.bz2")
   647  	content := []byte("content")
   648  	err := ioutil.WriteFile(guiPath, content, 0644)
   649  	c.Assert(err, jc.ErrorIsNil)
   650  	cfg := makeBootstrapConfig("precise").setGUI("file://" + filepath.ToSlash(guiPath))
   651  	guiJson, err := json.Marshal(cfg.Bootstrap.GUI)
   652  	c.Assert(err, jc.ErrorIsNil)
   653  	base64Content := base64.StdEncoding.EncodeToString(content)
   654  	expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`gui='/var/lib/juju/gui'
   655  mkdir -p $gui
   656  install -D -m 644 /dev/null '/var/lib/juju/gui/gui.tar.bz2'
   657  printf %%s %s | base64 -d > '/var/lib/juju/gui/gui.tar.bz2'
   658  [ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256
   659  [ -f $gui/jujugui.sha256 ] && (grep '1234' $gui/jujugui.sha256 && printf %%s '%s' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch)
   660  rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt
   661  `, base64Content, guiJson))
   662  	checkCloudInitWithGUI(c, cfg, expectedScripts, "")
   663  }
   664  
   665  func (*cloudinitSuite) TestCloudInitWithRemoteGUI(c *gc.C) {
   666  	cfg := makeBootstrapConfig("precise").setGUI("https://1.2.3.4/gui.tar.bz2")
   667  	guiJson, err := json.Marshal(cfg.Bootstrap.GUI)
   668  	c.Assert(err, jc.ErrorIsNil)
   669  	expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`gui='/var/lib/juju/gui'
   670  mkdir -p $gui
   671  curl -sSf -o $gui/gui.tar.bz2 --retry 10 'https://1.2.3.4/gui.tar.bz2' || echo Unable to retrieve Juju GUI
   672  [ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256
   673  [ -f $gui/jujugui.sha256 ] && (grep '1234' $gui/jujugui.sha256 && printf %%s '%s' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch)
   674  rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt
   675  `, guiJson))
   676  	checkCloudInitWithGUI(c, cfg, expectedScripts, "")
   677  }
   678  
   679  func (*cloudinitSuite) TestCloudInitWithGUIReadError(c *gc.C) {
   680  	cfg := makeBootstrapConfig("precise").setGUI("file:///no/such/gui.tar.bz2")
   681  	expectedError := "cannot set up Juju GUI: cannot read Juju GUI archive: .*"
   682  	checkCloudInitWithGUI(c, cfg, "", expectedError)
   683  }
   684  
   685  func (*cloudinitSuite) TestCloudInitWithGUIURLError(c *gc.C) {
   686  	cfg := makeBootstrapConfig("precise").setGUI(":")
   687  	expectedError := "cannot set up Juju GUI: cannot parse Juju GUI URL: .*"
   688  	checkCloudInitWithGUI(c, cfg, "", expectedError)
   689  }
   690  
   691  func checkCloudInitWithGUI(c *gc.C, cfg *testInstanceConfig, expectedScripts string, expectedError string) {
   692  	envConfig := minimalModelConfig(c)
   693  	testConfig := cfg.maybeSetModelConfig(envConfig).render()
   694  	ci, err := cloudinit.New(testConfig.Series)
   695  	c.Assert(err, jc.ErrorIsNil)
   696  	udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci)
   697  	c.Assert(err, jc.ErrorIsNil)
   698  	err = udata.Configure()
   699  	if expectedError != "" {
   700  		c.Assert(err, gc.ErrorMatches, expectedError)
   701  		return
   702  	}
   703  	c.Assert(err, jc.ErrorIsNil)
   704  	c.Check(ci, gc.NotNil)
   705  	data, err := ci.RenderYAML()
   706  	c.Assert(err, jc.ErrorIsNil)
   707  
   708  	configKeyValues := make(map[interface{}]interface{})
   709  	err = goyaml.Unmarshal(data, &configKeyValues)
   710  	c.Assert(err, jc.ErrorIsNil)
   711  
   712  	scripts := getScripts(configKeyValues)
   713  	assertScriptMatch(c, scripts, expectedScripts, false)
   714  }
   715  
   716  func (*cloudinitSuite) TestCloudInitConfigure(c *gc.C) {
   717  	for i, test := range cloudinitTests {
   718  		testConfig := test.cfg.maybeSetModelConfig(minimalModelConfig(c)).render()
   719  		c.Logf("test %d (Configure)", i)
   720  		cloudcfg, err := cloudinit.New(testConfig.Series)
   721  		c.Assert(err, jc.ErrorIsNil)
   722  		udata, err := cloudconfig.NewUserdataConfig(&testConfig, cloudcfg)
   723  		c.Assert(err, jc.ErrorIsNil)
   724  		err = udata.Configure()
   725  		c.Assert(err, jc.ErrorIsNil)
   726  	}
   727  }
   728  
   729  func (s *cloudinitSuite) TestCloudInitConfigCloudInitUserData(c *gc.C) {
   730  	environConfig := minimalModelConfig(c)
   731  	environConfig, err := environConfig.Apply(map[string]interface{}{
   732  		config.CloudInitUserDataKey: validCloudInitUserData,
   733  	})
   734  	c.Assert(err, jc.ErrorIsNil)
   735  	instanceCfg := s.createInstanceConfig(c, environConfig)
   736  	cloudcfg, err := cloudinit.New("xenial")
   737  	c.Assert(err, jc.ErrorIsNil)
   738  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
   739  	c.Assert(err, jc.ErrorIsNil)
   740  	err = udata.Configure()
   741  	c.Assert(err, jc.ErrorIsNil)
   742  
   743  	// Verify the settings against cloudinit-userdata
   744  	cfgPackages := cloudcfg.Packages()
   745  	expectedPackages := []string{
   746  		`ubuntu-fan`, // last juju specified package
   747  		`python-keystoneclient`,
   748  		`python-glanceclient`,
   749  	}
   750  	c.Assert(len(cfgPackages), jc.GreaterThan, 2)
   751  	c.Assert(cfgPackages[len(cfgPackages)-3:], gc.DeepEquals, expectedPackages)
   752  
   753  	cmds := cloudcfg.RunCmds()
   754  	beginning := []string{
   755  		`mkdir /tmp/preruncmd`,
   756  		`mkdir /tmp/preruncmd2`,
   757  		`set -xe`, // first line of juju specified cmds
   758  	}
   759  	ending := []string{
   760  		`rm $bin/tools.tar.gz && rm $bin/juju2.3.4-quantal-amd64.sha256`, // last line of juju specified cmds
   761  		`mkdir /tmp/postruncmd`,
   762  		`mkdir /tmp/postruncmd2`,
   763  	}
   764  	c.Assert(len(cmds), jc.GreaterThan, 6)
   765  	c.Assert(cmds[:3], gc.DeepEquals, beginning)
   766  	c.Assert(cmds[len(cmds)-3:], gc.DeepEquals, ending)
   767  
   768  	c.Assert(cloudcfg.SystemUpgrade(), gc.Equals, false)
   769  
   770  	// Render to check for the "unexpected" cloudinit text.
   771  	// cloudconfig doesn't have public access to all attrs.
   772  	data, err := cloudcfg.RenderYAML()
   773  	c.Assert(err, jc.ErrorIsNil)
   774  	ciContent := make(map[interface{}]interface{})
   775  	err = goyaml.Unmarshal(data, &ciContent)
   776  	c.Assert(err, jc.ErrorIsNil)
   777  	testCmd, ok := ciContent["test-key"].([]interface{})
   778  	c.Assert(ok, jc.IsTrue)
   779  	c.Check(testCmd, gc.DeepEquals, []interface{}{"test line one"})
   780  }
   781  
   782  var validCloudInitUserData = `
   783  packages:
   784    - 'python-keystoneclient'
   785    - 'python-glanceclient'
   786  preruncmd:
   787    - mkdir /tmp/preruncmd
   788    - mkdir /tmp/preruncmd2
   789  postruncmd:
   790    - mkdir /tmp/postruncmd
   791    - mkdir /tmp/postruncmd2
   792  package_upgrade: false
   793  test-key:
   794    - test line one
   795  `[1:]
   796  
   797  func (*cloudinitSuite) bootstrapConfigScripts(c *gc.C) []string {
   798  	loggo.GetLogger("").SetLogLevel(loggo.INFO)
   799  	envConfig := minimalModelConfig(c)
   800  	instConfig := makeBootstrapConfig("quantal").maybeSetModelConfig(envConfig)
   801  	rendered := instConfig.render()
   802  	cloudcfg, err := cloudinit.New(rendered.Series)
   803  	c.Assert(err, jc.ErrorIsNil)
   804  	udata, err := cloudconfig.NewUserdataConfig(&rendered, cloudcfg)
   805  
   806  	c.Assert(err, jc.ErrorIsNil)
   807  	err = udata.Configure()
   808  	c.Assert(err, jc.ErrorIsNil)
   809  	data, err := cloudcfg.RenderYAML()
   810  	c.Assert(err, jc.ErrorIsNil)
   811  	configKeyValues := make(map[interface{}]interface{})
   812  	err = goyaml.Unmarshal(data, &configKeyValues)
   813  	c.Assert(err, jc.ErrorIsNil)
   814  
   815  	scripts := getScripts(configKeyValues)
   816  	for i, script := range scripts {
   817  		if strings.Contains(script, "bootstrap") {
   818  			c.Logf("scripts[%d]: %q", i, script)
   819  		}
   820  	}
   821  	return scripts
   822  }
   823  
   824  func (s *cloudinitSuite) TestCloudInitConfigureBootstrapLogging(c *gc.C) {
   825  	scripts := s.bootstrapConfigScripts(c)
   826  	expected := "jujud bootstrap-state .* --show-log .*"
   827  	assertScriptMatch(c, scripts, expected, false)
   828  }
   829  
   830  func (s *cloudinitSuite) TestCloudInitConfigureBootstrapFeatureFlags(c *gc.C) {
   831  	s.SetFeatureFlags("special", "foo")
   832  	scripts := s.bootstrapConfigScripts(c)
   833  	expected := "JUJU_DEV_FEATURE_FLAGS=foo,special .*/jujud bootstrap-state .*"
   834  	assertScriptMatch(c, scripts, expected, false)
   835  }
   836  
   837  func (*cloudinitSuite) TestCloudInitConfigureUsesGivenConfig(c *gc.C) {
   838  	// Create a simple cloudinit config with a 'runcmd' statement.
   839  	cloudcfg, err := cloudinit.New("quantal")
   840  	c.Assert(err, jc.ErrorIsNil)
   841  	script := "test script"
   842  	cloudcfg.AddRunCmd(script)
   843  	envConfig := minimalModelConfig(c)
   844  	testConfig := cloudinitTests[0].cfg.maybeSetModelConfig(envConfig).render()
   845  	udata, err := cloudconfig.NewUserdataConfig(&testConfig, cloudcfg)
   846  	c.Assert(err, jc.ErrorIsNil)
   847  	err = udata.Configure()
   848  	c.Assert(err, jc.ErrorIsNil)
   849  	data, err := cloudcfg.RenderYAML()
   850  	c.Assert(err, jc.ErrorIsNil)
   851  
   852  	ciContent := make(map[interface{}]interface{})
   853  	err = goyaml.Unmarshal(data, &ciContent)
   854  	c.Assert(err, jc.ErrorIsNil)
   855  	// The 'runcmd' statement is at the beginning of the list
   856  	// of 'runcmd' statements.
   857  	runCmd := ciContent["runcmd"].([]interface{})
   858  	c.Check(runCmd[0], gc.Equals, script)
   859  }
   860  
   861  func getScripts(configKeyValue map[interface{}]interface{}) []string {
   862  	var scripts []string
   863  	if bootcmds, ok := configKeyValue["bootcmd"]; ok {
   864  		for _, s := range bootcmds.([]interface{}) {
   865  			scripts = append(scripts, s.(string))
   866  		}
   867  	}
   868  	for _, s := range configKeyValue["runcmd"].([]interface{}) {
   869  		scripts = append(scripts, s.(string))
   870  	}
   871  	return scripts
   872  }
   873  
   874  type line struct {
   875  	index int
   876  	line  string
   877  }
   878  
   879  func assertScriptMatch(c *gc.C, got []string, expect string, exact bool) {
   880  
   881  	// Convert string slice into line struct slice
   882  	assembleLines := func(lines []string, lineProcessor func(string) string) []line {
   883  		var assembledLines []line
   884  		for lineIdx, currLine := range lines {
   885  			if nil != lineProcessor {
   886  				currLine = lineProcessor(currLine)
   887  			}
   888  			assembledLines = append(assembledLines, line{
   889  				index: lineIdx,
   890  				line:  currLine,
   891  			})
   892  		}
   893  		return assembledLines
   894  	}
   895  
   896  	pats := assembleLines(strings.Split(strings.Trim(expect, "\n"), "\n"), nil)
   897  	scripts := assembleLines(got, func(line string) string {
   898  		return strings.Replace(line, "\n", "\\n", -1) // make .* work
   899  	})
   900  
   901  	// Pop patterns and scripts off the head as we find pairs
   902  	for {
   903  		switch {
   904  		case len(pats) == 0 && len(scripts) == 0:
   905  			return
   906  		case len(pats) == 0:
   907  			if exact {
   908  				c.Fatalf("too many scripts found (got %q at line %d)", scripts[0].line, scripts[0].index)
   909  			}
   910  			return
   911  		case len(scripts) == 0:
   912  			if exact {
   913  				c.Fatalf("too few scripts found (expected %q at line %d)", pats[0].line, pats[0].index)
   914  			}
   915  			c.Fatalf("could not find match for %q\ngot:\n%s", pats[0].line, strings.Join(got, "\n"))
   916  		default:
   917  			ok, err := regexp.MatchString(pats[0].line, scripts[0].line)
   918  			c.Assert(err, jc.ErrorIsNil, gc.Commentf("invalid regexp: %q", pats[0].line))
   919  			if ok {
   920  				pats = pats[1:]
   921  				scripts = scripts[1:]
   922  			} else if exact {
   923  				c.Assert(scripts[0].line, gc.Matches, pats[0].line, gc.Commentf("line %d; expected %q; got %q; paths: %#v", scripts[0].index, pats[0].line, scripts[0].line, pats))
   924  			} else {
   925  				scripts = scripts[1:]
   926  			}
   927  		}
   928  	}
   929  }
   930  
   931  // checkPackage checks that the cloudinit will or won't install the given
   932  // package, depending on the value of match.
   933  func checkPackage(c *gc.C, x map[interface{}]interface{}, pkg string, match bool) {
   934  	pkgs0 := x["packages"]
   935  	if pkgs0 == nil {
   936  		if match {
   937  			c.Errorf("cloudinit has no entry for packages")
   938  		}
   939  		return
   940  	}
   941  
   942  	pkgs := pkgs0.([]interface{})
   943  
   944  	found := false
   945  	for _, p0 := range pkgs {
   946  		p := p0.(string)
   947  		// p might be a space separate list of packages eg 'foo bar qed' so split them up
   948  		manyPkgs := set.NewStrings(strings.Split(p, " ")...)
   949  		hasPkg := manyPkgs.Contains(pkg)
   950  		if p == pkg || hasPkg {
   951  			found = true
   952  			break
   953  		}
   954  	}
   955  	switch {
   956  	case match && !found:
   957  		c.Errorf("package %q not found in %v", pkg, pkgs)
   958  	case !match && found:
   959  		c.Errorf("%q found but not expected in %v", pkg, pkgs)
   960  	}
   961  }
   962  
   963  // checkAptSource checks that the cloudinit will or won't install the given
   964  // source, depending on the value of match.
   965  func checkAptSource(c *gc.C, x map[interface{}]interface{}, source, key string, match bool) {
   966  	sources0 := x["apt_sources"]
   967  	if sources0 == nil {
   968  		if match {
   969  			c.Errorf("cloudinit has no entry for apt_sources")
   970  		}
   971  		return
   972  	}
   973  
   974  	sources := sources0.([]interface{})
   975  
   976  	found := false
   977  	for _, s0 := range sources {
   978  		s := s0.(map[interface{}]interface{})
   979  		if s["source"] == source && s["key"] == key {
   980  			found = true
   981  		}
   982  	}
   983  	switch {
   984  	case match && !found:
   985  		c.Errorf("source %q not found in %v", source, sources)
   986  	case !match && found:
   987  		c.Errorf("%q found but not expected in %v", source, sources)
   988  	}
   989  }
   990  
   991  // When mutate is called on a known-good InstanceConfig,
   992  // there should be an error complaining about the missing
   993  // field named by the adjacent err.
   994  var verifyTests = []struct {
   995  	err    string
   996  	mutate func(*instancecfg.InstanceConfig)
   997  }{
   998  	{"invalid machine id", func(cfg *instancecfg.InstanceConfig) {
   999  		cfg.MachineId = "-1"
  1000  	}},
  1001  	{"invalid bootstrap configuration: missing model configuration", func(cfg *instancecfg.InstanceConfig) {
  1002  		cfg.Bootstrap.ControllerModelConfig = nil
  1003  	}},
  1004  	{"invalid controller configuration: missing state info", func(cfg *instancecfg.InstanceConfig) {
  1005  		cfg.Controller.MongoInfo = nil
  1006  	}},
  1007  	{"missing API info", func(cfg *instancecfg.InstanceConfig) {
  1008  		cfg.APIInfo = nil
  1009  	}},
  1010  	{"missing model tag", func(cfg *instancecfg.InstanceConfig) {
  1011  		cfg.APIInfo = &api.Info{
  1012  			Addrs:  []string{"foo:35"},
  1013  			Tag:    names.NewMachineTag("99"),
  1014  			CACert: testing.CACert,
  1015  		}
  1016  	}},
  1017  	{"invalid controller configuration: missing state hosts", func(cfg *instancecfg.InstanceConfig) {
  1018  		cfg.Bootstrap = nil
  1019  		cfg.Controller.MongoInfo = &mongo.MongoInfo{
  1020  			Tag: names.NewMachineTag("99"),
  1021  			Info: mongo.Info{
  1022  				CACert: testing.CACert,
  1023  			},
  1024  		}
  1025  	}},
  1026  	{"missing API hosts", func(cfg *instancecfg.InstanceConfig) {
  1027  		cfg.Bootstrap = nil
  1028  		cfg.Controller.MongoInfo.Tag = names.NewMachineTag("99")
  1029  		cfg.APIInfo = &api.Info{
  1030  			Tag:      names.NewMachineTag("99"),
  1031  			CACert:   testing.CACert,
  1032  			ModelTag: testing.ModelTag,
  1033  		}
  1034  	}},
  1035  	{"invalid controller configuration: missing CA certificate", func(cfg *instancecfg.InstanceConfig) {
  1036  		cfg.Controller.MongoInfo.CACert = ""
  1037  	}},
  1038  	{"invalid bootstrap configuration: missing controller certificate", func(cfg *instancecfg.InstanceConfig) {
  1039  		cfg.Bootstrap.StateServingInfo.Cert = ""
  1040  	}},
  1041  	{"invalid bootstrap configuration: missing controller private key", func(cfg *instancecfg.InstanceConfig) {
  1042  		cfg.Bootstrap.StateServingInfo.PrivateKey = ""
  1043  	}},
  1044  	{"invalid bootstrap configuration: missing ca cert private key", func(cfg *instancecfg.InstanceConfig) {
  1045  		cfg.Bootstrap.StateServingInfo.CAPrivateKey = ""
  1046  	}},
  1047  	{"invalid bootstrap configuration: missing state port", func(cfg *instancecfg.InstanceConfig) {
  1048  		cfg.Bootstrap.StateServingInfo.StatePort = 0
  1049  	}},
  1050  	{"invalid bootstrap configuration: missing API port", func(cfg *instancecfg.InstanceConfig) {
  1051  		cfg.Bootstrap.StateServingInfo.APIPort = 0
  1052  	}},
  1053  	{"missing var directory", func(cfg *instancecfg.InstanceConfig) {
  1054  		cfg.DataDir = ""
  1055  	}},
  1056  	{"missing log directory", func(cfg *instancecfg.InstanceConfig) {
  1057  		cfg.LogDir = ""
  1058  	}},
  1059  	{"missing cloud-init output log path", func(cfg *instancecfg.InstanceConfig) {
  1060  		cfg.CloudInitOutputLog = ""
  1061  	}},
  1062  	{"invalid controller configuration: entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
  1063  		cfg.Bootstrap = nil
  1064  		cfg.Controller.MongoInfo.Tag = names.NewMachineTag("0")
  1065  	}},
  1066  	{"invalid controller configuration: entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
  1067  		cfg.Bootstrap = nil
  1068  		cfg.Controller.MongoInfo.Tag = nil // admin user
  1069  	}},
  1070  	{"API entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
  1071  		cfg.Bootstrap = nil
  1072  		cfg.Controller.MongoInfo.Tag = names.NewMachineTag("99")
  1073  		cfg.APIInfo.Tag = names.NewMachineTag("0")
  1074  	}},
  1075  	{"API entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
  1076  		cfg.Bootstrap = nil
  1077  		cfg.Controller.MongoInfo.Tag = names.NewMachineTag("99")
  1078  		cfg.APIInfo.Tag = nil
  1079  	}},
  1080  	{"invalid bootstrap configuration: entity tag must be nil when bootstrapping", func(cfg *instancecfg.InstanceConfig) {
  1081  		cfg.Controller.MongoInfo.Tag = names.NewMachineTag("0")
  1082  	}},
  1083  	{"invalid bootstrap configuration: entity tag must be nil when bootstrapping", func(cfg *instancecfg.InstanceConfig) {
  1084  		cfg.APIInfo.Tag = names.NewMachineTag("0")
  1085  	}},
  1086  	{"missing machine nonce", func(cfg *instancecfg.InstanceConfig) {
  1087  		cfg.MachineNonce = ""
  1088  	}},
  1089  	{"missing machine agent service name", func(cfg *instancecfg.InstanceConfig) {
  1090  		cfg.MachineAgentServiceName = ""
  1091  	}},
  1092  	{"invalid bootstrap configuration: missing bootstrap machine instance ID", func(cfg *instancecfg.InstanceConfig) {
  1093  		cfg.Bootstrap.BootstrapMachineInstanceId = ""
  1094  	}},
  1095  }
  1096  
  1097  // TestCloudInitVerify checks that required fields are appropriately
  1098  // checked for by NewCloudInit.
  1099  func (*cloudinitSuite) TestCloudInitVerify(c *gc.C) {
  1100  	toolsList := tools.List{
  1101  		newSimpleTools("9.9.9-quantal-arble"),
  1102  	}
  1103  
  1104  	makeCfgWithoutTools := func() instancecfg.InstanceConfig {
  1105  		return instancecfg.InstanceConfig{
  1106  			Bootstrap: &instancecfg.BootstrapConfig{
  1107  				StateInitializationParams: instancecfg.StateInitializationParams{
  1108  					BootstrapMachineInstanceId: "i-bootstrap",
  1109  					ControllerModelConfig:      minimalModelConfig(c),
  1110  					HostedModelConfig:          map[string]interface{}{"name": "hosted-model"},
  1111  				},
  1112  				StateServingInfo: stateServingInfo,
  1113  			},
  1114  			Controller: &instancecfg.ControllerConfig{
  1115  				MongoInfo: &mongo.MongoInfo{
  1116  					Info: mongo.Info{
  1117  						Addrs:  []string{"host:98765"},
  1118  						CACert: testing.CACert,
  1119  					},
  1120  					Password: "password",
  1121  				},
  1122  			},
  1123  			ControllerTag:    testing.ControllerTag,
  1124  			MachineId:        "99",
  1125  			AuthorizedKeys:   "sshkey1",
  1126  			Series:           "quantal",
  1127  			AgentEnvironment: map[string]string{agent.ProviderType: "dummy"},
  1128  			APIInfo: &api.Info{
  1129  				Addrs:    []string{"host:9999"},
  1130  				CACert:   testing.CACert,
  1131  				ModelTag: testing.ModelTag,
  1132  			},
  1133  			DataDir:                 jujuDataDir("quantal"),
  1134  			LogDir:                  jujuLogDir("quantal"),
  1135  			MetricsSpoolDir:         metricsSpoolDir("quantal"),
  1136  			Jobs:                    normalMachineJobs,
  1137  			CloudInitOutputLog:      cloudInitOutputLog("quantal"),
  1138  			MachineNonce:            "FAKE_NONCE",
  1139  			MachineAgentServiceName: "jujud-machine-99",
  1140  		}
  1141  	}
  1142  
  1143  	// check that the base configuration does not give an error
  1144  	ci, err := cloudinit.New("quantal")
  1145  	c.Assert(err, jc.ErrorIsNil)
  1146  
  1147  	// check that missing tools causes an error.
  1148  	cfg := makeCfgWithoutTools()
  1149  	udata, err := cloudconfig.NewUserdataConfig(&cfg, ci)
  1150  	c.Assert(err, jc.ErrorIsNil)
  1151  	err = udata.Configure()
  1152  	c.Assert(err, gc.ErrorMatches, "invalid machine configuration: missing agent binaries")
  1153  
  1154  	for i, test := range verifyTests {
  1155  		c.Logf("test %d. %s", i, test.err)
  1156  		cfg := makeCfgWithoutTools()
  1157  		err := cfg.SetTools(toolsList)
  1158  		c.Assert(err, jc.ErrorIsNil)
  1159  
  1160  		// check that the base configuration does not give an error
  1161  		udata, err := cloudconfig.NewUserdataConfig(&cfg, ci)
  1162  		c.Assert(err, jc.ErrorIsNil)
  1163  		err = udata.Configure()
  1164  		c.Assert(err, jc.ErrorIsNil)
  1165  
  1166  		test.mutate(&cfg)
  1167  		udata, err = cloudconfig.NewUserdataConfig(&cfg, ci)
  1168  		c.Assert(err, jc.ErrorIsNil)
  1169  		err = udata.Configure()
  1170  		c.Check(err, gc.ErrorMatches, "invalid machine configuration: "+test.err)
  1171  	}
  1172  }
  1173  
  1174  func (*cloudinitSuite) createInstanceConfig(c *gc.C, environConfig *config.Config) *instancecfg.InstanceConfig {
  1175  	machineId := "42"
  1176  	machineNonce := "fake-nonce"
  1177  	apiInfo := jujutesting.FakeAPIInfo(machineId)
  1178  	instanceConfig, err := instancecfg.NewInstanceConfig(testing.ControllerTag, machineId, machineNonce, imagemetadata.ReleasedStream, "quantal", apiInfo)
  1179  	c.Assert(err, jc.ErrorIsNil)
  1180  	instanceConfig.SetTools(tools.List{
  1181  		&tools.Tools{
  1182  			Version: version.MustParseBinary("2.3.4-quantal-amd64"),
  1183  			URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
  1184  		},
  1185  	})
  1186  	err = instancecfg.FinishInstanceConfig(instanceConfig, environConfig)
  1187  	c.Assert(err, jc.ErrorIsNil)
  1188  	return instanceConfig
  1189  }
  1190  
  1191  func (s *cloudinitSuite) TestAptProxyNotWrittenIfNotSet(c *gc.C) {
  1192  	environConfig := minimalModelConfig(c)
  1193  	instanceCfg := s.createInstanceConfig(c, environConfig)
  1194  	cloudcfg, err := cloudinit.New("quantal")
  1195  	c.Assert(err, jc.ErrorIsNil)
  1196  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1197  	c.Assert(err, jc.ErrorIsNil)
  1198  	err = udata.Configure()
  1199  	c.Assert(err, jc.ErrorIsNil)
  1200  
  1201  	cmds := cloudcfg.BootCmds()
  1202  	c.Assert(cmds, gc.IsNil)
  1203  }
  1204  
  1205  func (s *cloudinitSuite) TestAptProxyWritten(c *gc.C) {
  1206  	environConfig := minimalModelConfig(c)
  1207  	environConfig, err := environConfig.Apply(map[string]interface{}{
  1208  		"apt-http-proxy": "http://user@10.0.0.1",
  1209  	})
  1210  	c.Assert(err, jc.ErrorIsNil)
  1211  	instanceCfg := s.createInstanceConfig(c, environConfig)
  1212  	cloudcfg, err := cloudinit.New("quantal")
  1213  	c.Assert(err, jc.ErrorIsNil)
  1214  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1215  	c.Assert(err, jc.ErrorIsNil)
  1216  	err = udata.Configure()
  1217  	c.Assert(err, jc.ErrorIsNil)
  1218  
  1219  	cmds := cloudcfg.BootCmds()
  1220  	expected := "printf '%s\\n' 'Acquire::http::Proxy \"http://user@10.0.0.1\";' > /etc/apt/apt.conf.d/95-juju-proxy-settings"
  1221  	c.Assert(cmds, jc.DeepEquals, []string{expected})
  1222  }
  1223  
  1224  func (s *cloudinitSuite) TestProxyWritten(c *gc.C) {
  1225  	environConfig := minimalModelConfig(c)
  1226  	environConfig, err := environConfig.Apply(map[string]interface{}{
  1227  		"http-proxy": "http://user@10.0.0.1",
  1228  		"no-proxy":   "localhost,10.0.3.1",
  1229  	})
  1230  	c.Assert(err, jc.ErrorIsNil)
  1231  	instanceCfg := s.createInstanceConfig(c, environConfig)
  1232  	cloudcfg, err := cloudinit.New("quantal")
  1233  	c.Assert(err, jc.ErrorIsNil)
  1234  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1235  	c.Assert(err, jc.ErrorIsNil)
  1236  	err = udata.Configure()
  1237  	c.Assert(err, jc.ErrorIsNil)
  1238  
  1239  	cmds := cloudcfg.RunCmds()
  1240  	first := `[ -e /etc/profile.d/juju-proxy.sh ] || printf '\n# Added by juju\n[ -f "/etc/juju-proxy.conf" ] && . "/etc/juju-proxy.conf"\n' >> /etc/profile.d/juju-proxy.sh`
  1241  	expected := []string{
  1242  		`export http_proxy=http://user@10.0.0.1`,
  1243  		`export HTTP_PROXY=http://user@10.0.0.1`,
  1244  		`export no_proxy=0.1.2.3,10.0.3.1,localhost`,
  1245  		`export NO_PROXY=0.1.2.3,10.0.3.1,localhost`,
  1246  		`(printf '%s\n' 'export http_proxy=http://user@10.0.0.1
  1247  export HTTP_PROXY=http://user@10.0.0.1
  1248  export no_proxy=0.1.2.3,10.0.3.1,localhost
  1249  export NO_PROXY=0.1.2.3,10.0.3.1,localhost' > /etc/juju-proxy.conf && chmod 0644 /etc/juju-proxy.conf)`,
  1250  		`printf '%s\n' '# To allow juju to control the global systemd proxy settings,
  1251  # create symbolic links to this file from within /etc/systemd/system.conf.d/
  1252  # and /etc/systemd/users.conf.d/.
  1253  [Manager]
  1254  DefaultEnvironment="http_proxy=http://user@10.0.0.1" "HTTP_PROXY=http://user@10.0.0.1" "no_proxy=0.1.2.3,10.0.3.1,localhost" "NO_PROXY=0.1.2.3,10.0.3.1,localhost" 
  1255  ' > /etc/juju-proxy-systemd.conf`,
  1256  	}
  1257  	found := false
  1258  	for i, cmd := range cmds {
  1259  		if cmd == first {
  1260  			c.Assert(cmds[i+1:i+7], jc.DeepEquals, expected)
  1261  			found = true
  1262  			break
  1263  		}
  1264  	}
  1265  	c.Assert(found, jc.IsTrue)
  1266  }
  1267  
  1268  // Ensure the bootstrap curl which fetch tools respects the proxy settings
  1269  func (s *cloudinitSuite) TestProxyArgsAddedToCurlCommand(c *gc.C) {
  1270  	series := "bionic"
  1271  	instcfg := makeBootstrapConfig("bionic").maybeSetModelConfig(
  1272  		minimalModelConfig(c),
  1273  	).render()
  1274  	instcfg.JujuProxySettings = proxy.Settings{
  1275  		Http: "0.1.2.3",
  1276  	}
  1277  
  1278  	// create the cloud configuration
  1279  	cldcfg, err := cloudinit.New(series)
  1280  	c.Assert(err, jc.ErrorIsNil)
  1281  
  1282  	// create the user data configuration setup
  1283  	udata, err := cloudconfig.NewUserdataConfig(&instcfg, cldcfg)
  1284  	c.Assert(err, jc.ErrorIsNil)
  1285  
  1286  	// configure the user data
  1287  	err = udata.Configure()
  1288  	c.Assert(err, jc.ErrorIsNil)
  1289  
  1290  	// check to see that the first boot curl command to download tools
  1291  	// respects the configured proxy settings.
  1292  	cmds := cldcfg.RunCmds()
  1293  	expectedCurlCommand := "curl -sSfw 'agent binaries from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s ' --retry 10 --proxy 0.1.2.3 -o $bin/tools.tar.gz"
  1294  	assertCommandsContain(c, cmds, expectedCurlCommand)
  1295  }
  1296  
  1297  // search a list of first boot commands to ensure it contains the specified curl
  1298  // command.
  1299  func assertCommandsContain(c *gc.C, runCmds []string, str string) {
  1300  	found := false
  1301  	for _, runCmd := range runCmds {
  1302  		if strings.Contains(runCmd, str) {
  1303  			found = true
  1304  		}
  1305  	}
  1306  	c.Logf("expecting to find %q in %#v", str, runCmds)
  1307  	c.Assert(found, jc.IsTrue)
  1308  }
  1309  
  1310  func (s *cloudinitSuite) TestAptMirror(c *gc.C) {
  1311  	environConfig := minimalModelConfig(c)
  1312  	environConfig, err := environConfig.Apply(map[string]interface{}{
  1313  		"apt-mirror": "http://my.archive.ubuntu.com/ubuntu",
  1314  	})
  1315  	c.Assert(err, jc.ErrorIsNil)
  1316  	s.testAptMirror(c, environConfig, "http://my.archive.ubuntu.com/ubuntu")
  1317  }
  1318  
  1319  func (s *cloudinitSuite) TestAptMirrorNotSet(c *gc.C) {
  1320  	environConfig := minimalModelConfig(c)
  1321  	s.testAptMirror(c, environConfig, "")
  1322  }
  1323  
  1324  func (s *cloudinitSuite) testAptMirror(c *gc.C, cfg *config.Config, expect string) {
  1325  	instanceCfg := s.createInstanceConfig(c, cfg)
  1326  	cloudcfg, err := cloudinit.New("quantal")
  1327  	c.Assert(err, jc.ErrorIsNil)
  1328  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1329  	c.Assert(err, jc.ErrorIsNil)
  1330  	err = udata.Configure()
  1331  	c.Assert(err, jc.ErrorIsNil)
  1332  	//mirror, ok := cloudcfg.AptMirror()
  1333  	mirror := cloudcfg.PackageMirror()
  1334  	c.Assert(mirror, gc.Equals, expect)
  1335  	//c.Assert(ok, gc.Equals, expect != "")
  1336  }
  1337  
  1338  var serverCert = []byte(`
  1339  SERVER CERT
  1340  -----BEGIN CERTIFICATE-----
  1341  MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwHjENMAsGA1UEChMEanVqdTEN
  1342  MAsGA1UEAxMEcm9vdDAeFw0xMjExMDgxNjIyMzRaFw0xMzExMDgxNjI3MzRaMBwx
  1343  DDAKBgNVBAoTA2htbTEMMAoGA1UEAxMDYW55MFowCwYJKoZIhvcNAQEBA0sAMEgC
  1344  QQCACqz6JPwM7nbxAWub+APpnNB7myckWJ6nnsPKi9SipP1hyhfzkp8RGMJ5Uv7y
  1345  8CSTtJ8kg/ibka1VV8LvP9tnAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIAsDAdBgNV
  1346  HQ4EFgQU6G1ERaHCgfAv+yoDMFVpDbLOmIQwHwYDVR0jBBgwFoAUP/mfUdwOlHfk
  1347  fR+gLQjslxf64w0wCwYJKoZIhvcNAQEFA0EAbn0MaxWVgGYBomeLYfDdb8vCq/5/
  1348  G/2iCUQCXsVrBparMLFnor/iKOkJB5n3z3rtu70rFt+DpX6L8uBR3LB3+A==
  1349  -----END CERTIFICATE-----
  1350  `[1:])
  1351  
  1352  var serverKey = []byte(`
  1353  SERVER KEY
  1354  -----BEGIN RSA PRIVATE KEY-----
  1355  MIIBPAIBAAJBAIAKrPok/AzudvEBa5v4A+mc0HubJyRYnqeew8qL1KKk/WHKF/OS
  1356  nxEYwnlS/vLwJJO0nySD+JuRrVVXwu8/22cCAwEAAQJBAJsk1F0wTRuaIhJ5xxqw
  1357  FIWPFep/n5jhrDOsIs6cSaRbfIBy3rAl956pf/MHKvf/IXh7KlG9p36IW49hjQHK
  1358  7HkCIQD2CqyV1ppNPFSoCI8mSwO8IZppU3i2V4MhpwnqHz3H0wIhAIU5XIlhLJW8
  1359  TNOaFMEia/TuYofdwJnYvi9t0v4UKBWdAiEA76AtvjEoTpi3in/ri0v78zp2/KXD
  1360  JzPMDvZ0fYS30ukCIA1stlJxpFiCXQuFn0nG+jH4Q52FTv8xxBhrbLOFvHRRAiEA
  1361  2Vc9NN09ty+HZgxpwqIA1fHVuYJY9GMPG1LnTnZ9INg=
  1362  -----END RSA PRIVATE KEY-----
  1363  `[1:])
  1364  
  1365  var windowsCloudinitTests = []cloudinitTest{{
  1366  	cfg: makeNormalConfig("win8").setMachineID("10").mutate(func(cfg *testInstanceConfig) {
  1367  		cfg.APIInfo.CACert = "CA CERT\n" + string(serverCert)
  1368  	}),
  1369  	setEnvConfig:  false,
  1370  	expectScripts: WindowsUserdata,
  1371  }}
  1372  
  1373  func (*cloudinitSuite) TestWindowsCloudInit(c *gc.C) {
  1374  	for i, test := range windowsCloudinitTests {
  1375  		testConfig := test.cfg.render()
  1376  		c.Logf("test %d", i)
  1377  		ci, err := cloudinit.New("win8")
  1378  		c.Assert(err, jc.ErrorIsNil)
  1379  		udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci)
  1380  
  1381  		c.Assert(err, jc.ErrorIsNil)
  1382  		err = udata.Configure()
  1383  
  1384  		c.Assert(err, jc.ErrorIsNil)
  1385  		c.Check(ci, gc.NotNil)
  1386  		data, err := ci.RenderYAML()
  1387  		c.Assert(err, jc.ErrorIsNil)
  1388  
  1389  		stringData := strings.Replace(string(data), "\r\n", "\n", -1)
  1390  		stringData = strings.Replace(stringData, "\t", " ", -1)
  1391  		stringData = strings.TrimSpace(stringData)
  1392  
  1393  		compareString := strings.Replace(string(test.expectScripts), "\r\n", "\n", -1)
  1394  		compareString = strings.Replace(compareString, "\t", " ", -1)
  1395  		compareString = strings.TrimSpace(compareString)
  1396  
  1397  		testing.CheckString(c, stringData, compareString)
  1398  	}
  1399  }
  1400  
  1401  func (*cloudinitSuite) TestToolsDownloadCommand(c *gc.C) {
  1402  	command := cloudconfig.ToolsDownloadCommand("download", []string{"a", "b", "c"})
  1403  
  1404  	expected := `
  1405  n=1
  1406  while true; do
  1407  
  1408      printf "Attempt $n to download agent binaries from %s...\n" 'a'
  1409      download 'a' && echo "Agent binaries downloaded successfully." && break
  1410  
  1411      printf "Attempt $n to download agent binaries from %s...\n" 'b'
  1412      download 'b' && echo "Agent binaries downloaded successfully." && break
  1413  
  1414      printf "Attempt $n to download agent binaries from %s...\n" 'c'
  1415      download 'c' && echo "Agent binaries downloaded successfully." && break
  1416  
  1417      echo "Download failed, retrying in 15s"
  1418      sleep 15
  1419      n=$((n+1))
  1420  done`
  1421  	c.Assert(command, gc.Equals, expected)
  1422  }
  1423  
  1424  func expectedUbuntuUser(groups, keys []string) map[string]interface{} {
  1425  	user := map[string]interface{}{
  1426  		"name":        "ubuntu",
  1427  		"lock_passwd": true,
  1428  		"shell":       "/bin/bash",
  1429  		"sudo":        []interface{}{"ALL=(ALL) NOPASSWD:ALL"},
  1430  	}
  1431  	if groups != nil {
  1432  		user["groups"] = groups
  1433  	}
  1434  	if keys != nil {
  1435  		user["ssh-authorized-keys"] = keys
  1436  	}
  1437  	return map[string]interface{}{
  1438  		"users": []map[string]interface{}{user},
  1439  	}
  1440  }
  1441  
  1442  func (*cloudinitSuite) TestSetUbuntuUserPrecise(c *gc.C) {
  1443  	ci, err := cloudinit.New("precise")
  1444  	c.Assert(err, jc.ErrorIsNil)
  1445  	cloudconfig.SetUbuntuUser(ci, "akey")
  1446  	data, err := ci.RenderYAML()
  1447  	c.Assert(err, jc.ErrorIsNil)
  1448  	expected := map[string]interface{}{"ssh_authorized_keys": []string{
  1449  		"akey",
  1450  	}}
  1451  	c.Assert(string(data), jc.YAMLEquals, expected)
  1452  }
  1453  
  1454  func (*cloudinitSuite) TestSetUbuntuUserPreciseNoKeys(c *gc.C) {
  1455  	ci, err := cloudinit.New("precise")
  1456  	c.Assert(err, jc.ErrorIsNil)
  1457  	cloudconfig.SetUbuntuUser(ci, "")
  1458  	data, err := ci.RenderYAML()
  1459  	c.Assert(err, jc.ErrorIsNil)
  1460  	c.Assert(string(data), jc.YAMLEquals, map[string]interface{}{})
  1461  }
  1462  
  1463  func (*cloudinitSuite) TestSetUbuntuUserQuantal(c *gc.C) {
  1464  	ci, err := cloudinit.New("quantal")
  1465  	c.Assert(err, jc.ErrorIsNil)
  1466  	cloudconfig.SetUbuntuUser(ci, "akey")
  1467  	data, err := ci.RenderYAML()
  1468  	c.Assert(err, jc.ErrorIsNil)
  1469  	keys := []string{"akey"}
  1470  	expected := expectedUbuntuUser(cloudconfig.UbuntuGroups, keys)
  1471  	c.Assert(string(data), jc.YAMLEquals, expected)
  1472  }
  1473  
  1474  func (*cloudinitSuite) TestSetUbuntuUserCentOS(c *gc.C) {
  1475  	ci, err := cloudinit.New("centos7")
  1476  	c.Assert(err, jc.ErrorIsNil)
  1477  	cloudconfig.SetUbuntuUser(ci, "akey\n#also\nbkey")
  1478  	data, err := ci.RenderYAML()
  1479  	c.Assert(err, jc.ErrorIsNil)
  1480  	keys := []string{"akey", "bkey"}
  1481  	expected := expectedUbuntuUser(cloudconfig.CentOSGroups, keys)
  1482  	c.Assert(string(data), jc.YAMLEquals, expected)
  1483  }
  1484  
  1485  func (*cloudinitSuite) TestCloudInitBootstrapInitialSSHKeys(c *gc.C) {
  1486  	instConfig := makeBootstrapConfig("quantal").maybeSetModelConfig(
  1487  		minimalModelConfig(c),
  1488  	).render()
  1489  	instConfig.Bootstrap.InitialSSHHostKeys.RSA = &instancecfg.SSHKeyPair{
  1490  		Private: "private",
  1491  		Public:  "public",
  1492  	}
  1493  	cloudcfg, err := cloudinit.New(instConfig.Series)
  1494  	c.Assert(err, jc.ErrorIsNil)
  1495  
  1496  	udata, err := cloudconfig.NewUserdataConfig(&instConfig, cloudcfg)
  1497  	c.Assert(err, jc.ErrorIsNil)
  1498  	err = udata.Configure()
  1499  	c.Assert(err, jc.ErrorIsNil)
  1500  	data, err := cloudcfg.RenderYAML()
  1501  	c.Assert(err, jc.ErrorIsNil)
  1502  
  1503  	configKeyValues := make(map[interface{}]interface{})
  1504  	err = goyaml.Unmarshal(data, &configKeyValues)
  1505  	c.Assert(err, jc.ErrorIsNil)
  1506  	c.Assert(configKeyValues["ssh_keys"], jc.DeepEquals, map[interface{}]interface{}{
  1507  		"rsa_private": "private",
  1508  		"rsa_public":  "public",
  1509  	})
  1510  
  1511  	cmds := cloudcfg.BootCmds()
  1512  	c.Assert(cmds, jc.DeepEquals, []string{
  1513  		`echo 'Regenerating SSH RSA host key' >&$JUJU_PROGRESS_FD`,
  1514  		`rm /etc/ssh/ssh_host_rsa_key*`,
  1515  		`ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key`,
  1516  		`ssh-keygen -t dsa -N "" -f /etc/ssh/ssh_host_dsa_key`,
  1517  		`ssh-keygen -t ecdsa -N "" -f /etc/ssh/ssh_host_ecdsa_key`,
  1518  	})
  1519  }
  1520  
  1521  func (*cloudinitSuite) TestSetUbuntuUserOpenSUSE(c *gc.C) {
  1522  	ci, err := cloudinit.New("opensuseleap")
  1523  	c.Assert(err, jc.ErrorIsNil)
  1524  	cloudconfig.SetUbuntuUser(ci, "akey\n#also\nbkey")
  1525  	data, err := ci.RenderYAML()
  1526  	c.Assert(err, jc.ErrorIsNil)
  1527  	keys := []string{"akey", "bkey"}
  1528  	expected := expectedUbuntuUser(cloudconfig.OpenSUSEGroups, keys)
  1529  	c.Assert(string(data), jc.YAMLEquals, expected)
  1530  }