github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/sysconfig/cloudinit_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package sysconfig_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/sysconfig"
    34  	"github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  // Hook up check.v1 into the "go test" runner
    38  func Test(t *testing.T) { TestingT(t) }
    39  
    40  type sysconfigSuite struct {
    41  	testutil.BaseTest
    42  
    43  	tmpdir string
    44  }
    45  
    46  var _ = Suite(&sysconfigSuite{})
    47  
    48  func (s *sysconfigSuite) SetUpTest(c *C) {
    49  	s.BaseTest.SetUpTest(c)
    50  
    51  	s.tmpdir = c.MkDir()
    52  	dirs.SetRootDir(s.tmpdir)
    53  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    54  }
    55  
    56  func (s *sysconfigSuite) makeCloudCfgSrcDirFiles(c *C) string {
    57  	cloudCfgSrcDir := c.MkDir()
    58  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
    59  		err := ioutil.WriteFile(filepath.Join(cloudCfgSrcDir, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
    60  		c.Assert(err, IsNil)
    61  	}
    62  	return cloudCfgSrcDir
    63  }
    64  
    65  func (s *sysconfigSuite) makeGadgetCloudConfFile(c *C) string {
    66  	gadgetDir := c.MkDir()
    67  	gadgetCloudConf := filepath.Join(gadgetDir, "cloud.conf")
    68  	err := ioutil.WriteFile(gadgetCloudConf, []byte("gadget cloud config"), 0644)
    69  	c.Assert(err, IsNil)
    70  
    71  	return gadgetDir
    72  }
    73  
    74  func (s *sysconfigSuite) TestHasGadgetCloudConf(c *C) {
    75  	// no cloud.conf is false
    76  	c.Assert(sysconfig.HasGadgetCloudConf("non-existent-dir-place"), Equals, false)
    77  
    78  	// the dir is not enough
    79  	gadgetDir := c.MkDir()
    80  	c.Assert(sysconfig.HasGadgetCloudConf(gadgetDir), Equals, false)
    81  
    82  	// creating one now is true
    83  	gadgetCloudConf := filepath.Join(gadgetDir, "cloud.conf")
    84  	err := ioutil.WriteFile(gadgetCloudConf, []byte("gadget cloud config"), 0644)
    85  	c.Assert(err, IsNil)
    86  
    87  	c.Assert(sysconfig.HasGadgetCloudConf(gadgetDir), Equals, true)
    88  }
    89  
    90  // this test is for initramfs calls that disable cloud-init for the ephemeral
    91  // writable partition that is used while running during install or recover mode
    92  func (s *sysconfigSuite) TestEphemeralModeInitramfsCloudInitDisables(c *C) {
    93  	writableDefaultsDir := sysconfig.WritableDefaultsDir(boot.InitramfsWritableDir)
    94  	err := sysconfig.DisableCloudInit(writableDefaultsDir)
    95  	c.Assert(err, IsNil)
    96  
    97  	ubuntuDataCloudDisabled := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
    98  	c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
    99  }
   100  
   101  func (s *sysconfigSuite) TestInstallModeCloudInitDisablesByDefaultRunMode(c *C) {
   102  	err := sysconfig.ConfigureTargetSystem(&sysconfig.Options{
   103  		TargetRootDir: boot.InstallHostWritableDir,
   104  	})
   105  	c.Assert(err, IsNil)
   106  
   107  	ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
   108  	c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
   109  }
   110  
   111  func (s *sysconfigSuite) TestInstallModeCloudInitDisallowedIgnoresOtherOptions(c *C) {
   112  	cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c)
   113  	gadgetDir := s.makeGadgetCloudConfFile(c)
   114  
   115  	err := sysconfig.ConfigureTargetSystem(&sysconfig.Options{
   116  		AllowCloudInit:  false,
   117  		CloudInitSrcDir: cloudCfgSrcDir,
   118  		GadgetDir:       gadgetDir,
   119  		TargetRootDir:   boot.InstallHostWritableDir,
   120  	})
   121  	c.Assert(err, IsNil)
   122  
   123  	ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
   124  	c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
   125  
   126  	// did not copy ubuntu-seed src files
   127  	ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
   128  	c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileAbsent)
   129  	c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileAbsent)
   130  
   131  	// also did not copy gadget cloud.conf
   132  	c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileAbsent)
   133  }
   134  
   135  func (s *sysconfigSuite) TestInstallModeCloudInitAllowedDoesNotDisable(c *C) {
   136  	err := sysconfig.ConfigureTargetSystem(&sysconfig.Options{
   137  		AllowCloudInit: true,
   138  		TargetRootDir:  boot.InstallHostWritableDir,
   139  	})
   140  	c.Assert(err, IsNil)
   141  
   142  	ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
   143  	c.Check(ubuntuDataCloudDisabled, testutil.FileAbsent)
   144  }
   145  
   146  // this test is the same as the logic from install mode devicestate, where we
   147  // want to install cloud-init configuration not onto the running, ephemeral
   148  // writable, but rather the host writable partition that will be used upon
   149  // reboot into run mode
   150  func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunMode(c *C) {
   151  	cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c)
   152  
   153  	err := sysconfig.ConfigureTargetSystem(&sysconfig.Options{
   154  		AllowCloudInit:  true,
   155  		CloudInitSrcDir: cloudCfgSrcDir,
   156  		TargetRootDir:   boot.InstallHostWritableDir,
   157  	})
   158  	c.Assert(err, IsNil)
   159  
   160  	// and did copy the cloud-init files
   161  	ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
   162  	c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileEquals, "foo.cfg config")
   163  	c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileEquals, "bar.cfg config")
   164  }
   165  
   166  func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunModeWithGadgetCloudConf(c *C) {
   167  	gadgetDir := s.makeGadgetCloudConfFile(c)
   168  	err := sysconfig.ConfigureTargetSystem(&sysconfig.Options{
   169  		AllowCloudInit: true,
   170  		GadgetDir:      gadgetDir,
   171  		TargetRootDir:  boot.InstallHostWritableDir,
   172  	})
   173  	c.Assert(err, IsNil)
   174  
   175  	// and did copy the gadget cloud-init file
   176  	ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
   177  	c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileEquals, "gadget cloud config")
   178  }
   179  
   180  func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunModeWithGadgetCloudConfIgnoresUbuntuSeedConfig(c *C) {
   181  	cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c)
   182  	gadgetDir := s.makeGadgetCloudConfFile(c)
   183  
   184  	err := sysconfig.ConfigureTargetSystem(&sysconfig.Options{
   185  		AllowCloudInit:  true,
   186  		CloudInitSrcDir: cloudCfgSrcDir,
   187  		GadgetDir:       gadgetDir,
   188  		TargetRootDir:   boot.InstallHostWritableDir,
   189  	})
   190  	c.Assert(err, IsNil)
   191  
   192  	// and did copy the gadget cloud-init file
   193  	ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
   194  	c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileEquals, "gadget cloud config")
   195  
   196  	// but did not copy the ubuntu-seed files
   197  	c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileAbsent)
   198  	c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileAbsent)
   199  }
   200  
   201  func (s *sysconfigSuite) TestCloudInitStatusUnhappy(c *C) {
   202  	cmd := testutil.MockCommand(c, "cloud-init", `
   203  echo cloud-init borken
   204  exit 1
   205  `)
   206  
   207  	status, err := sysconfig.CloudInitStatus()
   208  	c.Assert(err, ErrorMatches, "cloud-init borken")
   209  	c.Assert(status, Equals, sysconfig.CloudInitErrored)
   210  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   211  		{"cloud-init", "status"},
   212  	})
   213  }
   214  
   215  func (s *sysconfigSuite) TestCloudInitStatus(c *C) {
   216  	tt := []struct {
   217  		comment         string
   218  		cloudInitOutput string
   219  		exp             sysconfig.CloudInitState
   220  		restrictedFile  bool
   221  		disabledFile    bool
   222  		expError        string
   223  	}{
   224  		{
   225  			comment:         "done",
   226  			cloudInitOutput: "status: done",
   227  			exp:             sysconfig.CloudInitDone,
   228  		},
   229  		{
   230  			comment:         "running",
   231  			cloudInitOutput: "status: running",
   232  			exp:             sysconfig.CloudInitEnabled,
   233  		},
   234  		{
   235  			comment:         "not run",
   236  			cloudInitOutput: "status: not run",
   237  			exp:             sysconfig.CloudInitEnabled,
   238  		},
   239  		{
   240  			comment:         "new unrecognized state",
   241  			cloudInitOutput: "status: newfangledstatus",
   242  			exp:             sysconfig.CloudInitEnabled,
   243  		},
   244  		{
   245  			comment:        "restricted by snapd",
   246  			restrictedFile: true,
   247  			exp:            sysconfig.CloudInitRestrictedBySnapd,
   248  		},
   249  		{
   250  			comment:         "disabled temporarily",
   251  			cloudInitOutput: "status: disabled",
   252  			exp:             sysconfig.CloudInitUntriggered,
   253  		},
   254  		{
   255  			comment:      "disabled permanently via file",
   256  			disabledFile: true,
   257  			exp:          sysconfig.CloudInitDisabledPermanently,
   258  		},
   259  		{
   260  			comment:         "errored",
   261  			cloudInitOutput: "status: error",
   262  			exp:             sysconfig.CloudInitErrored,
   263  		},
   264  		{
   265  			comment:         "broken cloud-init output",
   266  			cloudInitOutput: "broken cloud-init output",
   267  			expError:        "invalid cloud-init output: broken cloud-init output",
   268  		},
   269  	}
   270  
   271  	for _, t := range tt {
   272  		old := dirs.GlobalRootDir
   273  		dirs.SetRootDir(c.MkDir())
   274  		defer func() { dirs.SetRootDir(old) }()
   275  		cmd := testutil.MockCommand(c, "cloud-init", fmt.Sprintf(`
   276  if [ "$1" = "status" ]; then
   277  	echo '%s'
   278  else 
   279  	echo "unexpected args, $"
   280  	exit 1
   281  fi
   282  		`, t.cloudInitOutput))
   283  
   284  		if t.disabledFile {
   285  			cloudDir := filepath.Join(dirs.GlobalRootDir, "etc/cloud")
   286  			err := os.MkdirAll(cloudDir, 0755)
   287  			c.Assert(err, IsNil)
   288  			err = ioutil.WriteFile(filepath.Join(cloudDir, "cloud-init.disabled"), nil, 0644)
   289  			c.Assert(err, IsNil)
   290  		}
   291  
   292  		if t.restrictedFile {
   293  			cloudDir := filepath.Join(dirs.GlobalRootDir, "etc/cloud/cloud.cfg.d")
   294  			err := os.MkdirAll(cloudDir, 0755)
   295  			c.Assert(err, IsNil)
   296  			err = ioutil.WriteFile(filepath.Join(cloudDir, "zzzz_snapd.cfg"), nil, 0644)
   297  			c.Assert(err, IsNil)
   298  		}
   299  
   300  		status, err := sysconfig.CloudInitStatus()
   301  		if t.expError != "" {
   302  			c.Assert(err, ErrorMatches, t.expError, Commentf(t.comment))
   303  		} else {
   304  			c.Assert(err, IsNil)
   305  			c.Assert(status, Equals, t.exp, Commentf(t.comment))
   306  		}
   307  
   308  		// if the restricted file was there we don't call cloud-init status
   309  		var expCalls [][]string
   310  		if !t.restrictedFile && !t.disabledFile {
   311  			expCalls = [][]string{
   312  				{"cloud-init", "status"},
   313  			}
   314  		}
   315  
   316  		c.Assert(cmd.Calls(), DeepEquals, expCalls, Commentf(t.comment))
   317  		cmd.Restore()
   318  	}
   319  }
   320  
   321  func (s *sysconfigSuite) TestCloudInitNotFoundStatus(c *C) {
   322  	emptyDir := c.MkDir()
   323  	oldPath := os.Getenv("PATH")
   324  	defer func() {
   325  		c.Assert(os.Setenv("PATH", oldPath), IsNil)
   326  	}()
   327  	os.Setenv("PATH", emptyDir)
   328  
   329  	status, err := sysconfig.CloudInitStatus()
   330  	c.Assert(err, IsNil)
   331  	c.Check(status, Equals, sysconfig.CloudInitNotFound)
   332  }
   333  
   334  var gceCloudInitStatusJSON = `{
   335  	"v1": {
   336  	 "datasource": "DataSourceGCE",
   337  	 "init": {
   338  	  "errors": [],
   339  	  "finished": 1591751113.4536479,
   340  	  "start": 1591751112.130069
   341  	 },
   342  	 "stage": null
   343  	}
   344  }
   345  `
   346  
   347  var multipassNoCloudCloudInitStatusJSON = `{
   348   "v1": {
   349    "datasource": "DataSourceNoCloud [seed=/dev/sr0][dsmode=net]",
   350    "init": {
   351     "errors": [],
   352     "finished": 1591788514.4656117,
   353     "start": 1591788514.2607572
   354    },
   355    "stage": null
   356   }
   357  }`
   358  
   359  var localNoneCloudInitStatusJSON = `{
   360   "v1": {
   361    "datasource": "DataSourceNone",
   362    "init": {
   363     "errors": [],
   364     "finished": 1591788514.4656117,
   365     "start": 1591788514.2607572
   366    },
   367    "stage": null
   368   }
   369  }`
   370  
   371  var lxdNoCloudCloudInitStatusJSON = `{
   372   "v1": {
   373    "datasource": "DataSourceNoCloud [seed=/var/lib/cloud/seed/nocloud-net][dsmode=net]",
   374    "init": {
   375     "errors": [],
   376     "finished": 1591788737.3982718,
   377     "start": 1591788736.9015596
   378    },
   379    "stage": null
   380   }
   381  }`
   382  
   383  var restrictNoCloudYaml = `datasource_list: [NoCloud]
   384  datasource:
   385    NoCloud:
   386      fs_label: null
   387  manual_cache_clean: true
   388  `
   389  
   390  func (s *sysconfigSuite) TestRestrictCloudInit(c *C) {
   391  	tt := []struct {
   392  		comment                string
   393  		state                  sysconfig.CloudInitState
   394  		sysconfOpts            *sysconfig.CloudInitRestrictOptions
   395  		cloudInitStatusJSON    string
   396  		expError               string
   397  		expRestrictYamlWritten string
   398  		expDatasource          string
   399  		expAction              string
   400  		expDisableFile         bool
   401  	}{
   402  		{
   403  			comment:  "already disabled",
   404  			state:    sysconfig.CloudInitDisabledPermanently,
   405  			expError: "cannot restrict cloud-init: already disabled",
   406  		},
   407  		{
   408  			comment:  "already restricted",
   409  			state:    sysconfig.CloudInitRestrictedBySnapd,
   410  			expError: "cannot restrict cloud-init: already restricted",
   411  		},
   412  		{
   413  			comment:  "errored",
   414  			state:    sysconfig.CloudInitErrored,
   415  			expError: "cannot restrict cloud-init in error or enabled state",
   416  		},
   417  		{
   418  			comment:  "enable (not running)",
   419  			state:    sysconfig.CloudInitEnabled,
   420  			expError: "cannot restrict cloud-init in error or enabled state",
   421  		},
   422  		{
   423  			comment: "errored w/ force disable",
   424  			state:   sysconfig.CloudInitErrored,
   425  			sysconfOpts: &sysconfig.CloudInitRestrictOptions{
   426  				ForceDisable: true,
   427  			},
   428  			expAction:      "disable",
   429  			expDisableFile: true,
   430  		},
   431  		{
   432  			comment: "enable (not running) w/ force disable",
   433  			state:   sysconfig.CloudInitEnabled,
   434  			sysconfOpts: &sysconfig.CloudInitRestrictOptions{
   435  				ForceDisable: true,
   436  			},
   437  			expAction:      "disable",
   438  			expDisableFile: true,
   439  		},
   440  		{
   441  			comment:        "untriggered",
   442  			state:          sysconfig.CloudInitUntriggered,
   443  			expAction:      "disable",
   444  			expDisableFile: true,
   445  		},
   446  		{
   447  			comment:        "unknown status",
   448  			state:          -1,
   449  			expAction:      "disable",
   450  			expDisableFile: true,
   451  		},
   452  		{
   453  			comment:             "gce done",
   454  			state:               sysconfig.CloudInitDone,
   455  			cloudInitStatusJSON: gceCloudInitStatusJSON,
   456  			expDatasource:       "GCE",
   457  			expAction:           "restrict",
   458  			expRestrictYamlWritten: `datasource_list: [GCE]
   459  `,
   460  		},
   461  		{
   462  			comment:                "nocloud done",
   463  			state:                  sysconfig.CloudInitDone,
   464  			cloudInitStatusJSON:    multipassNoCloudCloudInitStatusJSON,
   465  			expDatasource:          "NoCloud",
   466  			expAction:              "restrict",
   467  			expRestrictYamlWritten: restrictNoCloudYaml,
   468  		},
   469  		{
   470  			comment:             "nocloud uc20 done",
   471  			state:               sysconfig.CloudInitDone,
   472  			cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON,
   473  			sysconfOpts: &sysconfig.CloudInitRestrictOptions{
   474  				DisableAfterLocalDatasourcesRun: true,
   475  			},
   476  			expDatasource:  "NoCloud",
   477  			expAction:      "disable",
   478  			expDisableFile: true,
   479  		},
   480  		{
   481  			comment:             "none uc20 done",
   482  			state:               sysconfig.CloudInitDone,
   483  			cloudInitStatusJSON: localNoneCloudInitStatusJSON,
   484  			sysconfOpts: &sysconfig.CloudInitRestrictOptions{
   485  				DisableAfterLocalDatasourcesRun: true,
   486  			},
   487  			expDatasource:  "None",
   488  			expAction:      "disable",
   489  			expDisableFile: true,
   490  		},
   491  
   492  		// the two cases for lxd and multipass are effectively the same, but as
   493  		// the largest known users of cloud-init w/ UC, we leave them as
   494  		// separate test cases for their different cloud-init status.json
   495  		// content
   496  		{
   497  			comment:                "nocloud multipass done",
   498  			state:                  sysconfig.CloudInitDone,
   499  			cloudInitStatusJSON:    multipassNoCloudCloudInitStatusJSON,
   500  			expDatasource:          "NoCloud",
   501  			expAction:              "restrict",
   502  			expRestrictYamlWritten: restrictNoCloudYaml,
   503  		},
   504  		{
   505  			comment:                "nocloud seed lxd done",
   506  			state:                  sysconfig.CloudInitDone,
   507  			cloudInitStatusJSON:    lxdNoCloudCloudInitStatusJSON,
   508  			expDatasource:          "NoCloud",
   509  			expAction:              "restrict",
   510  			expRestrictYamlWritten: restrictNoCloudYaml,
   511  		},
   512  		{
   513  			comment:             "nocloud uc20 multipass done",
   514  			state:               sysconfig.CloudInitDone,
   515  			cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON,
   516  			sysconfOpts: &sysconfig.CloudInitRestrictOptions{
   517  				DisableAfterLocalDatasourcesRun: true,
   518  			},
   519  			expDatasource:  "NoCloud",
   520  			expAction:      "disable",
   521  			expDisableFile: true,
   522  		},
   523  		{
   524  			comment:             "nocloud uc20 seed lxd done",
   525  			state:               sysconfig.CloudInitDone,
   526  			cloudInitStatusJSON: lxdNoCloudCloudInitStatusJSON,
   527  			sysconfOpts: &sysconfig.CloudInitRestrictOptions{
   528  				DisableAfterLocalDatasourcesRun: true,
   529  			},
   530  			expDatasource:  "NoCloud",
   531  			expAction:      "disable",
   532  			expDisableFile: true,
   533  		},
   534  		{
   535  			comment:        "no cloud-init in $PATH",
   536  			state:          sysconfig.CloudInitNotFound,
   537  			expAction:      "disable",
   538  			expDisableFile: true,
   539  		},
   540  	}
   541  
   542  	for _, t := range tt {
   543  		comment := Commentf("%s", t.comment)
   544  		// setup status.json
   545  		old := dirs.GlobalRootDir
   546  		dirs.SetRootDir(c.MkDir())
   547  		defer func() { dirs.SetRootDir(old) }()
   548  		statusJSONFile := filepath.Join(dirs.GlobalRootDir, "/run/cloud-init/status.json")
   549  		if t.cloudInitStatusJSON != "" {
   550  			err := os.MkdirAll(filepath.Dir(statusJSONFile), 0755)
   551  			c.Assert(err, IsNil, comment)
   552  			err = ioutil.WriteFile(statusJSONFile, []byte(t.cloudInitStatusJSON), 0644)
   553  			c.Assert(err, IsNil, comment)
   554  		}
   555  
   556  		// if we expect snapd to write a yaml config file for cloud-init, ensure
   557  		// the dir exists before hand
   558  		if t.expRestrictYamlWritten != "" {
   559  			err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud.cfg.d"), 0755)
   560  			c.Assert(err, IsNil, comment)
   561  		}
   562  
   563  		res, err := sysconfig.RestrictCloudInit(t.state, t.sysconfOpts)
   564  		if t.expError == "" {
   565  			c.Assert(err, IsNil, comment)
   566  			c.Assert(res.DataSource, Equals, t.expDatasource, comment)
   567  			c.Assert(res.Action, Equals, t.expAction, comment)
   568  			if t.expRestrictYamlWritten != "" {
   569  				// check the snapd restrict yaml file that should have been written
   570  				c.Assert(
   571  					filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud.cfg.d/zzzz_snapd.cfg"),
   572  					testutil.FileEquals,
   573  					t.expRestrictYamlWritten,
   574  					comment,
   575  				)
   576  			}
   577  
   578  			// if we expect the disable file to be written then check for it
   579  			// otherwise ensure it was not written accidentally
   580  			var fileCheck Checker
   581  			if t.expDisableFile {
   582  				fileCheck = testutil.FilePresent
   583  			} else {
   584  				fileCheck = testutil.FileAbsent
   585  			}
   586  
   587  			c.Assert(
   588  				filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud-init.disabled"),
   589  				fileCheck,
   590  				comment,
   591  			)
   592  
   593  		} else {
   594  			c.Assert(err, ErrorMatches, t.expError, comment)
   595  		}
   596  	}
   597  }