Merge lp:~chipaca/snappy/config-modules into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 782
Merged at revision: 791
Proposed branch: lp:~chipaca/snappy/config-modules
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 444 lines (+296/-10)
2 files modified
coreconfig/config.go (+107/-6)
coreconfig/config_test.go (+189/-4)
To merge this branch: bzr merge lp:~chipaca/snappy/config-modules
Reviewer Review Type Date Requested Status
Oliver Grawert Approve
Michael Vogt (community) Approve
Review via email: mp+274987@code.launchpad.net

Commit message

"modules" ubuntu-core config handling

Description of the change

This adds the "config" option to ubuntu-core config.

It's the first one of these that isn't just a blob of the file, so please comment on the api as exposed.

You add a module to be loaded at boot via:

echo "config: {ubuntu-core: {modules: {potato: true}}}" | uPOST /1.0/packages/ubuntu-core.ubuntu/config

and you remove it by setting it to "false".

Another api that was considered was simply setting it to a list. Downside is, adding a single module to a system that already has a few gets tedious; upside, simpler.

Please do comment.

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

Oh, I also made some non-related changes to config. Please ignore those: switching ioutil.WriteFile to helpers.AtomicWriteFile, and its->it's.

Revision history for this message
John Lenton (chipaca) wrote :

(unless you're actually reviewing and not feedback'ing on the feature; in that case, do not ignore)

Revision history for this message
John Lenton (chipaca) wrote :

I'll go have breakfast now.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks, this looks good, one nitpick inline.

review: Approve
Revision history for this message
Oliver Grawert (ogra) wrote :

i wouldnt make the module a boolean but a list i.e.:

echo "config: {ubuntu-core: {modules: {potato, banana, apple}}}" | uPOST /1.0/packages/ubuntu-core.ubuntu/config

to result in /etc/modules-load.d/ubuntu-core.conf like:

# DO NOT EDIT THIS FILE
# it is auto-generated, and will be overwritten.
potato
banana
apple

i imagine that we have the case more often that you need to load more than one module (especially for firewalling there are a ton of options to manually enable by loading the respective module), the list saves you from having to use multiple commands for this.

Revision history for this message
John Lenton (chipaca) wrote :

it's a map, so with this api you'd do*

echo "config: {ubuntu-core: {modules: {potato: true, banana: true, apple: true}}}" | uPOST /1.0/packages/ubuntu-core.ubuntu/config

to have that conf.

The advantage of this is that if you later want to change banana for ip6_banana, you do

echo "config: {ubuntu-core: {modules: {banana: false, ip6_banana: true}}}" | uPOST /1.0/packages/ubuntu-core.ubuntu/config

The alternative api would be, to set them up:

echo "config: {ubuntu-core: {modules: [potato, banana, apple]}}" | uPOST /1.0/packages/ubuntu-core.ubuntu/config

and then to change it you've got to specify them all again:

echo "config: {ubuntu-core: {modules: [potato, ip6_banana, apple]}}" | uPOST /1.0/packages/ubuntu-core.ubuntu/config

* i'd need to double-check that yaml, but you get the idea

Revision history for this message
John Lenton (chipaca) wrote :

oh, in all the above, "snappy config ubuntu-core -" instead of uPOST works too ;)

Revision history for this message
Oliver Grawert (ogra) wrote :

oh, thanks, i didnt get that you can make a list out of the booleans :)
we should make sure thats clear in the docs ;)

Revision history for this message
Oliver Grawert (ogra) :
review: Approve
Revision history for this message
John Lenton (chipaca) wrote :

Please do not top-approve until the relevant ubuntu-core-config branch lands.

Revision history for this message
John Lenton (chipaca) wrote :

Gustavo has indicated that it would be better to expose this as an additive list, with optional removal being indicated with a prefixed dash.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Indeed.. it feels like an additive list would be more friendly to write and read, and it would also allow snappy itself to make changes to that list without it being unintendedly overwritten by snaps that were not even aware that they were killing them.

I've also added a few more notes inline:

Revision history for this message
John Lenton (chipaca) wrote :

