[lxc-devel] [distrobuilder/master] *: Support yaml override on the command line
monstermunchkin on Github
lxc-bot at linuxcontainers.org
Wed Mar 14 21:35:57 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 379 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180314/fe8b9cd9/attachment.bin>
-------------- next part --------------
From 13be123f2dfc98b9347fa60cbb359a803c26a1f6 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 14 Mar 2018 22:34:46 +0100
Subject: [PATCH] *: Support yaml override on the command line
Resolves #32
Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
distrobuilder/main.go | 24 +++++++++++++--
shared/definition.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++
shared/definition_test.go | 55 ++++++++++++++++++++++++++++++++++
3 files changed, 151 insertions(+), 3 deletions(-)
diff --git a/distrobuilder/main.go b/distrobuilder/main.go
index c101f21..7636361 100644
--- a/distrobuilder/main.go
+++ b/distrobuilder/main.go
@@ -54,10 +54,12 @@ import "C"
import (
"bufio"
"bytes"
+ "errors"
"fmt"
"io"
"os"
"path/filepath"
+ "strings"
lxd "github.com/lxc/lxd/shared"
"github.com/spf13/cobra"
@@ -70,6 +72,7 @@ import (
type cmdGlobal struct {
flagCleanup bool
flagCacheDir string
+ flagOptions []string
definition *shared.Definition
sourceDir string
@@ -97,6 +100,8 @@ func main() {
"Clean up cache directory")
app.PersistentFlags().StringVar(&globalCmd.flagCacheDir, "cache-dir",
"/var/cache/distrobuilder", "Cache directory"+"``")
+ app.PersistentFlags().StringSliceVarP(&globalCmd.flagOptions, "options", "o",
+ []string{}, "Override options (list of key=value)"+"``")
// LXC sub-commands
LXCCmd := cmdLXC{global: &globalCmd}
@@ -152,7 +157,7 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error {
}
// Get the image definition
- c.definition, err = getDefinition(args[0])
+ c.definition, err = getDefinition(args[0], c.flagOptions)
if err != nil {
return err
}
@@ -235,7 +240,7 @@ func (c *cmdGlobal) preRunPack(cmd *cobra.Command, args []string) error {
}
// Get the image definition
- c.definition, err = getDefinition(args[0])
+ c.definition, err = getDefinition(args[0], c.flagOptions)
if err != nil {
return err
}
@@ -252,7 +257,7 @@ func (c *cmdGlobal) postRun(cmd *cobra.Command, args []string) error {
return nil
}
-func getDefinition(fname string) (*shared.Definition, error) {
+func getDefinition(fname string, options []string) (*shared.Definition, error) {
// Read the provided file, or if none was given, read from stdin
var buf bytes.Buffer
if fname == "" || fname == "-" {
@@ -280,6 +285,19 @@ func getDefinition(fname string) (*shared.Definition, error) {
return nil, err
}
+ // Set options from the command line
+ for _, o := range options {
+ parts := strings.Split(o, "=")
+ if len(parts) != 2 {
+ return nil, errors.New("Options need to be of type key=value")
+ }
+
+ err := def.SetValue(parts[0], parts[1])
+ if err != nil {
+ return nil, fmt.Errorf("Failed to set option %s: %s", o, err)
+ }
+ }
+
// Apply some defaults on top of the provided configuration
shared.SetDefinitionDefaults(&def)
diff --git a/shared/definition.go b/shared/definition.go
index 9e58456..9a44c38 100644
--- a/shared/definition.go
+++ b/shared/definition.go
@@ -3,6 +3,8 @@ package shared
import (
"errors"
"fmt"
+ "reflect"
+ "strconv"
"strings"
"time"
@@ -93,6 +95,40 @@ type Definition struct {
Mappings DefinitionMappings `yaml:"mappings,omitempty"`
}
+// SetValue writes the provided value to a field represented by the yaml tag 'key'.
+func (d *Definition) SetValue(key string, value interface{}) error {
+ // Walk through the definition and find the field with the given key
+ field, err := getFieldByTag(reflect.ValueOf(d).Elem(), reflect.TypeOf(d).Elem(), key)
+ if err != nil {
+ return err
+ }
+
+ // Fail if the field cannot be set
+ if !field.CanSet() {
+ return fmt.Errorf("Cannot set value for %s", key)
+ }
+
+ if reflect.TypeOf(value).Kind() != field.Kind() {
+ return fmt.Errorf("Cannot assign %s value to %s",
+ reflect.TypeOf(value).Kind(), field.Kind())
+ }
+
+ switch reflect.TypeOf(value).Kind() {
+ case reflect.Bool:
+ field.SetBool(value.(bool))
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ field.SetInt(value.(int64))
+ case reflect.String:
+ field.SetString(value.(string))
+ case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ field.SetUint(value.(uint64))
+ default:
+ return fmt.Errorf("Unknown value type %s", reflect.TypeOf(value).Kind())
+ }
+
+ return nil
+}
+
// SetDefinitionDefaults sets some default values for the given Definition.
func SetDefinitionDefaults(def *Definition) {
// default to local arch
@@ -187,3 +223,42 @@ func ValidateDefinition(def Definition) error {
return nil
}
+
+func getFieldByTag(v reflect.Value, t reflect.Type, tag string) (reflect.Value, error) {
+ parts := strings.SplitN(tag, ".", 2)
+
+ if t.Kind() == reflect.Slice {
+ // Get index, e.g. '0' from tag 'foo.0'
+ value, err := strconv.Atoi(parts[0])
+ if err != nil {
+ return reflect.Value{}, err
+ }
+
+ if t.Elem().Kind() == reflect.Struct {
+ // Make sure we are in range, otherwise return error
+ if value < 0 || value >= v.Len() {
+ return reflect.Value{}, errors.New("Index out of range")
+ }
+ return getFieldByTag(v.Index(value), t.Elem(), parts[1])
+ }
+
+ // Primitive type
+ return v.Index(value), nil
+ }
+
+ if t.Kind() == reflect.Struct {
+ // Find struct field with correct tag
+ for i := 0; i < t.NumField(); i++ {
+ value, ok := t.Field(i).Tag.Lookup("yaml")
+ if ok && strings.Split(value, ",")[0] == parts[0] {
+ if len(parts) == 1 {
+ return v.Field(i), nil
+ }
+ return getFieldByTag(v.Field(i), t.Field(i).Type, parts[1])
+ }
+ }
+ }
+
+ // Return its value if it's a primitive type
+ return v, nil
+}
diff --git a/shared/definition_test.go b/shared/definition_test.go
index ecb2f42..75e9506 100644
--- a/shared/definition_test.go
+++ b/shared/definition_test.go
@@ -201,3 +201,58 @@ func TestValidateDefinition(t *testing.T) {
}
}
}
+
+func TestDefinitionSetValue(t *testing.T) {
+ d := Definition{
+ Image: DefinitionImage{
+ Distribution: "ubuntu",
+ Release: "artful",
+ },
+ Source: DefinitionSource{
+ Downloader: "debootstrap",
+ URL: "https://ubuntu.com",
+ Keys: []string{"0xCODE"},
+ },
+ Packages: DefinitionPackages{
+ Manager: "apt",
+ },
+ Actions: []DefinitionAction{
+ {
+ Trigger: "post-update",
+ Action: "/bin/true",
+ },
+ {
+ Trigger: "post-packages",
+ Action: "/bin/false",
+ },
+ },
+ }
+
+ err := d.SetValue("image.release", "bionic")
+ if err != nil {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+ if d.Image.Release != "bionic" {
+ t.Fatalf("Expected '%s', got '%s'", "bionic", d.Image.Release)
+ }
+
+ err = d.SetValue("actions.0.trigger", "post-files")
+ if err != nil {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+ if d.Actions[0].Trigger != "post-files" {
+ t.Fatalf("Expected '%s', got '%s'", "post-files", d.Actions[0].Trigger)
+ }
+
+ // Index out of bounds
+ err = d.SetValue("actions.3.trigger", "post-files")
+ if err == nil || err.Error() != "Index out of range" {
+ t.Fatal("Expected index out of range")
+ }
+
+ // Nonsense
+ err = d.SetValue("image", "[foo: bar]")
+ if err == nil || err.Error() != "Cannot assign string value to struct" {
+ t.Fatal("Expected unsupported assignment")
+ }
+}
More information about the lxc-devel
mailing list