mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
The cloud-init filtering writes a bunch of files into /tmp that are not removed. So avoid cluttering /tmp on the hosts when unit tests are run this commit sets TMPDIR to the unit test tmpdir so that it's properly cleaned up.
1454 lines
43 KiB
Go
1454 lines
43 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2020 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package sysconfig_test
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
. "gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/boot"
|
|
"github.com/snapcore/snapd/dirs"
|
|
"github.com/snapcore/snapd/sysconfig"
|
|
"github.com/snapcore/snapd/testutil"
|
|
)
|
|
|
|
// Hook up check.v1 into the "go test" runner
|
|
func Test(t *testing.T) { TestingT(t) }
|
|
|
|
type sysconfigSuite struct {
|
|
testutil.BaseTest
|
|
|
|
tmpdir string
|
|
}
|
|
|
|
var _ = Suite(&sysconfigSuite{})
|
|
|
|
func (s *sysconfigSuite) SetUpTest(c *C) {
|
|
s.BaseTest.SetUpTest(c)
|
|
|
|
s.tmpdir = c.MkDir()
|
|
dirs.SetRootDir(s.tmpdir)
|
|
s.AddCleanup(func() { dirs.SetRootDir("/") })
|
|
|
|
oldTmpdir := os.Getenv("TMPDIR")
|
|
os.Setenv("TMPDIR", s.tmpdir)
|
|
s.AddCleanup(func() { os.Unsetenv(oldTmpdir) })
|
|
}
|
|
|
|
func (s *sysconfigSuite) makeCloudCfgSrcDirFiles(c *C, cfgs ...string) (string, []string) {
|
|
cloudCfgSrcDir := c.MkDir()
|
|
names := make([]string, 0, len(cfgs))
|
|
for i, mockCfg := range cfgs {
|
|
configFileName := fmt.Sprintf("seed-config-%d.cfg", i)
|
|
err := ioutil.WriteFile(filepath.Join(cloudCfgSrcDir, configFileName), []byte(mockCfg), 0644)
|
|
c.Assert(err, IsNil)
|
|
names = append(names, configFileName)
|
|
}
|
|
return cloudCfgSrcDir, names
|
|
}
|
|
|
|
func (s *sysconfigSuite) makeGadgetCloudConfFile(c *C, content string) string {
|
|
gadgetDir := c.MkDir()
|
|
gadgetCloudConf := filepath.Join(gadgetDir, "cloud.conf")
|
|
if content == "" {
|
|
content = "#cloud-config some gadget cloud config"
|
|
}
|
|
err := ioutil.WriteFile(gadgetCloudConf, []byte(content), 0644)
|
|
c.Assert(err, IsNil)
|
|
|
|
return gadgetDir
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestHasGadgetCloudConf(c *C) {
|
|
// no cloud.conf is false
|
|
c.Assert(sysconfig.HasGadgetCloudConf("non-existent-dir-place"), Equals, false)
|
|
|
|
// the dir is not enough
|
|
gadgetDir := c.MkDir()
|
|
c.Assert(sysconfig.HasGadgetCloudConf(gadgetDir), Equals, false)
|
|
|
|
// creating one now is true
|
|
gadgetCloudConf := filepath.Join(gadgetDir, "cloud.conf")
|
|
err := ioutil.WriteFile(gadgetCloudConf, []byte("gadget cloud config"), 0644)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(sysconfig.HasGadgetCloudConf(gadgetDir), Equals, true)
|
|
}
|
|
|
|
// this test is for initramfs calls that disable cloud-init for the ephemeral
|
|
// writable partition that is used while running during install or recover mode
|
|
func (s *sysconfigSuite) TestEphemeralModeInitramfsCloudInitDisables(c *C) {
|
|
writableDefaultsDir := sysconfig.WritableDefaultsDir(boot.InitramfsWritableDir)
|
|
err := sysconfig.DisableCloudInit(writableDefaultsDir)
|
|
c.Assert(err, IsNil)
|
|
|
|
ubuntuDataCloudDisabled := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
|
|
c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestInstallModeCloudInitDisablesByDefaultRunMode(c *C) {
|
|
err := sysconfig.ConfigureTargetSystem(fake20Model("signed"), &sysconfig.Options{
|
|
TargetRootDir: boot.InstallHostWritableDir,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
|
|
c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestInstallModeCloudInitDisallowedIgnoresOtherOptions(c *C) {
|
|
cloudCfgSrcDir, _ := s.makeCloudCfgSrcDirFiles(c)
|
|
gadgetDir := s.makeGadgetCloudConfFile(c, "")
|
|
|
|
err := sysconfig.ConfigureTargetSystem(fake20Model("signed"), &sysconfig.Options{
|
|
AllowCloudInit: false,
|
|
CloudInitSrcDir: cloudCfgSrcDir,
|
|
GadgetDir: gadgetDir,
|
|
TargetRootDir: boot.InstallHostWritableDir,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
|
|
c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
|
|
|
|
// did not copy ubuntu-seed src files
|
|
ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileAbsent)
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileAbsent)
|
|
|
|
// also did not copy gadget cloud.conf
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileAbsent)
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestInstallModeCloudInitAllowedPermutations(c *C) {
|
|
|
|
// common inputs in the test cases below
|
|
defaultMAASCfgs := []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg3, // generic config that's filtered out
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
}
|
|
defaultMAASHappyInstalled := []bool{
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
}
|
|
defaultMAASNoneInstalled := []bool{false, false, false, false, false}
|
|
|
|
explicitNonSupportedDatasource := `datasource_list: [GCE]`
|
|
explicitSupportedDatasource := maasCfg2
|
|
explicitNoDatasourceAllowed := `datasource_list: []`
|
|
explicitSupportedAndNonSupportedDatasources := `datasource_list: [GCE, MAAS]`
|
|
|
|
implictMentionNonSupportedDatasource := `#cloud-config
|
|
datasource:
|
|
gce:
|
|
metadata_url: foo
|
|
`
|
|
implicitMentionSupportedDatasource := maasCfg1
|
|
|
|
restrictDatasourceMAASFile := `datasource_list: [MAAS]
|
|
`
|
|
// restrictDatasourceNoneFile := `datasource_list: []
|
|
// `
|
|
|
|
tt := []struct {
|
|
grade string
|
|
gadgetCfg string
|
|
// use lists here to install the files in the order that the file
|
|
// content appears
|
|
seedCfgs []string
|
|
// whether each file in seedCfgs gets installed
|
|
resultCfgCopied []bool
|
|
expDsRestrictFileContents string
|
|
comment string
|
|
}{
|
|
{
|
|
comment: "no config anywhere, but cloud-init not disabled",
|
|
},
|
|
{
|
|
grade: "dangerous",
|
|
comment: "grade dangerous, no config anywhere, but cloud-init not disabled",
|
|
},
|
|
{
|
|
comment: "no gadget config but MAAS allowed",
|
|
seedCfgs: defaultMAASCfgs,
|
|
resultCfgCopied: defaultMAASHappyInstalled,
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "grade signed NoCloud not allowed",
|
|
seedCfgs: []string{
|
|
`#cloud-config
|
|
datasource:
|
|
NoCloud:
|
|
something-no-cloudy: foo
|
|
`,
|
|
},
|
|
resultCfgCopied: []bool{false},
|
|
},
|
|
|
|
{
|
|
comment: "MAAS in seed and gadget explicit gets installed",
|
|
seedCfgs: defaultMAASCfgs,
|
|
resultCfgCopied: defaultMAASHappyInstalled,
|
|
gadgetCfg: explicitSupportedDatasource,
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "MAAS in seed and gadget implicit gets installed",
|
|
seedCfgs: defaultMAASCfgs,
|
|
resultCfgCopied: defaultMAASHappyInstalled,
|
|
gadgetCfg: implicitMentionSupportedDatasource,
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "MAAS in seed not installed due to implicit unsupported datasource in gadget",
|
|
seedCfgs: defaultMAASCfgs,
|
|
resultCfgCopied: defaultMAASNoneInstalled,
|
|
gadgetCfg: implictMentionNonSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "MAAS in seed not installed due to gadget disallowing any datasource",
|
|
seedCfgs: defaultMAASCfgs,
|
|
resultCfgCopied: defaultMAASNoneInstalled,
|
|
gadgetCfg: explicitNoDatasourceAllowed,
|
|
},
|
|
{
|
|
comment: "MAAS in seed not installed due to gadget explicitly allowing other datasource",
|
|
seedCfgs: defaultMAASCfgs,
|
|
resultCfgCopied: defaultMAASNoneInstalled,
|
|
gadgetCfg: explicitNonSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "MAAS in seed not installed due to seed config disallowing itself with gadget",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg3, // generic config that's filtered out
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
explicitNoDatasourceAllowed, // extra that disables all datasources
|
|
},
|
|
resultCfgCopied: []bool{false, false, false, false, false, false},
|
|
gadgetCfg: explicitNonSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "MAAS in seed not installed due to seed config disallowing itself without gadget",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg3, // generic config that's filtered out
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
explicitNoDatasourceAllowed, // extra that disables all datasources
|
|
},
|
|
resultCfgCopied: []bool{false, false, false, false, false, false},
|
|
},
|
|
{
|
|
comment: "implictly mentioned datasource in seed not installed because it's unsupported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
resultCfgCopied: []bool{
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false, // the implicit GCE one
|
|
},
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "implictly mentioned datasource in seed not installed because it's unsupported + gadget with explicit supported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
resultCfgCopied: []bool{
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false, // the implicit GCE one
|
|
},
|
|
gadgetCfg: explicitSupportedDatasource,
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "implicit mentioned datasource in seed not installed because it's unsupported + gadget with explicit supported and non-supported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
|
|
resultCfgCopied: []bool{
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
false, // the implicit GCE one
|
|
},
|
|
gadgetCfg: explicitSupportedAndNonSupportedDatasources,
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "implicit mentioned datasource and supported datasource in seed all not installed because no supported datasource in intersection with gadget with implicit non-supported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
|
|
resultCfgCopied: []bool{
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false, // the implicit GCE one
|
|
},
|
|
gadgetCfg: implictMentionNonSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "implicit mentioned datasource and supported datasource in seed all not installed because no supported datasource in intersection with gadget with explicit non-supported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg2, // MAAS specific config - this sets the datasource_list
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
|
|
resultCfgCopied: []bool{
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
false, // the implicit GCE one
|
|
},
|
|
gadgetCfg: explicitNonSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "implicit mentioned datasource and supported datasource in seed all not installed because no supported datasource in intersection with gadget with implicit non-supported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
|
|
resultCfgCopied: []bool{
|
|
false,
|
|
false,
|
|
false, // the implicit GCE one
|
|
false,
|
|
},
|
|
gadgetCfg: implictMentionNonSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "implicit mentioned datasource in seed not installed and supported datasource in seed installed due to intersection with gadget with implicit supported",
|
|
seedCfgs: []string{
|
|
maasCfg1, // MAAS specific config
|
|
maasCfg4, // generic config that passes filtering
|
|
maasCfg5, // MAAS specific config
|
|
implictMentionNonSupportedDatasource, // extra that implicitly mentions GCE
|
|
},
|
|
resultCfgCopied: []bool{
|
|
true,
|
|
true,
|
|
true,
|
|
false, // the implicit GCE one
|
|
},
|
|
gadgetCfg: explicitSupportedAndNonSupportedDatasources,
|
|
expDsRestrictFileContents: restrictDatasourceMAASFile,
|
|
},
|
|
{
|
|
comment: "entirely filtered out seed config not installed",
|
|
seedCfgs: []string{maasCfg3},
|
|
resultCfgCopied: []bool{false},
|
|
},
|
|
{
|
|
comment: "entirely filtered out seed config not installed + gadget with explicit supported datasource",
|
|
seedCfgs: []string{maasCfg3},
|
|
resultCfgCopied: []bool{false},
|
|
gadgetCfg: explicitSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "implicitly mentioned supported datasource in gadget + explicit no datasource in seed",
|
|
seedCfgs: []string{
|
|
explicitNoDatasourceAllowed,
|
|
},
|
|
resultCfgCopied: []bool{false},
|
|
gadgetCfg: implicitMentionSupportedDatasource,
|
|
},
|
|
{
|
|
comment: "MAAS and GCE in seed gets installed in dangerous",
|
|
grade: "dangerous",
|
|
seedCfgs: []string{
|
|
maasCfg1,
|
|
maasCfg2,
|
|
maasCfg3,
|
|
maasCfg4,
|
|
maasCfg5,
|
|
implictMentionNonSupportedDatasource,
|
|
},
|
|
resultCfgCopied: []bool{true, true, true, true, true, true},
|
|
},
|
|
{
|
|
comment: "MAAS and GCE in seed gets installed in dangerous with gadget MAAS",
|
|
grade: "dangerous",
|
|
seedCfgs: []string{
|
|
maasCfg1,
|
|
maasCfg2,
|
|
maasCfg3,
|
|
maasCfg4,
|
|
maasCfg5,
|
|
implictMentionNonSupportedDatasource,
|
|
},
|
|
gadgetCfg: implicitMentionSupportedDatasource,
|
|
resultCfgCopied: []bool{true, true, true, true, true, true},
|
|
},
|
|
}
|
|
|
|
for _, t := range tt {
|
|
comment := Commentf(t.comment)
|
|
var seedSrcNames []string
|
|
var cloudCfgSrcDir string
|
|
c.Assert(t.seedCfgs, HasLen, len(t.resultCfgCopied), comment)
|
|
if len(t.seedCfgs) != 0 {
|
|
cloudCfgSrcDir, seedSrcNames = s.makeCloudCfgSrcDirFiles(c, t.seedCfgs...)
|
|
}
|
|
|
|
var gadgetDir string
|
|
if t.gadgetCfg != "" {
|
|
gadgetDir = s.makeGadgetCloudConfFile(c, t.gadgetCfg)
|
|
}
|
|
|
|
if t.grade == "" {
|
|
t.grade = "signed"
|
|
}
|
|
err := sysconfig.ConfigureTargetSystem(fake20Model(t.grade), &sysconfig.Options{
|
|
AllowCloudInit: true,
|
|
TargetRootDir: boot.InstallHostWritableDir,
|
|
CloudInitSrcDir: cloudCfgSrcDir,
|
|
GadgetDir: gadgetDir,
|
|
})
|
|
c.Assert(err, IsNil, comment)
|
|
|
|
ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
|
|
for i, name := range seedSrcNames {
|
|
if t.resultCfgCopied[i] {
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "90_"+name), testutil.FilePresent, comment)
|
|
} else {
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "90_"+name), testutil.FileAbsent, comment)
|
|
}
|
|
}
|
|
|
|
if t.gadgetCfg != "" {
|
|
ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileEquals, t.gadgetCfg)
|
|
|
|
}
|
|
|
|
// check the restrict file we should have installed in some cases too
|
|
restrictFile := filepath.Join(ubuntuDataCloudCfg, "99_snapd_datasource.cfg")
|
|
if t.expDsRestrictFileContents != "" {
|
|
c.Check(restrictFile, testutil.FileEquals, t.expDsRestrictFileContents, comment)
|
|
} else {
|
|
c.Check(restrictFile, testutil.FileAbsent, comment)
|
|
}
|
|
|
|
// make sure the disabled file is absent
|
|
ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
|
|
c.Check(ubuntuDataCloudDisabled, testutil.FileAbsent)
|
|
|
|
// need to clear this dir each time as it doesn't change for each
|
|
// iteration
|
|
c.Assert(os.RemoveAll(boot.InstallHostWritableDir), IsNil)
|
|
}
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestInstallModeCloudInitDisallowedGradeSecuredDoesDisable(c *C) {
|
|
err := sysconfig.ConfigureTargetSystem(fake20Model("secured"), &sysconfig.Options{
|
|
AllowCloudInit: false,
|
|
TargetRootDir: boot.InstallHostWritableDir,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
|
|
c.Check(ubuntuDataCloudDisabled, testutil.FilePresent)
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestInstallModeCloudInitAllowedGradeSecuredIgnoresSrcButDoesNotDisable(c *C) {
|
|
cloudCfgSrcDir, _ := s.makeCloudCfgSrcDirFiles(c)
|
|
|
|
err := sysconfig.ConfigureTargetSystem(fake20Model("secured"), &sysconfig.Options{
|
|
AllowCloudInit: true,
|
|
CloudInitSrcDir: cloudCfgSrcDir,
|
|
TargetRootDir: boot.InstallHostWritableDir,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
// the disable file is not present
|
|
ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")
|
|
c.Check(ubuntuDataCloudDisabled, testutil.FileAbsent)
|
|
|
|
// but we did not copy the config files from ubuntu-seed, even though they
|
|
// are there and cloud-init is not disabled
|
|
ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileAbsent)
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileAbsent)
|
|
}
|
|
|
|
// this test is the same as the logic from install mode devicestate, where we
|
|
// want to install cloud-init configuration not onto the running, ephemeral
|
|
// writable, but rather the host writable partition that will be used upon
|
|
// reboot into run mode
|
|
func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunMode(c *C) {
|
|
cloudCfgSrcDir, cfgFileNames := s.makeCloudCfgSrcDirFiles(c, "#cloud-config foo", "#cloud-config bar")
|
|
|
|
err := sysconfig.ConfigureTargetSystem(fake20Model("dangerous"), &sysconfig.Options{
|
|
AllowCloudInit: true,
|
|
CloudInitSrcDir: cloudCfgSrcDir,
|
|
TargetRootDir: boot.InstallHostWritableDir,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
// and did copy the cloud-init files
|
|
ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/")
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "90_"+cfgFileNames[0]), testutil.FileEquals, "#cloud-config foo")
|
|
c.Check(filepath.Join(ubuntuDataCloudCfg, "90_"+cfgFileNames[1]), testutil.FileEquals, "#cloud-config bar")
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestCloudInitStatusUnhappy(c *C) {
|
|
cmd := testutil.MockCommand(c, "cloud-init", `
|
|
echo cloud-init borken
|
|
exit 1
|
|
`)
|
|
|
|
status, err := sysconfig.CloudInitStatus()
|
|
c.Assert(err, ErrorMatches, "cloud-init borken")
|
|
c.Assert(status, Equals, sysconfig.CloudInitErrored)
|
|
c.Assert(cmd.Calls(), DeepEquals, [][]string{
|
|
{"cloud-init", "status"},
|
|
})
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestCloudInitStatus(c *C) {
|
|
tt := []struct {
|
|
comment string
|
|
cloudInitOutput string
|
|
exitCode int
|
|
exp sysconfig.CloudInitState
|
|
restrictedFile bool
|
|
disabledFile bool
|
|
expError string
|
|
}{
|
|
{
|
|
comment: "done",
|
|
cloudInitOutput: "status: done",
|
|
exp: sysconfig.CloudInitDone,
|
|
},
|
|
{
|
|
comment: "running",
|
|
cloudInitOutput: "status: running",
|
|
exp: sysconfig.CloudInitEnabled,
|
|
},
|
|
{
|
|
comment: "not run",
|
|
cloudInitOutput: "status: not run",
|
|
exp: sysconfig.CloudInitEnabled,
|
|
},
|
|
{
|
|
comment: "new unrecognized state",
|
|
cloudInitOutput: "status: newfangledstatus",
|
|
exp: sysconfig.CloudInitEnabled,
|
|
},
|
|
{
|
|
comment: "restricted by snapd",
|
|
restrictedFile: true,
|
|
exp: sysconfig.CloudInitRestrictedBySnapd,
|
|
},
|
|
{
|
|
comment: "disabled temporarily",
|
|
cloudInitOutput: "status: disabled",
|
|
exp: sysconfig.CloudInitUntriggered,
|
|
},
|
|
{
|
|
comment: "disabled permanently via file",
|
|
disabledFile: true,
|
|
exp: sysconfig.CloudInitDisabledPermanently,
|
|
},
|
|
{
|
|
comment: "errored w/ exit code 0",
|
|
cloudInitOutput: "status: error",
|
|
exp: sysconfig.CloudInitErrored,
|
|
exitCode: 0,
|
|
},
|
|
{
|
|
comment: "errored w/ exit code 1",
|
|
cloudInitOutput: "status: error",
|
|
exp: sysconfig.CloudInitErrored,
|
|
exitCode: 1,
|
|
},
|
|
{
|
|
comment: "broken cloud-init output w/ exit code 0",
|
|
cloudInitOutput: "broken cloud-init output",
|
|
expError: "invalid cloud-init output: broken cloud-init output",
|
|
},
|
|
{
|
|
comment: "broken cloud-init output w/ exit code 1",
|
|
cloudInitOutput: "broken cloud-init output",
|
|
exitCode: 1,
|
|
expError: "broken cloud-init output",
|
|
},
|
|
{
|
|
comment: "normal cloud-init output w/ exit code 1",
|
|
cloudInitOutput: "status: foobar",
|
|
exitCode: 1,
|
|
expError: "cloud-init errored: status: foobar",
|
|
},
|
|
}
|
|
|
|
for _, t := range tt {
|
|
old := dirs.GlobalRootDir
|
|
dirs.SetRootDir(c.MkDir())
|
|
defer func() { dirs.SetRootDir(old) }()
|
|
cmd := testutil.MockCommand(c, "cloud-init", fmt.Sprintf(`
|
|
if [ "$1" = "status" ]; then
|
|
echo '%s'
|
|
exit %d
|
|
else
|
|
echo "unexpected args, $"
|
|
exit 1
|
|
fi
|
|
`, t.cloudInitOutput, t.exitCode))
|
|
|
|
if t.disabledFile {
|
|
cloudDir := filepath.Join(dirs.GlobalRootDir, "etc/cloud")
|
|
err := os.MkdirAll(cloudDir, 0755)
|
|
c.Assert(err, IsNil)
|
|
err = ioutil.WriteFile(filepath.Join(cloudDir, "cloud-init.disabled"), nil, 0644)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
if t.restrictedFile {
|
|
cloudDir := filepath.Join(dirs.GlobalRootDir, "etc/cloud/cloud.cfg.d")
|
|
err := os.MkdirAll(cloudDir, 0755)
|
|
c.Assert(err, IsNil)
|
|
err = ioutil.WriteFile(filepath.Join(cloudDir, "zzzz_snapd.cfg"), nil, 0644)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
status, err := sysconfig.CloudInitStatus()
|
|
if t.expError != "" {
|
|
c.Assert(err, ErrorMatches, t.expError, Commentf(t.comment))
|
|
} else {
|
|
c.Assert(err, IsNil)
|
|
c.Assert(status, Equals, t.exp, Commentf(t.comment))
|
|
}
|
|
|
|
// if the restricted file was there we don't call cloud-init status
|
|
var expCalls [][]string
|
|
if !t.restrictedFile && !t.disabledFile {
|
|
expCalls = [][]string{
|
|
{"cloud-init", "status"},
|
|
}
|
|
}
|
|
|
|
c.Assert(cmd.Calls(), DeepEquals, expCalls, Commentf(t.comment))
|
|
cmd.Restore()
|
|
}
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestCloudInitNotFoundStatus(c *C) {
|
|
emptyDir := c.MkDir()
|
|
oldPath := os.Getenv("PATH")
|
|
defer func() {
|
|
c.Assert(os.Setenv("PATH", oldPath), IsNil)
|
|
}()
|
|
os.Setenv("PATH", emptyDir)
|
|
|
|
status, err := sysconfig.CloudInitStatus()
|
|
c.Assert(err, IsNil)
|
|
c.Check(status, Equals, sysconfig.CloudInitNotFound)
|
|
}
|
|
|
|
var gceCloudInitStatusJSON = `{
|
|
"v1": {
|
|
"datasource": "DataSourceGCE",
|
|
"init": {
|
|
"errors": [],
|
|
"finished": 1591751113.4536479,
|
|
"start": 1591751112.130069
|
|
},
|
|
"stage": null
|
|
}
|
|
}
|
|
`
|
|
|
|
var multipassNoCloudCloudInitStatusJSON = `{
|
|
"v1": {
|
|
"datasource": "DataSourceNoCloud [seed=/dev/sr0][dsmode=net]",
|
|
"init": {
|
|
"errors": [],
|
|
"finished": 1591788514.4656117,
|
|
"start": 1591788514.2607572
|
|
},
|
|
"stage": null
|
|
}
|
|
}`
|
|
|
|
var localNoneCloudInitStatusJSON = `{
|
|
"v1": {
|
|
"datasource": "DataSourceNone",
|
|
"init": {
|
|
"errors": [],
|
|
"finished": 1591788514.4656117,
|
|
"start": 1591788514.2607572
|
|
},
|
|
"stage": null
|
|
}
|
|
}`
|
|
|
|
var lxdNoCloudCloudInitStatusJSON = `{
|
|
"v1": {
|
|
"datasource": "DataSourceNoCloud [seed=/var/lib/cloud/seed/nocloud-net][dsmode=net]",
|
|
"init": {
|
|
"errors": [],
|
|
"finished": 1591788737.3982718,
|
|
"start": 1591788736.9015596
|
|
},
|
|
"stage": null
|
|
}
|
|
}`
|
|
|
|
var restrictNoCloudYaml = `datasource_list: [NoCloud]
|
|
datasource:
|
|
NoCloud:
|
|
fs_label: null
|
|
manual_cache_clean: true
|
|
`
|
|
|
|
func (s *sysconfigSuite) TestRestrictCloudInit(c *C) {
|
|
tt := []struct {
|
|
comment string
|
|
state sysconfig.CloudInitState
|
|
sysconfOpts *sysconfig.CloudInitRestrictOptions
|
|
cloudInitStatusJSON string
|
|
expError string
|
|
expRestrictYamlWritten string
|
|
expDatasource string
|
|
expAction string
|
|
expDisableFile bool
|
|
}{
|
|
{
|
|
comment: "already disabled",
|
|
state: sysconfig.CloudInitDisabledPermanently,
|
|
expError: "cannot restrict cloud-init: already disabled",
|
|
},
|
|
{
|
|
comment: "already restricted",
|
|
state: sysconfig.CloudInitRestrictedBySnapd,
|
|
expError: "cannot restrict cloud-init: already restricted",
|
|
},
|
|
{
|
|
comment: "errored",
|
|
state: sysconfig.CloudInitErrored,
|
|
expError: "cannot restrict cloud-init in error or enabled state",
|
|
},
|
|
{
|
|
comment: "enable (not running)",
|
|
state: sysconfig.CloudInitEnabled,
|
|
expError: "cannot restrict cloud-init in error or enabled state",
|
|
},
|
|
{
|
|
comment: "errored w/ force disable",
|
|
state: sysconfig.CloudInitErrored,
|
|
sysconfOpts: &sysconfig.CloudInitRestrictOptions{
|
|
ForceDisable: true,
|
|
},
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "enable (not running) w/ force disable",
|
|
state: sysconfig.CloudInitEnabled,
|
|
sysconfOpts: &sysconfig.CloudInitRestrictOptions{
|
|
ForceDisable: true,
|
|
},
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "untriggered",
|
|
state: sysconfig.CloudInitUntriggered,
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "unknown status",
|
|
state: -1,
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "gce done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: gceCloudInitStatusJSON,
|
|
expDatasource: "GCE",
|
|
expAction: "restrict",
|
|
expRestrictYamlWritten: `datasource_list: [GCE]
|
|
`,
|
|
},
|
|
{
|
|
comment: "nocloud done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON,
|
|
expDatasource: "NoCloud",
|
|
expAction: "restrict",
|
|
expRestrictYamlWritten: restrictNoCloudYaml,
|
|
},
|
|
{
|
|
comment: "nocloud uc20 done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON,
|
|
sysconfOpts: &sysconfig.CloudInitRestrictOptions{
|
|
DisableAfterLocalDatasourcesRun: true,
|
|
},
|
|
expDatasource: "NoCloud",
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "none uc20 done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: localNoneCloudInitStatusJSON,
|
|
sysconfOpts: &sysconfig.CloudInitRestrictOptions{
|
|
DisableAfterLocalDatasourcesRun: true,
|
|
},
|
|
expDatasource: "None",
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
|
|
// the two cases for lxd and multipass are effectively the same, but as
|
|
// the largest known users of cloud-init w/ UC, we leave them as
|
|
// separate test cases for their different cloud-init status.json
|
|
// content
|
|
{
|
|
comment: "nocloud multipass done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON,
|
|
expDatasource: "NoCloud",
|
|
expAction: "restrict",
|
|
expRestrictYamlWritten: restrictNoCloudYaml,
|
|
},
|
|
{
|
|
comment: "nocloud seed lxd done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: lxdNoCloudCloudInitStatusJSON,
|
|
expDatasource: "NoCloud",
|
|
expAction: "restrict",
|
|
expRestrictYamlWritten: restrictNoCloudYaml,
|
|
},
|
|
{
|
|
comment: "nocloud uc20 multipass done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON,
|
|
sysconfOpts: &sysconfig.CloudInitRestrictOptions{
|
|
DisableAfterLocalDatasourcesRun: true,
|
|
},
|
|
expDatasource: "NoCloud",
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "nocloud uc20 seed lxd done",
|
|
state: sysconfig.CloudInitDone,
|
|
cloudInitStatusJSON: lxdNoCloudCloudInitStatusJSON,
|
|
sysconfOpts: &sysconfig.CloudInitRestrictOptions{
|
|
DisableAfterLocalDatasourcesRun: true,
|
|
},
|
|
expDatasource: "NoCloud",
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
{
|
|
comment: "no cloud-init in $PATH",
|
|
state: sysconfig.CloudInitNotFound,
|
|
expAction: "disable",
|
|
expDisableFile: true,
|
|
},
|
|
}
|
|
|
|
for _, t := range tt {
|
|
comment := Commentf("%s", t.comment)
|
|
// setup status.json
|
|
old := dirs.GlobalRootDir
|
|
dirs.SetRootDir(c.MkDir())
|
|
defer func() { dirs.SetRootDir(old) }()
|
|
statusJSONFile := filepath.Join(dirs.GlobalRootDir, "/run/cloud-init/status.json")
|
|
if t.cloudInitStatusJSON != "" {
|
|
err := os.MkdirAll(filepath.Dir(statusJSONFile), 0755)
|
|
c.Assert(err, IsNil, comment)
|
|
err = ioutil.WriteFile(statusJSONFile, []byte(t.cloudInitStatusJSON), 0644)
|
|
c.Assert(err, IsNil, comment)
|
|
}
|
|
|
|
// if we expect snapd to write a yaml config file for cloud-init, ensure
|
|
// the dir exists before hand
|
|
if t.expRestrictYamlWritten != "" {
|
|
err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud.cfg.d"), 0755)
|
|
c.Assert(err, IsNil, comment)
|
|
}
|
|
|
|
res, err := sysconfig.RestrictCloudInit(t.state, t.sysconfOpts)
|
|
if t.expError == "" {
|
|
c.Assert(err, IsNil, comment)
|
|
c.Assert(res.DataSource, Equals, t.expDatasource, comment)
|
|
c.Assert(res.Action, Equals, t.expAction, comment)
|
|
if t.expRestrictYamlWritten != "" {
|
|
// check the snapd restrict yaml file that should have been written
|
|
c.Assert(
|
|
filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud.cfg.d/zzzz_snapd.cfg"),
|
|
testutil.FileEquals,
|
|
t.expRestrictYamlWritten,
|
|
comment,
|
|
)
|
|
}
|
|
|
|
// if we expect the disable file to be written then check for it
|
|
// otherwise ensure it was not written accidentally
|
|
var fileCheck Checker
|
|
if t.expDisableFile {
|
|
fileCheck = testutil.FilePresent
|
|
} else {
|
|
fileCheck = testutil.FileAbsent
|
|
}
|
|
|
|
c.Assert(
|
|
filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud-init.disabled"),
|
|
fileCheck,
|
|
comment,
|
|
)
|
|
|
|
} else {
|
|
c.Assert(err, ErrorMatches, t.expError, comment)
|
|
}
|
|
}
|
|
}
|
|
|
|
const maasCloudInitImplicitYAML = `
|
|
datasource:
|
|
MAAS:
|
|
foo: bar
|
|
`
|
|
|
|
const gceCloudInitImplicitYAML = `
|
|
datasource:
|
|
GCE:
|
|
foo: bar
|
|
`
|
|
|
|
const maasGadgetCloudInitImplicitLowerCaseYAML = `
|
|
datasource:
|
|
maas:
|
|
foo: bar
|
|
`
|
|
|
|
const explicitlyNoDatasourceYAML = `datasource_list: []`
|
|
|
|
const explicitlyNoDatasourceButAlsoImplicitlyAnotherYAML = `
|
|
datasource_list: []
|
|
reporting:
|
|
NoCloud:
|
|
foo: bar
|
|
`
|
|
|
|
const explicitlyMultipleMixedCaseMentioned = `
|
|
reporting:
|
|
NoCloud:
|
|
foo: bar
|
|
maas:
|
|
foo: bar
|
|
datasource:
|
|
MAAS:
|
|
foo: bar
|
|
NOCLOUD:
|
|
foo: bar
|
|
`
|
|
|
|
func (s *sysconfigSuite) TestCloudDatasourcesInUse(c *C) {
|
|
tt := []struct {
|
|
configFileContent string
|
|
expError string
|
|
expRes *sysconfig.CloudDatasourcesInUseResult
|
|
comment string
|
|
}{
|
|
{
|
|
configFileContent: `datasource_list: [MAAS]`,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "explicitly allowed via datasource_list in upper case",
|
|
},
|
|
{
|
|
configFileContent: `datasource_list: [maas]`,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "explicitly allowed via datasource_list in lower case",
|
|
},
|
|
{
|
|
configFileContent: `datasource_list: [mAaS]`,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "explicitly allowed via datasource_list in random case",
|
|
},
|
|
{
|
|
configFileContent: `datasource_list: [maas, maas]`,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "duplicated datasource in datasource_list",
|
|
},
|
|
{
|
|
configFileContent: `datasource_list: [maas, MAAS]`,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "duplicated datasource in datasource_list with different cases",
|
|
},
|
|
{
|
|
configFileContent: `datasource_list: [maas, GCE]`,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"GCE", "MAAS"},
|
|
Mentioned: []string{"GCE", "MAAS"},
|
|
},
|
|
comment: "multiple datasources in datasource list",
|
|
},
|
|
{
|
|
configFileContent: maasCloudInitImplicitYAML,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "implicitly mentioned datasource",
|
|
},
|
|
{
|
|
configFileContent: maasGadgetCloudInitImplicitLowerCaseYAML,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "implicitly mentioned datasource in lower case",
|
|
},
|
|
{
|
|
configFileContent: explicitlyNoDatasourceYAML,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyNoneAllowed: true,
|
|
},
|
|
comment: "no datasources allowed at all",
|
|
},
|
|
{
|
|
configFileContent: explicitlyNoDatasourceButAlsoImplicitlyAnotherYAML,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyNoneAllowed: true,
|
|
Mentioned: []string{"NOCLOUD"},
|
|
},
|
|
comment: "explicitly no datasources allowed, but still some mentioned",
|
|
},
|
|
{
|
|
configFileContent: explicitlyMultipleMixedCaseMentioned,
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"MAAS", "NOCLOUD"},
|
|
},
|
|
comment: "multiple of same datasources mentioned in different cases",
|
|
},
|
|
{
|
|
configFileContent: "i'm not yaml",
|
|
expError: "yaml: unmarshal errors.*\n.*cannot unmarshal.*",
|
|
comment: "invalid yaml",
|
|
},
|
|
}
|
|
|
|
for _, t := range tt {
|
|
comment := Commentf(t.comment)
|
|
configFile := filepath.Join(c.MkDir(), "cloud.conf")
|
|
err := ioutil.WriteFile(configFile, []byte(t.configFileContent), 0644)
|
|
c.Assert(err, IsNil, comment)
|
|
res, err := sysconfig.CloudDatasourcesInUse(configFile)
|
|
if t.expError != "" {
|
|
c.Assert(err, ErrorMatches, t.expError, comment)
|
|
continue
|
|
}
|
|
|
|
c.Assert(res, DeepEquals, t.expRes, comment)
|
|
}
|
|
}
|
|
|
|
func (s *sysconfigSuite) TestCloudDatasourcesInUseForDirInUse(c *C) {
|
|
tt := []struct {
|
|
configFilesContents map[string]string
|
|
expError string
|
|
expRes *sysconfig.CloudDatasourcesInUseResult
|
|
comment string
|
|
}{
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"maas.cfg": `datasource_list: [MAAS]`,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "explicitly allowed via datasource_list",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"1_maas.cfg": `datasource_list: [MAAS]`,
|
|
"2_none.cfg": `datasource_list: []`,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyNoneAllowed: true,
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "explicit none overwriting explicit allowing",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"1_none.cfg": `datasource_list: []`,
|
|
"2_maas.cfg": `datasource_list: [MAAS]`,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyNoneAllowed: false,
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "explicit allowing overwriting explicit none",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"maas.cfg": maasCloudInitImplicitYAML,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"MAAS"},
|
|
},
|
|
comment: "implicit datasource",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"1_gce.cfg": gceCloudInitImplicitYAML,
|
|
"2_maas.cfg": maasCloudInitImplicitYAML,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"GCE", "MAAS"},
|
|
},
|
|
comment: "multiple implicit datasources",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"1_maas.cfg": maasCloudInitImplicitYAML,
|
|
"2_gce.cfg": gceCloudInitImplicitYAML,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"GCE", "MAAS"},
|
|
},
|
|
comment: "multiple implicit datasources in different lexical order",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"maas.cfg": `datasource_list: [MAAS]`,
|
|
"gce.cfg": gceCloudInitImplicitYAML,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: []string{"MAAS"},
|
|
Mentioned: []string{"GCE", "MAAS"},
|
|
},
|
|
comment: "implicit datasources and explicit datasource",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
ExplicitlyAllowed: nil,
|
|
ExplicitlyNoneAllowed: false,
|
|
Mentioned: nil,
|
|
},
|
|
comment: "no files means empty result",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"maas.conf": `datasource_list: [MAAS]`,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{},
|
|
comment: "only .cfg files are allowed",
|
|
},
|
|
{
|
|
configFilesContents: map[string]string{
|
|
"maas": `datasource_list: [MAAS]`,
|
|
"gce.cfg": gceCloudInitImplicitYAML,
|
|
},
|
|
expRes: &sysconfig.CloudDatasourcesInUseResult{
|
|
Mentioned: []string{"GCE"},
|
|
},
|
|
comment: "with .cfg and non-.cfg files, only .cfg files are allowed",
|
|
},
|
|
}
|
|
|
|
for _, t := range tt {
|
|
comment := Commentf(t.comment)
|
|
|
|
dir := c.MkDir()
|
|
for basename, content := range t.configFilesContents {
|
|
configFile := filepath.Join(dir, basename)
|
|
err := ioutil.WriteFile(configFile, []byte(content), 0644)
|
|
c.Assert(err, IsNil, comment)
|
|
}
|
|
|
|
res, err := sysconfig.CloudDatasourcesInUseForDir(dir)
|
|
if t.expError != "" {
|
|
c.Assert(err, ErrorMatches, t.expError, comment)
|
|
continue
|
|
}
|
|
|
|
c.Assert(res, DeepEquals, t.expRes, comment)
|
|
}
|
|
}
|
|
|
|
const maasCfg1 = `#cloud-config
|
|
reporting:
|
|
maas:
|
|
type: webhook
|
|
endpoint: http://172-16-99-0--24.maas-internal:5248/MAAS/metadata/status/foo
|
|
consumer_key: foothefoo
|
|
token_key: foothefoothesecond
|
|
token_secret: foothesecretfoo
|
|
`
|
|
|
|
const maasCfg2 = `datasource_list: [ MAAS ]
|
|
`
|
|
|
|
const maasCfg3 = `#cloud-config
|
|
snappy:
|
|
email: foo@foothewebsite.com
|
|
`
|
|
|
|
const maasCfg4 = `#cloud-config
|
|
network:
|
|
config:
|
|
- id: enp3s0
|
|
mac_address: 52:54:00:b4:9e:25
|
|
mtu: 1500
|
|
name: enp3s0
|
|
subnets:
|
|
- address: 172.16.99.7/24
|
|
dns_nameservers:
|
|
- 172.16.99.1
|
|
dns_search:
|
|
- maas
|
|
type: static
|
|
type: physical
|
|
- address: 172.16.99.1
|
|
search:
|
|
- maas
|
|
type: nameserver
|
|
version: 1
|
|
`
|
|
|
|
const maasCfg5 = `#cloud-config
|
|
datasource:
|
|
MAAS:
|
|
consumer_key: foothefoo
|
|
metadata_url: http://172-16-99-0--24.maas-internal:5248/MAAS/metadata/
|
|
token_key: foothefoothesecond
|
|
token_secret: foothesecretfoo
|
|
`
|
|
|
|
func (s *sysconfigSuite) TestFilterCloudCfgFile(c *C) {
|
|
tt := []struct {
|
|
comment string
|
|
inStr string
|
|
outStr string
|
|
err string
|
|
}{
|
|
{
|
|
comment: "maas reporting cloud-init config",
|
|
inStr: maasCfg1,
|
|
outStr: maasCfg1,
|
|
},
|
|
{
|
|
comment: "maas datasource list cloud-init config",
|
|
inStr: maasCfg2,
|
|
outStr: `#cloud-config
|
|
datasource_list:
|
|
- MAAS
|
|
`,
|
|
},
|
|
{
|
|
comment: "maas snappy user cloud-init config",
|
|
inStr: maasCfg3,
|
|
// we don't support using the snappy key
|
|
outStr: "",
|
|
},
|
|
{
|
|
comment: "maas networking cloud-init config",
|
|
inStr: maasCfg4,
|
|
outStr: maasCfg4,
|
|
},
|
|
{
|
|
comment: "maas datasource cloud-init config",
|
|
inStr: maasCfg5,
|
|
outStr: maasCfg5,
|
|
},
|
|
{
|
|
comment: "unsupported datasource in datasource section cloud-init config",
|
|
inStr: `#cloud-config
|
|
datasource:
|
|
NoCloud:
|
|
consumer_key: fooooooo
|
|
`,
|
|
outStr: "",
|
|
},
|
|
{
|
|
comment: "unsupported datasource in reporting section cloud-init config",
|
|
inStr: `#cloud-config
|
|
reporting:
|
|
NoCloud:
|
|
consumer_key: fooooooo
|
|
`,
|
|
outStr: "",
|
|
},
|
|
{
|
|
comment: "unsupported datasource in datasource_list with supported one",
|
|
inStr: `#cloud-config
|
|
datasource_list: [MAAS, NoCloud]
|
|
`,
|
|
outStr: `#cloud-config
|
|
datasource_list:
|
|
- MAAS
|
|
`,
|
|
},
|
|
{
|
|
comment: "unsupported datasources in multiple keys with supported ones",
|
|
inStr: `#cloud-config
|
|
datasource:
|
|
MAAS:
|
|
consumer_key: fooooooo
|
|
NoCloud:
|
|
consumer_key: fooooooo
|
|
|
|
reporting:
|
|
MAAS:
|
|
type: webhook
|
|
NoCloud:
|
|
type: webhook
|
|
|
|
datasource_list: [MAAS, NoCloud]
|
|
`,
|
|
outStr: `#cloud-config
|
|
datasource:
|
|
MAAS:
|
|
consumer_key: fooooooo
|
|
datasource_list:
|
|
- MAAS
|
|
reporting:
|
|
MAAS:
|
|
type: webhook
|
|
`,
|
|
},
|
|
{
|
|
comment: "unrelated keys",
|
|
inStr: `#cloud-config
|
|
datasource:
|
|
MAAS:
|
|
consumer_key: fooooooo
|
|
foo: bar
|
|
|
|
reporting:
|
|
MAAS:
|
|
type: webhook
|
|
new_foo: new_bar
|
|
|
|
extra_foo: extra_bar
|
|
`,
|
|
outStr: `#cloud-config
|
|
datasource:
|
|
MAAS:
|
|
consumer_key: fooooooo
|
|
reporting:
|
|
MAAS:
|
|
type: webhook
|
|
`,
|
|
},
|
|
}
|
|
|
|
dir := c.MkDir()
|
|
for i, t := range tt {
|
|
comment := Commentf(t.comment)
|
|
inFile := filepath.Join(dir, fmt.Sprintf("%d.cfg", i))
|
|
err := ioutil.WriteFile(inFile, []byte(t.inStr), 0755)
|
|
c.Assert(err, IsNil, comment)
|
|
|
|
out, err := sysconfig.FilterCloudCfgFile(inFile, []string{"MAAS"})
|
|
if t.err != "" {
|
|
c.Assert(err, ErrorMatches, t.err, comment)
|
|
continue
|
|
}
|
|
c.Assert(err, IsNil, comment)
|
|
|
|
// no expected output means that everything was filtered out
|
|
if t.outStr == "" {
|
|
c.Assert(out, Equals, "", comment)
|
|
continue
|
|
}
|
|
|
|
// otherwise we have expected output in the file
|
|
b, err := ioutil.ReadFile(out)
|
|
c.Assert(err, IsNil, comment)
|
|
c.Assert(string(b), Equals, t.outStr, comment)
|
|
}
|
|
}
|