gustavo, snaps don't call this, system integrators and administrators call this

Revision history for this message
John Lenton (chipaca) :
Revision history for this message
John Lenton (chipaca) wrote :

Refactored into an additive list thing.

echo "config: {ubuntu-core: {modules: [potato, banana, apple]}}" | sudo snappy config ubuntu-core -

to add those to the list, and

echo "config: {ubuntu-core: {modules: [-banana, ip6_banana]}}" | sudo snappy config ubuntu-core -

to remove them

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

More inline responses..

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

FWIW, I much prefer the additive list over the boolean approach.

lp:~chipaca/snappy/config-modules updated
782. By John Lenton

couple more tweaks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'coreconfig/config.go'
--- coreconfig/config.go 2015-09-18 15:16:17 +0000
+++ coreconfig/config.go 2015-10-20 20:00:45 +0000
@@ -20,12 +20,15 @@
20package coreconfig20package coreconfig
2121
22import (22import (
23 "bufio"
24 "bytes"
23 "errors"25 "errors"
24 "io/ioutil"26 "io/ioutil"
25 "os"27 "os"
26 "os/exec"28 "os/exec"
27 "path/filepath"29 "path/filepath"
28 "reflect"30 "reflect"
31 "sort"
29 "strings"32 "strings"
30 "syscall"33 "syscall"
3134
@@ -52,6 +55,7 @@
5255
53var (56var (
54 modprobePath = "/etc/modprobe.d/ubuntu-core.conf"57 modprobePath = "/etc/modprobe.d/ubuntu-core.conf"
58 modulesPath = "/etc/modules-load.d/ubuntu-core.conf"
55 interfacesRoot = "/etc/network/interfaces.d/"59 interfacesRoot = "/etc/network/interfaces.d/"
56 pppRoot = "/etc/ppp/"60 pppRoot = "/etc/ppp/"
57 watchdogConfigPath = "/etc/watchdog.conf"61 watchdogConfigPath = "/etc/watchdog.conf"
@@ -73,6 +77,7 @@
73 Timezone *string `yaml:"timezone,omitempty"`77 Timezone *string `yaml:"timezone,omitempty"`
74 Hostname *string `yaml:"hostname,omitempty"`78 Hostname *string `yaml:"hostname,omitempty"`
75 Modprobe *string `yaml:"modprobe,omitempty"`79 Modprobe *string `yaml:"modprobe,omitempty"`
80 Modules []string `yaml:"load-kernel-modules,omitempty"`
76 Network *networkConfig `yaml:"network,omitempty"`81 Network *networkConfig `yaml:"network,omitempty"`
77 Watchdog *watchdogConfig `yaml:"watchdog,omitempty"`82 Watchdog *watchdogConfig `yaml:"watchdog,omitempty"`
78}83}
@@ -119,6 +124,10 @@
119 if err != nil {124 if err != nil {
120 return nil, err125 return nil, err
121 }126 }
127 modules, err := getModules()
128 if err != nil {
129 return nil, err
130 }
122 interfaces, err := getInterfaces()131 interfaces, err := getInterfaces()
123 if err != nil {132 if err != nil {
124 return nil, err133 return nil, err
@@ -145,6 +154,7 @@
145 Timezone: &tz,154 Timezone: &tz,
146 Hostname: &hostname,155 Hostname: &hostname,
147 Modprobe: &modprobe,156 Modprobe: &modprobe,
157 Modules: modules,
148 Network: network,158 Network: network,
149 Watchdog: watchdog,159 Watchdog: watchdog,
150 }160 }
@@ -244,6 +254,10 @@
244 if err := setModprobe(*newConfig.Modprobe); err != nil {254 if err := setModprobe(*newConfig.Modprobe); err != nil {
245 return "", err255 return "", err
246 }256 }
257 case "Modules":
258 if err := setModules(newConfig.Modules); err != nil {
259 return "", err
260 }
247 case "Network":261 case "Network":
248 if oldConfig.Network == nil || !passthroughEqual(oldConfig.Network.Interfaces, newConfig.Network.Interfaces) {262 if oldConfig.Network == nil || !passthroughEqual(oldConfig.Network.Interfaces, newConfig.Network.Interfaces) {
249 if err := setInterfaces(newConfig.Network.Interfaces); err != nil {263 if err := setInterfaces(newConfig.Network.Interfaces); err != nil {
@@ -297,7 +311,7 @@
297 return err311 return err
298 }312 }
299313
300 return ioutil.WriteFile(tzFile(), []byte(timezone), 0644)314 return helpers.AtomicWriteFile(tzFile(), []byte(timezone), 0644)
301}315}
302316
303func getPassthrough(rootDir string) (pc []passthroughConfig, err error) {317func getPassthrough(rootDir string) (pc []passthroughConfig, err error) {
@@ -326,7 +340,7 @@
326 os.Remove(path)340 os.Remove(path)
327 continue341 continue
328 }342 }
329 if err := ioutil.WriteFile(path, []byte(c.Content), 0644); err != nil {343 if err := helpers.AtomicWriteFile(path, []byte(c.Content), 0644); err != nil {
330 return err344 return err
331 }345 }
332 }346 }
@@ -362,7 +376,94 @@
362376
363// setModprobe sets the specified modprobe config377// setModprobe sets the specified modprobe config
364var setModprobe = func(modprobe string) error {378var setModprobe = func(modprobe string) error {
365 return ioutil.WriteFile(modprobePath, []byte(modprobe), 0644)379 return helpers.AtomicWriteFile(modprobePath, []byte(modprobe), 0644)
380}
381
382func getModules() ([]string, error) {
383 f, err := os.Open(modulesPath)
384 if err != nil {
385 if os.IsNotExist(err) {
386 return nil, nil
387 }
388
389 return nil, err
390 }
391
392 // there's a warning at the top of the file
393 // but you know they're just going to edit it anyway
394 // so be kind
395
396 var modules []string
397 scanner := bufio.NewScanner(f)
398 for scanner.Scan() {
399 line := strings.TrimSpace(scanner.Text())
400 if len(line) == 0 {
401 continue
402 }
403 if line[0] == '#' || line[0] == ';' {
404 continue
405 }
406
407 modules = append(modules, line)
408 }
409 if err := scanner.Err(); err != nil {
410 return nil, err
411 }
412
413 // doing the sort on get makes testing easier
414 sort.Strings(modules)
415
416 return modules, nil
417}
418
419const modulesHeader = `#
420# DO NOT EDIT THIS FILE
421# it is auto-generated, and will be overwritten.
422`
423
424func setModules(modules []string) error {
425 oldModules, err := getModules()
426 if err != nil {
427 return err
428 }
429
430 for i := range modules {
431 m := strings.TrimSpace(modules[i])
432 if len(m) == 0 {
433 continue
434 }
435
436 if m[0] == '-' {
437 m = m[1:]
438 idx := sort.SearchStrings(oldModules, m)
439 if idx == len(oldModules) || oldModules[idx] != m {
440 // not found
441 continue
442 }
443 oldModules = append(oldModules[:idx], oldModules[idx+1:]...)
444 } else {
445 idx := sort.SearchStrings(oldModules, m)
446 if idx < len(oldModules) && oldModules[idx] == m {
447 // already got it
448 continue
449 }
450 oldModules = append(oldModules, "")
451 copy(oldModules[idx+1:], oldModules[idx:])
452 oldModules[idx] = m
453 }
454 }
455
456 var buf bytes.Buffer
457
458 // bytes' Write* methods always return nil error
459 buf.WriteString(modulesHeader)
460
461 for i := range oldModules {
462 buf.WriteString(oldModules[i])
463 buf.WriteByte('\n')
464 }
465
466 return helpers.AtomicWriteFile(modulesPath, buf.Bytes(), 0644)
366}467}
367468
368// getWatchdog returns the current watchdog config469// getWatchdog returns the current watchdog config
@@ -388,11 +489,11 @@
388489
389// setWatchdog sets the specified watchdog config490// setWatchdog sets the specified watchdog config
390var setWatchdog = func(wf *watchdogConfig) error {491var setWatchdog = func(wf *watchdogConfig) error {
391 if err := ioutil.WriteFile(watchdogStartupPath, []byte(wf.Startup), 0644); err != nil {492 if err := helpers.AtomicWriteFile(watchdogStartupPath, []byte(wf.Startup), 0644); err != nil {
392 return err493 return err
393 }494 }
394495
395 return ioutil.WriteFile(watchdogConfigPath, []byte(wf.Config), 0644)496 return helpers.AtomicWriteFile(watchdogConfigPath, []byte(wf.Config), 0644)
396}497}
397498
398// for testing purposes499// for testing purposes
@@ -467,5 +568,5 @@
467 return err568 return err
468 }569 }
469570
470 return ioutil.WriteFile(hostnamePath, hostnameB, 0644)571 return helpers.AtomicWriteFile(hostnamePath, hostnameB, 0644)
471}572}
472573
=== modified file 'coreconfig/config_test.go'
--- coreconfig/config_test.go 2015-09-21 15:14:41 +0000
+++ coreconfig/config_test.go 2015-10-20 20:00:45 +0000
@@ -51,6 +51,7 @@
51 originalCmdSystemctl = cmdSystemctl51 originalCmdSystemctl = cmdSystemctl
52 originalHostnamePath = hostnamePath52 originalHostnamePath = hostnamePath
53 originalModprobePath = modprobePath53 originalModprobePath = modprobePath
54 originalModulesPath = modulesPath
54 originalInterfacesRoot = interfacesRoot55 originalInterfacesRoot = interfacesRoot
55 originalPppRoot = pppRoot56 originalPppRoot = pppRoot
56 originalWatchdogStartupPath = watchdogStartupPath57 originalWatchdogStartupPath = watchdogStartupPath
@@ -107,6 +108,7 @@
107 cmdAutopilotEnabled = originalCmdAutopilotEnabled108 cmdAutopilotEnabled = originalCmdAutopilotEnabled
108 cmdSystemctl = originalCmdSystemctl109 cmdSystemctl = originalCmdSystemctl
109 modprobePath = originalModprobePath110 modprobePath = originalModprobePath
111 modulesPath = originalModulesPath
110 interfacesRoot = originalInterfacesRoot112 interfacesRoot = originalInterfacesRoot
111 pppRoot = originalPppRoot113 pppRoot = originalPppRoot
112 watchdogStartupPath = originalWatchdogStartupPath114 watchdogStartupPath = originalWatchdogStartupPath
@@ -489,12 +491,195 @@
489 _, err := Set(input)491 _, err := Set(input)
490 c.Assert(err, IsNil)492 c.Assert(err, IsNil)
491493
492 // ensure its really there494 // ensure it's really there
493 content, err := ioutil.ReadFile(modprobePath)495 content, err := ioutil.ReadFile(modprobePath)
494 c.Assert(err, IsNil)496 c.Assert(err, IsNil)
495 c.Assert(string(content), Equals, "blacklist floppy\nsoftdep mlx4_core post: mlx4_en\n")497 c.Assert(string(content), Equals, "blacklist floppy\nsoftdep mlx4_core post: mlx4_en\n")
496}498}
497499
500func (cts *ConfigTestSuite) TestModules(c *C) {
501 modulesPath = filepath.Join(c.MkDir(), "test.conf")
502
503 modules, err := getModules()
504 c.Assert(err, IsNil)
505 c.Check(modules, HasLen, 0)
506
507 c.Assert(setModules([]string{"foo"}), IsNil)
508
509 modules, err = getModules()
510 c.Assert(err, IsNil)
511 c.Check(modules, DeepEquals, []string{"foo"})
512
513 c.Assert(setModules([]string{"bar"}), IsNil)
514
515 modules, err = getModules()
516 c.Assert(err, IsNil)
517 c.Check(modules, DeepEquals, []string{"bar", "foo"})
518
519 c.Assert(setModules([]string{"-foo"}), IsNil)
520
521 modules, err = getModules()
522 c.Assert(err, IsNil)
523 c.Check(modules, DeepEquals, []string{"bar"})
524}
525
526func (cts *ConfigTestSuite) TestModulesRemoveAbsent(c *C) {
527 modulesPath = filepath.Join(c.MkDir(), "test.conf")
528
529 c.Assert(setModules([]string{"foo"}), IsNil)
530 c.Assert(setModules([]string{"-bar"}), IsNil)
531
532 modules, err := getModules()
533 c.Assert(err, IsNil)
534 c.Check(modules, DeepEquals, []string{"foo"})
535}
536
537func (cts *ConfigTestSuite) TestModulesRemoveEmpty(c *C) {
538 modulesPath = filepath.Join(c.MkDir(), "test.conf")
539
540 c.Assert(setModules([]string{"foo"}), IsNil)
541 c.Assert(setModules([]string{"-"}), IsNil)
542
543 modules, err := getModules()
544 c.Assert(err, IsNil)
545 c.Check(modules, DeepEquals, []string{"foo"})
546}
547
548func (cts *ConfigTestSuite) TestModulesRemoveBlank(c *C) {
549 modulesPath = filepath.Join(c.MkDir(), "test.conf")
550
551 c.Assert(setModules([]string{"foo"}), IsNil)
552 c.Assert(setModules([]string{"- "}), IsNil)
553
554 modules, err := getModules()
555 c.Assert(err, IsNil)
556 c.Check(modules, DeepEquals, []string{"foo"})
557}
558
559func (cts *ConfigTestSuite) TestModulesAddDupe(c *C) {
560 modulesPath = filepath.Join(c.MkDir(), "test.conf")
561
562 c.Assert(setModules([]string{"foo"}), IsNil)
563 c.Assert(setModules([]string{"foo"}), IsNil)
564
565 modules, err := getModules()
566 c.Assert(err, IsNil)
567 c.Check(modules, DeepEquals, []string{"foo"})
568}
569
570func (cts *ConfigTestSuite) TestModulesAddEmpty(c *C) {
571 modulesPath = filepath.Join(c.MkDir(), "test.conf")
572
573 c.Assert(setModules([]string{"foo"}), IsNil)
574 c.Assert(setModules([]string{""}), IsNil)
575
576 modules, err := getModules()
577 c.Assert(err, IsNil)
578 c.Check(modules, DeepEquals, []string{"foo"})
579}
580
581func (cts *ConfigTestSuite) TestModulesAddBlank(c *C) {
582 modulesPath = filepath.Join(c.MkDir(), "test.conf")
583
584 c.Assert(setModules([]string{"foo"}), IsNil)
585 c.Assert(setModules([]string{" "}), IsNil)
586
587 modules, err := getModules()
588 c.Assert(err, IsNil)
589 c.Check(modules, DeepEquals, []string{"foo"})
590}
591
592func (cts *ConfigTestSuite) TestModulesHasWarning(c *C) {
593 modulesPath = filepath.Join(c.MkDir(), "test.conf")
594
595 c.Assert(setModules([]string{"foo"}), IsNil)
596
597 bs, err := ioutil.ReadFile(modulesPath)
598 c.Assert(err, IsNil)
599 c.Check(string(bs), Matches, `(?s).*DO NOT EDIT.*`)
600}
601
602func (cts *ConfigTestSuite) TestModulesIsKind(c *C) {
603 modulesPath = filepath.Join(c.MkDir(), "test.conf")
604 c.Assert(ioutil.WriteFile(modulesPath, []byte(`# hello
605# this is what happens when soembody comes and edits the file
606# just to be sure
607; modules-load.d(5) says comments can also start with a ;
608 ; actually not even start
609 # it's the first non-whitespace that counts
610# also here's an empty line:
611
612# and here's a module with spurious whitespace:
613 oops
614# that is all. Have a good day.
615`), 0644), IsNil)
616
617 modules, err := getModules()
618 c.Check(err, IsNil)
619 c.Check(modules, DeepEquals, []string{"oops"})
620}
621
622func (cts *ConfigTestSuite) TestModulesYaml(c *C) {
623 modulesPath = filepath.Join(c.MkDir(), "test.conf")
624
625 c.Assert(setModules([]string{"foo"}), IsNil)
626
627 cfg, err := newSystemConfig()
628 c.Assert(err, IsNil)
629 c.Check(cfg.Modules, DeepEquals, []string{"foo"})
630
631 input := `config:
632 ubuntu-core:
633 load-kernel-modules: [-foo, bar]
634`
635 _, err = Set(input)
636 c.Assert(err, IsNil)
637
638 // ensure it's really there
639 content, err := ioutil.ReadFile(modulesPath)
640 c.Assert(err, IsNil)
641 c.Assert(string(content), Matches, `(?sm).*^bar$.*`)
642
643 modules, err := getModules()
644 c.Assert(err, IsNil)
645 c.Check(modules, DeepEquals, []string{"bar"})
646}
647
648func (cts *ConfigTestSuite) TestModulesErrorWrite(c *C) {
649 // modulesPath is not writable, but only notexist read error
650 modulesPath = filepath.Join(c.MkDir(), "not-there", "test.conf")
651
652 c.Check(setModules([]string{"bar"}), NotNil)
653
654 input := `config:
655 ubuntu-core:
656 load-kernel-modules: [foo]
657`
658 _, err := Set(input)
659 c.Check(err, NotNil)
660
661 _, err = getModules()
662 c.Check(err, IsNil)
663
664 _, err = newSystemConfig()
665 c.Check(err, IsNil)
666}
667
668func (cts *ConfigTestSuite) TestModulesErrorRW(c *C) {
669 modulesPath = c.MkDir()
670
671 modules, err := getModules()
672 c.Check(err, NotNil)
673 c.Check(modules, HasLen, 0)
674 c.Check(setModules([]string{"bar"}), NotNil)
675
676 _, err = newSystemConfig()
677 c.Check(err, NotNil)
678
679 _, err = Set("config: {ubuntu-core: {modules: [foo]}}")
680 c.Check(err, NotNil)
681}
682
498func (cts *ConfigTestSuite) TestNetworkGet(c *C) {683func (cts *ConfigTestSuite) TestNetworkGet(c *C) {
499 path := filepath.Join(interfacesRoot, "eth0")684 path := filepath.Join(interfacesRoot, "eth0")
500 content := "auto eth0"685 content := "auto eth0"
@@ -573,7 +758,7 @@
573 _, err := Set(input)758 _, err := Set(input)
574 c.Assert(err, IsNil)759 c.Assert(err, IsNil)
575760
576 // ensure its really there761 // ensure it's really there
577 content, err := ioutil.ReadFile(filepath.Join(interfacesRoot, "eth0"))762 content, err := ioutil.ReadFile(filepath.Join(interfacesRoot, "eth0"))
578 c.Assert(err, IsNil)763 c.Assert(err, IsNil)
579 c.Assert(string(content), Equals, "auto dhcp")764 c.Assert(string(content), Equals, "auto dhcp")
@@ -593,7 +778,7 @@
593 _, err := Set(input)778 _, err := Set(input)
594 c.Assert(err, IsNil)779 c.Assert(err, IsNil)
595780
596 // ensure its really there781 // ensure it's really there
597 content, err := ioutil.ReadFile(filepath.Join(pppRoot, "chap-secret"))782 content, err := ioutil.ReadFile(filepath.Join(pppRoot, "chap-secret"))
598 c.Assert(err, IsNil)783 c.Assert(err, IsNil)
599 c.Assert(string(content), Equals, "password")784 c.Assert(string(content), Equals, "password")
@@ -670,7 +855,7 @@
670 _, err := Set(input)855 _, err := Set(input)
671 c.Assert(err, IsNil)856 c.Assert(err, IsNil)
672857
673 // ensure its really there858 // ensure it's really there
674 content, err := ioutil.ReadFile(watchdogStartupPath)859 content, err := ioutil.ReadFile(watchdogStartupPath)
675 c.Assert(err, IsNil)860 c.Assert(err, IsNil)
676 c.Assert(string(content), Equals, "some startup")861 c.Assert(string(content), Equals, "some startup")

Subscribers

People subscribed via source and target branches