github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cloudconfig/cloudinit/cloudinit_test.go (about)

     1  // Copyright 2011, 2012, 2013, 2015 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package cloudinit_test
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils/packaging"
    13  	sshtesting "github.com/juju/utils/ssh/testing"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/cloudconfig/cloudinit"
    17  	coretesting "github.com/juju/juju/testing"
    18  )
    19  
    20  // TODO integration tests, but how?
    21  
    22  type S struct {
    23  	coretesting.BaseSuite
    24  }
    25  
    26  var _ = gc.Suite(S{})
    27  
    28  func Test1(t *testing.T) {
    29  	gc.TestingT(t)
    30  }
    31  
    32  var ctests = []struct {
    33  	name      string
    34  	expect    map[string]interface{}
    35  	setOption func(cfg cloudinit.CloudConfig)
    36  }{{
    37  	"PackageUpgrade",
    38  	map[string]interface{}{"package_upgrade": true},
    39  	func(cfg cloudinit.CloudConfig) {
    40  		cfg.SetSystemUpgrade(true)
    41  	},
    42  }, {
    43  	"PackageUpdate",
    44  	map[string]interface{}{"package_update": true},
    45  	func(cfg cloudinit.CloudConfig) {
    46  		cfg.SetSystemUpdate(true)
    47  	},
    48  }, {
    49  	"PackageProxy",
    50  	map[string]interface{}{"apt_proxy": "http://foo.com"},
    51  	func(cfg cloudinit.CloudConfig) {
    52  		cfg.SetPackageProxy("http://foo.com")
    53  	},
    54  }, {
    55  	"PackageMirror",
    56  	map[string]interface{}{"apt_mirror": "http://foo.com"},
    57  	func(cfg cloudinit.CloudConfig) {
    58  		cfg.SetPackageMirror("http://foo.com")
    59  	},
    60  }, {
    61  	"DisableEC2Metadata",
    62  	map[string]interface{}{"disable_ec2_metadata": true},
    63  	func(cfg cloudinit.CloudConfig) {
    64  		cfg.SetDisableEC2Metadata(true)
    65  	},
    66  }, {
    67  	"FinalMessage",
    68  	map[string]interface{}{"final_message": "goodbye"},
    69  	func(cfg cloudinit.CloudConfig) {
    70  		cfg.SetFinalMessage("goodbye")
    71  	},
    72  }, {
    73  	"Locale",
    74  	map[string]interface{}{"locale": "en_us"},
    75  	func(cfg cloudinit.CloudConfig) {
    76  		cfg.SetLocale("en_us")
    77  	},
    78  }, {
    79  	"DisableRoot",
    80  	map[string]interface{}{"disable_root": false},
    81  	func(cfg cloudinit.CloudConfig) {
    82  		cfg.SetDisableRoot(false)
    83  	},
    84  }, {
    85  	"SetSSHAuthorizedKeys with two keys",
    86  	map[string]interface{}{"ssh_authorized_keys": []string{
    87  		fmt.Sprintf("%s Juju:user@host", sshtesting.ValidKeyOne.Key),
    88  		fmt.Sprintf("%s Juju:another@host", sshtesting.ValidKeyTwo.Key),
    89  	}},
    90  	func(cfg cloudinit.CloudConfig) {
    91  		cfg.SetSSHAuthorizedKeys(
    92  			sshtesting.ValidKeyOne.Key + " Juju:user@host\n" +
    93  				sshtesting.ValidKeyTwo.Key + " another@host")
    94  	},
    95  }, {
    96  	"SetSSHAuthorizedKeys with comments in keys",
    97  	map[string]interface{}{"ssh_authorized_keys": []string{
    98  		fmt.Sprintf("%s Juju:sshkey", sshtesting.ValidKeyOne.Key),
    99  		fmt.Sprintf("%s Juju:user@host", sshtesting.ValidKeyTwo.Key),
   100  		fmt.Sprintf("%s Juju:another@host", sshtesting.ValidKeyThree.Key),
   101  	}},
   102  	func(cfg cloudinit.CloudConfig) {
   103  		cfg.SetSSHAuthorizedKeys(
   104  			"#command\n" + sshtesting.ValidKeyOne.Key + "\n" +
   105  				sshtesting.ValidKeyTwo.Key + " user@host\n" +
   106  				"# comment\n\n" +
   107  				sshtesting.ValidKeyThree.Key + " another@host")
   108  	},
   109  }, {
   110  	"SetSSHAuthorizedKeys unsets keys",
   111  	map[string]interface{}{},
   112  	func(cfg cloudinit.CloudConfig) {
   113  		cfg.SetSSHAuthorizedKeys(sshtesting.ValidKeyOne.Key)
   114  		cfg.SetSSHAuthorizedKeys("")
   115  	},
   116  }, {
   117  	"AddUser with keys",
   118  	map[string]interface{}{"users": []interface{}{map[string]interface{}{
   119  		"name":        "auser",
   120  		"lock_passwd": true,
   121  		"ssh-authorized-keys": []string{
   122  			fmt.Sprintf("%s Juju:user@host", sshtesting.ValidKeyOne.Key),
   123  			fmt.Sprintf("%s Juju:another@host", sshtesting.ValidKeyTwo.Key),
   124  		},
   125  	}}},
   126  	func(cfg cloudinit.CloudConfig) {
   127  		keys := (sshtesting.ValidKeyOne.Key + " Juju:user@host\n" +
   128  			sshtesting.ValidKeyTwo.Key + " another@host")
   129  		cfg.AddUser(&cloudinit.User{
   130  			Name:              "auser",
   131  			SSHAuthorizedKeys: keys,
   132  		})
   133  	},
   134  }, {
   135  	"AddUser with groups",
   136  	map[string]interface{}{"users": []interface{}{map[string]interface{}{
   137  		"name":        "auser",
   138  		"lock_passwd": true,
   139  		"groups":      []string{"agroup", "bgroup"},
   140  	}}},
   141  	func(cfg cloudinit.CloudConfig) {
   142  		cfg.AddUser(&cloudinit.User{
   143  			Name:   "auser",
   144  			Groups: []string{"agroup", "bgroup"},
   145  		})
   146  	},
   147  }, {
   148  	"AddUser with everything",
   149  	map[string]interface{}{"users": []interface{}{map[string]interface{}{
   150  		"name":        "auser",
   151  		"lock_passwd": true,
   152  		"groups":      []string{"agroup", "bgroup"},
   153  		"shell":       "/bin/sh",
   154  		"ssh-authorized-keys": []string{
   155  			sshtesting.ValidKeyOne.Key + " Juju:sshkey",
   156  		},
   157  		"sudo": []string{"ALL=(ALL) ALL"},
   158  	}}},
   159  	func(cfg cloudinit.CloudConfig) {
   160  		cfg.AddUser(&cloudinit.User{
   161  			Name:              "auser",
   162  			Groups:            []string{"agroup", "bgroup"},
   163  			Shell:             "/bin/sh",
   164  			SSHAuthorizedKeys: sshtesting.ValidKeyOne.Key + "\n",
   165  			Sudo:              []string{"ALL=(ALL) ALL"},
   166  		})
   167  	},
   168  }, {
   169  	"AddUser with only name",
   170  	map[string]interface{}{"users": []interface{}{map[string]interface{}{
   171  		"name":        "auser",
   172  		"lock_passwd": true,
   173  	}}},
   174  	func(cfg cloudinit.CloudConfig) {
   175  		cfg.AddUser(&cloudinit.User{
   176  			Name: "auser",
   177  		})
   178  	},
   179  }, {
   180  	"Output",
   181  	map[string]interface{}{"output": map[string]interface{}{
   182  		"all": []string{">foo", "|bar"},
   183  	}},
   184  	func(cfg cloudinit.CloudConfig) {
   185  		cfg.SetOutput("all", ">foo", "|bar")
   186  	},
   187  }, {
   188  	"Output",
   189  	map[string]interface{}{"output": map[string]interface{}{
   190  		"all": ">foo",
   191  	}},
   192  	func(cfg cloudinit.CloudConfig) {
   193  		cfg.SetOutput(cloudinit.OutAll, ">foo", "")
   194  	},
   195  }, {
   196  	"PackageSources",
   197  	map[string]interface{}{"apt_sources": []map[string]interface{}{
   198  		{
   199  			"source": "keyName",
   200  			"key":    "someKey",
   201  		},
   202  	}},
   203  	func(cfg cloudinit.CloudConfig) {
   204  		cfg.AddPackageSource(packaging.PackageSource{URL: "keyName", Key: "someKey"})
   205  	},
   206  }, {
   207  	"PackageSources with preferences",
   208  	map[string]interface{}{
   209  		"apt_sources": []map[string]interface{}{
   210  			{
   211  				"source": "keyName",
   212  				"key":    "someKey",
   213  			},
   214  		},
   215  		"bootcmd": []string{
   216  			"install -D -m 644 /dev/null '/some/path'",
   217  			"printf '%s\\n' 'Explanation: test\n" +
   218  				"Package: *\n" +
   219  				"Pin: release n=series\n" +
   220  				"Pin-Priority: 123\n" +
   221  				"' > '/some/path'",
   222  		},
   223  	},
   224  	func(cfg cloudinit.CloudConfig) {
   225  		prefs := packaging.PackagePreferences{
   226  			Path:        "/some/path",
   227  			Explanation: "test",
   228  			Package:     "*",
   229  			Pin:         "release n=series",
   230  			Priority:    123,
   231  		}
   232  		cfg.AddPackageSource(packaging.PackageSource{URL: "keyName", Key: "someKey"})
   233  		cfg.AddPackagePreferences(prefs)
   234  	},
   235  }, {
   236  	"Packages",
   237  	map[string]interface{}{"packages": []string{
   238  		"juju",
   239  		"ubuntu",
   240  	}},
   241  	func(cfg cloudinit.CloudConfig) {
   242  		cfg.AddPackage("juju")
   243  		cfg.AddPackage("ubuntu")
   244  	},
   245  }, {
   246  	"Packages on precise, needing cloud-tools",
   247  	map[string]interface{}{"packages": []string{
   248  		// Regular packages (not needing the cloud-tools archive)
   249  		"juju",
   250  		"curl",
   251  		// The following need to be among the list of cloud-tools
   252  		// packages (see cloudArchivePackagesUbuntu in juju/utils
   253  		// repo).
   254  		"--target-release", "precise-updates/cloud-tools", "cloud-utils",
   255  		"--target-release", "precise-updates/cloud-tools", "cloud-image-utils",
   256  		// Other regular packages.
   257  		"ubuntu",
   258  	}},
   259  	func(cfg cloudinit.CloudConfig) {
   260  		cfg.AddPackage("juju")
   261  		cfg.AddPackage("curl")
   262  		// cloud-tools packages need to appear in the list of packages
   263  		// after the "--target-release" and
   264  		// "precise-updates/cloud-tools" lines to work around the
   265  		// broken 0.6.3 cloud-init on precise. cloud-init 0.6.3
   266  		// concatenates all space-separated arguments for each entry
   267  		// in the packages list, and then escapes the result in single
   268  		// quotes, which in turn leads to incorrectly rendered apt-get
   269  		// command (e.g. instead of "apt-get install --target-release
   270  		// foo/bar package" 0.6.3 will try to execute "apt-get install
   271  		// '--target-release foo/bar package'".). See bug #1424777 for
   272  		// more info.
   273  		cfg.AddPackage("--target-release")
   274  		cfg.AddPackage("precise-updates/cloud-tools")
   275  		cfg.AddPackage("cloud-utils")
   276  
   277  		cfg.AddPackage("--target-release")
   278  		cfg.AddPackage("precise-updates/cloud-tools")
   279  		cfg.AddPackage("cloud-image-utils")
   280  
   281  		cfg.AddPackage("ubuntu")
   282  	},
   283  }, {
   284  	"BootCmd",
   285  	map[string]interface{}{"bootcmd": []string{
   286  		"ls > /dev",
   287  		"ls >with space",
   288  	}},
   289  	func(cfg cloudinit.CloudConfig) {
   290  		cfg.AddBootCmd("ls > /dev")
   291  		cfg.AddBootCmd("ls >with space")
   292  	},
   293  }, {
   294  	"Mounts",
   295  	map[string]interface{}{"mounts": [][]string{
   296  		{"x", "y"},
   297  		{"z", "w"},
   298  	}},
   299  	func(cfg cloudinit.CloudConfig) {
   300  		cfg.AddMount("x", "y")
   301  		cfg.AddMount("z", "w")
   302  	},
   303  }, {
   304  	"Attr",
   305  	map[string]interface{}{"arbitraryAttr": "someValue"},
   306  	func(cfg cloudinit.CloudConfig) {
   307  		cfg.SetAttr("arbitraryAttr", "someValue")
   308  	},
   309  }, {
   310  	"RunCmd",
   311  	map[string]interface{}{"runcmd": []string{
   312  		"ifconfig",
   313  	}},
   314  	func(cfg cloudinit.CloudConfig) {
   315  		cfg.AddRunCmd("ifconfig")
   316  	},
   317  }, {
   318  	"AddScripts",
   319  	map[string]interface{}{"runcmd": []string{
   320  		"echo 'Hello World'",
   321  		"ifconfig",
   322  	}},
   323  	func(cfg cloudinit.CloudConfig) {
   324  		cfg.AddScripts(
   325  			"echo 'Hello World'",
   326  			"ifconfig",
   327  		)
   328  	},
   329  }, {
   330  	"AddTextFile",
   331  	map[string]interface{}{"runcmd": []string{
   332  		"install -D -m 644 /dev/null '/etc/apt/apt.conf.d/99proxy'",
   333  		"printf '%s\\n' '\"Acquire::http::Proxy \"http://10.0.3.1:3142\";' > '/etc/apt/apt.conf.d/99proxy'",
   334  	}},
   335  	func(cfg cloudinit.CloudConfig) {
   336  		cfg.AddRunTextFile(
   337  			"/etc/apt/apt.conf.d/99proxy",
   338  			`"Acquire::http::Proxy "http://10.0.3.1:3142";`,
   339  			0644,
   340  		)
   341  	},
   342  }, {
   343  	"AddBinaryFile",
   344  	map[string]interface{}{"runcmd": []string{
   345  		"install -D -m 644 /dev/null '/dev/nonsense'",
   346  		"printf %s AAECAw== | base64 -d > '/dev/nonsense'",
   347  	}},
   348  	func(cfg cloudinit.CloudConfig) {
   349  		cfg.AddRunBinaryFile(
   350  			"/dev/nonsense",
   351  			[]byte{0, 1, 2, 3},
   352  			0644,
   353  		)
   354  	},
   355  }, {
   356  	"AddBootTextFile",
   357  	map[string]interface{}{"bootcmd": []string{
   358  		"install -D -m 644 /dev/null '/etc/apt/apt.conf.d/99proxy'",
   359  		"printf '%s\\n' '\"Acquire::http::Proxy \"http://10.0.3.1:3142\";' > '/etc/apt/apt.conf.d/99proxy'",
   360  	}},
   361  	func(cfg cloudinit.CloudConfig) {
   362  		cfg.AddBootTextFile(
   363  			"/etc/apt/apt.conf.d/99proxy",
   364  			`"Acquire::http::Proxy "http://10.0.3.1:3142";`,
   365  			0644,
   366  		)
   367  	},
   368  },
   369  }
   370  
   371  func (S) TestOutput(c *gc.C) {
   372  	for i, t := range ctests {
   373  		c.Logf("test %d: %s", i, t.name)
   374  		cfg, err := cloudinit.New("precise")
   375  		c.Assert(err, jc.ErrorIsNil)
   376  		t.setOption(cfg)
   377  		data, err := cfg.RenderYAML()
   378  		c.Assert(err, jc.ErrorIsNil)
   379  		c.Assert(data, gc.NotNil)
   380  		c.Assert(string(data), jc.YAMLEquals, t.expect)
   381  		data, err = cfg.RenderYAML()
   382  		c.Assert(err, jc.ErrorIsNil)
   383  		c.Assert(data, gc.NotNil)
   384  		c.Assert(string(data), jc.YAMLEquals, t.expect)
   385  	}
   386  }
   387  
   388  func (S) TestRunCmds(c *gc.C) {
   389  	cfg, err := cloudinit.New("precise")
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	c.Assert(cfg.RunCmds(), gc.HasLen, 0)
   392  	cfg.AddScripts("a", "b")
   393  	cfg.AddRunCmd("e")
   394  	c.Assert(cfg.RunCmds(), gc.DeepEquals, []string{
   395  		"a", "b", "e",
   396  	})
   397  }
   398  
   399  func (S) TestPackages(c *gc.C) {
   400  	cfg, err := cloudinit.New("precise")
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	c.Assert(cfg.Packages(), gc.HasLen, 0)
   403  	cfg.AddPackage("a b c")
   404  	cfg.AddPackage("d!")
   405  	expectedPackages := []string{"a b c", "d!"}
   406  	c.Assert(cfg.Packages(), gc.DeepEquals, expectedPackages)
   407  }
   408  
   409  func (S) TestSetOutput(c *gc.C) {
   410  	type test struct {
   411  		kind   cloudinit.OutputKind
   412  		stdout string
   413  		stderr string
   414  	}
   415  	tests := []test{{
   416  		cloudinit.OutAll, "a", "",
   417  	}, {
   418  		cloudinit.OutAll, "", "b",
   419  	}, {
   420  		cloudinit.OutInit, "a", "b",
   421  	}, {
   422  		cloudinit.OutAll, "a", "b",
   423  	}, {
   424  		cloudinit.OutAll, "", "",
   425  	}}
   426  
   427  	cfg, err := cloudinit.New("trusty")
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	stdout, stderr := cfg.Output(cloudinit.OutAll)
   430  	c.Assert(stdout, gc.Equals, "")
   431  	c.Assert(stderr, gc.Equals, "")
   432  	for i, t := range tests {
   433  		c.Logf("test %d: %+v", i, t)
   434  		cfg.SetOutput(t.kind, t.stdout, t.stderr)
   435  		stdout, stderr = cfg.Output(t.kind)
   436  		c.Assert(stdout, gc.Equals, t.stdout)
   437  		c.Assert(stderr, gc.Equals, t.stderr)
   438  	}
   439  }
   440  
   441  func (S) TestWindowsRender(c *gc.C) {
   442  	compareOutput := "#ps1_sysnative\r\n\r\npowershell"
   443  	cfg, err := cloudinit.New("win8")
   444  	c.Assert(err, jc.ErrorIsNil)
   445  	cfg.AddRunCmd("powershell")
   446  	data, err := cfg.RenderYAML()
   447  	c.Assert(err, jc.ErrorIsNil)
   448  	c.Assert(data, gc.NotNil)
   449  	c.Assert(string(data), gc.Equals, compareOutput, gc.Commentf("test %q output differs", "windows renderer"))
   450  }