gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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("#cloud-config 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(fake20Model("signed"), &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(fake20Model("signed"), &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) TestInstallModeCloudInitAllowedGradeSignedDoesNotDisable(c *C) { 136 err := sysconfig.ConfigureTargetSystem(fake20Model("signed"), &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 func (s *sysconfigSuite) TestInstallModeCloudInitAllowedGradeSignedDoesNotInstallUbuntuSeedConfig(c *C) { 147 cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c) 148 149 err := sysconfig.ConfigureTargetSystem(fake20Model("signed"), &sysconfig.Options{ 150 AllowCloudInit: true, 151 TargetRootDir: boot.InstallHostWritableDir, 152 CloudInitSrcDir: cloudCfgSrcDir, 153 }) 154 c.Assert(err, IsNil) 155 156 ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/") 157 c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileAbsent) 158 c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileAbsent) 159 } 160 161 func (s *sysconfigSuite) TestInstallModeCloudInitAllowedGradeDangerousDoesNotDisable(c *C) { 162 err := sysconfig.ConfigureTargetSystem(fake20Model("dangerous"), &sysconfig.Options{ 163 AllowCloudInit: true, 164 TargetRootDir: boot.InstallHostWritableDir, 165 }) 166 c.Assert(err, IsNil) 167 168 ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 169 c.Check(ubuntuDataCloudDisabled, testutil.FileAbsent) 170 } 171 172 func (s *sysconfigSuite) TestInstallModeCloudInitDisallowedGradeSecuredDoesDisable(c *C) { 173 err := sysconfig.ConfigureTargetSystem(fake20Model("secured"), &sysconfig.Options{ 174 AllowCloudInit: false, 175 TargetRootDir: boot.InstallHostWritableDir, 176 }) 177 c.Assert(err, IsNil) 178 179 ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 180 c.Check(ubuntuDataCloudDisabled, testutil.FilePresent) 181 } 182 183 func (s *sysconfigSuite) TestInstallModeCloudInitAllowedGradeSecuredIgnoresSrcButDoesNotDisable(c *C) { 184 cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c) 185 186 err := sysconfig.ConfigureTargetSystem(fake20Model("secured"), &sysconfig.Options{ 187 AllowCloudInit: true, 188 CloudInitSrcDir: cloudCfgSrcDir, 189 TargetRootDir: boot.InstallHostWritableDir, 190 }) 191 c.Assert(err, IsNil) 192 193 // the disable file is not present 194 ubuntuDataCloudDisabled := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 195 c.Check(ubuntuDataCloudDisabled, testutil.FileAbsent) 196 197 // but we did not copy the config files from ubuntu-seed, even though they 198 // are there and cloud-init is not disabled 199 ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/") 200 c.Check(filepath.Join(ubuntuDataCloudCfg, "foo.cfg"), testutil.FileAbsent) 201 c.Check(filepath.Join(ubuntuDataCloudCfg, "bar.cfg"), testutil.FileAbsent) 202 } 203 204 // this test is the same as the logic from install mode devicestate, where we 205 // want to install cloud-init configuration not onto the running, ephemeral 206 // writable, but rather the host writable partition that will be used upon 207 // reboot into run mode 208 func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunMode(c *C) { 209 cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c) 210 211 err := sysconfig.ConfigureTargetSystem(fake20Model("dangerous"), &sysconfig.Options{ 212 AllowCloudInit: true, 213 CloudInitSrcDir: cloudCfgSrcDir, 214 TargetRootDir: boot.InstallHostWritableDir, 215 }) 216 c.Assert(err, IsNil) 217 218 // and did copy the cloud-init files 219 ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/") 220 c.Check(filepath.Join(ubuntuDataCloudCfg, "90_foo.cfg"), testutil.FileEquals, "foo.cfg config") 221 c.Check(filepath.Join(ubuntuDataCloudCfg, "90_bar.cfg"), testutil.FileEquals, "bar.cfg config") 222 } 223 224 func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunModeWithGadgetCloudConf(c *C) { 225 gadgetDir := s.makeGadgetCloudConfFile(c) 226 err := sysconfig.ConfigureTargetSystem(fake20Model("secured"), &sysconfig.Options{ 227 AllowCloudInit: true, 228 GadgetDir: gadgetDir, 229 TargetRootDir: boot.InstallHostWritableDir, 230 }) 231 c.Assert(err, IsNil) 232 233 // and did copy the gadget cloud-init file 234 ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/") 235 c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileEquals, "#cloud-config gadget cloud config") 236 } 237 238 func (s *sysconfigSuite) TestInstallModeCloudInitInstallsOntoHostRunModeWithGadgetCloudConfAlsoInstallsUbuntuSeedConfig(c *C) { 239 cloudCfgSrcDir := s.makeCloudCfgSrcDirFiles(c) 240 gadgetDir := s.makeGadgetCloudConfFile(c) 241 242 err := sysconfig.ConfigureTargetSystem(fake20Model("dangerous"), &sysconfig.Options{ 243 AllowCloudInit: true, 244 CloudInitSrcDir: cloudCfgSrcDir, 245 GadgetDir: gadgetDir, 246 TargetRootDir: boot.InstallHostWritableDir, 247 }) 248 c.Assert(err, IsNil) 249 250 // we did copy the gadget cloud-init file 251 ubuntuDataCloudCfg := filepath.Join(boot.InstallHostWritableDir, "_writable_defaults/etc/cloud/cloud.cfg.d/") 252 c.Check(filepath.Join(ubuntuDataCloudCfg, "80_device_gadget.cfg"), testutil.FileEquals, "#cloud-config gadget cloud config") 253 254 // and we also copied the ubuntu-seed files with a new prefix such that they 255 // take precedence over the gadget file by being ordered lexically after the 256 // gadget file 257 c.Check(filepath.Join(ubuntuDataCloudCfg, "90_foo.cfg"), testutil.FileEquals, "foo.cfg config") 258 c.Check(filepath.Join(ubuntuDataCloudCfg, "90_bar.cfg"), testutil.FileEquals, "bar.cfg config") 259 } 260 261 func (s *sysconfigSuite) TestCloudInitStatusUnhappy(c *C) { 262 cmd := testutil.MockCommand(c, "cloud-init", ` 263 echo cloud-init borken 264 exit 1 265 `) 266 267 status, err := sysconfig.CloudInitStatus() 268 c.Assert(err, ErrorMatches, "cloud-init borken") 269 c.Assert(status, Equals, sysconfig.CloudInitErrored) 270 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 271 {"cloud-init", "status"}, 272 }) 273 } 274 275 func (s *sysconfigSuite) TestCloudInitStatus(c *C) { 276 tt := []struct { 277 comment string 278 cloudInitOutput string 279 exitCode int 280 exp sysconfig.CloudInitState 281 restrictedFile bool 282 disabledFile bool 283 expError string 284 }{ 285 { 286 comment: "done", 287 cloudInitOutput: "status: done", 288 exp: sysconfig.CloudInitDone, 289 }, 290 { 291 comment: "running", 292 cloudInitOutput: "status: running", 293 exp: sysconfig.CloudInitEnabled, 294 }, 295 { 296 comment: "not run", 297 cloudInitOutput: "status: not run", 298 exp: sysconfig.CloudInitEnabled, 299 }, 300 { 301 comment: "new unrecognized state", 302 cloudInitOutput: "status: newfangledstatus", 303 exp: sysconfig.CloudInitEnabled, 304 }, 305 { 306 comment: "restricted by snapd", 307 restrictedFile: true, 308 exp: sysconfig.CloudInitRestrictedBySnapd, 309 }, 310 { 311 comment: "disabled temporarily", 312 cloudInitOutput: "status: disabled", 313 exp: sysconfig.CloudInitUntriggered, 314 }, 315 { 316 comment: "disabled permanently via file", 317 disabledFile: true, 318 exp: sysconfig.CloudInitDisabledPermanently, 319 }, 320 { 321 comment: "errored w/ exit code 0", 322 cloudInitOutput: "status: error", 323 exp: sysconfig.CloudInitErrored, 324 exitCode: 0, 325 }, 326 { 327 comment: "errored w/ exit code 1", 328 cloudInitOutput: "status: error", 329 exp: sysconfig.CloudInitErrored, 330 exitCode: 1, 331 }, 332 { 333 comment: "broken cloud-init output w/ exit code 0", 334 cloudInitOutput: "broken cloud-init output", 335 expError: "invalid cloud-init output: broken cloud-init output", 336 }, 337 { 338 comment: "broken cloud-init output w/ exit code 1", 339 cloudInitOutput: "broken cloud-init output", 340 exitCode: 1, 341 expError: "broken cloud-init output", 342 }, 343 { 344 comment: "normal cloud-init output w/ exit code 1", 345 cloudInitOutput: "status: foobar", 346 exitCode: 1, 347 expError: "cloud-init errored: status: foobar", 348 }, 349 } 350 351 for _, t := range tt { 352 old := dirs.GlobalRootDir 353 dirs.SetRootDir(c.MkDir()) 354 defer func() { dirs.SetRootDir(old) }() 355 cmd := testutil.MockCommand(c, "cloud-init", fmt.Sprintf(` 356 if [ "$1" = "status" ]; then 357 echo '%s' 358 exit %d 359 else 360 echo "unexpected args, $" 361 exit 1 362 fi 363 `, t.cloudInitOutput, t.exitCode)) 364 365 if t.disabledFile { 366 cloudDir := filepath.Join(dirs.GlobalRootDir, "etc/cloud") 367 err := os.MkdirAll(cloudDir, 0755) 368 c.Assert(err, IsNil) 369 err = ioutil.WriteFile(filepath.Join(cloudDir, "cloud-init.disabled"), nil, 0644) 370 c.Assert(err, IsNil) 371 } 372 373 if t.restrictedFile { 374 cloudDir := filepath.Join(dirs.GlobalRootDir, "etc/cloud/cloud.cfg.d") 375 err := os.MkdirAll(cloudDir, 0755) 376 c.Assert(err, IsNil) 377 err = ioutil.WriteFile(filepath.Join(cloudDir, "zzzz_snapd.cfg"), nil, 0644) 378 c.Assert(err, IsNil) 379 } 380 381 status, err := sysconfig.CloudInitStatus() 382 if t.expError != "" { 383 c.Assert(err, ErrorMatches, t.expError, Commentf(t.comment)) 384 } else { 385 c.Assert(err, IsNil) 386 c.Assert(status, Equals, t.exp, Commentf(t.comment)) 387 } 388 389 // if the restricted file was there we don't call cloud-init status 390 var expCalls [][]string 391 if !t.restrictedFile && !t.disabledFile { 392 expCalls = [][]string{ 393 {"cloud-init", "status"}, 394 } 395 } 396 397 c.Assert(cmd.Calls(), DeepEquals, expCalls, Commentf(t.comment)) 398 cmd.Restore() 399 } 400 } 401 402 func (s *sysconfigSuite) TestCloudInitNotFoundStatus(c *C) { 403 emptyDir := c.MkDir() 404 oldPath := os.Getenv("PATH") 405 defer func() { 406 c.Assert(os.Setenv("PATH", oldPath), IsNil) 407 }() 408 os.Setenv("PATH", emptyDir) 409 410 status, err := sysconfig.CloudInitStatus() 411 c.Assert(err, IsNil) 412 c.Check(status, Equals, sysconfig.CloudInitNotFound) 413 } 414 415 var gceCloudInitStatusJSON = `{ 416 "v1": { 417 "datasource": "DataSourceGCE", 418 "init": { 419 "errors": [], 420 "finished": 1591751113.4536479, 421 "start": 1591751112.130069 422 }, 423 "stage": null 424 } 425 } 426 ` 427 428 var multipassNoCloudCloudInitStatusJSON = `{ 429 "v1": { 430 "datasource": "DataSourceNoCloud [seed=/dev/sr0][dsmode=net]", 431 "init": { 432 "errors": [], 433 "finished": 1591788514.4656117, 434 "start": 1591788514.2607572 435 }, 436 "stage": null 437 } 438 }` 439 440 var localNoneCloudInitStatusJSON = `{ 441 "v1": { 442 "datasource": "DataSourceNone", 443 "init": { 444 "errors": [], 445 "finished": 1591788514.4656117, 446 "start": 1591788514.2607572 447 }, 448 "stage": null 449 } 450 }` 451 452 var lxdNoCloudCloudInitStatusJSON = `{ 453 "v1": { 454 "datasource": "DataSourceNoCloud [seed=/var/lib/cloud/seed/nocloud-net][dsmode=net]", 455 "init": { 456 "errors": [], 457 "finished": 1591788737.3982718, 458 "start": 1591788736.9015596 459 }, 460 "stage": null 461 } 462 }` 463 464 var restrictNoCloudYaml = `datasource_list: [NoCloud] 465 datasource: 466 NoCloud: 467 fs_label: null 468 manual_cache_clean: true 469 ` 470 471 func (s *sysconfigSuite) TestRestrictCloudInit(c *C) { 472 tt := []struct { 473 comment string 474 state sysconfig.CloudInitState 475 sysconfOpts *sysconfig.CloudInitRestrictOptions 476 cloudInitStatusJSON string 477 expError string 478 expRestrictYamlWritten string 479 expDatasource string 480 expAction string 481 expDisableFile bool 482 }{ 483 { 484 comment: "already disabled", 485 state: sysconfig.CloudInitDisabledPermanently, 486 expError: "cannot restrict cloud-init: already disabled", 487 }, 488 { 489 comment: "already restricted", 490 state: sysconfig.CloudInitRestrictedBySnapd, 491 expError: "cannot restrict cloud-init: already restricted", 492 }, 493 { 494 comment: "errored", 495 state: sysconfig.CloudInitErrored, 496 expError: "cannot restrict cloud-init in error or enabled state", 497 }, 498 { 499 comment: "enable (not running)", 500 state: sysconfig.CloudInitEnabled, 501 expError: "cannot restrict cloud-init in error or enabled state", 502 }, 503 { 504 comment: "errored w/ force disable", 505 state: sysconfig.CloudInitErrored, 506 sysconfOpts: &sysconfig.CloudInitRestrictOptions{ 507 ForceDisable: true, 508 }, 509 expAction: "disable", 510 expDisableFile: true, 511 }, 512 { 513 comment: "enable (not running) w/ force disable", 514 state: sysconfig.CloudInitEnabled, 515 sysconfOpts: &sysconfig.CloudInitRestrictOptions{ 516 ForceDisable: true, 517 }, 518 expAction: "disable", 519 expDisableFile: true, 520 }, 521 { 522 comment: "untriggered", 523 state: sysconfig.CloudInitUntriggered, 524 expAction: "disable", 525 expDisableFile: true, 526 }, 527 { 528 comment: "unknown status", 529 state: -1, 530 expAction: "disable", 531 expDisableFile: true, 532 }, 533 { 534 comment: "gce done", 535 state: sysconfig.CloudInitDone, 536 cloudInitStatusJSON: gceCloudInitStatusJSON, 537 expDatasource: "GCE", 538 expAction: "restrict", 539 expRestrictYamlWritten: `datasource_list: [GCE] 540 `, 541 }, 542 { 543 comment: "nocloud done", 544 state: sysconfig.CloudInitDone, 545 cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON, 546 expDatasource: "NoCloud", 547 expAction: "restrict", 548 expRestrictYamlWritten: restrictNoCloudYaml, 549 }, 550 { 551 comment: "nocloud uc20 done", 552 state: sysconfig.CloudInitDone, 553 cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON, 554 sysconfOpts: &sysconfig.CloudInitRestrictOptions{ 555 DisableAfterLocalDatasourcesRun: true, 556 }, 557 expDatasource: "NoCloud", 558 expAction: "disable", 559 expDisableFile: true, 560 }, 561 { 562 comment: "none uc20 done", 563 state: sysconfig.CloudInitDone, 564 cloudInitStatusJSON: localNoneCloudInitStatusJSON, 565 sysconfOpts: &sysconfig.CloudInitRestrictOptions{ 566 DisableAfterLocalDatasourcesRun: true, 567 }, 568 expDatasource: "None", 569 expAction: "disable", 570 expDisableFile: true, 571 }, 572 573 // the two cases for lxd and multipass are effectively the same, but as 574 // the largest known users of cloud-init w/ UC, we leave them as 575 // separate test cases for their different cloud-init status.json 576 // content 577 { 578 comment: "nocloud multipass done", 579 state: sysconfig.CloudInitDone, 580 cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON, 581 expDatasource: "NoCloud", 582 expAction: "restrict", 583 expRestrictYamlWritten: restrictNoCloudYaml, 584 }, 585 { 586 comment: "nocloud seed lxd done", 587 state: sysconfig.CloudInitDone, 588 cloudInitStatusJSON: lxdNoCloudCloudInitStatusJSON, 589 expDatasource: "NoCloud", 590 expAction: "restrict", 591 expRestrictYamlWritten: restrictNoCloudYaml, 592 }, 593 { 594 comment: "nocloud uc20 multipass done", 595 state: sysconfig.CloudInitDone, 596 cloudInitStatusJSON: multipassNoCloudCloudInitStatusJSON, 597 sysconfOpts: &sysconfig.CloudInitRestrictOptions{ 598 DisableAfterLocalDatasourcesRun: true, 599 }, 600 expDatasource: "NoCloud", 601 expAction: "disable", 602 expDisableFile: true, 603 }, 604 { 605 comment: "nocloud uc20 seed lxd done", 606 state: sysconfig.CloudInitDone, 607 cloudInitStatusJSON: lxdNoCloudCloudInitStatusJSON, 608 sysconfOpts: &sysconfig.CloudInitRestrictOptions{ 609 DisableAfterLocalDatasourcesRun: true, 610 }, 611 expDatasource: "NoCloud", 612 expAction: "disable", 613 expDisableFile: true, 614 }, 615 { 616 comment: "no cloud-init in $PATH", 617 state: sysconfig.CloudInitNotFound, 618 expAction: "disable", 619 expDisableFile: true, 620 }, 621 } 622 623 for _, t := range tt { 624 comment := Commentf("%s", t.comment) 625 // setup status.json 626 old := dirs.GlobalRootDir 627 dirs.SetRootDir(c.MkDir()) 628 defer func() { dirs.SetRootDir(old) }() 629 statusJSONFile := filepath.Join(dirs.GlobalRootDir, "/run/cloud-init/status.json") 630 if t.cloudInitStatusJSON != "" { 631 err := os.MkdirAll(filepath.Dir(statusJSONFile), 0755) 632 c.Assert(err, IsNil, comment) 633 err = ioutil.WriteFile(statusJSONFile, []byte(t.cloudInitStatusJSON), 0644) 634 c.Assert(err, IsNil, comment) 635 } 636 637 // if we expect snapd to write a yaml config file for cloud-init, ensure 638 // the dir exists before hand 639 if t.expRestrictYamlWritten != "" { 640 err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud.cfg.d"), 0755) 641 c.Assert(err, IsNil, comment) 642 } 643 644 res, err := sysconfig.RestrictCloudInit(t.state, t.sysconfOpts) 645 if t.expError == "" { 646 c.Assert(err, IsNil, comment) 647 c.Assert(res.DataSource, Equals, t.expDatasource, comment) 648 c.Assert(res.Action, Equals, t.expAction, comment) 649 if t.expRestrictYamlWritten != "" { 650 // check the snapd restrict yaml file that should have been written 651 c.Assert( 652 filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud.cfg.d/zzzz_snapd.cfg"), 653 testutil.FileEquals, 654 t.expRestrictYamlWritten, 655 comment, 656 ) 657 } 658 659 // if we expect the disable file to be written then check for it 660 // otherwise ensure it was not written accidentally 661 var fileCheck Checker 662 if t.expDisableFile { 663 fileCheck = testutil.FilePresent 664 } else { 665 fileCheck = testutil.FileAbsent 666 } 667 668 c.Assert( 669 filepath.Join(dirs.GlobalRootDir, "/etc/cloud/cloud-init.disabled"), 670 fileCheck, 671 comment, 672 ) 673 674 } else { 675 c.Assert(err, ErrorMatches, t.expError, comment) 676 } 677 } 678 } 679 680 const maasGadgetCloudInitImplictYAML = ` 681 datasource: 682 MAAS: 683 foo: bar 684 ` 685 686 const maasGadgetCloudInitImplictLowerCaseYAML = ` 687 datasource: 688 maas: 689 foo: bar 690 ` 691 692 const explicitlyNoDatasourceYAML = `datasource_list: []` 693 694 const explicitlyNoDatasourceButAlsoImplicitlyAnotherYAML = ` 695 datasource_list: [] 696 reporting: 697 NoCloud: 698 foo: bar 699 ` 700 701 const explicitlyMultipleMixedCaseMentioned = ` 702 reporting: 703 NoCloud: 704 foo: bar 705 maas: 706 foo: bar 707 datasource: 708 MAAS: 709 foo: bar 710 NOCLOUD: 711 foo: bar 712 ` 713 714 func (s *sysconfigSuite) TestCloudDatasourcesInUse(c *C) { 715 tt := []struct { 716 configFileContent string 717 expError string 718 expRes *sysconfig.CloudDatasourcesInUseResult 719 comment string 720 }{ 721 { 722 configFileContent: `datasource_list: [MAAS]`, 723 expRes: &sysconfig.CloudDatasourcesInUseResult{ 724 ExplicitlyAllowed: []string{"MAAS"}, 725 Mentioned: []string{"MAAS"}, 726 }, 727 comment: "explicitly allowed via datasource_list in upper case", 728 }, 729 { 730 configFileContent: `datasource_list: [maas]`, 731 expRes: &sysconfig.CloudDatasourcesInUseResult{ 732 ExplicitlyAllowed: []string{"MAAS"}, 733 Mentioned: []string{"MAAS"}, 734 }, 735 comment: "explicitly allowed via datasource_list in lower case", 736 }, 737 { 738 configFileContent: `datasource_list: [mAaS]`, 739 expRes: &sysconfig.CloudDatasourcesInUseResult{ 740 ExplicitlyAllowed: []string{"MAAS"}, 741 Mentioned: []string{"MAAS"}, 742 }, 743 comment: "explicitly allowed via datasource_list in random case", 744 }, 745 { 746 configFileContent: `datasource_list: [maas, maas]`, 747 expRes: &sysconfig.CloudDatasourcesInUseResult{ 748 ExplicitlyAllowed: []string{"MAAS"}, 749 Mentioned: []string{"MAAS"}, 750 }, 751 comment: "duplicated datasource in datasource_list", 752 }, 753 { 754 configFileContent: `datasource_list: [maas, MAAS]`, 755 expRes: &sysconfig.CloudDatasourcesInUseResult{ 756 ExplicitlyAllowed: []string{"MAAS"}, 757 Mentioned: []string{"MAAS"}, 758 }, 759 comment: "duplicated datasource in datasource_list with different cases", 760 }, 761 { 762 configFileContent: `datasource_list: [maas, GCE]`, 763 expRes: &sysconfig.CloudDatasourcesInUseResult{ 764 ExplicitlyAllowed: []string{"GCE", "MAAS"}, 765 Mentioned: []string{"GCE", "MAAS"}, 766 }, 767 comment: "multiple datasources in datasource list", 768 }, 769 { 770 configFileContent: maasGadgetCloudInitImplictYAML, 771 expRes: &sysconfig.CloudDatasourcesInUseResult{ 772 Mentioned: []string{"MAAS"}, 773 }, 774 comment: "implicitly mentioned datasource", 775 }, 776 { 777 configFileContent: maasGadgetCloudInitImplictLowerCaseYAML, 778 expRes: &sysconfig.CloudDatasourcesInUseResult{ 779 Mentioned: []string{"MAAS"}, 780 }, 781 comment: "implicitly mentioned datasource in lower case", 782 }, 783 { 784 configFileContent: explicitlyNoDatasourceYAML, 785 expRes: &sysconfig.CloudDatasourcesInUseResult{ 786 ExplicitlyNoneAllowed: true, 787 }, 788 comment: "no datasources allowed at all", 789 }, 790 { 791 configFileContent: explicitlyNoDatasourceButAlsoImplicitlyAnotherYAML, 792 expRes: &sysconfig.CloudDatasourcesInUseResult{ 793 ExplicitlyNoneAllowed: true, 794 Mentioned: []string{"NOCLOUD"}, 795 }, 796 comment: "explicitly no datasources allowed, but still some mentioned", 797 }, 798 { 799 configFileContent: explicitlyMultipleMixedCaseMentioned, 800 expRes: &sysconfig.CloudDatasourcesInUseResult{ 801 Mentioned: []string{"MAAS", "NOCLOUD"}, 802 }, 803 comment: "multiple of same datasources mentioned in different cases", 804 }, 805 { 806 configFileContent: "i'm not yaml", 807 expError: "yaml: unmarshal errors.*\n.*cannot unmarshal.*", 808 comment: "invalid yaml", 809 }, 810 } 811 812 for _, t := range tt { 813 comment := Commentf(t.comment) 814 configFile := filepath.Join(c.MkDir(), "cloud.conf") 815 err := ioutil.WriteFile(configFile, []byte(t.configFileContent), 0644) 816 c.Assert(err, IsNil, comment) 817 res, err := sysconfig.CloudDatasourcesInUse(configFile) 818 if t.expError != "" { 819 c.Assert(err, ErrorMatches, t.expError, comment) 820 continue 821 } 822 823 c.Assert(res, DeepEquals, t.expRes, comment) 824 } 825 } 826 827 const maasCfg1 = `#cloud-config 828 reporting: 829 maas: 830 type: webhook 831 endpoint: http://172-16-99-0--24.maas-internal:5248/MAAS/metadata/status/foo 832 consumer_key: foothefoo 833 token_key: foothefoothesecond 834 token_secret: foothesecretfoo 835 ` 836 837 const maasCfg2 = `datasource_list: [ MAAS ] 838 ` 839 840 const maasCfg3 = `#cloud-config 841 snappy: 842 email: foo@foothewebsite.com 843 ` 844 845 const maasCfg4 = `#cloud-config 846 network: 847 config: 848 - id: enp3s0 849 mac_address: 52:54:00:b4:9e:25 850 mtu: 1500 851 name: enp3s0 852 subnets: 853 - address: 172.16.99.7/24 854 dns_nameservers: 855 - 172.16.99.1 856 dns_search: 857 - maas 858 type: static 859 type: physical 860 - address: 172.16.99.1 861 search: 862 - maas 863 type: nameserver 864 version: 1 865 ` 866 867 const maasCfg5 = `#cloud-config 868 datasource: 869 MAAS: 870 consumer_key: foothefoo 871 metadata_url: http://172-16-99-0--24.maas-internal:5248/MAAS/metadata/ 872 token_key: foothefoothesecond 873 token_secret: foothesecretfoo 874 ` 875 876 func (s *sysconfigSuite) TestFilterCloudCfgFile(c *C) { 877 tt := []struct { 878 comment string 879 inStr string 880 outStr string 881 err string 882 }{ 883 { 884 comment: "maas reporting cloud-init config", 885 inStr: maasCfg1, 886 outStr: maasCfg1, 887 }, 888 { 889 comment: "maas datasource list cloud-init config", 890 inStr: maasCfg2, 891 outStr: `#cloud-config 892 datasource_list: 893 - MAAS 894 `, 895 }, 896 { 897 comment: "maas snappy user cloud-init config", 898 inStr: maasCfg3, 899 // we don't support using the snappy key 900 outStr: "", 901 }, 902 { 903 comment: "maas networking cloud-init config", 904 inStr: maasCfg4, 905 outStr: maasCfg4, 906 }, 907 { 908 comment: "maas datasource cloud-init config", 909 inStr: maasCfg5, 910 outStr: maasCfg5, 911 }, 912 { 913 comment: "unsupported datasource in datasource section cloud-init config", 914 inStr: `#cloud-config 915 datasource: 916 NoCloud: 917 consumer_key: fooooooo 918 `, 919 outStr: "", 920 }, 921 { 922 comment: "unsupported datasource in reporting section cloud-init config", 923 inStr: `#cloud-config 924 reporting: 925 NoCloud: 926 consumer_key: fooooooo 927 `, 928 outStr: "", 929 }, 930 { 931 comment: "unsupported datasource in datasource_list with supported one", 932 inStr: `#cloud-config 933 datasource_list: [MAAS, NoCloud] 934 `, 935 outStr: `#cloud-config 936 datasource_list: 937 - MAAS 938 `, 939 }, 940 { 941 comment: "unsupported datasources in multiple keys with supported ones", 942 inStr: `#cloud-config 943 datasource: 944 MAAS: 945 consumer_key: fooooooo 946 NoCloud: 947 consumer_key: fooooooo 948 949 reporting: 950 MAAS: 951 type: webhook 952 NoCloud: 953 type: webhook 954 955 datasource_list: [MAAS, NoCloud] 956 `, 957 outStr: `#cloud-config 958 datasource: 959 MAAS: 960 consumer_key: fooooooo 961 datasource_list: 962 - MAAS 963 reporting: 964 MAAS: 965 type: webhook 966 `, 967 }, 968 { 969 comment: "unrelated keys", 970 inStr: `#cloud-config 971 datasource: 972 MAAS: 973 consumer_key: fooooooo 974 foo: bar 975 976 reporting: 977 MAAS: 978 type: webhook 979 new_foo: new_bar 980 981 extra_foo: extra_bar 982 `, 983 outStr: `#cloud-config 984 datasource: 985 MAAS: 986 consumer_key: fooooooo 987 reporting: 988 MAAS: 989 type: webhook 990 `, 991 }, 992 } 993 994 dir := c.MkDir() 995 for i, t := range tt { 996 comment := Commentf(t.comment) 997 inFile := filepath.Join(dir, fmt.Sprintf("%d.cfg", i)) 998 err := ioutil.WriteFile(inFile, []byte(t.inStr), 0755) 999 c.Assert(err, IsNil, comment) 1000 1001 out, err := sysconfig.FilterCloudCfgFile(inFile, []string{"MAAS"}) 1002 if t.err != "" { 1003 c.Assert(err, ErrorMatches, t.err, comment) 1004 continue 1005 } 1006 c.Assert(err, IsNil, comment) 1007 1008 // no expected output means that everything was filtered out 1009 if t.outStr == "" { 1010 c.Assert(out, Equals, "", comment) 1011 continue 1012 } 1013 1014 // otherwise we have expected output in the file 1015 b, err := ioutil.ReadFile(out) 1016 c.Assert(err, IsNil, comment) 1017 c.Assert(string(b), Equals, t.outStr, comment) 1018 } 1019 }