From c0b1baefa4602948d1de5d34c87aad8499a0edd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Fri, 15 Apr 2016 14:17:55 +0200 Subject: [PATCH 01/14] providers: ignition, basic config, version and config --- builtin/providers/ignition/provider.go | 18 ++ builtin/providers/ignition/provider_test.go | 13 ++ .../ignition/resource_ignition_file.go | 189 ++++++++++++++++++ .../ignition/resource_ignition_file_test.go | 105 ++++++++++ 4 files changed, 325 insertions(+) create mode 100644 builtin/providers/ignition/provider.go create mode 100644 builtin/providers/ignition/provider_test.go create mode 100644 builtin/providers/ignition/resource_ignition_file.go create mode 100644 builtin/providers/ignition/resource_ignition_file_test.go diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go new file mode 100644 index 000000000000..6280367ff7cb --- /dev/null +++ b/builtin/providers/ignition/provider.go @@ -0,0 +1,18 @@ +package ignition + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testProviders = map[string]terraform.ResourceProvider{ + "ignition": Provider(), +} + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "ignition_file": resourceFile(), + }, + } +} diff --git a/builtin/providers/ignition/provider_test.go b/builtin/providers/ignition/provider_test.go new file mode 100644 index 000000000000..7a07f0eca94e --- /dev/null +++ b/builtin/providers/ignition/provider_test.go @@ -0,0 +1,13 @@ +package ignition + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" +) + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/builtin/providers/ignition/resource_ignition_file.go b/builtin/providers/ignition/resource_ignition_file.go new file mode 100644 index 000000000000..8fa67d20bd43 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_file.go @@ -0,0 +1,189 @@ +package ignition + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "net/url" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/coreos/ignition/config/types" +) + +var configReference = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The URL of the config. Supported schemes are http. Note: When using http, it is advisable to use the verification option to ensure the contents haven’t been modified.", + }, + "verification": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The hash of the config (SHA512)", + }, + }, +} + +func resourceFile() *schema.Resource { + return &schema.Resource{ + Create: resourceIgnitionFileCreate, + Delete: resourceIgnitionFileDelete, + Exists: resourceIgnitionFileExists, + Read: resourceIgnitionFileRead, + + Schema: map[string]*schema.Schema{ + "version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "2.0.0", + Description: "The semantic version number of the spec", + ForceNew: true, + }, + "config": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "replace": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: configReference, + }, + "append": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: configReference, + }, + }, + }, + }, + "rendered": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "rendered template", + }, + }, + } +} + +func resourceIgnitionFileCreate(d *schema.ResourceData, meta interface{}) error { + rendered, err := renderIgnition(d) + if err != nil { + return err + } + + if err := d.Set("rendered", rendered); err != nil { + return err + } + + d.SetId(hash(rendered)) + return nil +} + +func resourceIgnitionFileDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceIgnitionFileExists(d *schema.ResourceData, meta interface{}) (bool, error) { + rendered, err := renderIgnition(d) + if err != nil { + return false, err + } + + return hash(rendered) == d.Id(), nil +} + +func resourceIgnitionFileRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func renderIgnition(d *schema.ResourceData) (string, error) { + i, err := buildIgnition(d) + if err != nil { + return "", err + } + + bytes, err := json.MarshalIndent(i, " ", " ") + if err != nil { + return "", err + } + + return string(bytes), nil +} + +func buildIgnition(d *schema.ResourceData) (*types.Ignition, error) { + var err error + + i := &types.Ignition{} + i.Version.UnmarshalJSON([]byte(`"2.0.0"`)) + + rr := d.Get("config.0.replace.0").(map[string]interface{}) + if len(rr) != 0 { + i.Config.Replace, err = buildConfigReference(rr) + if err != nil { + return nil, err + } + } + + ar := d.Get("config.0.append").([]interface{}) + if len(ar) != 0 { + for _, rr := range ar { + r, err := buildConfigReference(rr.(map[string]interface{})) + if err != nil { + return nil, err + } + + i.Config.Append = append(i.Config.Append, *r) + } + } + + return i, nil +} + +func buildConfigReference(raw map[string]interface{}) (*types.ConfigReference, error) { + r := &types.ConfigReference{} + + src, err := buildURL(raw["source"].(string)) + if err != nil { + return nil, err + } + + r.Source = src + + hash, err := buildHash(raw["verification"].(string)) + if err != nil { + return nil, err + } + + r.Verification.Hash = &hash + + return r, nil +} + +func buildURL(raw string) (types.Url, error) { + u, err := url.Parse(raw) + if err != nil { + return types.Url{}, err + } + + return types.Url(*u), nil +} + +func buildHash(raw string) (types.Hash, error) { + h := types.Hash{} + err := h.UnmarshalJSON([]byte(fmt.Sprintf("%q", raw))) + + return h, err +} + +func hash(s string) string { + sha := sha256.Sum256([]byte(s)) + return hex.EncodeToString(sha[:]) +} diff --git a/builtin/providers/ignition/resource_ignition_file_test.go b/builtin/providers/ignition/resource_ignition_file_test.go new file mode 100644 index 000000000000..76375f37c99a --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_file_test.go @@ -0,0 +1,105 @@ +package ignition + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestIngnitionFileReplace(t *testing.T) { + testIgnition(t, ` + resource "ignition_file" "test" { + config { + replace { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + } + `, func(i *types.Ignition) error { + r := i.Config.Replace + if r == nil { + return fmt.Errorf("unable to find replace config") + } + + if r.Source.String() != "foo" { + return fmt.Errorf("config.replace.source, found %q", r.Source) + } + + if r.Verification.Hash.Sum != "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" { + return fmt.Errorf("config.replace.verification, found %q", r.Verification.Hash) + } + + return nil + }) +} + +func TestIngnitionFileAppend(t *testing.T) { + testIgnition(t, ` + resource "ignition_file" "test" { + config { + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + } + `, func(i *types.Ignition) error { + a := i.Config.Append + if len(a) != 2 { + return fmt.Errorf("unable to find append config, expected 2") + } + + if a[0].Source.String() != "foo" { + return fmt.Errorf("config.replace.source, found %q", a[0].Source) + } + + if a[0].Verification.Hash.Sum != "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" { + return fmt.Errorf("config.replace.verification, found %q", a[0].Verification.Hash) + } + + return nil + }) +} + +func testIgnition(t *testing.T, input string, assert func(*types.Ignition) error) { + check := func(s *terraform.State) error { + got := s.RootModule().Outputs["rendered"] + + i := &types.Ignition{} + err := json.Unmarshal([]byte(got), i) + if err != nil { + return err + } + + return assert(i) + } + + resource.Test(t, resource.TestCase{ + Providers: testProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testTemplate, input), + Check: check, + }, + }, + }) +} + +var testTemplate = ` +%s + +output "rendered" { + value = "${ignition_file.test.rendered}" +} + +` From ebe1436ae6505ca752bb20dac16bc247da44b967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Fri, 15 Apr 2016 16:28:12 +0200 Subject: [PATCH 02/14] providers: ignition, user and passwd example, general cache implementation --- builtin/providers/ignition/provider.go | 34 ++++- builtin/providers/ignition/provider_test.go | 5 + ...on_file.go => resource_ignition_config.go} | 119 +++++++++++------- ...st.go => resource_ignition_config_test.go} | 56 +++++---- .../ignition/resource_ignition_user.go | 64 ++++++++++ .../ignition/resource_ignition_user_test.go | 53 ++++++++ 6 files changed, 259 insertions(+), 72 deletions(-) rename builtin/providers/ignition/{resource_ignition_file.go => resource_ignition_config.go} (59%) rename builtin/providers/ignition/{resource_ignition_file_test.go => resource_ignition_config_test.go} (61%) create mode 100644 builtin/providers/ignition/resource_ignition_user.go create mode 100644 builtin/providers/ignition/resource_ignition_user_test.go diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go index 6280367ff7cb..130965e51c86 100644 --- a/builtin/providers/ignition/provider.go +++ b/builtin/providers/ignition/provider.go @@ -1,18 +1,46 @@ package ignition import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + + "github.com/coreos/ignition/config/types" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) -var testProviders = map[string]terraform.ResourceProvider{ - "ignition": Provider(), +type cache struct { + users map[string]*types.User } func Provider() terraform.ResourceProvider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ - "ignition_file": resourceFile(), + "ignition_config": resourceConfig(), + "ignition_user": resourceUser(), + }, + ConfigureFunc: func(*schema.ResourceData) (interface{}, error) { + return &cache{ + users: make(map[string]*types.User, 0), + }, nil }, } } + +func (c *cache) addUser(u *types.User) string { + id := id(u) + c.users[id] = u + + return id +} + +func id(input interface{}) string { + b, _ := json.Marshal(input) + return hash(string(b)) +} + +func hash(s string) string { + sha := sha256.Sum256([]byte(s)) + return hex.EncodeToString(sha[:]) +} diff --git a/builtin/providers/ignition/provider_test.go b/builtin/providers/ignition/provider_test.go index 7a07f0eca94e..10f95481c886 100644 --- a/builtin/providers/ignition/provider_test.go +++ b/builtin/providers/ignition/provider_test.go @@ -4,8 +4,13 @@ import ( "testing" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" ) +var testProviders = map[string]terraform.ResourceProvider{ + "ignition": Provider(), +} + func TestProvider(t *testing.T) { if err := Provider().(*schema.Provider).InternalValidate(); err != nil { t.Fatalf("err: %s", err) diff --git a/builtin/providers/ignition/resource_ignition_file.go b/builtin/providers/ignition/resource_ignition_config.go similarity index 59% rename from builtin/providers/ignition/resource_ignition_file.go rename to builtin/providers/ignition/resource_ignition_config.go index 8fa67d20bd43..c79da6c8a418 100644 --- a/builtin/providers/ignition/resource_ignition_file.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -1,8 +1,6 @@ package ignition import ( - "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "net/url" @@ -12,7 +10,36 @@ import ( "github.com/coreos/ignition/config/types" ) -var configReference = &schema.Resource{ +var ignitionResource = &schema.Resource{ + Create: resourceIgnitionFileCreate, + Delete: resourceIgnitionFileDelete, + Exists: resourceIgnitionFileExists, + Read: resourceIgnitionFileRead, + Schema: map[string]*schema.Schema{ + "config": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "replace": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: configReferenceResource, + }, + "append": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: configReferenceResource, + }, + }, + }, + }, + }, +} +var configReferenceResource = &schema.Resource{ Schema: map[string]*schema.Schema{ "source": &schema.Schema{ Type: schema.TypeString, @@ -27,41 +54,25 @@ var configReference = &schema.Resource{ }, } -func resourceFile() *schema.Resource { +func resourceConfig() *schema.Resource { return &schema.Resource{ Create: resourceIgnitionFileCreate, Delete: resourceIgnitionFileDelete, Exists: resourceIgnitionFileExists, Read: resourceIgnitionFileRead, - Schema: map[string]*schema.Schema{ - "version": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "2.0.0", - Description: "The semantic version number of the spec", - ForceNew: true, - }, - "config": &schema.Schema{ + "ignition": &schema.Schema{ Type: schema.TypeList, Optional: true, MaxItems: 1, ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "replace": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: configReference, - }, - "append": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: configReference, - }, - }, - }, + Elem: ignitionResource, + }, + "users": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, "rendered": &schema.Schema{ Type: schema.TypeString, @@ -73,7 +84,7 @@ func resourceFile() *schema.Resource { } func resourceIgnitionFileCreate(d *schema.ResourceData, meta interface{}) error { - rendered, err := renderIgnition(d) + rendered, err := renderConfig(d, meta.(*cache)) if err != nil { return err } @@ -92,7 +103,7 @@ func resourceIgnitionFileDelete(d *schema.ResourceData, meta interface{}) error } func resourceIgnitionFileExists(d *schema.ResourceData, meta interface{}) (bool, error) { - rendered, err := renderIgnition(d) + rendered, err := renderConfig(d, meta.(*cache)) if err != nil { return false, err } @@ -104,8 +115,8 @@ func resourceIgnitionFileRead(d *schema.ResourceData, meta interface{}) error { return nil } -func renderIgnition(d *schema.ResourceData) (string, error) { - i, err := buildIgnition(d) +func renderConfig(d *schema.ResourceData, c *cache) (string, error) { + i, err := buildConfig(d, c) if err != nil { return "", err } @@ -118,26 +129,42 @@ func renderIgnition(d *schema.ResourceData) (string, error) { return string(bytes), nil } -func buildIgnition(d *schema.ResourceData) (*types.Ignition, error) { +func buildConfig(d *schema.ResourceData, c *cache) (*types.Config, error) { + var err error + config := &types.Config{} + config.Ignition, err = buildIgnition(d) + if err != nil { + return nil, err + } + + config.Passwd, err = buildPasswd(d, c) + if err != nil { + return nil, err + } + + return config, nil +} + +func buildIgnition(d *schema.ResourceData) (types.Ignition, error) { var err error - i := &types.Ignition{} + i := types.Ignition{} i.Version.UnmarshalJSON([]byte(`"2.0.0"`)) - rr := d.Get("config.0.replace.0").(map[string]interface{}) + rr := d.Get("ignition.0.config.0.replace.0").(map[string]interface{}) if len(rr) != 0 { i.Config.Replace, err = buildConfigReference(rr) if err != nil { - return nil, err + return i, err } } - ar := d.Get("config.0.append").([]interface{}) + ar := d.Get("ignition.0.config.0.append").([]interface{}) if len(ar) != 0 { for _, rr := range ar { r, err := buildConfigReference(rr.(map[string]interface{})) if err != nil { - return nil, err + return i, err } i.Config.Append = append(i.Config.Append, *r) @@ -167,6 +194,17 @@ func buildConfigReference(raw map[string]interface{}) (*types.ConfigReference, e return r, nil } +func buildPasswd(d *schema.ResourceData, c *cache) (types.Passwd, error) { + passwd := types.Passwd{} + + for _, id := range d.Get("users").([]interface{}) { + passwd.Users = append(passwd.Users, *c.users[id.(string)]) + } + + return passwd, nil + +} + func buildURL(raw string) (types.Url, error) { u, err := url.Parse(raw) if err != nil { @@ -182,8 +220,3 @@ func buildHash(raw string) (types.Hash, error) { return h, err } - -func hash(s string) string { - sha := sha256.Sum256([]byte(s)) - return hex.EncodeToString(sha[:]) -} diff --git a/builtin/providers/ignition/resource_ignition_file_test.go b/builtin/providers/ignition/resource_ignition_config_test.go similarity index 61% rename from builtin/providers/ignition/resource_ignition_file_test.go rename to builtin/providers/ignition/resource_ignition_config_test.go index 76375f37c99a..62a189968dc8 100644 --- a/builtin/providers/ignition/resource_ignition_file_test.go +++ b/builtin/providers/ignition/resource_ignition_config_test.go @@ -12,16 +12,18 @@ import ( func TestIngnitionFileReplace(t *testing.T) { testIgnition(t, ` - resource "ignition_file" "test" { - config { - replace { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } + resource "ignition_config" "test" { + ignition { + config { + replace { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } } } - `, func(i *types.Ignition) error { - r := i.Config.Replace + `, func(c *types.Config) error { + r := c.Ignition.Config.Replace if r == nil { return fmt.Errorf("unable to find replace config") } @@ -40,21 +42,23 @@ func TestIngnitionFileReplace(t *testing.T) { func TestIngnitionFileAppend(t *testing.T) { testIgnition(t, ` - resource "ignition_file" "test" { - config { - append { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } - - append { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } + resource "ignition_config" "test" { + ignition { + config { + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } } } - `, func(i *types.Ignition) error { - a := i.Config.Append + `, func(c *types.Config) error { + a := c.Ignition.Config.Append if len(a) != 2 { return fmt.Errorf("unable to find append config, expected 2") } @@ -71,17 +75,17 @@ func TestIngnitionFileAppend(t *testing.T) { }) } -func testIgnition(t *testing.T, input string, assert func(*types.Ignition) error) { +func testIgnition(t *testing.T, input string, assert func(*types.Config) error) { check := func(s *terraform.State) error { got := s.RootModule().Outputs["rendered"] - i := &types.Ignition{} - err := json.Unmarshal([]byte(got), i) + c := &types.Config{} + err := json.Unmarshal([]byte(got), c) if err != nil { return err } - return assert(i) + return assert(c) } resource.Test(t, resource.TestCase{ @@ -99,7 +103,7 @@ var testTemplate = ` %s output "rendered" { - value = "${ignition_file.test.rendered}" + value = "${ignition_config.test.rendered}" } ` diff --git a/builtin/providers/ignition/resource_ignition_user.go b/builtin/providers/ignition/resource_ignition_user.go new file mode 100644 index 000000000000..b3f4c2ea3094 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_user.go @@ -0,0 +1,64 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceUser() *schema.Resource { + return &schema.Resource{ + Create: resourceUserCreate, + Delete: resourceUserDelete, + Exists: resourceUserExists, + Read: resourceUserRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password_hash": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceUserCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildUser(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + d.Set("id", id) + return nil +} + +func resourceUserDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceUserExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildUser(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceUserRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildUser(d *schema.ResourceData, c *cache) (string, error) { + u := &types.User{} + u.Name = d.Get("name").(string) + u.PasswordHash = d.Get("password_hash").(string) + + return c.addUser(u), nil +} diff --git a/builtin/providers/ignition/resource_ignition_user_test.go b/builtin/providers/ignition/resource_ignition_user_test.go new file mode 100644 index 000000000000..151f0ef7a1f4 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_user_test.go @@ -0,0 +1,53 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitiondUsers(t *testing.T) { + testIgnition(t, ` + resource "ignition_user" "foo" { + name = "foo" + password_hash = "foo" + } + + resource "ignition_user" "qux" { + name = "qux" + password_hash = "qux" + } + + resource "ignition_config" "test" { + ignition { + config { + replace { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + } + + users = [ + "${ignition_user.foo.id}", + "${ignition_user.qux.id}", + ] + } + `, func(c *types.Config) error { + r := c.Ignition.Config.Replace + if r == nil { + return fmt.Errorf("unable to find replace config") + } + + if r.Source.String() != "foo" { + return fmt.Errorf("config.replace.source, found %q", r.Source) + } + + if r.Verification.Hash.Sum != "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" { + return fmt.Errorf("config.replace.verification, found %q", r.Verification.Hash) + } + + return nil + }) +} From b3fadb05d5e3834169c36e5d3fe63b707c111a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Fri, 15 Apr 2016 15:43:19 +0000 Subject: [PATCH 03/14] vendor: Capture new dependency upstream-pkg --- vendor/github.com/alecthomas/units/COPYING | 19 ++ vendor/github.com/alecthomas/units/README.md | 11 + vendor/github.com/alecthomas/units/bytes.go | 83 ++++++ vendor/github.com/alecthomas/units/doc.go | 13 + vendor/github.com/alecthomas/units/si.go | 26 ++ vendor/github.com/alecthomas/units/util.go | 138 ++++++++++ vendor/github.com/coreos/go-semver/LICENSE | 202 +++++++++++++++ .../coreos/go-semver/semver/semver.go | 244 ++++++++++++++++++ .../coreos/go-semver/semver/sort.go | 38 +++ vendor/github.com/coreos/ignition/LICENSE | 202 +++++++++++++++ vendor/github.com/coreos/ignition/NOTICE | 5 + .../ignition/config/types/compression.go | 54 ++++ .../coreos/ignition/config/types/config.go | 34 +++ .../coreos/ignition/config/types/disk.go | 167 ++++++++++++ .../coreos/ignition/config/types/file.go | 78 ++++++ .../ignition/config/types/filesystem.go | 181 +++++++++++++ .../coreos/ignition/config/types/group.go | 22 ++ .../coreos/ignition/config/types/hash.go | 81 ++++++ .../coreos/ignition/config/types/ignition.go | 77 ++++++ .../coreos/ignition/config/types/networkd.go | 19 ++ .../coreos/ignition/config/types/partition.go | 137 ++++++++++ .../coreos/ignition/config/types/passwd.go | 20 ++ .../coreos/ignition/config/types/path.go | 59 +++++ .../coreos/ignition/config/types/raid.go | 65 +++++ .../coreos/ignition/config/types/storage.go | 22 ++ .../coreos/ignition/config/types/systemd.go | 19 ++ .../coreos/ignition/config/types/unit.go | 135 ++++++++++ .../coreos/ignition/config/types/url.go | 52 ++++ .../coreos/ignition/config/types/user.go | 35 +++ .../ignition/config/types/verification.go | 19 ++ 30 files changed, 2257 insertions(+) create mode 100644 vendor/github.com/alecthomas/units/COPYING create mode 100644 vendor/github.com/alecthomas/units/README.md create mode 100644 vendor/github.com/alecthomas/units/bytes.go create mode 100644 vendor/github.com/alecthomas/units/doc.go create mode 100644 vendor/github.com/alecthomas/units/si.go create mode 100644 vendor/github.com/alecthomas/units/util.go create mode 100644 vendor/github.com/coreos/go-semver/LICENSE create mode 100644 vendor/github.com/coreos/go-semver/semver/semver.go create mode 100644 vendor/github.com/coreos/go-semver/semver/sort.go create mode 100644 vendor/github.com/coreos/ignition/LICENSE create mode 100644 vendor/github.com/coreos/ignition/NOTICE create mode 100644 vendor/github.com/coreos/ignition/config/types/compression.go create mode 100644 vendor/github.com/coreos/ignition/config/types/config.go create mode 100644 vendor/github.com/coreos/ignition/config/types/disk.go create mode 100644 vendor/github.com/coreos/ignition/config/types/file.go create mode 100644 vendor/github.com/coreos/ignition/config/types/filesystem.go create mode 100644 vendor/github.com/coreos/ignition/config/types/group.go create mode 100644 vendor/github.com/coreos/ignition/config/types/hash.go create mode 100644 vendor/github.com/coreos/ignition/config/types/ignition.go create mode 100644 vendor/github.com/coreos/ignition/config/types/networkd.go create mode 100644 vendor/github.com/coreos/ignition/config/types/partition.go create mode 100644 vendor/github.com/coreos/ignition/config/types/passwd.go create mode 100644 vendor/github.com/coreos/ignition/config/types/path.go create mode 100644 vendor/github.com/coreos/ignition/config/types/raid.go create mode 100644 vendor/github.com/coreos/ignition/config/types/storage.go create mode 100644 vendor/github.com/coreos/ignition/config/types/systemd.go create mode 100644 vendor/github.com/coreos/ignition/config/types/unit.go create mode 100644 vendor/github.com/coreos/ignition/config/types/url.go create mode 100644 vendor/github.com/coreos/ignition/config/types/user.go create mode 100644 vendor/github.com/coreos/ignition/config/types/verification.go diff --git a/vendor/github.com/alecthomas/units/COPYING b/vendor/github.com/alecthomas/units/COPYING new file mode 100644 index 000000000000..2993ec085d33 --- /dev/null +++ b/vendor/github.com/alecthomas/units/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2014 Alec Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/alecthomas/units/README.md b/vendor/github.com/alecthomas/units/README.md new file mode 100644 index 000000000000..bee884e3c1ca --- /dev/null +++ b/vendor/github.com/alecthomas/units/README.md @@ -0,0 +1,11 @@ +# Units - Helpful unit multipliers and functions for Go + +The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package. + +It allows for code like this: + +```go +n, err := ParseBase2Bytes("1KB") +// n == 1024 +n = units.Mebibyte * 512 +``` diff --git a/vendor/github.com/alecthomas/units/bytes.go b/vendor/github.com/alecthomas/units/bytes.go new file mode 100644 index 000000000000..eaadeb8005a5 --- /dev/null +++ b/vendor/github.com/alecthomas/units/bytes.go @@ -0,0 +1,83 @@ +package units + +// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte, +// etc.). +type Base2Bytes int64 + +// Base-2 byte units. +const ( + Kibibyte Base2Bytes = 1024 + KiB = Kibibyte + Mebibyte = Kibibyte * 1024 + MiB = Mebibyte + Gibibyte = Mebibyte * 1024 + GiB = Gibibyte + Tebibyte = Gibibyte * 1024 + TiB = Tebibyte + Pebibyte = Tebibyte * 1024 + PiB = Pebibyte + Exbibyte = Pebibyte * 1024 + EiB = Exbibyte +) + +var ( + bytesUnitMap = MakeUnitMap("iB", "B", 1024) + oldBytesUnitMap = MakeUnitMap("B", "B", 1024) +) + +// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB +// and KiB are both 1024. +func ParseBase2Bytes(s string) (Base2Bytes, error) { + n, err := ParseUnit(s, bytesUnitMap) + if err != nil { + n, err = ParseUnit(s, oldBytesUnitMap) + } + return Base2Bytes(n), err +} + +func (b Base2Bytes) String() string { + return ToString(int64(b), 1024, "iB", "B") +} + +var ( + metricBytesUnitMap = MakeUnitMap("B", "B", 1000) +) + +// MetricBytes are SI byte units (1000 bytes in a kilobyte). +type MetricBytes SI + +// SI base-10 byte units. +const ( + Kilobyte MetricBytes = 1000 + KB = Kilobyte + Megabyte = Kilobyte * 1000 + MB = Megabyte + Gigabyte = Megabyte * 1000 + GB = Gigabyte + Terabyte = Gigabyte * 1000 + TB = Terabyte + Petabyte = Terabyte * 1000 + PB = Petabyte + Exabyte = Petabyte * 1000 + EB = Exabyte +) + +// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes. +func ParseMetricBytes(s string) (MetricBytes, error) { + n, err := ParseUnit(s, metricBytesUnitMap) + return MetricBytes(n), err +} + +func (m MetricBytes) String() string { + return ToString(int64(m), 1000, "B", "B") +} + +// ParseStrictBytes supports both iB and B suffixes for base 2 and metric, +// respectively. That is, KiB represents 1024 and KB represents 1000. +func ParseStrictBytes(s string) (int64, error) { + n, err := ParseUnit(s, bytesUnitMap) + if err != nil { + n, err = ParseUnit(s, metricBytesUnitMap) + } + return int64(n), err +} diff --git a/vendor/github.com/alecthomas/units/doc.go b/vendor/github.com/alecthomas/units/doc.go new file mode 100644 index 000000000000..156ae386723c --- /dev/null +++ b/vendor/github.com/alecthomas/units/doc.go @@ -0,0 +1,13 @@ +// Package units provides helpful unit multipliers and functions for Go. +// +// The goal of this package is to have functionality similar to the time [1] package. +// +// +// [1] http://golang.org/pkg/time/ +// +// It allows for code like this: +// +// n, err := ParseBase2Bytes("1KB") +// // n == 1024 +// n = units.Mebibyte * 512 +package units diff --git a/vendor/github.com/alecthomas/units/si.go b/vendor/github.com/alecthomas/units/si.go new file mode 100644 index 000000000000..8234a9d52cb3 --- /dev/null +++ b/vendor/github.com/alecthomas/units/si.go @@ -0,0 +1,26 @@ +package units + +// SI units. +type SI int64 + +// SI unit multiples. +const ( + Kilo SI = 1000 + Mega = Kilo * 1000 + Giga = Mega * 1000 + Tera = Giga * 1000 + Peta = Tera * 1000 + Exa = Peta * 1000 +) + +func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 { + return map[string]float64{ + shortSuffix: 1, + "K" + suffix: float64(scale), + "M" + suffix: float64(scale * scale), + "G" + suffix: float64(scale * scale * scale), + "T" + suffix: float64(scale * scale * scale * scale), + "P" + suffix: float64(scale * scale * scale * scale * scale), + "E" + suffix: float64(scale * scale * scale * scale * scale * scale), + } +} diff --git a/vendor/github.com/alecthomas/units/util.go b/vendor/github.com/alecthomas/units/util.go new file mode 100644 index 000000000000..6527e92d1645 --- /dev/null +++ b/vendor/github.com/alecthomas/units/util.go @@ -0,0 +1,138 @@ +package units + +import ( + "errors" + "fmt" + "strings" +) + +var ( + siUnits = []string{"", "K", "M", "G", "T", "P", "E"} +) + +func ToString(n int64, scale int64, suffix, baseSuffix string) string { + mn := len(siUnits) + out := make([]string, mn) + for i, m := range siUnits { + if n%scale != 0 || i == 0 && n == 0 { + s := suffix + if i == 0 { + s = baseSuffix + } + out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s) + } + n /= scale + if n == 0 { + break + } + } + return strings.Join(out, "") +} + +// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123 +var errLeadingInt = errors.New("units: bad [0-9]*") // never printed + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x int64, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x >= (1<<63-10)/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + int64(c) - '0' + } + return x, s[i:], nil +} + +func ParseUnit(s string, unitMap map[string]float64) (int64, error) { + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + orig := s + f := float64(0) + neg := false + + // Consume [-+]? + if s != "" { + c := s[0] + if c == '-' || c == '+' { + neg = c == '-' + s = s[1:] + } + } + // Special case: if all that is left is "0", this is zero. + if s == "0" { + return 0, nil + } + if s == "" { + return 0, errors.New("units: invalid " + orig) + } + for s != "" { + g := float64(0) // this element of the sequence + + var x int64 + var err error + + // The next character must be [0-9.] + if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) { + return 0, errors.New("units: invalid " + orig) + } + // Consume [0-9]* + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("units: invalid " + orig) + } + g = float64(x) + pre := pl != len(s) // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + post := false + if s != "" && s[0] == '.' { + s = s[1:] + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("units: invalid " + orig) + } + scale := 1.0 + for n := pl - len(s); n > 0; n-- { + scale *= 10 + } + g += float64(x) / scale + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errors.New("units: invalid " + orig) + } + + // Consume unit. + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c == '.' || ('0' <= c && c <= '9') { + break + } + } + u := s[:i] + s = s[i:] + unit, ok := unitMap[u] + if !ok { + return 0, errors.New("units: unknown unit " + u + " in " + orig) + } + + f += g * unit + } + + if neg { + f = -f + } + if f < float64(-1<<63) || f > float64(1<<63-1) { + return 0, errors.New("units: overflow parsing unit") + } + return int64(f), nil +} diff --git a/vendor/github.com/coreos/go-semver/LICENSE b/vendor/github.com/coreos/go-semver/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/vendor/github.com/coreos/go-semver/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/coreos/go-semver/semver/semver.go b/vendor/github.com/coreos/go-semver/semver/semver.go new file mode 100644 index 000000000000..a14ca142254b --- /dev/null +++ b/vendor/github.com/coreos/go-semver/semver/semver.go @@ -0,0 +1,244 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Semantic Versions http://semver.org +package semver + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" +) + +type Version struct { + Major int64 + Minor int64 + Patch int64 + PreRelease PreRelease + Metadata string +} + +type PreRelease string + +func splitOff(input *string, delim string) (val string) { + parts := strings.SplitN(*input, delim, 2) + + if len(parts) == 2 { + *input = parts[0] + val = parts[1] + } + + return val +} + +func NewVersion(version string) (*Version, error) { + v := Version{} + + v.Metadata = splitOff(&version, "+") + v.PreRelease = PreRelease(splitOff(&version, "-")) + + dotParts := strings.SplitN(version, ".", 3) + + if len(dotParts) != 3 { + return nil, errors.New(fmt.Sprintf("%s is not in dotted-tri format", version)) + } + + parsed := make([]int64, 3, 3) + + for i, v := range dotParts[:3] { + val, err := strconv.ParseInt(v, 10, 64) + parsed[i] = val + if err != nil { + return nil, err + } + } + + v.Major = parsed[0] + v.Minor = parsed[1] + v.Patch = parsed[2] + + return &v, nil +} + +func Must(v *Version, err error) *Version { + if err != nil { + panic(err) + } + return v +} + +func (v Version) String() string { + var buffer bytes.Buffer + + fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch) + + if v.PreRelease != "" { + fmt.Fprintf(&buffer, "-%s", v.PreRelease) + } + + if v.Metadata != "" { + fmt.Fprintf(&buffer, "+%s", v.Metadata) + } + + return buffer.String() +} + +func (v Version) MarshalJSON() ([]byte, error) { + return []byte(`"` + v.String() + `"`), nil +} + +func (v *Version) UnmarshalJSON(data []byte) error { + l := len(data) + if l == 0 || string(data) == `""` { + return nil + } + if l < 2 || data[0] != '"' || data[l-1] != '"' { + return errors.New("invalid semver string") + } + vv, err := NewVersion(string(data[1 : l-1])) + if err != nil { + return err + } + *v = *vv + return nil +} + +func (v Version) LessThan(versionB Version) bool { + versionA := v + cmp := recursiveCompare(versionA.Slice(), versionB.Slice()) + + if cmp == 0 { + cmp = preReleaseCompare(versionA, versionB) + } + + if cmp == -1 { + return true + } + + return false +} + +/* Slice converts the comparable parts of the semver into a slice of strings */ +func (v Version) Slice() []int64 { + return []int64{v.Major, v.Minor, v.Patch} +} + +func (p PreRelease) Slice() []string { + preRelease := string(p) + return strings.Split(preRelease, ".") +} + +func preReleaseCompare(versionA Version, versionB Version) int { + a := versionA.PreRelease + b := versionB.PreRelease + + /* Handle the case where if two versions are otherwise equal it is the + * one without a PreRelease that is greater */ + if len(a) == 0 && (len(b) > 0) { + return 1 + } else if len(b) == 0 && (len(a) > 0) { + return -1 + } + + // If there is a prelease, check and compare each part. + return recursivePreReleaseCompare(a.Slice(), b.Slice()) +} + +func recursiveCompare(versionA []int64, versionB []int64) int { + if len(versionA) == 0 { + return 0 + } + + a := versionA[0] + b := versionB[0] + + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursiveCompare(versionA[1:], versionB[1:]) +} + +func recursivePreReleaseCompare(versionA []string, versionB []string) int { + // Handle slice length disparity. + if len(versionA) == 0 { + // Nothing to compare too, so we return 0 + return 0 + } else if len(versionB) == 0 { + // We're longer than versionB so return 1. + return 1 + } + + a := versionA[0] + b := versionB[0] + + aInt := false + bInt := false + + aI, err := strconv.Atoi(versionA[0]) + if err == nil { + aInt = true + } + + bI, err := strconv.Atoi(versionB[0]) + if err == nil { + bInt = true + } + + // Handle Integer Comparison + if aInt && bInt { + if aI > bI { + return 1 + } else if aI < bI { + return -1 + } + } + + // Handle String Comparison + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursivePreReleaseCompare(versionA[1:], versionB[1:]) +} + +// BumpMajor increments the Major field by 1 and resets all other fields to their default values +func (v *Version) BumpMajor() { + v.Major += 1 + v.Minor = 0 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// BumpMinor increments the Minor field by 1 and resets all other fields to their default values +func (v *Version) BumpMinor() { + v.Minor += 1 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// BumpPatch increments the Patch field by 1 and resets all other fields to their default values +func (v *Version) BumpPatch() { + v.Patch += 1 + v.PreRelease = PreRelease("") + v.Metadata = "" +} diff --git a/vendor/github.com/coreos/go-semver/semver/sort.go b/vendor/github.com/coreos/go-semver/semver/sort.go new file mode 100644 index 000000000000..e256b41a5ddf --- /dev/null +++ b/vendor/github.com/coreos/go-semver/semver/sort.go @@ -0,0 +1,38 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package semver + +import ( + "sort" +) + +type Versions []*Version + +func (s Versions) Len() int { + return len(s) +} + +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s Versions) Less(i, j int) bool { + return s[i].LessThan(*s[j]) +} + +// Sort sorts the given slice of Version +func Sort(versions []*Version) { + sort.Sort(Versions(versions)) +} diff --git a/vendor/github.com/coreos/ignition/LICENSE b/vendor/github.com/coreos/ignition/LICENSE new file mode 100644 index 000000000000..e06d2081865a --- /dev/null +++ b/vendor/github.com/coreos/ignition/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/coreos/ignition/NOTICE b/vendor/github.com/coreos/ignition/NOTICE new file mode 100644 index 000000000000..e520005cdda3 --- /dev/null +++ b/vendor/github.com/coreos/ignition/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2015 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/ignition/config/types/compression.go b/vendor/github.com/coreos/ignition/config/types/compression.go new file mode 100644 index 000000000000..a8069756f68e --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/compression.go @@ -0,0 +1,54 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" +) + +var ( + ErrCompressionInvalid = errors.New("invalid compression method") +) + +type Compression string + +func (c *Compression) UnmarshalYAML(unmarshal func(interface{}) error) error { + return c.unmarshal(unmarshal) +} + +func (c *Compression) UnmarshalJSON(data []byte) error { + return c.unmarshal(func(tc interface{}) error { + return json.Unmarshal(data, tc) + }) +} + +func (c *Compression) unmarshal(unmarshal func(interface{}) error) error { + var tc string + if err := unmarshal(&tc); err != nil { + return err + } + *c = Compression(tc) + return c.assertValid() +} + +func (c Compression) assertValid() error { + switch c { + case "gzip": + default: + return ErrCompressionInvalid + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/config.go b/vendor/github.com/coreos/ignition/config/types/config.go new file mode 100644 index 000000000000..7d7b60face36 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/config.go @@ -0,0 +1,34 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/go-semver/semver" +) + +var ( + MaxVersion = semver.Version{ + Major: 2, + Minor: 0, + } +) + +type Config struct { + Ignition Ignition `json:"ignition" yaml:"ignition"` + Storage Storage `json:"storage,omitempty" yaml:"storage"` + Systemd Systemd `json:"systemd,omitempty" yaml:"systemd"` + Networkd Networkd `json:"networkd,omitempty" yaml:"networkd"` + Passwd Passwd `json:"passwd,omitempty" yaml:"passwd"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/disk.go b/vendor/github.com/coreos/ignition/config/types/disk.go new file mode 100644 index 000000000000..e0f39483aef7 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/disk.go @@ -0,0 +1,167 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "fmt" +) + +type Disk struct { + Device Path `json:"device,omitempty" yaml:"device"` + WipeTable bool `json:"wipeTable,omitempty" yaml:"wipe_table"` + Partitions []Partition `json:"partitions,omitempty" yaml:"partitions"` +} + +func (n *Disk) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := n.unmarshal(unmarshal); err != nil { + return err + } + if err := n.preparePartitions(); err != nil { + return err + } + return n.assertValid() +} + +func (n *Disk) UnmarshalJSON(data []byte) error { + err := n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) + if err != nil { + return err + } + return n.assertValid() +} + +type disk Disk + +func (n *Disk) unmarshal(unmarshal func(interface{}) error) error { + tn := disk(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = Disk(tn) + return nil +} + +func (n Disk) assertValid() error { + // This applies to YAML (post-prepare) and JSON unmarshals equally: + if len(n.Device) == 0 { + return fmt.Errorf("disk device is required") + } + if n.partitionNumbersCollide() { + return fmt.Errorf("disk %q: partition numbers collide", n.Device) + } + if n.partitionsOverlap() { + return fmt.Errorf("disk %q: partitions overlap", n.Device) + } + if n.partitionsMisaligned() { + return fmt.Errorf("disk %q: partitions misaligned", n.Device) + } + // Disks which get to this point will likely succeed in sgdisk + return nil +} + +// partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique. +func (n Disk) partitionNumbersCollide() bool { + m := map[int][]Partition{} + for _, p := range n.Partitions { + m[p.Number] = append(m[p.Number], p) + } + for _, n := range m { + if len(n) > 1 { + // TODO(vc): return information describing the collision for logging + return true + } + } + return false +} + +// end returns the last sector of a partition. +func (p Partition) end() PartitionDimension { + if p.Size == 0 { + // a size of 0 means "fill available", just return the start as the end for those. + return p.Start + } + return p.Start + p.Size - 1 +} + +// partitionsOverlap returns true if any explicitly dimensioned partitions overlap +func (n Disk) partitionsOverlap() bool { + for _, p := range n.Partitions { + // Starts of 0 are placed by sgdisk into the "largest available block" at that time. + // We aren't going to check those for overlap since we don't have the disk geometry. + if p.Start == 0 { + continue + } + + for _, o := range n.Partitions { + if p == o || o.Start == 0 { + continue + } + + // is p.Start within o? + if p.Start >= o.Start && p.Start <= o.end() { + return true + } + + // is p.end() within o? + if p.end() >= o.Start && p.end() <= o.end() { + return true + } + + // do p.Start and p.end() straddle o? + if p.Start < o.Start && p.end() > o.end() { + return true + } + } + } + return false +} + +// partitionsMisaligned returns true if any of the partitions don't start on a 2048-sector (1MiB) boundary. +func (n Disk) partitionsMisaligned() bool { + for _, p := range n.Partitions { + if (p.Start & (2048 - 1)) != 0 { + return true + } + } + return false +} + +// preparePartitions performs some checks and potentially adjusts the partitions for alignment. +// This is only invoked when unmarshalling YAML, since there we parse human-friendly units. +func (n *Disk) preparePartitions() error { + // On the YAML side we accept human-friendly units which may require massaging. + + // align partition starts + for i := range n.Partitions { + // skip automatically placed partitions + if n.Partitions[i].Start == 0 { + continue + } + + // keep partitions out of the first 2048 sectors + if n.Partitions[i].Start < 2048 { + n.Partitions[i].Start = 2048 + } + + // toss the bottom 11 bits + n.Partitions[i].Start &= ^PartitionDimension(2048 - 1) + } + + // TODO(vc): may be interesting to do something about potentially overlapping partitions + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/file.go b/vendor/github.com/coreos/ignition/config/types/file.go new file mode 100644 index 000000000000..528531f0a408 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/file.go @@ -0,0 +1,78 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" + "os" +) + +var ( + ErrFileIllegalMode = errors.New("illegal file mode") +) + +type File struct { + Filesystem string `json:"filesystem,omitempty" yaml:"filesystem"` + Path Path `json:"path,omitempty" yaml:"path"` + Contents FileContents `json:"contents,omitempty" yaml:"contents"` + Mode FileMode `json:"mode,omitempty" yaml:"mode"` + User FileUser `json:"user,omitempty" yaml:"uid"` + Group FileGroup `json:"group,omitempty" yaml:"gid"` +} + +type FileUser struct { + Id int `json:"id,omitempty" yaml:"id"` +} + +type FileGroup struct { + Id int `json:"id,omitempty" yaml:"id"` +} + +type FileContents struct { + Compression Compression `json:"compression,omitempty" yaml:"compression"` + Source Url `json:"source,omitempty" yaml:"source"` + Verification Verification `json:"verification,omitempty" yaml:"verification"` +} + +type FileMode os.FileMode + +func (m *FileMode) UnmarshalYAML(unmarshal func(interface{}) error) error { + return m.unmarshal(unmarshal) +} + +func (m *FileMode) UnmarshalJSON(data []byte) error { + return m.unmarshal(func(tm interface{}) error { + return json.Unmarshal(data, tm) + }) +} + +type fileMode FileMode + +func (m *FileMode) unmarshal(unmarshal func(interface{}) error) error { + tm := fileMode(*m) + if err := unmarshal(&tm); err != nil { + return err + } + *m = FileMode(tm) + return m.assertValid() +} + +func (m FileMode) assertValid() error { + if (m &^ 07777) != 0 { + return ErrFileIllegalMode + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/filesystem.go b/vendor/github.com/coreos/ignition/config/types/filesystem.go new file mode 100644 index 000000000000..44e8eaf36a86 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/filesystem.go @@ -0,0 +1,181 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" +) + +var ( + ErrFilesystemInvalidFormat = errors.New("invalid filesystem format") + ErrFilesystemNoMountPath = errors.New("filesystem is missing mount or path") + ErrFilesystemMountAndPath = errors.New("filesystem has both mount and path defined") +) + +type Filesystem struct { + Name string `json:"name,omitempty" yaml:"name"` + Mount *FilesystemMount `json:"mount,omitempty" yaml:"mount"` + Path Path `json:"path,omitempty" yaml:"path"` +} + +type FilesystemMount struct { + Device Path `json:"device,omitempty" yaml:"device"` + Format FilesystemFormat `json:"format,omitempty" yaml:"format"` + Create *FilesystemCreate `json:"create,omitempty" yaml:"create"` +} + +type FilesystemCreate struct { + Force bool `json:"force,omitempty" yaml:"force"` + Options MkfsOptions `json:"options,omitempty" yaml:"options"` +} + +func (f *Filesystem) UnmarshalYAML(unmarshal func(interface{}) error) error { + return f.unmarshal(unmarshal) +} + +func (f *Filesystem) UnmarshalJSON(data []byte) error { + return f.unmarshal(func(tf interface{}) error { + return json.Unmarshal(data, tf) + }) +} + +type filesystem Filesystem + +func (f *Filesystem) unmarshal(unmarshal func(interface{}) error) error { + tf := filesystem(*f) + if err := unmarshal(&tf); err != nil { + return err + } + *f = Filesystem(tf) + return f.assertValid() +} + +func (f Filesystem) assertValid() error { + hasMount := false + hasPath := false + + if f.Mount != nil { + hasMount = true + if err := f.Mount.assertValid(); err != nil { + return err + } + } + + if len(f.Path) != 0 { + hasPath = true + if err := f.Path.assertValid(); err != nil { + return err + } + } + + if !hasMount && !hasPath { + return ErrFilesystemNoMountPath + } else if hasMount && hasPath { + return ErrFilesystemMountAndPath + } + + return nil +} + +func (f *FilesystemMount) UnmarshalYAML(unmarshal func(interface{}) error) error { + return f.unmarshal(unmarshal) +} + +func (f *FilesystemMount) UnmarshalJSON(data []byte) error { + return f.unmarshal(func(tf interface{}) error { + return json.Unmarshal(data, tf) + }) +} + +type filesystemMount FilesystemMount + +func (f *FilesystemMount) unmarshal(unmarshal func(interface{}) error) error { + tf := filesystemMount(*f) + if err := unmarshal(&tf); err != nil { + return err + } + *f = FilesystemMount(tf) + return f.assertValid() +} + +func (f FilesystemMount) assertValid() error { + if err := f.Device.assertValid(); err != nil { + return err + } + if err := f.Format.assertValid(); err != nil { + return err + } + return nil +} + +type FilesystemFormat string + +func (f *FilesystemFormat) UnmarshalYAML(unmarshal func(interface{}) error) error { + return f.unmarshal(unmarshal) +} + +func (f *FilesystemFormat) UnmarshalJSON(data []byte) error { + return f.unmarshal(func(tf interface{}) error { + return json.Unmarshal(data, tf) + }) +} + +type filesystemFormat FilesystemFormat + +func (f *FilesystemFormat) unmarshal(unmarshal func(interface{}) error) error { + tf := filesystemFormat(*f) + if err := unmarshal(&tf); err != nil { + return err + } + *f = FilesystemFormat(tf) + return f.assertValid() +} + +func (f FilesystemFormat) assertValid() error { + switch f { + case "ext4", "btrfs", "xfs": + return nil + default: + return ErrFilesystemInvalidFormat + } +} + +type MkfsOptions []string + +func (o *MkfsOptions) UnmarshalYAML(unmarshal func(interface{}) error) error { + return o.unmarshal(unmarshal) +} + +func (o *MkfsOptions) UnmarshalJSON(data []byte) error { + return o.unmarshal(func(to interface{}) error { + return json.Unmarshal(data, to) + }) +} + +type mkfsOptions MkfsOptions + +func (o *MkfsOptions) unmarshal(unmarshal func(interface{}) error) error { + to := mkfsOptions(*o) + if err := unmarshal(&to); err != nil { + return err + } + *o = MkfsOptions(to) + return o.assertValid() +} + +func (o MkfsOptions) assertValid() error { + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/group.go b/vendor/github.com/coreos/ignition/config/types/group.go new file mode 100644 index 000000000000..eb393398fafd --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/group.go @@ -0,0 +1,22 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Group struct { + Name string `json:"name,omitempty" yaml:"name"` + Gid *uint `json:"gid,omitempty" yaml:"gid"` + PasswordHash string `json:"passwordHash,omitempty" yaml:"password_hash"` + System bool `json:"system,omitempty" yaml:"system"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/hash.go b/vendor/github.com/coreos/ignition/config/types/hash.go new file mode 100644 index 000000000000..064dfdd9e10d --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/hash.go @@ -0,0 +1,81 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "crypto" + "encoding/hex" + "encoding/json" + "errors" + "strings" +) + +var ( + ErrHashMalformed = errors.New("malformed hash specifier") + ErrHashWrongSize = errors.New("incorrect size for hash sum") + ErrHashUnrecognized = errors.New("unrecognized hash function") +) + +type Hash struct { + Function string + Sum string +} + +func (h *Hash) UnmarshalYAML(unmarshal func(interface{}) error) error { + return h.unmarshal(unmarshal) +} + +func (h *Hash) UnmarshalJSON(data []byte) error { + return h.unmarshal(func(th interface{}) error { + return json.Unmarshal(data, th) + }) +} + +func (h Hash) MarshalJSON() ([]byte, error) { + return []byte(`"` + h.Function + "-" + h.Sum + `"`), nil +} + +func (h *Hash) unmarshal(unmarshal func(interface{}) error) error { + var th string + if err := unmarshal(&th); err != nil { + return err + } + + parts := strings.SplitN(th, "-", 2) + if len(parts) != 2 { + return ErrHashMalformed + } + + h.Function = parts[0] + h.Sum = parts[1] + + return h.assertValid() +} + +func (h Hash) assertValid() error { + var hash crypto.Hash + switch h.Function { + case "sha512": + hash = crypto.SHA512 + default: + return ErrHashUnrecognized + } + + if len(h.Sum) != hex.EncodedLen(hash.Size()) { + return ErrHashWrongSize + } + + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/ignition.go b/vendor/github.com/coreos/ignition/config/types/ignition.go new file mode 100644 index 000000000000..9d5f9f9674a4 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/ignition.go @@ -0,0 +1,77 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/coreos/go-semver/semver" +) + +var ( + ErrOldVersion = errors.New("incorrect config version (too old)") + ErrNewVersion = errors.New("incorrect config version (too new)") +) + +type Ignition struct { + Version IgnitionVersion `json:"version,omitempty" yaml:"version" merge:"old"` + Config IgnitionConfig `json:"config,omitempty" yaml:"config" merge:"new"` +} + +type IgnitionConfig struct { + Append []ConfigReference `json:"append,omitempty" yaml:"append"` + Replace *ConfigReference `json:"replace,omitempty" yaml:"replace"` +} + +type ConfigReference struct { + Source Url `json:"source,omitempty" yaml:"source"` + Verification Verification `json:"verification,omitempty" yaml:"verification"` +} + +type IgnitionVersion semver.Version + +func (v *IgnitionVersion) UnmarshalYAML(unmarshal func(interface{}) error) error { + return v.unmarshal(unmarshal) +} + +func (v *IgnitionVersion) UnmarshalJSON(data []byte) error { + return v.unmarshal(func(tv interface{}) error { + return json.Unmarshal(data, tv) + }) +} + +func (v IgnitionVersion) MarshalJSON() ([]byte, error) { + return semver.Version(v).MarshalJSON() +} + +func (v *IgnitionVersion) unmarshal(unmarshal func(interface{}) error) error { + tv := semver.Version(*v) + if err := unmarshal(&tv); err != nil { + return err + } + *v = IgnitionVersion(tv) + return nil +} + +func (v IgnitionVersion) AssertValid() error { + if MaxVersion.Major > v.Major { + return ErrOldVersion + } + if MaxVersion.LessThan(semver.Version(v)) { + return ErrNewVersion + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/networkd.go b/vendor/github.com/coreos/ignition/config/types/networkd.go new file mode 100644 index 000000000000..2dbbdc3d9e7c --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/networkd.go @@ -0,0 +1,19 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Networkd struct { + Units []NetworkdUnit `json:"units,omitempty" yaml:"units"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/partition.go b/vendor/github.com/coreos/ignition/config/types/partition.go new file mode 100644 index 000000000000..228ece4d546a --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/partition.go @@ -0,0 +1,137 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/alecthomas/units" +) + +type Partition struct { + Label PartitionLabel `json:"label,omitempty" yaml:"label"` + Number int `json:"number" yaml:"number"` + Size PartitionDimension `json:"size" yaml:"size"` + Start PartitionDimension `json:"start" yaml:"start"` + TypeGUID PartitionTypeGUID `json:"typeGuid,omitempty" yaml:"type_guid"` +} + +type PartitionLabel string + +func (n *PartitionLabel) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *PartitionLabel) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type partitionLabel PartitionLabel + +func (n *PartitionLabel) unmarshal(unmarshal func(interface{}) error) error { + tn := partitionLabel(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = PartitionLabel(tn) + return n.assertValid() +} + +func (n PartitionLabel) assertValid() error { + // http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries: + // 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units) + + // XXX(vc): note GPT calls it a name, we're using label for consistency + // with udev naming /dev/disk/by-partlabel/*. + if len(string(n)) > 36 { + return fmt.Errorf("partition labels may not exceed 36 characters") + } + return nil +} + +type PartitionDimension uint64 + +func (n *PartitionDimension) UnmarshalYAML(unmarshal func(interface{}) error) error { + // In YAML we allow human-readable dimensions like GiB/TiB etc. + var str string + if err := unmarshal(&str); err != nil { + return err + } + + b2b, err := units.ParseBase2Bytes(str) // TODO(vc): replace the units package + if err != nil { + return err + } + if b2b < 0 { + return fmt.Errorf("negative value inappropriate: %q", str) + } + + // Translate bytes into sectors + sectors := (b2b / 512) + if b2b%512 != 0 { + sectors++ + } + *n = PartitionDimension(uint64(sectors)) + return nil +} + +func (n *PartitionDimension) UnmarshalJSON(data []byte) error { + // In JSON we expect plain integral sectors. + // The YAML->JSON conversion is responsible for normalizing human units to sectors. + var pd uint64 + if err := json.Unmarshal(data, &pd); err != nil { + return err + } + *n = PartitionDimension(pd) + return nil +} + +type PartitionTypeGUID string + +func (d *PartitionTypeGUID) UnmarshalYAML(unmarshal func(interface{}) error) error { + return d.unmarshal(unmarshal) +} + +func (d *PartitionTypeGUID) UnmarshalJSON(data []byte) error { + return d.unmarshal(func(td interface{}) error { + return json.Unmarshal(data, td) + }) +} + +type partitionTypeGUID PartitionTypeGUID + +func (d *PartitionTypeGUID) unmarshal(unmarshal func(interface{}) error) error { + td := partitionTypeGUID(*d) + if err := unmarshal(&td); err != nil { + return err + } + *d = PartitionTypeGUID(td) + return d.assertValid() +} + +func (d PartitionTypeGUID) assertValid() error { + ok, err := regexp.MatchString("[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}", string(d)) + if err != nil { + return fmt.Errorf("error matching type-guid regexp: %v", err) + } + if !ok { + return fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)) + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/passwd.go b/vendor/github.com/coreos/ignition/config/types/passwd.go new file mode 100644 index 000000000000..f1436874eb89 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/passwd.go @@ -0,0 +1,20 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Passwd struct { + Users []User `json:"users,omitempty" yaml:"users"` + Groups []Group `json:"groups,omitempty" yaml:"groups"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/path.go b/vendor/github.com/coreos/ignition/config/types/path.go new file mode 100644 index 000000000000..eb8e3c2c731e --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/path.go @@ -0,0 +1,59 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" + "path/filepath" +) + +var ( + ErrPathRelative = errors.New("path not absolute") +) + +type Path string + +func (p *Path) UnmarshalYAML(unmarshal func(interface{}) error) error { + return p.unmarshal(unmarshal) +} + +func (p *Path) UnmarshalJSON(data []byte) error { + return p.unmarshal(func(td interface{}) error { + return json.Unmarshal(data, td) + }) +} + +func (p Path) MarshalJSON() ([]byte, error) { + return []byte(`"` + string(p) + `"`), nil +} + +type path Path + +func (p *Path) unmarshal(unmarshal func(interface{}) error) error { + td := path(*p) + if err := unmarshal(&td); err != nil { + return err + } + *p = Path(td) + return p.assertValid() +} + +func (p Path) assertValid() error { + if !filepath.IsAbs(string(p)) { + return ErrPathRelative + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/raid.go b/vendor/github.com/coreos/ignition/config/types/raid.go new file mode 100644 index 000000000000..62fe65ef4309 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/raid.go @@ -0,0 +1,65 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "fmt" +) + +type Raid struct { + Name string `json:"name" yaml:"name"` + Level string `json:"level" yaml:"level"` + Devices []Path `json:"devices,omitempty" yaml:"devices"` + Spares int `json:"spares,omitempty" yaml:"spares"` +} + +func (n *Raid) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *Raid) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type raid Raid + +func (n *Raid) unmarshal(unmarshal func(interface{}) error) error { + tn := raid(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = Raid(tn) + return n.assertValid() +} + +func (n Raid) assertValid() error { + switch n.Level { + case "linear", "raid0", "0", "stripe": + if n.Spares != 0 { + return fmt.Errorf("spares unsupported for %q arrays", n.Level) + } + case "raid1", "1", "mirror": + case "raid4", "4": + case "raid5", "5": + case "raid6", "6": + case "raid10", "10": + default: + return fmt.Errorf("unrecognized raid level: %q", n.Level) + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/config/types/storage.go b/vendor/github.com/coreos/ignition/config/types/storage.go new file mode 100644 index 000000000000..75bff0706a9c --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/storage.go @@ -0,0 +1,22 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Storage struct { + Disks []Disk `json:"disks,omitempty" yaml:"disks"` + Arrays []Raid `json:"raid,omitempty" yaml:"raid"` + Filesystems []Filesystem `json:"filesystems,omitempty" yaml:"filesystems"` + Files []File `json:"files,omitempty" yaml:"files"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/systemd.go b/vendor/github.com/coreos/ignition/config/types/systemd.go new file mode 100644 index 000000000000..ec764850ae68 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/systemd.go @@ -0,0 +1,19 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Systemd struct { + Units []SystemdUnit `json:"units,omitempty" yaml:"units"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/unit.go b/vendor/github.com/coreos/ignition/config/types/unit.go new file mode 100644 index 000000000000..3fd6a194dd45 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/unit.go @@ -0,0 +1,135 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "errors" + "path/filepath" +) + +type SystemdUnit struct { + Name SystemdUnitName `json:"name,omitempty" yaml:"name"` + Enable bool `json:"enable,omitempty" yaml:"enable"` + Mask bool `json:"mask,omitempty" yaml:"mask"` + Contents string `json:"contents,omitempty" yaml:"contents"` + DropIns []SystemdUnitDropIn `json:"dropins,omitempty" yaml:"dropins"` +} + +type SystemdUnitDropIn struct { + Name SystemdUnitDropInName `json:"name,omitempty" yaml:"name"` + Contents string `json:"contents,omitempty" yaml:"contents"` +} + +type SystemdUnitName string + +func (n *SystemdUnitName) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *SystemdUnitName) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type systemdUnitName SystemdUnitName + +func (n *SystemdUnitName) unmarshal(unmarshal func(interface{}) error) error { + tn := systemdUnitName(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = SystemdUnitName(tn) + return n.assertValid() +} + +func (n SystemdUnitName) assertValid() error { + switch filepath.Ext(string(n)) { + case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope": + return nil + default: + return errors.New("invalid systemd unit extension") + } +} + +type SystemdUnitDropInName string + +func (n *SystemdUnitDropInName) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *SystemdUnitDropInName) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type systemdUnitDropInName SystemdUnitDropInName + +func (n *SystemdUnitDropInName) unmarshal(unmarshal func(interface{}) error) error { + tn := systemdUnitDropInName(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = SystemdUnitDropInName(tn) + return n.assertValid() +} + +func (n SystemdUnitDropInName) assertValid() error { + switch filepath.Ext(string(n)) { + case ".conf": + return nil + default: + return errors.New("invalid systemd unit drop-in extension") + } +} + +type NetworkdUnit struct { + Name NetworkdUnitName `json:"name,omitempty" yaml:"name"` + Contents string `json:"contents,omitempty" yaml:"contents"` +} + +type NetworkdUnitName string + +func (n *NetworkdUnitName) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *NetworkdUnitName) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type networkdUnitName NetworkdUnitName + +func (n *NetworkdUnitName) unmarshal(unmarshal func(interface{}) error) error { + tn := networkdUnitName(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = NetworkdUnitName(tn) + return n.assertValid() +} + +func (n NetworkdUnitName) assertValid() error { + switch filepath.Ext(string(n)) { + case ".link", ".netdev", ".network": + return nil + default: + return errors.New("invalid networkd unit extension") + } +} diff --git a/vendor/github.com/coreos/ignition/config/types/url.go b/vendor/github.com/coreos/ignition/config/types/url.go new file mode 100644 index 000000000000..83d0338550f4 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/url.go @@ -0,0 +1,52 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "net/url" +) + +type Url url.URL + +func (u *Url) UnmarshalYAML(unmarshal func(interface{}) error) error { + return u.unmarshal(unmarshal) +} + +func (u *Url) UnmarshalJSON(data []byte) error { + return u.unmarshal(func(tu interface{}) error { + return json.Unmarshal(data, tu) + }) +} + +func (u Url) MarshalJSON() ([]byte, error) { + return []byte(`"` + u.String() + `"`), nil +} + +func (u *Url) unmarshal(unmarshal func(interface{}) error) error { + var tu string + if err := unmarshal(&tu); err != nil { + return err + } + + pu, err := url.Parse(tu) + *u = Url(*pu) + return err +} + +func (u Url) String() string { + tu := url.URL(u) + return (&tu).String() +} diff --git a/vendor/github.com/coreos/ignition/config/types/user.go b/vendor/github.com/coreos/ignition/config/types/user.go new file mode 100644 index 000000000000..80c3336cf0a1 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/user.go @@ -0,0 +1,35 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type User struct { + Name string `json:"name,omitempty" yaml:"name"` + PasswordHash string `json:"passwordHash,omitempty" yaml:"password_hash"` + SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty" yaml:"ssh_authorized_keys"` + Create *UserCreate `json:"create,omitempty" yaml:"create"` +} + +type UserCreate struct { + Uid *uint `json:"uid,omitempty" yaml:"uid"` + GECOS string `json:"gecos,omitempty" yaml:"gecos"` + Homedir string `json:"homeDir,omitempty" yaml:"home_dir"` + NoCreateHome bool `json:"noCreateHome,omitempty" yaml:"no_create_home"` + PrimaryGroup string `json:"primaryGroup,omitempty" yaml:"primary_group"` + Groups []string `json:"groups,omitempty" yaml:"groups"` + NoUserGroup bool `json:"noUserGroup,omitempty" yaml:"no_user_group"` + System bool `json:"system,omitempty" yaml:"system"` + NoLogInit bool `json:"noLogInit,omitempty" yaml:"no_log_init"` + Shell string `json:"shell,omitempty" yaml:"shell"` +} diff --git a/vendor/github.com/coreos/ignition/config/types/verification.go b/vendor/github.com/coreos/ignition/config/types/verification.go new file mode 100644 index 000000000000..f6093d6175da --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/verification.go @@ -0,0 +1,19 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Verification struct { + Hash *Hash `json:"hash,omitempty" yaml:"hash"` +} From 7c705944dd3948d7c0d32cea93fa263a8b3eef17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Sun, 17 Apr 2016 01:55:48 +0200 Subject: [PATCH 04/14] providers: ignition ignition_user --- builtin/providers/ignition/provider.go | 9 ++ .../ignition/resource_ignition_config.go | 6 +- .../ignition/resource_ignition_user.go | 85 +++++++++++++++-- .../ignition/resource_ignition_user_test.go | 94 ++++++++++++++----- 4 files changed, 163 insertions(+), 31 deletions(-) diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go index 130965e51c86..b4021dc407de 100644 --- a/builtin/providers/ignition/provider.go +++ b/builtin/providers/ignition/provider.go @@ -44,3 +44,12 @@ func hash(s string) string { sha := sha256.Sum256([]byte(s)) return hex.EncodeToString(sha[:]) } + +func castSliceInterface(i []interface{}) []string { + var o []string + for _, value := range i { + o = append(o, value.(string)) + } + + return o +} diff --git a/builtin/providers/ignition/resource_ignition_config.go b/builtin/providers/ignition/resource_ignition_config.go index c79da6c8a418..f597bde056bd 100644 --- a/builtin/providers/ignition/resource_ignition_config.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -75,9 +75,8 @@ func resourceConfig() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "rendered": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Description: "rendered template", + Type: schema.TypeString, + Computed: true, }, }, } @@ -122,6 +121,7 @@ func renderConfig(d *schema.ResourceData, c *cache) (string, error) { } bytes, err := json.MarshalIndent(i, " ", " ") + if err != nil { return "", err } diff --git a/builtin/providers/ignition/resource_ignition_user.go b/builtin/providers/ignition/resource_ignition_user.go index b3f4c2ea3094..ea93ef2c8c7f 100644 --- a/builtin/providers/ignition/resource_ignition_user.go +++ b/builtin/providers/ignition/resource_ignition_user.go @@ -13,13 +13,66 @@ func resourceUser() *schema.Resource { Read: resourceUserRead, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "", + }, + "password_hash": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, - "password_hash": &schema.Schema{ + "ssh_authorized_keys": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "uid": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "gecos": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "home_dir": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "no_create_home": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "primary_group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "groups": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "no_user_group": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "no_log_init": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "shell": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, }, @@ -33,7 +86,6 @@ func resourceUserCreate(d *schema.ResourceData, meta interface{}) error { } d.SetId(id) - d.Set("id", id) return nil } @@ -56,9 +108,28 @@ func resourceUserRead(d *schema.ResourceData, meta interface{}) error { } func buildUser(d *schema.ResourceData, c *cache) (string, error) { - u := &types.User{} - u.Name = d.Get("name").(string) - u.PasswordHash = d.Get("password_hash").(string) + var uid *uint + if value, ok := d.GetOk("uid"); ok { + u := uint(value.(int)) + uid = &u + } + + u := &types.User{ + Name: d.Get("name").(string), + PasswordHash: d.Get("password_hash").(string), + SSHAuthorizedKeys: castSliceInterface(d.Get("ssh_authorized_keys").([]interface{})), + Create: &types.UserCreate{ + Uid: uid, + GECOS: d.Get("gecos").(string), + Homedir: d.Get("home_dir").(string), + NoCreateHome: d.Get("no_create_home").(bool), + PrimaryGroup: d.Get("primary_group").(string), + Groups: castSliceInterface(d.Get("groups").([]interface{})), + NoUserGroup: d.Get("no_user_group").(bool), + NoLogInit: d.Get("no_log_init").(bool), + Shell: d.Get("shell").(string), + }, + } return c.addUser(u), nil } diff --git a/builtin/providers/ignition/resource_ignition_user_test.go b/builtin/providers/ignition/resource_ignition_user_test.go index 151f0ef7a1f4..f61b49084adf 100644 --- a/builtin/providers/ignition/resource_ignition_user_test.go +++ b/builtin/providers/ignition/resource_ignition_user_test.go @@ -7,45 +7,97 @@ import ( "github.com/coreos/ignition/config/types" ) -func TestIngnitiondUsers(t *testing.T) { +func TestIngnitiondUser(t *testing.T) { testIgnition(t, ` resource "ignition_user" "foo" { name = "foo" - password_hash = "foo" + password_hash = "password" + ssh_authorized_keys = ["keys"] + uid = 42 + gecos = "gecos" + home_dir = "home" + no_create_home = true + primary_group = "primary_group" + groups = ["group"] + no_user_group = true + no_log_init = true + shell = "shell" } resource "ignition_user" "qux" { name = "qux" - password_hash = "qux" - } - + } + resource "ignition_config" "test" { - ignition { - config { - replace { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } - } - } - + ignition {} users = [ "${ignition_user.foo.id}", "${ignition_user.qux.id}", ] } `, func(c *types.Config) error { - r := c.Ignition.Config.Replace - if r == nil { - return fmt.Errorf("unable to find replace config") + if len(c.Passwd.Users) != 2 { + return fmt.Errorf("users, found %d", len(c.Passwd.Users)) + } + + u := c.Passwd.Users[0] + + if u.Name != "foo" { + return fmt.Errorf("name, found %q", u.Name) + } + + if u.PasswordHash != "password" { + return fmt.Errorf("password_hash, found %q", u.PasswordHash) } - if r.Source.String() != "foo" { - return fmt.Errorf("config.replace.source, found %q", r.Source) + if len(u.SSHAuthorizedKeys) != 1 || u.SSHAuthorizedKeys[0] != "keys" { + return fmt.Errorf("ssh_authorized_keys, found %q", u.SSHAuthorizedKeys) + } + + if *u.Create.Uid != uint(42) { + return fmt.Errorf("uid, found %q", *u.Create.Uid) + } + + if u.Create.GECOS != "gecos" { + return fmt.Errorf("gecos, found %q", u.Create.GECOS) + } + + if u.Create.Homedir != "home" { + return fmt.Errorf("home_dir, found %q", u.Create.Homedir) + } + + if u.Create.NoCreateHome != true { + return fmt.Errorf("no_create_home, found %q", u.Create.NoCreateHome) + } + + if u.Create.PrimaryGroup != "primary_group" { + return fmt.Errorf("primary_group, found %q", u.Create.PrimaryGroup) + } + + if len(u.Create.Groups) != 1 || u.Create.Groups[0] != "group" { + return fmt.Errorf("groups, found %q", u.Create.Groups) + } + + if u.Create.NoUserGroup != true { + return fmt.Errorf("no_create_home, found %q", u.Create.NoCreateHome) + } + + if u.Create.NoLogInit != true { + return fmt.Errorf("no_log_init, found %q", u.Create.NoLogInit) + } + + if u.Create.Shell != "shell" { + return fmt.Errorf("shell, found %q", u.Create.Shell) + } + + u = c.Passwd.Users[1] + + if u.Name != "qux" { + return fmt.Errorf("name, found %q", u.Name) } - if r.Verification.Hash.Sum != "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" { - return fmt.Errorf("config.replace.verification, found %q", r.Verification.Hash) + if u.Create.Uid != nil { + return fmt.Errorf("uid, found %d", *u.Create.Uid) } return nil From 0749be65d35cbe3e1ec2ec054035389e0536c63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Sun, 17 Apr 2016 03:26:38 +0200 Subject: [PATCH 05/14] providers: ignition ignition_disk, ignition_group and ignition_raid --- builtin/providers/ignition/provider.go | 65 ++++++++++- .../ignition/resource_ignition_config.go | 64 +++++++++- .../ignition/resource_ignition_disk.go | 110 ++++++++++++++++++ .../ignition/resource_ignition_disk_test.go | 61 ++++++++++ .../ignition/resource_ignition_group.go | 68 +++++++++++ .../ignition/resource_ignition_group_test.go | 60 ++++++++++ .../ignition/resource_ignition_raid.go | 80 +++++++++++++ .../ignition/resource_ignition_raid_test.go | 49 ++++++++ .../ignition/resource_ignition_user.go | 21 +--- .../ignition/resource_ignition_user_test.go | 2 +- 10 files changed, 558 insertions(+), 22 deletions(-) create mode 100644 builtin/providers/ignition/resource_ignition_disk.go create mode 100644 builtin/providers/ignition/resource_ignition_disk_test.go create mode 100644 builtin/providers/ignition/resource_ignition_group.go create mode 100644 builtin/providers/ignition/resource_ignition_group_test.go create mode 100644 builtin/providers/ignition/resource_ignition_raid.go create mode 100644 builtin/providers/ignition/resource_ignition_raid_test.go diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go index b4021dc407de..32fad2ae18ac 100644 --- a/builtin/providers/ignition/provider.go +++ b/builtin/providers/ignition/provider.go @@ -4,37 +4,82 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "sync" "github.com/coreos/ignition/config/types" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) -type cache struct { - users map[string]*types.User -} - func Provider() terraform.ResourceProvider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ "ignition_config": resourceConfig(), + "ignition_disk": resourceDisk(), + "ignition_raid": resourceRaid(), "ignition_user": resourceUser(), + "ignition_group": resourceGroup(), }, ConfigureFunc: func(*schema.ResourceData) (interface{}, error) { return &cache{ - users: make(map[string]*types.User, 0), + disks: make(map[string]*types.Disk, 0), + arrays: make(map[string]*types.Raid, 0), + users: make(map[string]*types.User, 0), + groups: make(map[string]*types.Group, 0), }, nil }, } } +type cache struct { + disks map[string]*types.Disk + arrays map[string]*types.Raid + users map[string]*types.User + groups map[string]*types.Group + + sync.Mutex +} + +func (c *cache) addDisk(g *types.Disk) string { + c.Lock() + defer c.Unlock() + + id := id(g) + c.disks[id] = g + + return id +} + +func (c *cache) addRaid(r *types.Raid) string { + c.Lock() + defer c.Unlock() + + id := id(r) + c.arrays[id] = r + + return id +} + func (c *cache) addUser(u *types.User) string { + c.Lock() + defer c.Unlock() + id := id(u) c.users[id] = u return id } +func (c *cache) addGroup(g *types.Group) string { + c.Lock() + defer c.Unlock() + + id := id(g) + c.groups[id] = g + + return id +} + func id(input interface{}) string { b, _ := json.Marshal(input) return hash(string(b)) @@ -53,3 +98,13 @@ func castSliceInterface(i []interface{}) []string { return o } + +func getUInt(d *schema.ResourceData, key string) *uint { + var uid *uint + if value, ok := d.GetOk(key); ok { + u := uint(value.(int)) + uid = &u + } + + return uid +} diff --git a/builtin/providers/ignition/resource_ignition_config.go b/builtin/providers/ignition/resource_ignition_config.go index f597bde056bd..89cf87a0d176 100644 --- a/builtin/providers/ignition/resource_ignition_config.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -68,12 +68,30 @@ func resourceConfig() *schema.Resource { ForceNew: true, Elem: ignitionResource, }, + "disks": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "arrays": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "users": &schema.Schema{ Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "groups": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "rendered": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -142,6 +160,11 @@ func buildConfig(d *schema.ResourceData, c *cache) (*types.Config, error) { return nil, err } + config.Storage, err = buildStorage(d, c) + if err != nil { + return nil, err + } + return config, nil } @@ -198,13 +221,52 @@ func buildPasswd(d *schema.ResourceData, c *cache) (types.Passwd, error) { passwd := types.Passwd{} for _, id := range d.Get("users").([]interface{}) { - passwd.Users = append(passwd.Users, *c.users[id.(string)]) + u, ok := c.users[id.(string)] + if !ok { + return passwd, fmt.Errorf("invalid user %q, unknown user id", id) + } + + passwd.Users = append(passwd.Users, *u) + } + + for _, id := range d.Get("groups").([]interface{}) { + g, ok := c.groups[id.(string)] + if !ok { + return passwd, fmt.Errorf("invalid group %q, unknown group id", id) + } + + passwd.Groups = append(passwd.Groups, *g) } return passwd, nil } +func buildStorage(d *schema.ResourceData, c *cache) (types.Storage, error) { + storage := types.Storage{} + + for _, id := range d.Get("disks").([]interface{}) { + d, ok := c.disks[id.(string)] + if !ok { + return storage, fmt.Errorf("invalid disk %q, unknown disk id", id) + } + + storage.Disks = append(storage.Disks, *d) + } + + for _, id := range d.Get("arrays").([]interface{}) { + d, ok := c.arrays[id.(string)] + if !ok { + return storage, fmt.Errorf("invalid raid %q, unknown raid id", id) + } + + storage.Arrays = append(storage.Arrays, *d) + } + + return storage, nil + +} + func buildURL(raw string) (types.Url, error) { u, err := url.Parse(raw) if err != nil { diff --git a/builtin/providers/ignition/resource_ignition_disk.go b/builtin/providers/ignition/resource_ignition_disk.go new file mode 100644 index 000000000000..78d5de7e9b2f --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_disk.go @@ -0,0 +1,110 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDisk() *schema.Resource { + return &schema.Resource{ + Create: resourceDiskCreate, + Delete: resourceDiskDelete, + Exists: resourceDiskExists, + Read: resourceDiskRead, + Schema: map[string]*schema.Schema{ + "device": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "wipe_table": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "partition": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "label": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "number": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "start": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "type_guid": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourceDiskCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildDisk(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceDiskDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceDiskExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildDisk(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceDiskRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildDisk(d *schema.ResourceData, c *cache) (string, error) { + var partitions []types.Partition + for _, raw := range d.Get("partition").([]interface{}) { + v := raw.(map[string]interface{}) + + partitions = append(partitions, types.Partition{ + Label: types.PartitionLabel(v["label"].(string)), + Number: v["number"].(int), + Size: types.PartitionDimension(v["size"].(int)), + Start: types.PartitionDimension(v["start"].(int)), + TypeGUID: types.PartitionTypeGUID(v["type_guid"].(string)), + }) + } + + return c.addDisk(&types.Disk{ + Device: types.Path(d.Get("device").(string)), + WipeTable: d.Get("wipe_table").(bool), + Partitions: partitions, + }), nil +} diff --git a/builtin/providers/ignition/resource_ignition_disk_test.go b/builtin/providers/ignition/resource_ignition_disk_test.go new file mode 100644 index 000000000000..bb25d2840de2 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_disk_test.go @@ -0,0 +1,61 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionDisk(t *testing.T) { + testIgnition(t, ` + resource "ignition_disk" "foo" { + device = "/foo" + partition { + label = "qux" + size = 42 + start = 2048 + type_guid = "01234567-89AB-CDEF-EDCB-A98765432101" + } + } + + resource "ignition_config" "test" { + ignition {} + disks = [ + "${ignition_disk.foo.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Storage.Disks) != 1 { + return fmt.Errorf("disks, found %d", len(c.Storage.Disks)) + } + + d := c.Storage.Disks[0] + if d.Device != "/foo" { + return fmt.Errorf("name, found %q", d.Device) + } + + if len(d.Partitions) != 1 { + return fmt.Errorf("parition, found %d", len(d.Partitions)) + } + + p := d.Partitions[0] + if p.Label != "qux" { + return fmt.Errorf("parition.0.label, found %q", p.Label) + } + + if p.Size != 42 { + return fmt.Errorf("parition.0.size, found %q", p.Size) + } + + if p.Start != 2048 { + return fmt.Errorf("parition.0.start, found %q", p.Start) + } + + if p.TypeGUID != "01234567-89AB-CDEF-EDCB-A98765432101" { + return fmt.Errorf("parition.0.type_guid, found %q", p.TypeGUID) + } + + return nil + }) +} diff --git a/builtin/providers/ignition/resource_ignition_group.go b/builtin/providers/ignition/resource_ignition_group.go new file mode 100644 index 000000000000..62277733b90c --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_group.go @@ -0,0 +1,68 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceGroupCreate, + Delete: resourceGroupDelete, + Exists: resourceGroupExists, + Read: resourceGroupRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "gid": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "password_hash": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceGroupCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildGroup(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceGroupDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceGroupExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildGroup(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceGroupRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildGroup(d *schema.ResourceData, c *cache) (string, error) { + return c.addGroup(&types.Group{ + Name: d.Get("name").(string), + PasswordHash: d.Get("password_hash").(string), + Gid: getUInt(d, "gid"), + }), nil +} diff --git a/builtin/providers/ignition/resource_ignition_group_test.go b/builtin/providers/ignition/resource_ignition_group_test.go new file mode 100644 index 000000000000..c564e2d73664 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_group_test.go @@ -0,0 +1,60 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionGroup(t *testing.T) { + testIgnition(t, ` + resource "ignition_group" "foo" { + name = "foo" + password_hash = "password" + gid = 42 + } + + resource "ignition_group" "qux" { + name = "qux" + } + + resource "ignition_config" "test" { + ignition {} + groups = [ + "${ignition_group.foo.id}", + "${ignition_group.qux.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Passwd.Groups) != 2 { + return fmt.Errorf("groups, found %d", len(c.Passwd.Groups)) + } + + g := c.Passwd.Groups[0] + + if g.Name != "foo" { + return fmt.Errorf("name, found %q", g.Name) + } + + if g.PasswordHash != "password" { + return fmt.Errorf("password_hash, found %q", g.PasswordHash) + } + + if g.Gid == nil || *g.Gid != uint(42) { + return fmt.Errorf("gid, found %q", *g.Gid) + } + + g = c.Passwd.Groups[1] + + if g.Name != "qux" { + return fmt.Errorf("name, found %q", g.Name) + } + + if g.Gid != nil { + return fmt.Errorf("uid, found %d", *g.Gid) + } + + return nil + }) +} diff --git a/builtin/providers/ignition/resource_ignition_raid.go b/builtin/providers/ignition/resource_ignition_raid.go new file mode 100644 index 000000000000..7eccc716d8de --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_raid.go @@ -0,0 +1,80 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceRaid() *schema.Resource { + return &schema.Resource{ + Create: resourceRaidCreate, + Delete: resourceRaidDelete, + Exists: resourceRaidExists, + Read: resourceRaidRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "level": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "devices": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "spares": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceRaidCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildRaid(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceRaidDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceRaidExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildRaid(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceRaidRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildRaid(d *schema.ResourceData, c *cache) (string, error) { + var devices []types.Path + for _, value := range d.Get("devices").([]interface{}) { + devices = append(devices, types.Path(value.(string))) + } + + return c.addRaid(&types.Raid{ + Name: d.Get("name").(string), + Level: d.Get("level").(string), + Devices: devices, + Spares: d.Get("spares").(int), + }), nil +} diff --git a/builtin/providers/ignition/resource_ignition_raid_test.go b/builtin/providers/ignition/resource_ignition_raid_test.go new file mode 100644 index 000000000000..18eacfd8cb46 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_raid_test.go @@ -0,0 +1,49 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionRaid(t *testing.T) { + testIgnition(t, ` + resource "ignition_raid" "foo" { + name = "foo" + level = "raid10" + devices = ["/foo"] + spares = 42 + } + + resource "ignition_config" "test" { + ignition {} + arrays = [ + "${ignition_raid.foo.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Storage.Arrays) != 1 { + return fmt.Errorf("arrays, found %d", len(c.Storage.Arrays)) + } + + a := c.Storage.Arrays[0] + if a.Name != "foo" { + return fmt.Errorf("name, found %q", a.Name) + } + + if len(a.Devices) != 1 || a.Devices[0] != "/foo" { + return fmt.Errorf("devices, found %d", a.Devices) + } + + if a.Level != "raid10" { + return fmt.Errorf("level, found %q", a.Level) + } + + if a.Spares != 42 { + return fmt.Errorf("spares, found %q", a.Spares) + } + + return nil + }) +} diff --git a/builtin/providers/ignition/resource_ignition_user.go b/builtin/providers/ignition/resource_ignition_user.go index ea93ef2c8c7f..70009c99bf73 100644 --- a/builtin/providers/ignition/resource_ignition_user.go +++ b/builtin/providers/ignition/resource_ignition_user.go @@ -13,10 +13,9 @@ func resourceUser() *schema.Resource { Read: resourceUserRead, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "", + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "password_hash": &schema.Schema{ Type: schema.TypeString, @@ -108,18 +107,12 @@ func resourceUserRead(d *schema.ResourceData, meta interface{}) error { } func buildUser(d *schema.ResourceData, c *cache) (string, error) { - var uid *uint - if value, ok := d.GetOk("uid"); ok { - u := uint(value.(int)) - uid = &u - } - - u := &types.User{ + return c.addUser(&types.User{ Name: d.Get("name").(string), PasswordHash: d.Get("password_hash").(string), SSHAuthorizedKeys: castSliceInterface(d.Get("ssh_authorized_keys").([]interface{})), Create: &types.UserCreate{ - Uid: uid, + Uid: getUInt(d, "uid"), GECOS: d.Get("gecos").(string), Homedir: d.Get("home_dir").(string), NoCreateHome: d.Get("no_create_home").(bool), @@ -129,7 +122,5 @@ func buildUser(d *schema.ResourceData, c *cache) (string, error) { NoLogInit: d.Get("no_log_init").(bool), Shell: d.Get("shell").(string), }, - } - - return c.addUser(u), nil + }), nil } diff --git a/builtin/providers/ignition/resource_ignition_user_test.go b/builtin/providers/ignition/resource_ignition_user_test.go index f61b49084adf..188d95ca8720 100644 --- a/builtin/providers/ignition/resource_ignition_user_test.go +++ b/builtin/providers/ignition/resource_ignition_user_test.go @@ -7,7 +7,7 @@ import ( "github.com/coreos/ignition/config/types" ) -func TestIngnitiondUser(t *testing.T) { +func TestIngnitionUser(t *testing.T) { testIgnition(t, ` resource "ignition_user" "foo" { name = "foo" From 8e1a2ec047640ce0e2bf39401b08eae718c65532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Sun, 17 Apr 2016 12:54:52 +0200 Subject: [PATCH 06/14] providers: ignition_file and ignition_filesystem --- builtin/providers/ignition/provider.go | 52 +++-- .../ignition/resource_ignition_config.go | 48 ++++- .../ignition/resource_ignition_file.go | 189 ++++++++++++++++++ .../ignition/resource_ignition_file_test.go | 93 +++++++++ .../ignition/resource_ignition_filesystem.go | 111 ++++++++++ .../resource_ignition_filesystem_test.go | 101 ++++++++++ 6 files changed, 573 insertions(+), 21 deletions(-) create mode 100644 builtin/providers/ignition/resource_ignition_file.go create mode 100644 builtin/providers/ignition/resource_ignition_file_test.go create mode 100644 builtin/providers/ignition/resource_ignition_filesystem.go create mode 100644 builtin/providers/ignition/resource_ignition_filesystem_test.go diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go index 32fad2ae18ac..8e5bdfc64953 100644 --- a/builtin/providers/ignition/provider.go +++ b/builtin/providers/ignition/provider.go @@ -14,28 +14,34 @@ import ( func Provider() terraform.ResourceProvider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ - "ignition_config": resourceConfig(), - "ignition_disk": resourceDisk(), - "ignition_raid": resourceRaid(), - "ignition_user": resourceUser(), - "ignition_group": resourceGroup(), + "ignition_config": resourceConfig(), + "ignition_disk": resourceDisk(), + "ignition_raid": resourceRaid(), + "ignition_filesystem": resourceFilesystem(), + "ignition_file": resourceFile(), + "ignition_user": resourceUser(), + "ignition_group": resourceGroup(), }, ConfigureFunc: func(*schema.ResourceData) (interface{}, error) { return &cache{ - disks: make(map[string]*types.Disk, 0), - arrays: make(map[string]*types.Raid, 0), - users: make(map[string]*types.User, 0), - groups: make(map[string]*types.Group, 0), + disks: make(map[string]*types.Disk, 0), + arrays: make(map[string]*types.Raid, 0), + filesystems: make(map[string]*types.Filesystem, 0), + files: make(map[string]*types.File, 0), + users: make(map[string]*types.User, 0), + groups: make(map[string]*types.Group, 0), }, nil }, } } type cache struct { - disks map[string]*types.Disk - arrays map[string]*types.Raid - users map[string]*types.User - groups map[string]*types.Group + disks map[string]*types.Disk + arrays map[string]*types.Raid + filesystems map[string]*types.Filesystem + files map[string]*types.File + users map[string]*types.User + groups map[string]*types.Group sync.Mutex } @@ -60,6 +66,26 @@ func (c *cache) addRaid(r *types.Raid) string { return id } +func (c *cache) addFilesystem(f *types.Filesystem) string { + c.Lock() + defer c.Unlock() + + id := id(f) + c.filesystems[id] = f + + return id +} + +func (c *cache) addFile(f *types.File) string { + c.Lock() + defer c.Unlock() + + id := id(f) + c.files[id] = f + + return id +} + func (c *cache) addUser(u *types.User) string { c.Lock() defer c.Unlock() diff --git a/builtin/providers/ignition/resource_ignition_config.go b/builtin/providers/ignition/resource_ignition_config.go index 89cf87a0d176..a00c89b736e2 100644 --- a/builtin/providers/ignition/resource_ignition_config.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -25,12 +25,14 @@ var ignitionResource = &schema.Resource{ Schema: map[string]*schema.Schema{ "replace": &schema.Schema{ Type: schema.TypeList, + ForceNew: true, Optional: true, MaxItems: 1, Elem: configReferenceResource, }, "append": &schema.Schema{ Type: schema.TypeList, + ForceNew: true, Optional: true, Elem: configReferenceResource, }, @@ -42,14 +44,14 @@ var ignitionResource = &schema.Resource{ var configReferenceResource = &schema.Resource{ Schema: map[string]*schema.Schema{ "source": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Description: "The URL of the config. Supported schemes are http. Note: When using http, it is advisable to use the verification option to ensure the contents haven’t been modified.", + Type: schema.TypeString, + ForceNew: true, + Required: true, }, "verification": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Description: "The hash of the config (SHA512)", + Type: schema.TypeString, + ForceNew: true, + Required: true, }, }, } @@ -80,6 +82,18 @@ func resourceConfig() *schema.Resource { ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "filesystems": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "files": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "users": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -255,12 +269,30 @@ func buildStorage(d *schema.ResourceData, c *cache) (types.Storage, error) { } for _, id := range d.Get("arrays").([]interface{}) { - d, ok := c.arrays[id.(string)] + a, ok := c.arrays[id.(string)] if !ok { return storage, fmt.Errorf("invalid raid %q, unknown raid id", id) } - storage.Arrays = append(storage.Arrays, *d) + storage.Arrays = append(storage.Arrays, *a) + } + + for _, id := range d.Get("filesystems").([]interface{}) { + f, ok := c.filesystems[id.(string)] + if !ok { + return storage, fmt.Errorf("invalid filesystem %q, unknown filesystem id", id) + } + + storage.Filesystems = append(storage.Filesystems, *f) + } + + for _, id := range d.Get("files").([]interface{}) { + f, ok := c.files[id.(string)] + if !ok { + return storage, fmt.Errorf("invalid file %q, unknown file id", id) + } + + storage.Files = append(storage.Files, *f) } return storage, nil diff --git a/builtin/providers/ignition/resource_ignition_file.go b/builtin/providers/ignition/resource_ignition_file.go new file mode 100644 index 000000000000..ee707456205c --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_file.go @@ -0,0 +1,189 @@ +package ignition + +import ( + "encoding/base64" + "fmt" + + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceFile() *schema.Resource { + return &schema.Resource{ + Create: resourceFileCreate, + Delete: resourceFileDelete, + Exists: resourceFileExists, + Read: resourceFileRead, + Schema: map[string]*schema.Schema{ + "filesystem": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "path": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "content": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mime": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "text/plain", + }, + + "content": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + "source": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "compression": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "verification": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "mode": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "uid": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "gid": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceFileCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildFile(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceFileDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceFileExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildFile(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceFileRead(d *schema.ResourceData, metacontent interface{}) error { + return nil +} + +func buildFile(d *schema.ResourceData, c *cache) (string, error) { + _, hasContent := d.GetOk("content") + _, hasSource := d.GetOk("source") + if hasContent && hasSource { + return "", fmt.Errorf("content and source options are incompatible") + } + + if !hasContent && !hasSource { + return "", fmt.Errorf("content or source options must be present") + } + + var compression types.Compression + var source types.Url + var hash *types.Hash + var err error + + if hasContent { + source, err = encodeDataURL( + d.Get("content.0.mime").(string), + d.Get("content.0.content").(string), + ) + + if err != nil { + return "", err + } + } + + if hasSource { + source, err = buildURL(d.Get("source.0.source").(string)) + if err != nil { + return "", err + } + + compression = types.Compression(d.Get("source.0.compression").(string)) + h, err := buildHash(d.Get("source.0.verification").(string)) + if err != nil { + return "", err + } + + hash = &h + } + + return c.addFile(&types.File{ + Filesystem: d.Get("filesystem").(string), + Path: types.Path(d.Get("path").(string)), + Contents: types.FileContents{ + Compression: compression, + Source: source, + Verification: types.Verification{ + Hash: hash, + }, + }, + User: types.FileUser{ + Id: d.Get("uid").(int), + }, + Group: types.FileGroup{ + Id: d.Get("gid").(int), + }, + Mode: types.FileMode(d.Get("mode").(int)), + }), nil +} + +func encodeDataURL(mime, content string) (types.Url, error) { + base64 := base64.StdEncoding.EncodeToString([]byte(content)) + return buildURL( + fmt.Sprintf("data:%s;charset=utf-8;base64,%s", mime, base64), + ) +} diff --git a/builtin/providers/ignition/resource_ignition_file_test.go b/builtin/providers/ignition/resource_ignition_file_test.go new file mode 100644 index 000000000000..fe9870d01149 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_file_test.go @@ -0,0 +1,93 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionFile(t *testing.T) { + testIgnition(t, ` + resource "ignition_file" "foo" { + filesystem = "foo" + path = "/foo" + content { + content = "foo" + } + mode = 420 + uid = 42 + gid = 84 + } + + resource "ignition_file" "qux" { + filesystem = "qux" + path = "/qux" + source { + source = "qux" + compression = "gzip" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + + resource "ignition_config" "test" { + ignition {} + files = [ + "${ignition_file.foo.id}", + "${ignition_file.qux.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Storage.Files) != 2 { + return fmt.Errorf("arrays, found %d", len(c.Storage.Arrays)) + } + + f := c.Storage.Files[0] + if f.Filesystem != "foo" { + return fmt.Errorf("filesystem, found %q", f.Filesystem) + } + + if f.Path != "/foo" { + return fmt.Errorf("path, found %q", f.Path) + } + + if f.Contents.Source.String() != "data:text/plain;charset=utf-8;base64,Zm9v" { + return fmt.Errorf("contents.source, found %q", f.Contents.Source) + } + + if f.Mode != types.FileMode(420) { + return fmt.Errorf("mode, found %q", f.Mode) + } + + if f.User.Id != 42 { + return fmt.Errorf("uid, found %q", f.User.Id) + } + + if f.Group.Id != 84 { + return fmt.Errorf("gid, found %q", f.Group.Id) + } + + f = c.Storage.Files[1] + if f.Filesystem != "qux" { + return fmt.Errorf("filesystem, found %q", f.Filesystem) + } + + if f.Path != "/qux" { + return fmt.Errorf("path, found %q", f.Path) + } + + if f.Contents.Source.String() != "qux" { + return fmt.Errorf("contents.source, found %q", f.Contents.Source) + } + + if f.Contents.Compression != "gzip" { + return fmt.Errorf("contents.compression, found %q", f.Contents.Compression) + } + + if f.Contents.Verification.Hash.Sum != "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" { + return fmt.Errorf("config.replace.verification, found %q", f.Contents.Verification.Hash) + } + + return nil + }) +} diff --git a/builtin/providers/ignition/resource_ignition_filesystem.go b/builtin/providers/ignition/resource_ignition_filesystem.go new file mode 100644 index 000000000000..a623d4a27fff --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_filesystem.go @@ -0,0 +1,111 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceFilesystem() *schema.Resource { + return &schema.Resource{ + Create: resourceFilesystemCreate, + Delete: resourceFilesystemDelete, + Exists: resourceFilesystemExists, + Read: resourceFilesystemRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "mount": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "format": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "force": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "options": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceFilesystemCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildFilesystem(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceFilesystemDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceFilesystemExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildFilesystem(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceFilesystemRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildFilesystem(d *schema.ResourceData, c *cache) (string, error) { + var mount *types.FilesystemMount + if _, ok := d.GetOk("mount"); ok { + mount = &types.FilesystemMount{ + Device: types.Path(d.Get("mount.0.device").(string)), + Format: types.FilesystemFormat(d.Get("mount.0.format").(string)), + } + + force, hasForce := d.GetOk("mount.0.force") + options, hasOptions := d.GetOk("mount.0.options") + if hasOptions || hasForce { + mount.Create = &types.FilesystemCreate{ + Force: force.(bool), + Options: castSliceInterface(options.([]interface{})), + } + } + } + + return c.addFilesystem(&types.Filesystem{ + Name: d.Get("name").(string), + Mount: mount, + Path: types.Path(d.Get("path").(string)), + }), nil +} diff --git a/builtin/providers/ignition/resource_ignition_filesystem_test.go b/builtin/providers/ignition/resource_ignition_filesystem_test.go new file mode 100644 index 000000000000..768ee8cb7961 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_filesystem_test.go @@ -0,0 +1,101 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionFilesystem(t *testing.T) { + testIgnition(t, ` + resource "ignition_filesystem" "foo" { + name = "foo" + path = "/foo" + } + + resource "ignition_filesystem" "qux" { + name = "qux" + mount { + device = "/qux" + format = "ext4" + } + } + + resource "ignition_filesystem" "bar" { + name = "bar" + mount { + device = "/bar" + format = "ext4" + force = true + options = ["rw"] + } + } + + resource "ignition_config" "test" { + ignition {} + filesystems = [ + "${ignition_filesystem.foo.id}", + "${ignition_filesystem.qux.id}", + "${ignition_filesystem.bar.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Storage.Filesystems) != 3 { + return fmt.Errorf("disks, found %d", len(c.Storage.Filesystems)) + } + + f := c.Storage.Filesystems[0] + if f.Name != "foo" { + return fmt.Errorf("name, found %q", f.Name) + } + + if f.Mount != nil { + return fmt.Errorf("mount, found %q", f.Mount) + } + + if f.Path != "/foo" { + return fmt.Errorf("path, found %q", f.Path) + } + + f = c.Storage.Filesystems[1] + if f.Name != "qux" { + return fmt.Errorf("name, found %q", f.Name) + } + + if f.Mount.Device != "/qux" { + return fmt.Errorf("mount.0.device, found %q", f.Mount.Device) + } + + if f.Mount.Format != "ext4" { + return fmt.Errorf("mount.0.format, found %q", f.Mount.Format) + } + + if f.Mount.Create != nil { + return fmt.Errorf("mount, create was found %q", f.Mount.Create) + } + + f = c.Storage.Filesystems[2] + if f.Name != "bar" { + return fmt.Errorf("name, found %q", f.Name) + } + + if f.Mount.Device != "/bar" { + return fmt.Errorf("mount.0.device, found %q", f.Mount.Device) + } + + if f.Mount.Format != "ext4" { + return fmt.Errorf("mount.0.format, found %q", f.Mount.Format) + } + + if f.Mount.Create.Force != true { + return fmt.Errorf("mount.0.force, found %q", f.Mount.Create.Force) + } + + if len(f.Mount.Create.Options) != 1 || f.Mount.Create.Options[0] != "rw" { + return fmt.Errorf("mount.0.options, found %q", f.Mount.Create.Options) + } + + return nil + }) +} From 45e846d8a65fdc05b00c98ff63d8e6b9000f1892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Mon, 18 Apr 2016 00:00:49 +0200 Subject: [PATCH 07/14] providers: ignition_systemd_unit and ignition_networkd_unit --- builtin/providers/ignition/provider.go | 87 +++++++++++--- builtin/providers/ignition/provider_test.go | 14 +++ .../ignition/resource_ignition_config.go | 107 ++++++++++++----- .../resource_ignition_networkd_unit.go | 66 ++++++++++ .../resource_ignition_networkd_unit_test.go | 40 +++++++ .../resource_ignition_systemd_unit.go | 113 ++++++++++++++++++ .../resource_ignition_systemd_unit_test.go | 59 +++++++++ 7 files changed, 440 insertions(+), 46 deletions(-) create mode 100644 builtin/providers/ignition/resource_ignition_networkd_unit.go create mode 100644 builtin/providers/ignition/resource_ignition_networkd_unit_test.go create mode 100644 builtin/providers/ignition/resource_ignition_systemd_unit.go create mode 100644 builtin/providers/ignition/resource_ignition_systemd_unit_test.go diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go index 8e5bdfc64953..1bbc7bc74173 100644 --- a/builtin/providers/ignition/provider.go +++ b/builtin/providers/ignition/provider.go @@ -1,11 +1,15 @@ package ignition import ( + "bytes" "crypto/sha256" "encoding/hex" "encoding/json" + "fmt" + "io" "sync" + "github.com/coreos/go-systemd/unit" "github.com/coreos/ignition/config/types" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" @@ -14,34 +18,40 @@ import ( func Provider() terraform.ResourceProvider { return &schema.Provider{ ResourcesMap: map[string]*schema.Resource{ - "ignition_config": resourceConfig(), - "ignition_disk": resourceDisk(), - "ignition_raid": resourceRaid(), - "ignition_filesystem": resourceFilesystem(), - "ignition_file": resourceFile(), - "ignition_user": resourceUser(), - "ignition_group": resourceGroup(), + "ignition_config": resourceConfig(), + "ignition_disk": resourceDisk(), + "ignition_raid": resourceRaid(), + "ignition_filesystem": resourceFilesystem(), + "ignition_file": resourceFile(), + "ignition_systemd_unit": resourceSystemdUnit(), + "ignition_networkd_unit": resourceNetworkdUnit(), + "ignition_user": resourceUser(), + "ignition_group": resourceGroup(), }, ConfigureFunc: func(*schema.ResourceData) (interface{}, error) { return &cache{ - disks: make(map[string]*types.Disk, 0), - arrays: make(map[string]*types.Raid, 0), - filesystems: make(map[string]*types.Filesystem, 0), - files: make(map[string]*types.File, 0), - users: make(map[string]*types.User, 0), - groups: make(map[string]*types.Group, 0), + disks: make(map[string]*types.Disk, 0), + arrays: make(map[string]*types.Raid, 0), + filesystems: make(map[string]*types.Filesystem, 0), + files: make(map[string]*types.File, 0), + systemdUnits: make(map[string]*types.SystemdUnit, 0), + networkdUnits: make(map[string]*types.NetworkdUnit, 0), + users: make(map[string]*types.User, 0), + groups: make(map[string]*types.Group, 0), }, nil }, } } type cache struct { - disks map[string]*types.Disk - arrays map[string]*types.Raid - filesystems map[string]*types.Filesystem - files map[string]*types.File - users map[string]*types.User - groups map[string]*types.Group + disks map[string]*types.Disk + arrays map[string]*types.Raid + filesystems map[string]*types.Filesystem + files map[string]*types.File + systemdUnits map[string]*types.SystemdUnit + networkdUnits map[string]*types.NetworkdUnit + users map[string]*types.User + groups map[string]*types.Group sync.Mutex } @@ -86,6 +96,26 @@ func (c *cache) addFile(f *types.File) string { return id } +func (c *cache) addSystemdUnit(u *types.SystemdUnit) string { + c.Lock() + defer c.Unlock() + + id := id(u) + c.systemdUnits[id] = u + + return id +} + +func (c *cache) addNetworkdUnit(u *types.NetworkdUnit) string { + c.Lock() + defer c.Unlock() + + id := id(u) + c.networkdUnits[id] = u + + return id +} + func (c *cache) addUser(u *types.User) string { c.Lock() defer c.Unlock() @@ -134,3 +164,22 @@ func getUInt(d *schema.ResourceData, key string) *uint { return uid } + +func validateUnit(content string) error { + r := bytes.NewBuffer([]byte(content)) + + u, err := unit.Deserialize(r) + if len(u) == 0 { + return fmt.Errorf("invalid or empty unit content") + } + + if err == nil { + return nil + } + + if err == io.EOF { + return fmt.Errorf("unexpected EOF reading unit content") + } + + return err +} diff --git a/builtin/providers/ignition/provider_test.go b/builtin/providers/ignition/provider_test.go index 10f95481c886..d7e381471b85 100644 --- a/builtin/providers/ignition/provider_test.go +++ b/builtin/providers/ignition/provider_test.go @@ -16,3 +16,17 @@ func TestProvider(t *testing.T) { t.Fatalf("err: %s", err) } } + +func TestValidateUnit(t *testing.T) { + if err := validateUnit(""); err == nil { + t.Fatalf("error not found, expected error") + } + + if err := validateUnit("[foo]qux"); err == nil { + t.Fatalf("error not found, expected error") + } + + if err := validateUnit("[foo]\nqux=foo\nfoo"); err == nil { + t.Fatalf("error not found, expected error") + } +} diff --git a/builtin/providers/ignition/resource_ignition_config.go b/builtin/providers/ignition/resource_ignition_config.go index a00c89b736e2..7cec05b88dad 100644 --- a/builtin/providers/ignition/resource_ignition_config.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -94,6 +94,18 @@ func resourceConfig() *schema.Resource { ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "systemd": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "networkd": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "users": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -169,12 +181,22 @@ func buildConfig(d *schema.ResourceData, c *cache) (*types.Config, error) { return nil, err } - config.Passwd, err = buildPasswd(d, c) + config.Storage, err = buildStorage(d, c) if err != nil { return nil, err } - config.Storage, err = buildStorage(d, c) + config.Systemd, err = buildSystemd(d, c) + if err != nil { + return nil, err + } + + config.Networkd, err = buildNetworkd(d, c) + if err != nil { + return nil, err + } + + config.Passwd, err = buildPasswd(d, c) if err != nil { return nil, err } @@ -231,31 +253,6 @@ func buildConfigReference(raw map[string]interface{}) (*types.ConfigReference, e return r, nil } -func buildPasswd(d *schema.ResourceData, c *cache) (types.Passwd, error) { - passwd := types.Passwd{} - - for _, id := range d.Get("users").([]interface{}) { - u, ok := c.users[id.(string)] - if !ok { - return passwd, fmt.Errorf("invalid user %q, unknown user id", id) - } - - passwd.Users = append(passwd.Users, *u) - } - - for _, id := range d.Get("groups").([]interface{}) { - g, ok := c.groups[id.(string)] - if !ok { - return passwd, fmt.Errorf("invalid group %q, unknown group id", id) - } - - passwd.Groups = append(passwd.Groups, *g) - } - - return passwd, nil - -} - func buildStorage(d *schema.ResourceData, c *cache) (types.Storage, error) { storage := types.Storage{} @@ -299,6 +296,62 @@ func buildStorage(d *schema.ResourceData, c *cache) (types.Storage, error) { } +func buildSystemd(d *schema.ResourceData, c *cache) (types.Systemd, error) { + systemd := types.Systemd{} + + for _, id := range d.Get("systemd").([]interface{}) { + u, ok := c.systemdUnits[id.(string)] + if !ok { + return systemd, fmt.Errorf("invalid systemd unit %q, unknown systemd unit id", id) + } + + systemd.Units = append(systemd.Units, *u) + } + + return systemd, nil + +} + +func buildNetworkd(d *schema.ResourceData, c *cache) (types.Networkd, error) { + networkd := types.Networkd{} + + for _, id := range d.Get("networkd").([]interface{}) { + u, ok := c.networkdUnits[id.(string)] + if !ok { + return networkd, fmt.Errorf("invalid networkd unit %q, unknown networkd unit id", id) + } + + networkd.Units = append(networkd.Units, *u) + } + + return networkd, nil +} + +func buildPasswd(d *schema.ResourceData, c *cache) (types.Passwd, error) { + passwd := types.Passwd{} + + for _, id := range d.Get("users").([]interface{}) { + u, ok := c.users[id.(string)] + if !ok { + return passwd, fmt.Errorf("invalid user %q, unknown user id", id) + } + + passwd.Users = append(passwd.Users, *u) + } + + for _, id := range d.Get("groups").([]interface{}) { + g, ok := c.groups[id.(string)] + if !ok { + return passwd, fmt.Errorf("invalid group %q, unknown group id", id) + } + + passwd.Groups = append(passwd.Groups, *g) + } + + return passwd, nil + +} + func buildURL(raw string) (types.Url, error) { u, err := url.Parse(raw) if err != nil { diff --git a/builtin/providers/ignition/resource_ignition_networkd_unit.go b/builtin/providers/ignition/resource_ignition_networkd_unit.go new file mode 100644 index 000000000000..5b2518d64d4b --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_networkd_unit.go @@ -0,0 +1,66 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceNetworkdUnit() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkdUnitCreate, + Delete: resourceNetworkdUnitDelete, + Exists: resourceNetworkdUnitExists, + Read: resourceNetworkdUnitRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "content": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkdUnitCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildNetworkdUnit(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceNetworkdUnitDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceNetworkdUnitExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildNetworkdUnit(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceNetworkdUnitRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildNetworkdUnit(d *schema.ResourceData, c *cache) (string, error) { + if err := validateUnit(d.Get("content").(string)); err != nil { + return "", err + } + + return c.addNetworkdUnit(&types.NetworkdUnit{ + Name: types.NetworkdUnitName(d.Get("name").(string)), + Contents: d.Get("content").(string), + }), nil +} diff --git a/builtin/providers/ignition/resource_ignition_networkd_unit_test.go b/builtin/providers/ignition/resource_ignition_networkd_unit_test.go new file mode 100644 index 000000000000..262b80b25a5c --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_networkd_unit_test.go @@ -0,0 +1,40 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionNetworkdUnit(t *testing.T) { + testIgnition(t, ` + resource "ignition_networkd_unit" "foo" { + name = "foo.link" + content = "[Match]\nName=eth0\n\n[Network]\nAddress=10.0.1.7\n" + } + + resource "ignition_config" "test" { + ignition {} + networkd = [ + "${ignition_networkd_unit.foo.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Networkd.Units) != 1 { + return fmt.Errorf("networkd, found %d", len(c.Networkd.Units)) + } + + u := c.Networkd.Units[0] + + if u.Name != "foo.link" { + return fmt.Errorf("name, found %q", u.Name) + } + + if u.Contents != "[Match]\nName=eth0\n\n[Network]\nAddress=10.0.1.7\n" { + return fmt.Errorf("content, found %q", u.Contents) + } + + return nil + }) +} diff --git a/builtin/providers/ignition/resource_ignition_systemd_unit.go b/builtin/providers/ignition/resource_ignition_systemd_unit.go new file mode 100644 index 000000000000..1b2a94c2884b --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_systemd_unit.go @@ -0,0 +1,113 @@ +package ignition + +import ( + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceSystemdUnit() *schema.Resource { + return &schema.Resource{ + Create: resourceSystemdUnitCreate, + Delete: resourceSystemdUnitDelete, + Exists: resourceSystemdUnitExists, + Read: resourceSystemdUnitRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "enable": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + "mask": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "content": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "dropin": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "content": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourceSystemdUnitCreate(d *schema.ResourceData, meta interface{}) error { + id, err := buildSystemdUnit(d, meta.(*cache)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} + +func resourceSystemdUnitDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func resourceSystemdUnitExists(d *schema.ResourceData, meta interface{}) (bool, error) { + id, err := buildSystemdUnit(d, meta.(*cache)) + if err != nil { + return false, err + } + + return id == d.Id(), nil +} + +func resourceSystemdUnitRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildSystemdUnit(d *schema.ResourceData, c *cache) (string, error) { + if err := validateUnit(d.Get("content").(string)); err != nil { + return "", err + } + + var dropins []types.SystemdUnitDropIn + for _, raw := range d.Get("dropin").([]interface{}) { + value := raw.(map[string]interface{}) + + if err := validateUnit(value["content"].(string)); err != nil { + return "", err + } + + dropins = append(dropins, types.SystemdUnitDropIn{ + Name: types.SystemdUnitDropInName(value["name"].(string)), + Contents: value["content"].(string), + }) + } + + return c.addSystemdUnit(&types.SystemdUnit{ + Name: types.SystemdUnitName(d.Get("name").(string)), + Contents: d.Get("content").(string), + Enable: d.Get("enable").(bool), + Mask: d.Get("mask").(bool), + DropIns: dropins, + }), nil +} diff --git a/builtin/providers/ignition/resource_ignition_systemd_unit_test.go b/builtin/providers/ignition/resource_ignition_systemd_unit_test.go new file mode 100644 index 000000000000..186bd9a2a814 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_systemd_unit_test.go @@ -0,0 +1,59 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionSystemdUnit(t *testing.T) { + testIgnition(t, ` + resource "ignition_systemd_unit" "foo" { + name = "foo.service" + content = "[Match]\nName=eth0\n\n[Network]\nAddress=10.0.1.7\n" + enable = false + mask = true + + dropin { + name = "foo.conf" + content = "[Match]\nName=eth0\n\n[Network]\nAddress=10.0.1.7\n" + } + } + + resource "ignition_config" "test" { + ignition {} + systemd = [ + "${ignition_systemd_unit.foo.id}", + ] + } + `, func(c *types.Config) error { + if len(c.Systemd.Units) != 1 { + return fmt.Errorf("systemd, found %d", len(c.Systemd.Units)) + } + + u := c.Systemd.Units[0] + + if u.Name != "foo.service" { + return fmt.Errorf("name, found %q", u.Name) + } + + if u.Contents != "[Match]\nName=eth0\n\n[Network]\nAddress=10.0.1.7\n" { + return fmt.Errorf("content, found %q", u.Contents) + } + + if u.Mask != true { + return fmt.Errorf("mask, found %q", u.Mask) + } + + if u.Enable != false { + return fmt.Errorf("enable, found %q", u.Enable) + } + + if len(u.DropIns) != 1 { + return fmt.Errorf("dropins, found %q", u.DropIns) + } + + return nil + }) +} From 36265763ce0ef9c4350984ebcabb6d7d88d56025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Mon, 18 Apr 2016 00:19:50 +0200 Subject: [PATCH 08/14] providers: ignition_config improvements --- builtin/providers/ignition/provider.go | 17 ++++ .../ignition/resource_ignition_config.go | 90 ++++++------------- .../ignition/resource_ignition_config_test.go | 34 ++++--- .../ignition/resource_ignition_disk_test.go | 1 - .../ignition/resource_ignition_file_test.go | 1 - .../resource_ignition_filesystem_test.go | 1 - .../ignition/resource_ignition_group_test.go | 1 - .../resource_ignition_networkd_unit_test.go | 1 - .../ignition/resource_ignition_raid_test.go | 1 - .../resource_ignition_systemd_unit_test.go | 1 - .../ignition/resource_ignition_user_test.go | 1 - 11 files changed, 57 insertions(+), 92 deletions(-) diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go index 1bbc7bc74173..d6b4372f3fc1 100644 --- a/builtin/providers/ignition/provider.go +++ b/builtin/providers/ignition/provider.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "net/url" "sync" "github.com/coreos/go-systemd/unit" @@ -183,3 +184,19 @@ func validateUnit(content string) error { return err } + +func buildURL(raw string) (types.Url, error) { + u, err := url.Parse(raw) + if err != nil { + return types.Url{}, err + } + + return types.Url(*u), nil +} + +func buildHash(raw string) (types.Hash, error) { + h := types.Hash{} + err := h.UnmarshalJSON([]byte(fmt.Sprintf("%q", raw))) + + return h, err +} diff --git a/builtin/providers/ignition/resource_ignition_config.go b/builtin/providers/ignition/resource_ignition_config.go index 7cec05b88dad..706a968ad4c9 100644 --- a/builtin/providers/ignition/resource_ignition_config.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -3,44 +3,12 @@ package ignition import ( "encoding/json" "fmt" - "net/url" "github.com/hashicorp/terraform/helper/schema" "github.com/coreos/ignition/config/types" ) -var ignitionResource = &schema.Resource{ - Create: resourceIgnitionFileCreate, - Delete: resourceIgnitionFileDelete, - Exists: resourceIgnitionFileExists, - Read: resourceIgnitionFileRead, - Schema: map[string]*schema.Schema{ - "config": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "replace": &schema.Schema{ - Type: schema.TypeList, - ForceNew: true, - Optional: true, - MaxItems: 1, - Elem: configReferenceResource, - }, - "append": &schema.Schema{ - Type: schema.TypeList, - ForceNew: true, - Optional: true, - Elem: configReferenceResource, - }, - }, - }, - }, - }, -} var configReferenceResource = &schema.Resource{ Schema: map[string]*schema.Schema{ "source": &schema.Schema{ @@ -59,65 +27,73 @@ var configReferenceResource = &schema.Resource{ func resourceConfig() *schema.Resource { return &schema.Resource{ Create: resourceIgnitionFileCreate, + Update: resourceIgnitionFileCreate, Delete: resourceIgnitionFileDelete, Exists: resourceIgnitionFileExists, Read: resourceIgnitionFileRead, Schema: map[string]*schema.Schema{ - "ignition": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ForceNew: true, - Elem: ignitionResource, - }, "disks": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "arrays": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "filesystems": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "files": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "systemd": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "networkd": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "users": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "groups": &schema.Schema{ Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "config": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "replace": &schema.Schema{ + Type: schema.TypeList, + ForceNew: true, + Optional: true, + MaxItems: 1, + Elem: configReferenceResource, + }, + "append": &schema.Schema{ + Type: schema.TypeList, + ForceNew: true, + Optional: true, + Elem: configReferenceResource, + }, + }, + }, + }, "rendered": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -210,7 +186,7 @@ func buildIgnition(d *schema.ResourceData) (types.Ignition, error) { i := types.Ignition{} i.Version.UnmarshalJSON([]byte(`"2.0.0"`)) - rr := d.Get("ignition.0.config.0.replace.0").(map[string]interface{}) + rr := d.Get("config.0.replace.0").(map[string]interface{}) if len(rr) != 0 { i.Config.Replace, err = buildConfigReference(rr) if err != nil { @@ -218,7 +194,7 @@ func buildIgnition(d *schema.ResourceData) (types.Ignition, error) { } } - ar := d.Get("ignition.0.config.0.append").([]interface{}) + ar := d.Get("config.0.append").([]interface{}) if len(ar) != 0 { for _, rr := range ar { r, err := buildConfigReference(rr.(map[string]interface{})) @@ -351,19 +327,3 @@ func buildPasswd(d *schema.ResourceData, c *cache) (types.Passwd, error) { return passwd, nil } - -func buildURL(raw string) (types.Url, error) { - u, err := url.Parse(raw) - if err != nil { - return types.Url{}, err - } - - return types.Url(*u), nil -} - -func buildHash(raw string) (types.Hash, error) { - h := types.Hash{} - err := h.UnmarshalJSON([]byte(fmt.Sprintf("%q", raw))) - - return h, err -} diff --git a/builtin/providers/ignition/resource_ignition_config_test.go b/builtin/providers/ignition/resource_ignition_config_test.go index 62a189968dc8..3a4a3b0e4d7e 100644 --- a/builtin/providers/ignition/resource_ignition_config_test.go +++ b/builtin/providers/ignition/resource_ignition_config_test.go @@ -13,13 +13,11 @@ import ( func TestIngnitionFileReplace(t *testing.T) { testIgnition(t, ` resource "ignition_config" "test" { - ignition { - config { - replace { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } - } + config { + replace { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } } } `, func(c *types.Config) error { @@ -43,18 +41,16 @@ func TestIngnitionFileReplace(t *testing.T) { func TestIngnitionFileAppend(t *testing.T) { testIgnition(t, ` resource "ignition_config" "test" { - ignition { - config { - append { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } - - append { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } - } + config { + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } } } `, func(c *types.Config) error { diff --git a/builtin/providers/ignition/resource_ignition_disk_test.go b/builtin/providers/ignition/resource_ignition_disk_test.go index bb25d2840de2..c6df2227e384 100644 --- a/builtin/providers/ignition/resource_ignition_disk_test.go +++ b/builtin/providers/ignition/resource_ignition_disk_test.go @@ -20,7 +20,6 @@ func TestIngnitionDisk(t *testing.T) { } resource "ignition_config" "test" { - ignition {} disks = [ "${ignition_disk.foo.id}", ] diff --git a/builtin/providers/ignition/resource_ignition_file_test.go b/builtin/providers/ignition/resource_ignition_file_test.go index fe9870d01149..a3d029fdc0a6 100644 --- a/builtin/providers/ignition/resource_ignition_file_test.go +++ b/builtin/providers/ignition/resource_ignition_file_test.go @@ -31,7 +31,6 @@ func TestIngnitionFile(t *testing.T) { } resource "ignition_config" "test" { - ignition {} files = [ "${ignition_file.foo.id}", "${ignition_file.qux.id}", diff --git a/builtin/providers/ignition/resource_ignition_filesystem_test.go b/builtin/providers/ignition/resource_ignition_filesystem_test.go index 768ee8cb7961..348aaf0c9066 100644 --- a/builtin/providers/ignition/resource_ignition_filesystem_test.go +++ b/builtin/providers/ignition/resource_ignition_filesystem_test.go @@ -33,7 +33,6 @@ func TestIngnitionFilesystem(t *testing.T) { } resource "ignition_config" "test" { - ignition {} filesystems = [ "${ignition_filesystem.foo.id}", "${ignition_filesystem.qux.id}", diff --git a/builtin/providers/ignition/resource_ignition_group_test.go b/builtin/providers/ignition/resource_ignition_group_test.go index c564e2d73664..56a230463d36 100644 --- a/builtin/providers/ignition/resource_ignition_group_test.go +++ b/builtin/providers/ignition/resource_ignition_group_test.go @@ -20,7 +20,6 @@ func TestIngnitionGroup(t *testing.T) { } resource "ignition_config" "test" { - ignition {} groups = [ "${ignition_group.foo.id}", "${ignition_group.qux.id}", diff --git a/builtin/providers/ignition/resource_ignition_networkd_unit_test.go b/builtin/providers/ignition/resource_ignition_networkd_unit_test.go index 262b80b25a5c..ad2a1508faf6 100644 --- a/builtin/providers/ignition/resource_ignition_networkd_unit_test.go +++ b/builtin/providers/ignition/resource_ignition_networkd_unit_test.go @@ -15,7 +15,6 @@ func TestIngnitionNetworkdUnit(t *testing.T) { } resource "ignition_config" "test" { - ignition {} networkd = [ "${ignition_networkd_unit.foo.id}", ] diff --git a/builtin/providers/ignition/resource_ignition_raid_test.go b/builtin/providers/ignition/resource_ignition_raid_test.go index 18eacfd8cb46..bb0fdbb670d2 100644 --- a/builtin/providers/ignition/resource_ignition_raid_test.go +++ b/builtin/providers/ignition/resource_ignition_raid_test.go @@ -17,7 +17,6 @@ func TestIngnitionRaid(t *testing.T) { } resource "ignition_config" "test" { - ignition {} arrays = [ "${ignition_raid.foo.id}", ] diff --git a/builtin/providers/ignition/resource_ignition_systemd_unit_test.go b/builtin/providers/ignition/resource_ignition_systemd_unit_test.go index 186bd9a2a814..4b8f8f84112b 100644 --- a/builtin/providers/ignition/resource_ignition_systemd_unit_test.go +++ b/builtin/providers/ignition/resource_ignition_systemd_unit_test.go @@ -22,7 +22,6 @@ func TestIngnitionSystemdUnit(t *testing.T) { } resource "ignition_config" "test" { - ignition {} systemd = [ "${ignition_systemd_unit.foo.id}", ] diff --git a/builtin/providers/ignition/resource_ignition_user_test.go b/builtin/providers/ignition/resource_ignition_user_test.go index 188d95ca8720..e31427aae205 100644 --- a/builtin/providers/ignition/resource_ignition_user_test.go +++ b/builtin/providers/ignition/resource_ignition_user_test.go @@ -29,7 +29,6 @@ func TestIngnitionUser(t *testing.T) { } resource "ignition_config" "test" { - ignition {} users = [ "${ignition_user.foo.id}", "${ignition_user.qux.id}", From 1e20b983ab183c8f75ab0fe2eb35360354be4a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Mon, 18 Apr 2016 00:25:31 +0200 Subject: [PATCH 09/14] vendor: Capture new dependency upstream-pkg --- Godeps/Godeps.json | 20 +- vendor/github.com/coreos/go-systemd/LICENSE | 191 ++++++++++++ .../coreos/go-systemd/unit/deserialize.go | 276 ++++++++++++++++++ .../coreos/go-systemd/unit/escape.go | 116 ++++++++ .../coreos/go-systemd/unit/option.go | 54 ++++ .../coreos/go-systemd/unit/serialize.go | 75 +++++ 6 files changed, 731 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/coreos/go-systemd/LICENSE create mode 100644 vendor/github.com/coreos/go-systemd/unit/deserialize.go create mode 100644 vendor/github.com/coreos/go-systemd/unit/escape.go create mode 100644 vendor/github.com/coreos/go-systemd/unit/option.go create mode 100644 vendor/github.com/coreos/go-systemd/unit/serialize.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1ad4f26199cf..c5afe68f64cf 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,7 @@ { "ImportPath": "github.com/hashicorp/terraform", "GoVersion": "go1.6", - "GodepVersion": "v61", + "GodepVersion": "v62", "Packages": [ "./..." ], @@ -208,6 +208,10 @@ "ImportPath": "github.com/ajg/form", "Rev": "c9e1c3ae1f869d211cdaa085d23c6af2f5f83866" }, + { + "ImportPath": "github.com/alecthomas/units", + "Rev": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" + }, { "ImportPath": "github.com/apparentlymart/go-cidr/cidr", "Rev": "a3ebdb999b831ecb6ab8a226e31b07b2b9061c47" @@ -543,6 +547,20 @@ "Comment": "v2.3.0-alpha.0-652-ge552791", "Rev": "e5527914aa42cae3063f52892e1ca4518da0e4ae" }, + { + "ImportPath": "github.com/coreos/go-semver/semver", + "Rev": "37bb68f88c4418889f0a9bfea4a5333d36bd15d5" + }, + { + "ImportPath": "github.com/coreos/go-systemd/unit", + "Comment": "v5", + "Rev": "7b2428fec40033549c68f54e26e89e7ca9a9ce31" + }, + { + "ImportPath": "github.com/coreos/ignition/config/types", + "Comment": "v0.4.0-3-gd5e2176", + "Rev": "d5e21763171a55a0c687016ef3062197c9349cc0" + }, { "ImportPath": "github.com/cyberdelia/heroku-go/v3", "Rev": "81c5afa1abcf69cc18ccc24fa3716b5a455c9208" diff --git a/vendor/github.com/coreos/go-systemd/LICENSE b/vendor/github.com/coreos/go-systemd/LICENSE new file mode 100644 index 000000000000..37ec93a14fdc --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/coreos/go-systemd/unit/deserialize.go b/vendor/github.com/coreos/go-systemd/unit/deserialize.go new file mode 100644 index 000000000000..8a88162f98b0 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/unit/deserialize.go @@ -0,0 +1,276 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unit + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "strings" + "unicode" +) + +const ( + // SYSTEMD_LINE_MAX mimics the maximum line length that systemd can use. + // On typical systemd platforms (i.e. modern Linux), this will most + // commonly be 2048, so let's use that as a sanity check. + // Technically, we should probably pull this at runtime: + // SYSTEMD_LINE_MAX = int(C.sysconf(C.__SC_LINE_MAX)) + // but this would introduce an (unfortunate) dependency on cgo + SYSTEMD_LINE_MAX = 2048 + + // characters that systemd considers indicate a newline + SYSTEMD_NEWLINE = "\r\n" +) + +var ( + ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX) +) + +// Deserialize parses a systemd unit file into a list of UnitOption objects. +func Deserialize(f io.Reader) (opts []*UnitOption, err error) { + lexer, optchan, errchan := newLexer(f) + go lexer.lex() + + for opt := range optchan { + opts = append(opts, &(*opt)) + } + + err = <-errchan + return opts, err +} + +func newLexer(f io.Reader) (*lexer, <-chan *UnitOption, <-chan error) { + optchan := make(chan *UnitOption) + errchan := make(chan error, 1) + buf := bufio.NewReader(f) + + return &lexer{buf, optchan, errchan, ""}, optchan, errchan +} + +type lexer struct { + buf *bufio.Reader + optchan chan *UnitOption + errchan chan error + section string +} + +func (l *lexer) lex() { + var err error + defer func() { + close(l.optchan) + close(l.errchan) + }() + next := l.lexNextSection + for next != nil { + if l.buf.Buffered() >= SYSTEMD_LINE_MAX { + // systemd truncates lines longer than LINE_MAX + // https://bugs.freedesktop.org/show_bug.cgi?id=85308 + // Rather than allowing this to pass silently, let's + // explicitly gate people from encountering this + line, err := l.buf.Peek(SYSTEMD_LINE_MAX) + if err != nil { + l.errchan <- err + return + } + if bytes.IndexAny(line, SYSTEMD_NEWLINE) == -1 { + l.errchan <- ErrLineTooLong + return + } + } + + next, err = next() + if err != nil { + l.errchan <- err + return + } + } +} + +type lexStep func() (lexStep, error) + +func (l *lexer) lexSectionName() (lexStep, error) { + sec, err := l.buf.ReadBytes(']') + if err != nil { + return nil, errors.New("unable to find end of section") + } + + return l.lexSectionSuffixFunc(string(sec[:len(sec)-1])), nil +} + +func (l *lexer) lexSectionSuffixFunc(section string) lexStep { + return func() (lexStep, error) { + garbage, _, err := l.toEOL() + if err != nil { + return nil, err + } + + garbage = bytes.TrimSpace(garbage) + if len(garbage) > 0 { + return nil, fmt.Errorf("found garbage after section name %s: %v", l.section, garbage) + } + + return l.lexNextSectionOrOptionFunc(section), nil + } +} + +func (l *lexer) ignoreLineFunc(next lexStep) lexStep { + return func() (lexStep, error) { + for { + line, _, err := l.toEOL() + if err != nil { + return nil, err + } + + line = bytes.TrimSuffix(line, []byte{' '}) + + // lack of continuation means this line has been exhausted + if !bytes.HasSuffix(line, []byte{'\\'}) { + break + } + } + + // reached end of buffer, safe to exit + return next, nil + } +} + +func (l *lexer) lexNextSection() (lexStep, error) { + r, _, err := l.buf.ReadRune() + if err != nil { + if err == io.EOF { + err = nil + } + return nil, err + } + + if r == '[' { + return l.lexSectionName, nil + } else if isComment(r) { + return l.ignoreLineFunc(l.lexNextSection), nil + } + + return l.lexNextSection, nil +} + +func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep { + return func() (lexStep, error) { + r, _, err := l.buf.ReadRune() + if err != nil { + if err == io.EOF { + err = nil + } + return nil, err + } + + if unicode.IsSpace(r) { + return l.lexNextSectionOrOptionFunc(section), nil + } else if r == '[' { + return l.lexSectionName, nil + } else if isComment(r) { + return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil + } + + l.buf.UnreadRune() + return l.lexOptionNameFunc(section), nil + } +} + +func (l *lexer) lexOptionNameFunc(section string) lexStep { + return func() (lexStep, error) { + var partial bytes.Buffer + for { + r, _, err := l.buf.ReadRune() + if err != nil { + return nil, err + } + + if r == '\n' || r == '\r' { + return nil, errors.New("unexpected newline encountered while parsing option name") + } + + if r == '=' { + break + } + + partial.WriteRune(r) + } + + name := strings.TrimSpace(partial.String()) + return l.lexOptionValueFunc(section, name, bytes.Buffer{}), nil + } +} + +func (l *lexer) lexOptionValueFunc(section, name string, partial bytes.Buffer) lexStep { + return func() (lexStep, error) { + for { + line, eof, err := l.toEOL() + if err != nil { + return nil, err + } + + if len(bytes.TrimSpace(line)) == 0 { + break + } + + partial.Write(line) + + // lack of continuation means this value has been exhausted + idx := bytes.LastIndex(line, []byte{'\\'}) + if idx == -1 || idx != (len(line)-1) { + break + } + + if !eof { + partial.WriteRune('\n') + } + + return l.lexOptionValueFunc(section, name, partial), nil + } + + val := partial.String() + if strings.HasSuffix(val, "\n") { + // A newline was added to the end, so the file didn't end with a backslash. + // => Keep the newline + val = strings.TrimSpace(val) + "\n" + } else { + val = strings.TrimSpace(val) + } + l.optchan <- &UnitOption{Section: section, Name: name, Value: val} + + return l.lexNextSectionOrOptionFunc(section), nil + } +} + +// toEOL reads until the end-of-line or end-of-file. +// Returns (data, EOFfound, error) +func (l *lexer) toEOL() ([]byte, bool, error) { + line, err := l.buf.ReadBytes('\n') + // ignore EOF here since it's roughly equivalent to EOL + if err != nil && err != io.EOF { + return nil, false, err + } + + line = bytes.TrimSuffix(line, []byte{'\r'}) + line = bytes.TrimSuffix(line, []byte{'\n'}) + + return line, err == io.EOF, nil +} + +func isComment(r rune) bool { + return r == '#' || r == ';' +} diff --git a/vendor/github.com/coreos/go-systemd/unit/escape.go b/vendor/github.com/coreos/go-systemd/unit/escape.go new file mode 100644 index 000000000000..63b11726dba8 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/unit/escape.go @@ -0,0 +1,116 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Implements systemd-escape [--unescape] [--path] + +package unit + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + allowed = `:_.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789` +) + +// If isPath is true: +// We remove redundant '/'s, the leading '/', and trailing '/'. +// If the result is empty, a '/' is inserted. +// +// We always: +// Replace the following characters with `\x%x`: +// Leading `.` +// `-`, `\`, and anything not in this set: `:-_.\[0-9a-zA-Z]` +// Replace '/' with '-'. +func escape(unescaped string, isPath bool) string { + e := []byte{} + inSlashes := false + start := true + for i := 0; i < len(unescaped); i++ { + c := unescaped[i] + if isPath { + if c == '/' { + inSlashes = true + continue + } else if inSlashes { + inSlashes = false + if !start { + e = append(e, '-') + } + } + } + + if c == '/' { + e = append(e, '-') + } else if start && c == '.' || strings.IndexByte(allowed, c) == -1 { + e = append(e, []byte(fmt.Sprintf(`\x%x`, c))...) + } else { + e = append(e, c) + } + start = false + } + if isPath && len(e) == 0 { + e = append(e, '-') + } + return string(e) +} + +// If isPath is true: +// We always return a string beginning with '/'. +// +// We always: +// Replace '-' with '/'. +// Replace `\x%x` with the value represented in hex. +func unescape(escaped string, isPath bool) string { + u := []byte{} + for i := 0; i < len(escaped); i++ { + c := escaped[i] + if c == '-' { + c = '/' + } else if c == '\\' && len(escaped)-i >= 4 && escaped[i+1] == 'x' { + n, err := strconv.ParseInt(escaped[i+2:i+4], 16, 8) + if err == nil { + c = byte(n) + i += 3 + } + } + u = append(u, c) + } + if isPath && (len(u) == 0 || u[0] != '/') { + u = append([]byte("/"), u...) + } + return string(u) +} + +// UnitNameEscape escapes a string as `systemd-escape` would +func UnitNameEscape(unescaped string) string { + return escape(unescaped, false) +} + +// UnitNameUnescape unescapes a string as `systemd-escape --unescape` would +func UnitNameUnescape(escaped string) string { + return unescape(escaped, false) +} + +// UnitNamePathEscape escapes a string as `systemd-escape --path` would +func UnitNamePathEscape(unescaped string) string { + return escape(unescaped, true) +} + +// UnitNamePathUnescape unescapes a string as `systemd-escape --path --unescape` would +func UnitNamePathUnescape(escaped string) string { + return unescape(escaped, true) +} diff --git a/vendor/github.com/coreos/go-systemd/unit/option.go b/vendor/github.com/coreos/go-systemd/unit/option.go new file mode 100644 index 000000000000..e5d21e19d91d --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/unit/option.go @@ -0,0 +1,54 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unit + +import ( + "fmt" +) + +type UnitOption struct { + Section string + Name string + Value string +} + +func NewUnitOption(section, name, value string) *UnitOption { + return &UnitOption{Section: section, Name: name, Value: value} +} + +func (uo *UnitOption) String() string { + return fmt.Sprintf("{Section: %q, Name: %q, Value: %q}", uo.Section, uo.Name, uo.Value) +} + +func (uo *UnitOption) Match(other *UnitOption) bool { + return uo.Section == other.Section && + uo.Name == other.Name && + uo.Value == other.Value +} + +func AllMatch(u1 []*UnitOption, u2 []*UnitOption) bool { + length := len(u1) + if length != len(u2) { + return false + } + + for i := 0; i < length; i++ { + if !u1[i].Match(u2[i]) { + return false + } + } + + return true +} diff --git a/vendor/github.com/coreos/go-systemd/unit/serialize.go b/vendor/github.com/coreos/go-systemd/unit/serialize.go new file mode 100644 index 000000000000..e07799cad612 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/unit/serialize.go @@ -0,0 +1,75 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unit + +import ( + "bytes" + "io" +) + +// Serialize encodes all of the given UnitOption objects into a +// unit file. When serialized the options are sorted in their +// supplied order but grouped by section. +func Serialize(opts []*UnitOption) io.Reader { + var buf bytes.Buffer + + if len(opts) == 0 { + return &buf + } + + // Index of sections -> ordered options + idx := map[string][]*UnitOption{} + // Separately preserve order in which sections were seen + sections := []string{} + for _, opt := range opts { + sec := opt.Section + if _, ok := idx[sec]; !ok { + sections = append(sections, sec) + } + idx[sec] = append(idx[sec], opt) + } + + for i, sect := range sections { + writeSectionHeader(&buf, sect) + writeNewline(&buf) + + opts := idx[sect] + for _, opt := range opts { + writeOption(&buf, opt) + writeNewline(&buf) + } + if i < len(sections)-1 { + writeNewline(&buf) + } + } + + return &buf +} + +func writeNewline(buf *bytes.Buffer) { + buf.WriteRune('\n') +} + +func writeSectionHeader(buf *bytes.Buffer, section string) { + buf.WriteRune('[') + buf.WriteString(section) + buf.WriteRune(']') +} + +func writeOption(buf *bytes.Buffer, opt *UnitOption) { + buf.WriteString(opt.Name) + buf.WriteRune('=') + buf.WriteString(opt.Value) +} From 0db93111de7d8235850412f9da86af62ec041eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Mon, 18 Apr 2016 01:35:57 +0200 Subject: [PATCH 10/14] providers: ignition main --- builtin/bins/provider-ignition/main.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 builtin/bins/provider-ignition/main.go diff --git a/builtin/bins/provider-ignition/main.go b/builtin/bins/provider-ignition/main.go new file mode 100644 index 000000000000..8e30a9a23fc5 --- /dev/null +++ b/builtin/bins/provider-ignition/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/ignition" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: ignition.Provider, + }) +} From f9b37e767261f0510d9b262a5f53f9434d6f8ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 19 Apr 2016 00:25:16 +0200 Subject: [PATCH 11/14] documentation: ignition provider --- website/source/assets/stylesheets/_docs.scss | 1 + .../providers/ignition/index.html.markdown | 41 ++++++++++ .../docs/providers/ignition/r/config.html.md | 58 +++++++++++++ .../docs/providers/ignition/r/disk.html.md | 54 +++++++++++++ .../docs/providers/ignition/r/file.html.md | 81 +++++++++++++++++++ .../providers/ignition/r/filesystem.html.md | 52 ++++++++++++ .../docs/providers/ignition/r/group.html.md | 35 ++++++++ .../ignition/r/networkd_unit.html.md | 34 ++++++++ .../docs/providers/ignition/r/raid.html.md | 64 +++++++++++++++ .../providers/ignition/r/systemd_unit.html.md | 46 +++++++++++ .../docs/providers/ignition/r/user.html.md | 55 +++++++++++++ website/source/layouts/docs.erb | 4 + website/source/layouts/ignition.erb | 33 ++++++++ 13 files changed, 558 insertions(+) create mode 100644 website/source/docs/providers/ignition/index.html.markdown create mode 100644 website/source/docs/providers/ignition/r/config.html.md create mode 100644 website/source/docs/providers/ignition/r/disk.html.md create mode 100644 website/source/docs/providers/ignition/r/file.html.md create mode 100644 website/source/docs/providers/ignition/r/filesystem.html.md create mode 100644 website/source/docs/providers/ignition/r/group.html.md create mode 100644 website/source/docs/providers/ignition/r/networkd_unit.html.md create mode 100644 website/source/docs/providers/ignition/r/raid.html.md create mode 100644 website/source/docs/providers/ignition/r/systemd_unit.html.md create mode 100644 website/source/docs/providers/ignition/r/user.html.md create mode 100644 website/source/layouts/ignition.erb diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 2fdeba66de33..5a5568802dd4 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -25,6 +25,7 @@ body.layout-github, body.layout-fastly, body.layout-google, body.layout-heroku, +body.layout-ignition, body.layout-influxdb, body.layout-mailgun, body.layout-mysql, diff --git a/website/source/docs/providers/ignition/index.html.markdown b/website/source/docs/providers/ignition/index.html.markdown new file mode 100644 index 000000000000..4513c427cc4c --- /dev/null +++ b/website/source/docs/providers/ignition/index.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "ignition" +page_title: "Provider: Ignition" +sidebar_current: "docs-ignition-index" +description: |- + The Ignition provider is used to generate Ignition configuration files used by CoreOS Linux. +--- + +# Ignition Provider + +The Ignition provider is used to generate [Ignition](https://coreos.com/ignition/docs/latest/) configuration files. _Ignition_ is the provisioning utility used by [CoreOS](https://coreos.com/) Linux. + +The ignition provider is what we call a _logical provider_ and doesn't manage any _physical_ resources. It generates configurations files to be used by other resources. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +This config will write a single service unit (shown below) with the contents of an example service. This unit will be enabled as a dependency of multi-user.target and therefore start on boot + +``` +# Systemd unit resource containing the unit definition +resource "ignition_systemd_unit" "example" { + name = "example.service" + content = "[Service]\nType=oneshot\nExecStart=/usr/bin/echo Hello World\n\n[Install]\nWantedBy=multi-user.target" +} + +# Ingnition config include the previous defined systemd unit resource +resource "ignition_config" "example" { + systemd = [ + "${ignition_systemd_unit.example.id}", + ] +} + +# Create a CoreOS server using the Igntion config. +resource "aws_instance" "web" { + # ... + + user_data = "${ignition_config.example.rendered}" +} +``` diff --git a/website/source/docs/providers/ignition/r/config.html.md b/website/source/docs/providers/ignition/r/config.html.md new file mode 100644 index 000000000000..7197f9818e9a --- /dev/null +++ b/website/source/docs/providers/ignition/r/config.html.md @@ -0,0 +1,58 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_config" +sidebar_current: "docs-ignition-resource-config" +description: |- + Renders an ignition configuration as JSON +--- + +# ignition\_config + +Renders an ignition configuration as JSON. It contains all the disks, partitions, arrays, filesystems, files, users, groups and units. + +## Example Usage + +``` +resource "ignition_config" "example" { + systemd = [ + "${ignition_systemd_unit.example.id}", + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `disks` - (Optional) The list of disks to be configured and their options. + +* `arrays` - (Optional) The list of RAID arrays to be configured. + +* `filesystems` - (Optional) The list of filesystems to be configured and/or used in the _ignition_file_ resource. + +* `files` - (Optional) The list of files, rooted in this particular filesystem, to be written. + +* `systemd` - (Optional) The list of systemd units. Describes the desired state of the systemd units. + +* `networkd` - (Optional) The list of networkd units. Describes the desired state of the networkd files. + +* `users` - (Optional) The list of accounts to be added. + +* `groups` - (Optional) The list of groups to be added. + +* `append` - (Optional) A block with config that will replace the current. + +* `replace` - (Optional) Any number of blocks with the configs to be appended to the current config. + + +The `append` and `replace` blocks supports: + +* `source` - (Required) The URL of the config. Supported schemes are http. Note: When using http, it is advisable to use the verification option to ensure the contents haven’t been modified. + +* `verification` - (Optional) The hash of the config, in the form _\-\_ where type is sha512. + +## Attributes Reference + +The following attributes are exported: + +* `rendered` - The final rendered template. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/disk.html.md b/website/source/docs/providers/ignition/r/disk.html.md new file mode 100644 index 000000000000..8bee094c82ea --- /dev/null +++ b/website/source/docs/providers/ignition/r/disk.html.md @@ -0,0 +1,54 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_disk" +sidebar_current: "docs-ignition-resource-disk" +description: |- + Describes the desired state of a system’s disk. +--- + +# ignition\_disk + +Describes the desired state of a system’s disk. + +## Example Usage + +``` +resource "ignition_disk" "foo" { + device = "/dev/sda" + partition { + start = 2048 + size = 196037632 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `device` - (Required) The absolute path to the device. Devices are typically referenced by the _/dev/disk/by-*_ symlinks. + +* `wipe_table` - (Optional) Whether or not the partition tables shall be wiped. When true, the partition tables are erased before any further manipulation. Otherwise, the existing entries are left intact. + +* `partition` - (Optional) The list of partitions and their configuration for this particular disk.. + + +The `partition` block supports: + +* `label` - (Optional) The PARTLABEL for the partition. + +* `number` - (Optional) The partition number, which dictates it’s position in the partition table (one-indexed). If zero, use the next available partition slot. + +* `size` - (Optional) The size of the partition (in sectors). If zero, the partition will fill the remainder of the disk. + + +* `start` - (Optional) The start of the partition (in sectors). If zero, the partition will be positioned at the earliest available part of the disk. + + +* `type_guid` - (Optional) The GPT [partition type GUID](http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs). If omitted, the default will be _0FC63DAF-8483-4772-8E79-3D69D8477DE4_ (Linux filesystem data). + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/file.html.md b/website/source/docs/providers/ignition/r/file.html.md new file mode 100644 index 000000000000..dbcbc0a8875a --- /dev/null +++ b/website/source/docs/providers/ignition/r/file.html.md @@ -0,0 +1,81 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_file" +sidebar_current: "docs-ignition-resource-file" +description: |- + Describes a file to be written in a particular filesystem. +--- + +# ignition\_file + +Describes a file to be written in a particular filesystem. + +## Example Usage + +File with inline content: + + +``` +resource "ignition_file" "hello" { + filesystem = "foo" + path = "/hello.txt" + content { + content = "Hello World!" + } +} +``` + +File with remote content: + +``` +resource "ignition_file" "hello" { + filesystem = "qux" + path = "/hello.txt" + source { + source = "http://example.com/hello.txt.gz" + compression = "gzip" + verification = "sha512-0123456789abcdef0123456789...456789abcdef" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `filesystem` - (Optional) The internal identifier of the filesystem. This matches the last filesystem with the given identifier. This should be a valid name from a _ignition\_filesystem_ resource. + +* `path` - (Optional) The absolute path to the file. + +* `content` - (Optional) Block to provide the file content inline. + +* `source` - (Optional) Block to retrieve the file content from a remote location. + + __Note__: `content` and `source` are mutually exclusive + +* `mode` - (Optional) The list of partitions and their configuration for +this particular disk.. + +* `uid` - (Optional) The user ID of the owner. + +* `gid` - (Optional) The group ID of the owner. + +The `content` block supports: + +* `mime` - (Required) MIME format of the content (default _text/plain_). + +* `content` - (Required) Content of the file. + +The `source` block supports: + +* `source` - (Required) The URL of the file contents. When using http, it is advisable to use the verification option to ensure the contents haven’t been modified. + +* `compression` - (Optional) The type of compression used on the contents (null or gzip). + +* `verification` - (Optional) The hash of the config, in the form _\-\_ where type is sha512. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/filesystem.html.md b/website/source/docs/providers/ignition/r/filesystem.html.md new file mode 100644 index 000000000000..01af0c77fa05 --- /dev/null +++ b/website/source/docs/providers/ignition/r/filesystem.html.md @@ -0,0 +1,52 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_filesystem" +sidebar_current: "docs-ignition-resource-filesystem" +description: |- + Describes the desired state of a system’s filesystem. +--- + +# ignition\_filesystem + +Describes the desired state of a the system’s filesystems to be configured and/or used with the _ignition\_file_ resource. + +## Example Usage + +``` +resource "ignition_filesystem" "foo" { + name = "root" + mount { + device = "/dev/disk/by-label/ROOT" + format = "xfs" + force = true + options = ["-L", "ROOT"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Optional) The identifier for the filesystem, internal to Ignition. This is only required if the filesystem needs to be referenced in the a _ignition\_files_ resource. + +* `mount` - (Optional) Contains the set of mount and formatting options for the filesystem. A non-null entry indicates that the filesystem should be mounted before it is used by Ignition. + +* `path` - (Optional) The mount-point of the filesystem. A non-null entry indicates that the filesystem has already been mounted by the system at the specified path. This is really only useful for _/sysroot_. + + +The `mount` block supports: + +* `device` - (Required) The absolute path to the device. Devices are typically referenced by the _/dev/disk/by-*_ symlinks. + +* `format` - (Required) The filesystem format (ext4, btrfs, or xfs). + +* `force` - (Optional) Whether or not the create operation shall overwrite an existing filesystem. + +* `options` - (Optional) Any additional options to be passed to the format-specific mkfs utility. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/group.html.md b/website/source/docs/providers/ignition/r/group.html.md new file mode 100644 index 000000000000..6238c2a39dad --- /dev/null +++ b/website/source/docs/providers/ignition/r/group.html.md @@ -0,0 +1,35 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_group" +sidebar_current: "docs-ignition-resource-group" +description: |- + Describes the desired group additions to the passwd database. +--- + +# ignition\_group + +Describes the desired group additions to the passwd database. + +## Example Usage + +``` +resource "ignition_group" "foo" { + name = "foo" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The groupname for the account. + +* `password_hash` - (Optional) The encrypted password for the account. + +* `gid` - (Optional) The group ID of the new account. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/networkd_unit.html.md b/website/source/docs/providers/ignition/r/networkd_unit.html.md new file mode 100644 index 000000000000..63858a15a912 --- /dev/null +++ b/website/source/docs/providers/ignition/r/networkd_unit.html.md @@ -0,0 +1,34 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_networkd_unit" +sidebar_current: "docs-ignition-resource-networkd-unit" +description: |- + Describes the desired state of the networkd units. +--- + +# ignition\_networkd\_unit + +Describes the desired state of the networkd units. + +## Example Usage + +``` +resource "ignition_networkd_unit" "example" { + name = "00-eth0.network" + content = "[Match]\nName=eth0\n\n[Network]\nAddress=10.0.1.7" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the file. This must be suffixed with a valid unit type (e.g. _00-eth0.network_). + +* `content` - (Required) The contents of the networkd file. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/raid.html.md b/website/source/docs/providers/ignition/r/raid.html.md new file mode 100644 index 000000000000..e9869b4d0082 --- /dev/null +++ b/website/source/docs/providers/ignition/r/raid.html.md @@ -0,0 +1,64 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_raid" +sidebar_current: "docs-ignition-resource-raid" +description: |- + Describes the desired state of the system’s RAID. +--- + +# ignition\_raid + +Describes the desired state of the system’s RAID. + +## Example Usage + +``` +resource "ignition_raid" "md" { + name = "data" + level = "stripe" + devices = [ + "/dev/disk/by-partlabel/raid.1.1", + "/dev/disk/by-partlabel/raid.1.2" + ] +} + +resource "ignition_disk" "disk1" { + device = "/dev/sdb" + wipe_table = true + partition { + label = "raid.1.1" + number = 1 + size = 20480 + start = 0 + } +} + +resource "ignition_disk" "disk2" { + device = "/dev/sdc" + wipe_table = true + partition { + label = "raid.1.2" + number = 1 + size = 20480 + start = 0 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use for the resulting md device. + +* `level` - (Required) The redundancy level of the array (e.g. linear, raid1, raid5, etc.). + +* `devices` - (Required) The list of devices (referenced by their absolute path) in the array. + +* `spares` - (Optional) The number of spares (if applicable) in the array. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_ \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/systemd_unit.html.md b/website/source/docs/providers/ignition/r/systemd_unit.html.md new file mode 100644 index 000000000000..98494515d521 --- /dev/null +++ b/website/source/docs/providers/ignition/r/systemd_unit.html.md @@ -0,0 +1,46 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_systemd_unit" +sidebar_current: "docs-ignition-resource-systemd-unit" +description: |- + Describes the desired state of the systemd units. +--- + +# ignition\_systemd\_unit + +Describes the desired state of the systemd units. + +## Example Usage + +``` +resource "ignition_systemd_unit" "example" { + name = "example.service" + content = "[Service]\nType=oneshot\nExecStart=/usr/bin/echo Hello World\n\n[Install]\nWantedBy=multi-user.target" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Tthe name of the unit. This must be suffixed with a valid unit type (e.g. _thing.service_). + +* `enable` - (Optional) Whether or not the service shall be enabled. When true, the service is enabled. In order for this to have any effect, the unit must have an install section. (default true) + +* `mask` - (Optional) Whether or not the service shall be masked. When true, the service is masked by symlinking it to _/dev/null_. + +* `content` - (Required) The contents of the unit. + +* `dropin` - (Optional) The list of drop-ins for the unit. + +The `dropin` block supports: + +* `name` - (Required) The name of the drop-in. This must be suffixed with _.conf_. + +* `content` - (Optional) The contents of the drop-in. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/docs/providers/ignition/r/user.html.md b/website/source/docs/providers/ignition/r/user.html.md new file mode 100644 index 000000000000..d922ab77ae15 --- /dev/null +++ b/website/source/docs/providers/ignition/r/user.html.md @@ -0,0 +1,55 @@ +--- +layout: "ignition" +page_title: "Ignition: ignition_user" +sidebar_current: "docs-ignition-resource-user" +description: |- + Describes the desired user additions to the passwd database. +--- + +# ignition\_user + +Describes the desired user additions to the passwd database. + +## Example Usage + +``` +resource "ignition_user" "foo" { + name = "foo" + home_dir = "/home/foo/" + shell = "/bin/bash" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The username for the account. + +* `password_hash` - (Optional) The encrypted password for the account. + +* `ssh_authorized_keys` - (Optional) A list of SSH keys to be added to the user’s authorized_keys. + +* `uid` - (Optional) The user ID of the new account. + +* `gecos` - (Optional) The GECOS field of the new account. + +* `home_dir` - (Optional) The home directory of the new account. + +* `no_create_home` - (Optional) Whether or not to create the user’s home directory. + +* `primary_group` - (Optional) The name or ID of the primary group of the new account. + +* `groups` - (Optional) The list of supplementary groups of the new account. + +* `no_user_group` - (Optional) Whether or not to create a group with the same name as the user. + +* `no_log_init` - (Optional) Whether or not to add the user to the lastlog and faillog databases. + +* `shell` - (Optional) The login shell of the new account. + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID used to reference this resource in _ignition_config_. \ No newline at end of file diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 510637114775..cf67962a93bf 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -209,6 +209,10 @@ Heroku + > + Ignition + + > InfluxDB diff --git a/website/source/layouts/ignition.erb b/website/source/layouts/ignition.erb new file mode 100644 index 000000000000..a77da3a91db5 --- /dev/null +++ b/website/source/layouts/ignition.erb @@ -0,0 +1,33 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %> From 95c6a46fa848110ed37e59b52f732bd1a3ffe848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 19 Apr 2016 00:29:04 +0200 Subject: [PATCH 12/14] providers: ignition minor changes --- .../ignition/resource_ignition_config.go | 33 +++++++------------ .../ignition/resource_ignition_config_test.go | 26 +++++++-------- .../ignition/resource_ignition_filesystem.go | 2 +- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/builtin/providers/ignition/resource_ignition_config.go b/builtin/providers/ignition/resource_ignition_config.go index 706a968ad4c9..acecc077855f 100644 --- a/builtin/providers/ignition/resource_ignition_config.go +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -19,7 +19,7 @@ var configReferenceResource = &schema.Resource{ "verification": &schema.Schema{ Type: schema.TypeString, ForceNew: true, - Required: true, + Optional: true, }, }, } @@ -72,27 +72,18 @@ func resourceConfig() *schema.Resource { Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "config": &schema.Schema{ + "replace": &schema.Schema{ Type: schema.TypeList, + ForceNew: true, Optional: true, MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "replace": &schema.Schema{ - Type: schema.TypeList, - ForceNew: true, - Optional: true, - MaxItems: 1, - Elem: configReferenceResource, - }, - "append": &schema.Schema{ - Type: schema.TypeList, - ForceNew: true, - Optional: true, - Elem: configReferenceResource, - }, - }, - }, + Elem: configReferenceResource, + }, + "append": &schema.Schema{ + Type: schema.TypeList, + ForceNew: true, + Optional: true, + Elem: configReferenceResource, }, "rendered": &schema.Schema{ Type: schema.TypeString, @@ -186,7 +177,7 @@ func buildIgnition(d *schema.ResourceData) (types.Ignition, error) { i := types.Ignition{} i.Version.UnmarshalJSON([]byte(`"2.0.0"`)) - rr := d.Get("config.0.replace.0").(map[string]interface{}) + rr := d.Get("replace.0").(map[string]interface{}) if len(rr) != 0 { i.Config.Replace, err = buildConfigReference(rr) if err != nil { @@ -194,7 +185,7 @@ func buildIgnition(d *schema.ResourceData) (types.Ignition, error) { } } - ar := d.Get("config.0.append").([]interface{}) + ar := d.Get("append").([]interface{}) if len(ar) != 0 { for _, rr := range ar { r, err := buildConfigReference(rr.(map[string]interface{})) diff --git a/builtin/providers/ignition/resource_ignition_config_test.go b/builtin/providers/ignition/resource_ignition_config_test.go index 3a4a3b0e4d7e..cc197f729696 100644 --- a/builtin/providers/ignition/resource_ignition_config_test.go +++ b/builtin/providers/ignition/resource_ignition_config_test.go @@ -13,11 +13,9 @@ import ( func TestIngnitionFileReplace(t *testing.T) { testIgnition(t, ` resource "ignition_config" "test" { - config { - replace { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } + replace { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } } `, func(c *types.Config) error { @@ -41,16 +39,14 @@ func TestIngnitionFileReplace(t *testing.T) { func TestIngnitionFileAppend(t *testing.T) { testIgnition(t, ` resource "ignition_config" "test" { - config { - append { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } - - append { - source = "foo" - verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - } + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } } `, func(c *types.Config) error { diff --git a/builtin/providers/ignition/resource_ignition_filesystem.go b/builtin/providers/ignition/resource_ignition_filesystem.go index a623d4a27fff..9b0448802b0c 100644 --- a/builtin/providers/ignition/resource_ignition_filesystem.go +++ b/builtin/providers/ignition/resource_ignition_filesystem.go @@ -14,7 +14,7 @@ func resourceFilesystem() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, "mount": &schema.Schema{ From 81213cde0b475295c4490e2c20d002f5b5ed56c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 2 Aug 2016 00:20:47 +0200 Subject: [PATCH 13/14] providers: ignition, fix test --- builtin/providers/ignition/resource_ignition_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/ignition/resource_ignition_config_test.go b/builtin/providers/ignition/resource_ignition_config_test.go index cc197f729696..db4e700fa9a8 100644 --- a/builtin/providers/ignition/resource_ignition_config_test.go +++ b/builtin/providers/ignition/resource_ignition_config_test.go @@ -69,7 +69,7 @@ func TestIngnitionFileAppend(t *testing.T) { func testIgnition(t *testing.T, input string, assert func(*types.Config) error) { check := func(s *terraform.State) error { - got := s.RootModule().Outputs["rendered"] + got := s.RootModule().Outputs["rendered"].String() c := &types.Config{} err := json.Unmarshal([]byte(got), c) From 888e93dcec641055a6abf936532a89af1c5af51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Cuadros?= Date: Tue, 3 Jan 2017 11:54:04 +0100 Subject: [PATCH 14/14] fixing tests and latest versions --- .../ignition/resource_ignition_config_test.go | 2 +- .../ignition/resource_ignition_filesystem.go | 4 +- .../resource_ignition_filesystem_test.go | 6 +- .../ignition/config/types/compression.go | 30 +- .../coreos/ignition/config/types/config.go | 63 ++- .../coreos/ignition/config/types/disk.go | 93 +--- .../coreos/ignition/config/types/file.go | 60 +- .../ignition/config/types/filesystem.go | 152 +---- .../coreos/ignition/config/types/group.go | 8 +- .../coreos/ignition/config/types/hash.go | 32 +- .../coreos/ignition/config/types/ignition.go | 42 +- .../coreos/ignition/config/types/networkd.go | 2 +- .../coreos/ignition/config/types/partition.go | 106 +--- .../coreos/ignition/config/types/passwd.go | 4 +- .../coreos/ignition/config/types/path.go | 30 +- .../coreos/ignition/config/types/raid.go | 40 +- .../coreos/ignition/config/types/storage.go | 8 +- .../coreos/ignition/config/types/systemd.go | 2 +- .../coreos/ignition/config/types/unit.go | 102 +--- .../coreos/ignition/config/types/url.go | 52 +- .../coreos/ignition/config/types/user.go | 28 +- .../ignition/config/types/verification.go | 2 +- .../ignition/config/validate/report/report.go | 158 ++++++ .../vincent-petithory/dataurl/LICENSE | 20 + .../vincent-petithory/dataurl/README.md | 81 +++ .../vincent-petithory/dataurl/dataurl.go | 280 ++++++++++ .../vincent-petithory/dataurl/doc.go | 28 + .../vincent-petithory/dataurl/lex.go | 521 ++++++++++++++++++ .../vincent-petithory/dataurl/rfc2396.go | 130 +++++ .../vincent-petithory/dataurl/wercker.yml | 1 + vendor/vendor.json | 18 + 31 files changed, 1517 insertions(+), 588 deletions(-) create mode 100644 vendor/github.com/coreos/ignition/config/validate/report/report.go create mode 100644 vendor/github.com/vincent-petithory/dataurl/LICENSE create mode 100644 vendor/github.com/vincent-petithory/dataurl/README.md create mode 100644 vendor/github.com/vincent-petithory/dataurl/dataurl.go create mode 100644 vendor/github.com/vincent-petithory/dataurl/doc.go create mode 100644 vendor/github.com/vincent-petithory/dataurl/lex.go create mode 100644 vendor/github.com/vincent-petithory/dataurl/rfc2396.go create mode 100644 vendor/github.com/vincent-petithory/dataurl/wercker.yml diff --git a/builtin/providers/ignition/resource_ignition_config_test.go b/builtin/providers/ignition/resource_ignition_config_test.go index db4e700fa9a8..083d37301223 100644 --- a/builtin/providers/ignition/resource_ignition_config_test.go +++ b/builtin/providers/ignition/resource_ignition_config_test.go @@ -69,7 +69,7 @@ func TestIngnitionFileAppend(t *testing.T) { func testIgnition(t *testing.T, input string, assert func(*types.Config) error) { check := func(s *terraform.State) error { - got := s.RootModule().Outputs["rendered"].String() + got := s.RootModule().Outputs["rendered"].Value.(string) c := &types.Config{} err := json.Unmarshal([]byte(got), c) diff --git a/builtin/providers/ignition/resource_ignition_filesystem.go b/builtin/providers/ignition/resource_ignition_filesystem.go index 9b0448802b0c..54cbe24ed2fb 100644 --- a/builtin/providers/ignition/resource_ignition_filesystem.go +++ b/builtin/providers/ignition/resource_ignition_filesystem.go @@ -103,9 +103,11 @@ func buildFilesystem(d *schema.ResourceData, c *cache) (string, error) { } } + path := types.Path(d.Get("path").(string)) + return c.addFilesystem(&types.Filesystem{ Name: d.Get("name").(string), Mount: mount, - Path: types.Path(d.Get("path").(string)), + Path: &path, }), nil } diff --git a/builtin/providers/ignition/resource_ignition_filesystem_test.go b/builtin/providers/ignition/resource_ignition_filesystem_test.go index 348aaf0c9066..fe8d53f6d0b9 100644 --- a/builtin/providers/ignition/resource_ignition_filesystem_test.go +++ b/builtin/providers/ignition/resource_ignition_filesystem_test.go @@ -13,7 +13,7 @@ func TestIngnitionFilesystem(t *testing.T) { name = "foo" path = "/foo" } - + resource "ignition_filesystem" "qux" { name = "qux" mount { @@ -50,10 +50,10 @@ func TestIngnitionFilesystem(t *testing.T) { } if f.Mount != nil { - return fmt.Errorf("mount, found %q", f.Mount) + return fmt.Errorf("mount, found %q", f.Mount.Device) } - if f.Path != "/foo" { + if string(*f.Path) != "/foo" { return fmt.Errorf("path, found %q", f.Path) } diff --git a/vendor/github.com/coreos/ignition/config/types/compression.go b/vendor/github.com/coreos/ignition/config/types/compression.go index a8069756f68e..65c3e25cb18e 100644 --- a/vendor/github.com/coreos/ignition/config/types/compression.go +++ b/vendor/github.com/coreos/ignition/config/types/compression.go @@ -15,8 +15,9 @@ package types import ( - "encoding/json" "errors" + + "github.com/coreos/ignition/config/validate/report" ) var ( @@ -25,30 +26,11 @@ var ( type Compression string -func (c *Compression) UnmarshalYAML(unmarshal func(interface{}) error) error { - return c.unmarshal(unmarshal) -} - -func (c *Compression) UnmarshalJSON(data []byte) error { - return c.unmarshal(func(tc interface{}) error { - return json.Unmarshal(data, tc) - }) -} - -func (c *Compression) unmarshal(unmarshal func(interface{}) error) error { - var tc string - if err := unmarshal(&tc); err != nil { - return err - } - *c = Compression(tc) - return c.assertValid() -} - -func (c Compression) assertValid() error { +func (c Compression) Validate() report.Report { switch c { - case "gzip": + case "", "gzip": default: - return ErrCompressionInvalid + return report.ReportFromError(ErrCompressionInvalid, report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/config.go b/vendor/github.com/coreos/ignition/config/types/config.go index 7d7b60face36..385515891751 100644 --- a/vendor/github.com/coreos/ignition/config/types/config.go +++ b/vendor/github.com/coreos/ignition/config/types/config.go @@ -15,7 +15,11 @@ package types import ( + "fmt" + "github.com/coreos/go-semver/semver" + + "github.com/coreos/ignition/config/validate/report" ) var ( @@ -26,9 +30,58 @@ var ( ) type Config struct { - Ignition Ignition `json:"ignition" yaml:"ignition"` - Storage Storage `json:"storage,omitempty" yaml:"storage"` - Systemd Systemd `json:"systemd,omitempty" yaml:"systemd"` - Networkd Networkd `json:"networkd,omitempty" yaml:"networkd"` - Passwd Passwd `json:"passwd,omitempty" yaml:"passwd"` + Ignition Ignition `json:"ignition"` + Storage Storage `json:"storage,omitempty"` + Systemd Systemd `json:"systemd,omitempty"` + Networkd Networkd `json:"networkd,omitempty"` + Passwd Passwd `json:"passwd,omitempty"` +} + +func (c Config) Validate() report.Report { + r := report.Report{} + rules := []rule{ + checkFilesFilesystems, + checkDuplicateFilesystems, + } + + for _, rule := range rules { + rule(c, &r) + } + return r +} + +type rule func(cfg Config, report *report.Report) + +func checkFilesFilesystems(cfg Config, r *report.Report) { + filesystems := map[string]struct{}{"root": {}} + for _, filesystem := range cfg.Storage.Filesystems { + filesystems[filesystem.Name] = struct{}{} + } + for _, file := range cfg.Storage.Files { + if file.Filesystem == "" { + // Filesystem was not specified. This is an error, but its handled in types.File's Validate, not here + continue + } + _, ok := filesystems[file.Filesystem] + if !ok { + r.Add(report.Entry{ + Kind: report.EntryWarning, + Message: fmt.Sprintf("File %q references nonexistent filesystem %q. (This is ok if it is defined in a referenced config)", + file.Path, file.Filesystem), + }) + } + } +} + +func checkDuplicateFilesystems(cfg Config, r *report.Report) { + filesystems := map[string]struct{}{"root": {}} + for _, filesystem := range cfg.Storage.Filesystems { + if _, ok := filesystems[filesystem.Name]; ok { + r.Add(report.Entry{ + Kind: report.EntryWarning, + Message: fmt.Sprintf("Filesystem %q shadows exising filesystem definition", filesystem.Name), + }) + } + filesystems[filesystem.Name] = struct{}{} + } } diff --git a/vendor/github.com/coreos/ignition/config/types/disk.go b/vendor/github.com/coreos/ignition/config/types/disk.go index e0f39483aef7..275913b4ff34 100644 --- a/vendor/github.com/coreos/ignition/config/types/disk.go +++ b/vendor/github.com/coreos/ignition/config/types/disk.go @@ -15,63 +15,45 @@ package types import ( - "encoding/json" "fmt" + + "github.com/coreos/ignition/config/validate/report" ) type Disk struct { - Device Path `json:"device,omitempty" yaml:"device"` - WipeTable bool `json:"wipeTable,omitempty" yaml:"wipe_table"` - Partitions []Partition `json:"partitions,omitempty" yaml:"partitions"` -} - -func (n *Disk) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := n.unmarshal(unmarshal); err != nil { - return err - } - if err := n.preparePartitions(); err != nil { - return err - } - return n.assertValid() -} - -func (n *Disk) UnmarshalJSON(data []byte) error { - err := n.unmarshal(func(tn interface{}) error { - return json.Unmarshal(data, tn) - }) - if err != nil { - return err - } - return n.assertValid() -} - -type disk Disk - -func (n *Disk) unmarshal(unmarshal func(interface{}) error) error { - tn := disk(*n) - if err := unmarshal(&tn); err != nil { - return err - } - *n = Disk(tn) - return nil + Device Path `json:"device,omitempty"` + WipeTable bool `json:"wipeTable,omitempty"` + Partitions []Partition `json:"partitions,omitempty"` } -func (n Disk) assertValid() error { - // This applies to YAML (post-prepare) and JSON unmarshals equally: +func (n Disk) Validate() report.Report { + r := report.Report{} if len(n.Device) == 0 { - return fmt.Errorf("disk device is required") + r.Add(report.Entry{ + Message: "disk device is required", + Kind: report.EntryError, + }) } if n.partitionNumbersCollide() { - return fmt.Errorf("disk %q: partition numbers collide", n.Device) + r.Add(report.Entry{ + Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device), + Kind: report.EntryError, + }) } if n.partitionsOverlap() { - return fmt.Errorf("disk %q: partitions overlap", n.Device) + r.Add(report.Entry{ + Message: fmt.Sprintf("disk %q: partitions overlap", n.Device), + Kind: report.EntryError, + }) } if n.partitionsMisaligned() { - return fmt.Errorf("disk %q: partitions misaligned", n.Device) + r.Add(report.Entry{ + Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device), + Kind: report.EntryError, + }) } - // Disks which get to this point will likely succeed in sgdisk - return nil + // Disks which have no errors at this point will likely succeed in sgdisk + return r } // partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique. @@ -140,28 +122,3 @@ func (n Disk) partitionsMisaligned() bool { } return false } - -// preparePartitions performs some checks and potentially adjusts the partitions for alignment. -// This is only invoked when unmarshalling YAML, since there we parse human-friendly units. -func (n *Disk) preparePartitions() error { - // On the YAML side we accept human-friendly units which may require massaging. - - // align partition starts - for i := range n.Partitions { - // skip automatically placed partitions - if n.Partitions[i].Start == 0 { - continue - } - - // keep partitions out of the first 2048 sectors - if n.Partitions[i].Start < 2048 { - n.Partitions[i].Start = 2048 - } - - // toss the bottom 11 bits - n.Partitions[i].Start &= ^PartitionDimension(2048 - 1) - } - - // TODO(vc): may be interesting to do something about potentially overlapping partitions - return nil -} diff --git a/vendor/github.com/coreos/ignition/config/types/file.go b/vendor/github.com/coreos/ignition/config/types/file.go index 528531f0a408..cc660b559c11 100644 --- a/vendor/github.com/coreos/ignition/config/types/file.go +++ b/vendor/github.com/coreos/ignition/config/types/file.go @@ -15,64 +15,52 @@ package types import ( - "encoding/json" "errors" "os" + + "github.com/coreos/ignition/config/validate/report" ) var ( ErrFileIllegalMode = errors.New("illegal file mode") + ErrNoFilesystem = errors.New("no filesystem specified") ) type File struct { - Filesystem string `json:"filesystem,omitempty" yaml:"filesystem"` - Path Path `json:"path,omitempty" yaml:"path"` - Contents FileContents `json:"contents,omitempty" yaml:"contents"` - Mode FileMode `json:"mode,omitempty" yaml:"mode"` - User FileUser `json:"user,omitempty" yaml:"uid"` - Group FileGroup `json:"group,omitempty" yaml:"gid"` + Filesystem string `json:"filesystem,omitempty"` + Path Path `json:"path,omitempty"` + Contents FileContents `json:"contents,omitempty"` + Mode FileMode `json:"mode,omitempty"` + User FileUser `json:"user,omitempty"` + Group FileGroup `json:"group,omitempty"` +} + +func (f File) Validate() report.Report { + if f.Filesystem == "" { + return report.ReportFromError(ErrNoFilesystem, report.EntryError) + } + return report.Report{} } type FileUser struct { - Id int `json:"id,omitempty" yaml:"id"` + Id int `json:"id,omitempty"` } type FileGroup struct { - Id int `json:"id,omitempty" yaml:"id"` + Id int `json:"id,omitempty"` } type FileContents struct { - Compression Compression `json:"compression,omitempty" yaml:"compression"` - Source Url `json:"source,omitempty" yaml:"source"` - Verification Verification `json:"verification,omitempty" yaml:"verification"` + Compression Compression `json:"compression,omitempty"` + Source Url `json:"source,omitempty"` + Verification Verification `json:"verification,omitempty"` } type FileMode os.FileMode -func (m *FileMode) UnmarshalYAML(unmarshal func(interface{}) error) error { - return m.unmarshal(unmarshal) -} - -func (m *FileMode) UnmarshalJSON(data []byte) error { - return m.unmarshal(func(tm interface{}) error { - return json.Unmarshal(data, tm) - }) -} - -type fileMode FileMode - -func (m *FileMode) unmarshal(unmarshal func(interface{}) error) error { - tm := fileMode(*m) - if err := unmarshal(&tm); err != nil { - return err - } - *m = FileMode(tm) - return m.assertValid() -} - -func (m FileMode) assertValid() error { +func (m FileMode) Validate() report.Report { if (m &^ 07777) != 0 { - return ErrFileIllegalMode + return report.ReportFromError(ErrFileIllegalMode, report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/filesystem.go b/vendor/github.com/coreos/ignition/config/types/filesystem.go index 44e8eaf36a86..bbd0c75bd807 100644 --- a/vendor/github.com/coreos/ignition/config/types/filesystem.go +++ b/vendor/github.com/coreos/ignition/config/types/filesystem.go @@ -15,8 +15,9 @@ package types import ( - "encoding/json" "errors" + + "github.com/coreos/ignition/config/validate/report" ) var ( @@ -26,156 +27,41 @@ var ( ) type Filesystem struct { - Name string `json:"name,omitempty" yaml:"name"` - Mount *FilesystemMount `json:"mount,omitempty" yaml:"mount"` - Path Path `json:"path,omitempty" yaml:"path"` + Name string `json:"name,omitempty"` + Mount *FilesystemMount `json:"mount,omitempty"` + Path *Path `json:"path,omitempty"` } type FilesystemMount struct { - Device Path `json:"device,omitempty" yaml:"device"` - Format FilesystemFormat `json:"format,omitempty" yaml:"format"` - Create *FilesystemCreate `json:"create,omitempty" yaml:"create"` + Device Path `json:"device,omitempty"` + Format FilesystemFormat `json:"format,omitempty"` + Create *FilesystemCreate `json:"create,omitempty"` } type FilesystemCreate struct { - Force bool `json:"force,omitempty" yaml:"force"` - Options MkfsOptions `json:"options,omitempty" yaml:"options"` -} - -func (f *Filesystem) UnmarshalYAML(unmarshal func(interface{}) error) error { - return f.unmarshal(unmarshal) -} - -func (f *Filesystem) UnmarshalJSON(data []byte) error { - return f.unmarshal(func(tf interface{}) error { - return json.Unmarshal(data, tf) - }) -} - -type filesystem Filesystem - -func (f *Filesystem) unmarshal(unmarshal func(interface{}) error) error { - tf := filesystem(*f) - if err := unmarshal(&tf); err != nil { - return err - } - *f = Filesystem(tf) - return f.assertValid() -} - -func (f Filesystem) assertValid() error { - hasMount := false - hasPath := false - - if f.Mount != nil { - hasMount = true - if err := f.Mount.assertValid(); err != nil { - return err - } - } - - if len(f.Path) != 0 { - hasPath = true - if err := f.Path.assertValid(); err != nil { - return err - } - } - - if !hasMount && !hasPath { - return ErrFilesystemNoMountPath - } else if hasMount && hasPath { - return ErrFilesystemMountAndPath - } - - return nil -} - -func (f *FilesystemMount) UnmarshalYAML(unmarshal func(interface{}) error) error { - return f.unmarshal(unmarshal) -} - -func (f *FilesystemMount) UnmarshalJSON(data []byte) error { - return f.unmarshal(func(tf interface{}) error { - return json.Unmarshal(data, tf) - }) + Force bool `json:"force,omitempty"` + Options MkfsOptions `json:"options,omitempty"` } -type filesystemMount FilesystemMount - -func (f *FilesystemMount) unmarshal(unmarshal func(interface{}) error) error { - tf := filesystemMount(*f) - if err := unmarshal(&tf); err != nil { - return err - } - *f = FilesystemMount(tf) - return f.assertValid() -} - -func (f FilesystemMount) assertValid() error { - if err := f.Device.assertValid(); err != nil { - return err +func (f Filesystem) Validate() report.Report { + if f.Mount == nil && f.Path == nil { + return report.ReportFromError(ErrFilesystemNoMountPath, report.EntryError) } - if err := f.Format.assertValid(); err != nil { - return err + if f.Mount != nil && f.Path != nil { + return report.ReportFromError(ErrFilesystemMountAndPath, report.EntryError) } - return nil + return report.Report{} } type FilesystemFormat string -func (f *FilesystemFormat) UnmarshalYAML(unmarshal func(interface{}) error) error { - return f.unmarshal(unmarshal) -} - -func (f *FilesystemFormat) UnmarshalJSON(data []byte) error { - return f.unmarshal(func(tf interface{}) error { - return json.Unmarshal(data, tf) - }) -} - -type filesystemFormat FilesystemFormat - -func (f *FilesystemFormat) unmarshal(unmarshal func(interface{}) error) error { - tf := filesystemFormat(*f) - if err := unmarshal(&tf); err != nil { - return err - } - *f = FilesystemFormat(tf) - return f.assertValid() -} - -func (f FilesystemFormat) assertValid() error { +func (f FilesystemFormat) Validate() report.Report { switch f { case "ext4", "btrfs", "xfs": - return nil + return report.Report{} default: - return ErrFilesystemInvalidFormat + return report.ReportFromError(ErrFilesystemInvalidFormat, report.EntryError) } } type MkfsOptions []string - -func (o *MkfsOptions) UnmarshalYAML(unmarshal func(interface{}) error) error { - return o.unmarshal(unmarshal) -} - -func (o *MkfsOptions) UnmarshalJSON(data []byte) error { - return o.unmarshal(func(to interface{}) error { - return json.Unmarshal(data, to) - }) -} - -type mkfsOptions MkfsOptions - -func (o *MkfsOptions) unmarshal(unmarshal func(interface{}) error) error { - to := mkfsOptions(*o) - if err := unmarshal(&to); err != nil { - return err - } - *o = MkfsOptions(to) - return o.assertValid() -} - -func (o MkfsOptions) assertValid() error { - return nil -} diff --git a/vendor/github.com/coreos/ignition/config/types/group.go b/vendor/github.com/coreos/ignition/config/types/group.go index eb393398fafd..27e510488705 100644 --- a/vendor/github.com/coreos/ignition/config/types/group.go +++ b/vendor/github.com/coreos/ignition/config/types/group.go @@ -15,8 +15,8 @@ package types type Group struct { - Name string `json:"name,omitempty" yaml:"name"` - Gid *uint `json:"gid,omitempty" yaml:"gid"` - PasswordHash string `json:"passwordHash,omitempty" yaml:"password_hash"` - System bool `json:"system,omitempty" yaml:"system"` + Name string `json:"name,omitempty"` + Gid *uint `json:"gid,omitempty"` + PasswordHash string `json:"passwordHash,omitempty"` + System bool `json:"system,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/types/hash.go b/vendor/github.com/coreos/ignition/config/types/hash.go index 064dfdd9e10d..77d11d3ddede 100644 --- a/vendor/github.com/coreos/ignition/config/types/hash.go +++ b/vendor/github.com/coreos/ignition/config/types/hash.go @@ -20,6 +20,8 @@ import ( "encoding/json" "errors" "strings" + + "github.com/coreos/ignition/config/validate/report" ) var ( @@ -33,23 +35,9 @@ type Hash struct { Sum string } -func (h *Hash) UnmarshalYAML(unmarshal func(interface{}) error) error { - return h.unmarshal(unmarshal) -} - func (h *Hash) UnmarshalJSON(data []byte) error { - return h.unmarshal(func(th interface{}) error { - return json.Unmarshal(data, th) - }) -} - -func (h Hash) MarshalJSON() ([]byte, error) { - return []byte(`"` + h.Function + "-" + h.Sum + `"`), nil -} - -func (h *Hash) unmarshal(unmarshal func(interface{}) error) error { var th string - if err := unmarshal(&th); err != nil { + if err := json.Unmarshal(data, &th); err != nil { return err } @@ -61,21 +49,25 @@ func (h *Hash) unmarshal(unmarshal func(interface{}) error) error { h.Function = parts[0] h.Sum = parts[1] - return h.assertValid() + return nil } -func (h Hash) assertValid() error { +func (h Hash) MarshalJSON() ([]byte, error) { + return []byte(`"` + h.Function + "-" + h.Sum + `"`), nil +} + +func (h Hash) Validate() report.Report { var hash crypto.Hash switch h.Function { case "sha512": hash = crypto.SHA512 default: - return ErrHashUnrecognized + return report.ReportFromError(ErrHashUnrecognized, report.EntryError) } if len(h.Sum) != hex.EncodedLen(hash.Size()) { - return ErrHashWrongSize + return report.ReportFromError(ErrHashWrongSize, report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/ignition.go b/vendor/github.com/coreos/ignition/config/types/ignition.go index 9d5f9f9674a4..c9519a15a9af 100644 --- a/vendor/github.com/coreos/ignition/config/types/ignition.go +++ b/vendor/github.com/coreos/ignition/config/types/ignition.go @@ -19,6 +19,8 @@ import ( "errors" "github.com/coreos/go-semver/semver" + + "github.com/coreos/ignition/config/validate/report" ) var ( @@ -27,51 +29,41 @@ var ( ) type Ignition struct { - Version IgnitionVersion `json:"version,omitempty" yaml:"version" merge:"old"` - Config IgnitionConfig `json:"config,omitempty" yaml:"config" merge:"new"` + Version IgnitionVersion `json:"version,omitempty" merge:"old"` + Config IgnitionConfig `json:"config,omitempty" merge:"new"` } type IgnitionConfig struct { - Append []ConfigReference `json:"append,omitempty" yaml:"append"` - Replace *ConfigReference `json:"replace,omitempty" yaml:"replace"` + Append []ConfigReference `json:"append,omitempty"` + Replace *ConfigReference `json:"replace,omitempty"` } type ConfigReference struct { - Source Url `json:"source,omitempty" yaml:"source"` - Verification Verification `json:"verification,omitempty" yaml:"verification"` + Source Url `json:"source,omitempty"` + Verification Verification `json:"verification,omitempty"` } type IgnitionVersion semver.Version -func (v *IgnitionVersion) UnmarshalYAML(unmarshal func(interface{}) error) error { - return v.unmarshal(unmarshal) -} - func (v *IgnitionVersion) UnmarshalJSON(data []byte) error { - return v.unmarshal(func(tv interface{}) error { - return json.Unmarshal(data, tv) - }) -} - -func (v IgnitionVersion) MarshalJSON() ([]byte, error) { - return semver.Version(v).MarshalJSON() -} - -func (v *IgnitionVersion) unmarshal(unmarshal func(interface{}) error) error { tv := semver.Version(*v) - if err := unmarshal(&tv); err != nil { + if err := json.Unmarshal(data, &tv); err != nil { return err } *v = IgnitionVersion(tv) return nil } -func (v IgnitionVersion) AssertValid() error { +func (v IgnitionVersion) MarshalJSON() ([]byte, error) { + return semver.Version(v).MarshalJSON() +} + +func (v IgnitionVersion) Validate() report.Report { if MaxVersion.Major > v.Major { - return ErrOldVersion + return report.ReportFromError(ErrOldVersion, report.EntryError) } if MaxVersion.LessThan(semver.Version(v)) { - return ErrNewVersion + return report.ReportFromError(ErrNewVersion, report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/networkd.go b/vendor/github.com/coreos/ignition/config/types/networkd.go index 2dbbdc3d9e7c..470c721106a6 100644 --- a/vendor/github.com/coreos/ignition/config/types/networkd.go +++ b/vendor/github.com/coreos/ignition/config/types/networkd.go @@ -15,5 +15,5 @@ package types type Networkd struct { - Units []NetworkdUnit `json:"units,omitempty" yaml:"units"` + Units []NetworkdUnit `json:"units,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/types/partition.go b/vendor/github.com/coreos/ignition/config/types/partition.go index 228ece4d546a..9ed03c32fd7e 100644 --- a/vendor/github.com/coreos/ignition/config/types/partition.go +++ b/vendor/github.com/coreos/ignition/config/types/partition.go @@ -15,123 +15,45 @@ package types import ( - "encoding/json" "fmt" "regexp" - "github.com/alecthomas/units" + "github.com/coreos/ignition/config/validate/report" ) type Partition struct { - Label PartitionLabel `json:"label,omitempty" yaml:"label"` - Number int `json:"number" yaml:"number"` - Size PartitionDimension `json:"size" yaml:"size"` - Start PartitionDimension `json:"start" yaml:"start"` - TypeGUID PartitionTypeGUID `json:"typeGuid,omitempty" yaml:"type_guid"` + Label PartitionLabel `json:"label,omitempty"` + Number int `json:"number"` + Size PartitionDimension `json:"size"` + Start PartitionDimension `json:"start"` + TypeGUID PartitionTypeGUID `json:"typeGuid,omitempty"` } type PartitionLabel string -func (n *PartitionLabel) UnmarshalYAML(unmarshal func(interface{}) error) error { - return n.unmarshal(unmarshal) -} - -func (n *PartitionLabel) UnmarshalJSON(data []byte) error { - return n.unmarshal(func(tn interface{}) error { - return json.Unmarshal(data, tn) - }) -} - -type partitionLabel PartitionLabel - -func (n *PartitionLabel) unmarshal(unmarshal func(interface{}) error) error { - tn := partitionLabel(*n) - if err := unmarshal(&tn); err != nil { - return err - } - *n = PartitionLabel(tn) - return n.assertValid() -} - -func (n PartitionLabel) assertValid() error { +func (n PartitionLabel) Validate() report.Report { // http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries: // 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units) // XXX(vc): note GPT calls it a name, we're using label for consistency // with udev naming /dev/disk/by-partlabel/*. if len(string(n)) > 36 { - return fmt.Errorf("partition labels may not exceed 36 characters") + return report.ReportFromError(fmt.Errorf("partition labels may not exceed 36 characters"), report.EntryError) } - return nil + return report.Report{} } type PartitionDimension uint64 -func (n *PartitionDimension) UnmarshalYAML(unmarshal func(interface{}) error) error { - // In YAML we allow human-readable dimensions like GiB/TiB etc. - var str string - if err := unmarshal(&str); err != nil { - return err - } - - b2b, err := units.ParseBase2Bytes(str) // TODO(vc): replace the units package - if err != nil { - return err - } - if b2b < 0 { - return fmt.Errorf("negative value inappropriate: %q", str) - } - - // Translate bytes into sectors - sectors := (b2b / 512) - if b2b%512 != 0 { - sectors++ - } - *n = PartitionDimension(uint64(sectors)) - return nil -} - -func (n *PartitionDimension) UnmarshalJSON(data []byte) error { - // In JSON we expect plain integral sectors. - // The YAML->JSON conversion is responsible for normalizing human units to sectors. - var pd uint64 - if err := json.Unmarshal(data, &pd); err != nil { - return err - } - *n = PartitionDimension(pd) - return nil -} - type PartitionTypeGUID string -func (d *PartitionTypeGUID) UnmarshalYAML(unmarshal func(interface{}) error) error { - return d.unmarshal(unmarshal) -} - -func (d *PartitionTypeGUID) UnmarshalJSON(data []byte) error { - return d.unmarshal(func(td interface{}) error { - return json.Unmarshal(data, td) - }) -} - -type partitionTypeGUID PartitionTypeGUID - -func (d *PartitionTypeGUID) unmarshal(unmarshal func(interface{}) error) error { - td := partitionTypeGUID(*d) - if err := unmarshal(&td); err != nil { - return err - } - *d = PartitionTypeGUID(td) - return d.assertValid() -} - -func (d PartitionTypeGUID) assertValid() error { - ok, err := regexp.MatchString("[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}", string(d)) +func (d PartitionTypeGUID) Validate() report.Report { + ok, err := regexp.MatchString("^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$", string(d)) if err != nil { - return fmt.Errorf("error matching type-guid regexp: %v", err) + return report.ReportFromError(fmt.Errorf("error matching type-guid regexp: %v", err), report.EntryError) } if !ok { - return fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)) + return report.ReportFromError(fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)), report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/passwd.go b/vendor/github.com/coreos/ignition/config/types/passwd.go index f1436874eb89..0ffff43bb844 100644 --- a/vendor/github.com/coreos/ignition/config/types/passwd.go +++ b/vendor/github.com/coreos/ignition/config/types/passwd.go @@ -15,6 +15,6 @@ package types type Passwd struct { - Users []User `json:"users,omitempty" yaml:"users"` - Groups []Group `json:"groups,omitempty" yaml:"groups"` + Users []User `json:"users,omitempty"` + Groups []Group `json:"groups,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/types/path.go b/vendor/github.com/coreos/ignition/config/types/path.go index eb8e3c2c731e..9e00b37145da 100644 --- a/vendor/github.com/coreos/ignition/config/types/path.go +++ b/vendor/github.com/coreos/ignition/config/types/path.go @@ -15,9 +15,10 @@ package types import ( - "encoding/json" "errors" "path/filepath" + + "github.com/coreos/ignition/config/validate/report" ) var ( @@ -26,34 +27,13 @@ var ( type Path string -func (p *Path) UnmarshalYAML(unmarshal func(interface{}) error) error { - return p.unmarshal(unmarshal) -} - -func (p *Path) UnmarshalJSON(data []byte) error { - return p.unmarshal(func(td interface{}) error { - return json.Unmarshal(data, td) - }) -} - func (p Path) MarshalJSON() ([]byte, error) { return []byte(`"` + string(p) + `"`), nil } -type path Path - -func (p *Path) unmarshal(unmarshal func(interface{}) error) error { - td := path(*p) - if err := unmarshal(&td); err != nil { - return err - } - *p = Path(td) - return p.assertValid() -} - -func (p Path) assertValid() error { +func (p Path) Validate() report.Report { if !filepath.IsAbs(string(p)) { - return ErrPathRelative + return report.ReportFromError(ErrPathRelative, report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/raid.go b/vendor/github.com/coreos/ignition/config/types/raid.go index 62fe65ef4309..4236255d6f76 100644 --- a/vendor/github.com/coreos/ignition/config/types/raid.go +++ b/vendor/github.com/coreos/ignition/config/types/raid.go @@ -15,43 +15,23 @@ package types import ( - "encoding/json" "fmt" + + "github.com/coreos/ignition/config/validate/report" ) type Raid struct { - Name string `json:"name" yaml:"name"` - Level string `json:"level" yaml:"level"` - Devices []Path `json:"devices,omitempty" yaml:"devices"` - Spares int `json:"spares,omitempty" yaml:"spares"` -} - -func (n *Raid) UnmarshalYAML(unmarshal func(interface{}) error) error { - return n.unmarshal(unmarshal) -} - -func (n *Raid) UnmarshalJSON(data []byte) error { - return n.unmarshal(func(tn interface{}) error { - return json.Unmarshal(data, tn) - }) -} - -type raid Raid - -func (n *Raid) unmarshal(unmarshal func(interface{}) error) error { - tn := raid(*n) - if err := unmarshal(&tn); err != nil { - return err - } - *n = Raid(tn) - return n.assertValid() + Name string `json:"name"` + Level string `json:"level"` + Devices []Path `json:"devices,omitempty"` + Spares int `json:"spares,omitempty"` } -func (n Raid) assertValid() error { +func (n Raid) Validate() report.Report { switch n.Level { case "linear", "raid0", "0", "stripe": if n.Spares != 0 { - return fmt.Errorf("spares unsupported for %q arrays", n.Level) + return report.ReportFromError(fmt.Errorf("spares unsupported for %q arrays", n.Level), report.EntryError) } case "raid1", "1", "mirror": case "raid4", "4": @@ -59,7 +39,7 @@ func (n Raid) assertValid() error { case "raid6", "6": case "raid10", "10": default: - return fmt.Errorf("unrecognized raid level: %q", n.Level) + return report.ReportFromError(fmt.Errorf("unrecognized raid level: %q", n.Level), report.EntryError) } - return nil + return report.Report{} } diff --git a/vendor/github.com/coreos/ignition/config/types/storage.go b/vendor/github.com/coreos/ignition/config/types/storage.go index 75bff0706a9c..bd7343778a9e 100644 --- a/vendor/github.com/coreos/ignition/config/types/storage.go +++ b/vendor/github.com/coreos/ignition/config/types/storage.go @@ -15,8 +15,8 @@ package types type Storage struct { - Disks []Disk `json:"disks,omitempty" yaml:"disks"` - Arrays []Raid `json:"raid,omitempty" yaml:"raid"` - Filesystems []Filesystem `json:"filesystems,omitempty" yaml:"filesystems"` - Files []File `json:"files,omitempty" yaml:"files"` + Disks []Disk `json:"disks,omitempty"` + Arrays []Raid `json:"raid,omitempty"` + Filesystems []Filesystem `json:"filesystems,omitempty"` + Files []File `json:"files,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/types/systemd.go b/vendor/github.com/coreos/ignition/config/types/systemd.go index ec764850ae68..97194b911503 100644 --- a/vendor/github.com/coreos/ignition/config/types/systemd.go +++ b/vendor/github.com/coreos/ignition/config/types/systemd.go @@ -15,5 +15,5 @@ package types type Systemd struct { - Units []SystemdUnit `json:"units,omitempty" yaml:"units"` + Units []SystemdUnit `json:"units,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/types/unit.go b/vendor/github.com/coreos/ignition/config/types/unit.go index 3fd6a194dd45..44be844ea985 100644 --- a/vendor/github.com/coreos/ignition/config/types/unit.go +++ b/vendor/github.com/coreos/ignition/config/types/unit.go @@ -15,121 +15,59 @@ package types import ( - "encoding/json" "errors" "path/filepath" + + "github.com/coreos/ignition/config/validate/report" ) type SystemdUnit struct { - Name SystemdUnitName `json:"name,omitempty" yaml:"name"` - Enable bool `json:"enable,omitempty" yaml:"enable"` - Mask bool `json:"mask,omitempty" yaml:"mask"` - Contents string `json:"contents,omitempty" yaml:"contents"` - DropIns []SystemdUnitDropIn `json:"dropins,omitempty" yaml:"dropins"` + Name SystemdUnitName `json:"name,omitempty"` + Enable bool `json:"enable,omitempty"` + Mask bool `json:"mask,omitempty"` + Contents string `json:"contents,omitempty"` + DropIns []SystemdUnitDropIn `json:"dropins,omitempty"` } type SystemdUnitDropIn struct { - Name SystemdUnitDropInName `json:"name,omitempty" yaml:"name"` - Contents string `json:"contents,omitempty" yaml:"contents"` + Name SystemdUnitDropInName `json:"name,omitempty"` + Contents string `json:"contents,omitempty"` } type SystemdUnitName string -func (n *SystemdUnitName) UnmarshalYAML(unmarshal func(interface{}) error) error { - return n.unmarshal(unmarshal) -} - -func (n *SystemdUnitName) UnmarshalJSON(data []byte) error { - return n.unmarshal(func(tn interface{}) error { - return json.Unmarshal(data, tn) - }) -} - -type systemdUnitName SystemdUnitName - -func (n *SystemdUnitName) unmarshal(unmarshal func(interface{}) error) error { - tn := systemdUnitName(*n) - if err := unmarshal(&tn); err != nil { - return err - } - *n = SystemdUnitName(tn) - return n.assertValid() -} - -func (n SystemdUnitName) assertValid() error { +func (n SystemdUnitName) Validate() report.Report { switch filepath.Ext(string(n)) { case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope": - return nil + return report.Report{} default: - return errors.New("invalid systemd unit extension") + return report.ReportFromError(errors.New("invalid systemd unit extension"), report.EntryError) } } type SystemdUnitDropInName string -func (n *SystemdUnitDropInName) UnmarshalYAML(unmarshal func(interface{}) error) error { - return n.unmarshal(unmarshal) -} - -func (n *SystemdUnitDropInName) UnmarshalJSON(data []byte) error { - return n.unmarshal(func(tn interface{}) error { - return json.Unmarshal(data, tn) - }) -} - -type systemdUnitDropInName SystemdUnitDropInName - -func (n *SystemdUnitDropInName) unmarshal(unmarshal func(interface{}) error) error { - tn := systemdUnitDropInName(*n) - if err := unmarshal(&tn); err != nil { - return err - } - *n = SystemdUnitDropInName(tn) - return n.assertValid() -} - -func (n SystemdUnitDropInName) assertValid() error { +func (n SystemdUnitDropInName) Validate() report.Report { switch filepath.Ext(string(n)) { case ".conf": - return nil + return report.Report{} default: - return errors.New("invalid systemd unit drop-in extension") + return report.ReportFromError(errors.New("invalid systemd unit drop-in extension"), report.EntryError) } } type NetworkdUnit struct { - Name NetworkdUnitName `json:"name,omitempty" yaml:"name"` - Contents string `json:"contents,omitempty" yaml:"contents"` + Name NetworkdUnitName `json:"name,omitempty"` + Contents string `json:"contents,omitempty"` } type NetworkdUnitName string -func (n *NetworkdUnitName) UnmarshalYAML(unmarshal func(interface{}) error) error { - return n.unmarshal(unmarshal) -} - -func (n *NetworkdUnitName) UnmarshalJSON(data []byte) error { - return n.unmarshal(func(tn interface{}) error { - return json.Unmarshal(data, tn) - }) -} - -type networkdUnitName NetworkdUnitName - -func (n *NetworkdUnitName) unmarshal(unmarshal func(interface{}) error) error { - tn := networkdUnitName(*n) - if err := unmarshal(&tn); err != nil { - return err - } - *n = NetworkdUnitName(tn) - return n.assertValid() -} - -func (n NetworkdUnitName) assertValid() error { +func (n NetworkdUnitName) Validate() report.Report { switch filepath.Ext(string(n)) { case ".link", ".netdev", ".network": - return nil + return report.Report{} default: - return errors.New("invalid networkd unit extension") + return report.ReportFromError(errors.New("invalid networkd unit extension"), report.EntryError) } } diff --git a/vendor/github.com/coreos/ignition/config/types/url.go b/vendor/github.com/coreos/ignition/config/types/url.go index 83d0338550f4..8bf0e6eaab97 100644 --- a/vendor/github.com/coreos/ignition/config/types/url.go +++ b/vendor/github.com/coreos/ignition/config/types/url.go @@ -16,37 +16,57 @@ package types import ( "encoding/json" + "errors" "net/url" + + "github.com/coreos/ignition/config/validate/report" + "github.com/vincent-petithory/dataurl" ) -type Url url.URL +var ( + ErrInvalidScheme = errors.New("invalid url scheme") +) -func (u *Url) UnmarshalYAML(unmarshal func(interface{}) error) error { - return u.unmarshal(unmarshal) -} +type Url url.URL func (u *Url) UnmarshalJSON(data []byte) error { - return u.unmarshal(func(tu interface{}) error { - return json.Unmarshal(data, tu) - }) -} - -func (u Url) MarshalJSON() ([]byte, error) { - return []byte(`"` + u.String() + `"`), nil -} - -func (u *Url) unmarshal(unmarshal func(interface{}) error) error { var tu string - if err := unmarshal(&tu); err != nil { + if err := json.Unmarshal(data, &tu); err != nil { return err } pu, err := url.Parse(tu) + if err != nil { + return err + } + *u = Url(*pu) - return err + return nil +} + +func (u Url) MarshalJSON() ([]byte, error) { + return []byte(`"` + u.String() + `"`), nil } func (u Url) String() string { tu := url.URL(u) return (&tu).String() } + +func (u Url) Validate() report.Report { + // Empty url is valid, indicates an empty file + if u.String() == "" { + return report.Report{} + } + switch url.URL(u).Scheme { + case "http", "https", "oem": + return report.Report{} + case "data": + if _, err := dataurl.DecodeString(u.String()); err != nil { + return report.ReportFromError(err, report.EntryError) + } + return report.Report{} + default: + return report.ReportFromError(ErrInvalidScheme, report.EntryError) + } +} diff --git a/vendor/github.com/coreos/ignition/config/types/user.go b/vendor/github.com/coreos/ignition/config/types/user.go index 80c3336cf0a1..f6653e27494c 100644 --- a/vendor/github.com/coreos/ignition/config/types/user.go +++ b/vendor/github.com/coreos/ignition/config/types/user.go @@ -15,21 +15,21 @@ package types type User struct { - Name string `json:"name,omitempty" yaml:"name"` - PasswordHash string `json:"passwordHash,omitempty" yaml:"password_hash"` - SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty" yaml:"ssh_authorized_keys"` - Create *UserCreate `json:"create,omitempty" yaml:"create"` + Name string `json:"name,omitempty"` + PasswordHash string `json:"passwordHash,omitempty"` + SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"` + Create *UserCreate `json:"create,omitempty"` } type UserCreate struct { - Uid *uint `json:"uid,omitempty" yaml:"uid"` - GECOS string `json:"gecos,omitempty" yaml:"gecos"` - Homedir string `json:"homeDir,omitempty" yaml:"home_dir"` - NoCreateHome bool `json:"noCreateHome,omitempty" yaml:"no_create_home"` - PrimaryGroup string `json:"primaryGroup,omitempty" yaml:"primary_group"` - Groups []string `json:"groups,omitempty" yaml:"groups"` - NoUserGroup bool `json:"noUserGroup,omitempty" yaml:"no_user_group"` - System bool `json:"system,omitempty" yaml:"system"` - NoLogInit bool `json:"noLogInit,omitempty" yaml:"no_log_init"` - Shell string `json:"shell,omitempty" yaml:"shell"` + Uid *uint `json:"uid,omitempty"` + GECOS string `json:"gecos,omitempty"` + Homedir string `json:"homeDir,omitempty"` + NoCreateHome bool `json:"noCreateHome,omitempty"` + PrimaryGroup string `json:"primaryGroup,omitempty"` + Groups []string `json:"groups,omitempty"` + NoUserGroup bool `json:"noUserGroup,omitempty"` + System bool `json:"system,omitempty"` + NoLogInit bool `json:"noLogInit,omitempty"` + Shell string `json:"shell,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/types/verification.go b/vendor/github.com/coreos/ignition/config/types/verification.go index f6093d6175da..b7cef403e878 100644 --- a/vendor/github.com/coreos/ignition/config/types/verification.go +++ b/vendor/github.com/coreos/ignition/config/types/verification.go @@ -15,5 +15,5 @@ package types type Verification struct { - Hash *Hash `json:"hash,omitempty" yaml:"hash"` + Hash *Hash `json:"hash,omitempty"` } diff --git a/vendor/github.com/coreos/ignition/config/validate/report/report.go b/vendor/github.com/coreos/ignition/config/validate/report/report.go new file mode 100644 index 000000000000..e0d4fed8dc88 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/validate/report/report.go @@ -0,0 +1,158 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package report + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" +) + +type Report struct { + Entries []Entry +} + +func (into *Report) Merge(from Report) { + into.Entries = append(into.Entries, from.Entries...) +} + +func ReportFromError(err error, severity entryKind) Report { + if err == nil { + return Report{} + } + return Report{ + Entries: []Entry{ + { + Kind: severity, + Message: err.Error(), + }, + }, + } +} + +// Sort sorts the entries by line number, then column number +func (r *Report) Sort() { + sort.Sort(entries(r.Entries)) +} + +type entries []Entry + +func (e entries) Len() int { + return len(e) +} + +func (e entries) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +func (e entries) Less(i, j int) bool { + if e[i].Line != e[j].Line { + return e[i].Line < e[j].Line + } + return e[i].Column < e[j].Column +} + +const ( + EntryError entryKind = iota + EntryWarning + EntryInfo + EntryDeprecated +) + +// AddPosition updates all the entries with Line equal to 0 and sets the Line/Column fields to line/column. This is useful for +// when a type has a custom unmarshaller and thus can't determine an exact offset of the error with the type. In this case +// the offset for the entire chunk of json that got unmarshalled to the type can be used instead, which is still pretty good. +func (r *Report) AddPosition(line, col int, highlight string) { + for i, e := range r.Entries { + if e.Line == 0 { + r.Entries[i].Line = line + r.Entries[i].Column = col + r.Entries[i].Highlight = highlight + } + } +} + +func (r *Report) Add(e Entry) { + r.Entries = append(r.Entries, e) +} + +func (r Report) String() string { + var errs bytes.Buffer + for i, entry := range r.Entries { + if i != 0 { + // Only add line breaks on multiline reports + errs.WriteString("\n") + } + errs.WriteString(entry.String()) + } + return errs.String() +} + +// IsFatal returns if there were any errors that make the config invalid +func (r Report) IsFatal() bool { + for _, entry := range r.Entries { + if entry.Kind == EntryError { + return true + } + } + return false +} + +// IsDeprecated returns if the report has deprecations +func (r Report) IsDeprecated() bool { + for _, entry := range r.Entries { + if entry.Kind == EntryDeprecated { + return true + } + } + return false +} + +type Entry struct { + Kind entryKind `json:"kind"` + Message string `json:"message"` + Line int `json:"line,omitempty"` + Column int `json:"column,omitempty"` + Highlight string `json:"-"` +} + +func (e Entry) String() string { + if e.Line != 0 { + return fmt.Sprintf("%s at line %d, column %d\n%s%v", e.Kind.String(), e.Line, e.Column, e.Highlight, e.Message) + } + return fmt.Sprintf("%s: %v", e.Kind.String(), e.Message) +} + +type entryKind int + +func (e entryKind) String() string { + switch e { + case EntryError: + return "error" + case EntryWarning: + return "warning" + case EntryInfo: + return "info" + case EntryDeprecated: + return "deprecated" + default: + return "unknown error" + } +} + +func (e entryKind) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} diff --git a/vendor/github.com/vincent-petithory/dataurl/LICENSE b/vendor/github.com/vincent-petithory/dataurl/LICENSE new file mode 100644 index 000000000000..ae6cb62bc63b --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Vincent Petithory + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/vincent-petithory/dataurl/README.md b/vendor/github.com/vincent-petithory/dataurl/README.md new file mode 100644 index 000000000000..1ac59ad2558f --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/README.md @@ -0,0 +1,81 @@ +# Data URL Schemes for Go [![wercker status](https://app.wercker.com/status/6f9a2e144dfcc59e862c52459b452928/s "wercker status")](https://app.wercker.com/project/bykey/6f9a2e144dfcc59e862c52459b452928) [![GoDoc](https://godoc.org/github.com/vincent-petithory/dataurl?status.png)](https://godoc.org/github.com/vincent-petithory/dataurl) + +This package parses and generates Data URL Schemes for the Go language, according to [RFC 2397](http://tools.ietf.org/html/rfc2397). + +Data URLs are small chunks of data commonly used in browsers to display inline data, +typically like small images, or when you use the FileReader API of the browser. + +Common use-cases: + + * generate a data URL out of a `string`, `[]byte`, `io.Reader` for inclusion in HTML templates, + * parse a data URL sent by a browser in a http.Handler, and do something with the data (save to disk, etc.) + * ... + +Install the package with: +~~~ +go get github.com/vincent-petithory/dataurl +~~~ + +## Usage + +~~~ go +package main + +import ( + "github.com/vincent-petithory/dataurl" + "fmt" +) + +func main() { + dataURL, err := dataurl.DecodeString(`data:text/plain;charset=utf-8;base64,aGV5YQ==`) + if err != nil { + fmt.Println(err) + return + } + fmt.Printf("content type: %s, data: %s\n", dataURL.MediaType.ContentType(), string(dataURL.Data)) + // Output: content type: text/plain, data: heya +} +~~~ + +From a `http.Handler`: + +~~~ go +func handleDataURLUpload(w http.ResponseWriter, r *http.Request) { + dataURL, err := dataurl.Decode(r.Body) + defer r.Body.Close() + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if dataURL.ContentType() == "image/png" { + ioutil.WriteFile("image.png", dataURL.Data, 0644) + } else { + http.Error(w, "not a png", http.StatusBadRequest) + } +} +~~~ + +## Command + +For convenience, a `dataurl` command is provided to encode/decode dataurl streams. + +~~~ +dataurl - Encode or decode dataurl data and print to standard output + +Usage: dataurl [OPTION]... [FILE] + + dataurl encodes or decodes FILE or standard input if FILE is - or omitted, and prints to standard output. + Unless -mimetype is used, when FILE is specified, dataurl will attempt to detect its mimetype using Go's mime.TypeByExtension (http://golang.org/pkg/mime/#TypeByExtension). If this fails or data is read from STDIN, the mimetype will default to application/octet-stream. + +Options: + -a=false: encode data using ascii instead of base64 + -ascii=false: encode data using ascii instead of base64 + -d=false: decode data instead of encoding + -decode=false: decode data instead of encoding + -m="": force the mimetype of the data to encode to this value + -mimetype="": force the mimetype of the data to encode to this value +~~~ + +## Contributing + +Feel free to file an issue/make a pull request if you find any bug, or want to suggest enhancements. diff --git a/vendor/github.com/vincent-petithory/dataurl/dataurl.go b/vendor/github.com/vincent-petithory/dataurl/dataurl.go new file mode 100644 index 000000000000..bfd07654c969 --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/dataurl.go @@ -0,0 +1,280 @@ +package dataurl + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "strconv" + "strings" +) + +const ( + // EncodingBase64 is base64 encoding for the data url + EncodingBase64 = "base64" + // EncodingASCII is ascii encoding for the data url + EncodingASCII = "ascii" +) + +func defaultMediaType() MediaType { + return MediaType{ + "text", + "plain", + map[string]string{"charset": "US-ASCII"}, + } +} + +// MediaType is the combination of a media type, a media subtype +// and optional parameters. +type MediaType struct { + Type string + Subtype string + Params map[string]string +} + +// ContentType returns the content type of the dataurl's data, in the form type/subtype. +func (mt *MediaType) ContentType() string { + return fmt.Sprintf("%s/%s", mt.Type, mt.Subtype) +} + +// String implements the Stringer interface. +// +// Params values are escaped with the Escape function, rather than in a quoted string. +func (mt *MediaType) String() string { + var buf bytes.Buffer + for k, v := range mt.Params { + fmt.Fprintf(&buf, ";%s=%s", k, EscapeString(v)) + } + return mt.ContentType() + (&buf).String() +} + +// DataURL is the combination of a MediaType describing the type of its Data. +type DataURL struct { + MediaType + Encoding string + Data []byte +} + +// New returns a new DataURL initialized with data and +// a MediaType parsed from mediatype and paramPairs. +// mediatype must be of the form "type/subtype" or it will panic. +// paramPairs must have an even number of elements or it will panic. +// For more complex DataURL, initialize a DataURL struct. +// The DataURL is initialized with base64 encoding. +func New(data []byte, mediatype string, paramPairs ...string) *DataURL { + parts := strings.Split(mediatype, "/") + if len(parts) != 2 { + panic("dataurl: invalid mediatype") + } + + nParams := len(paramPairs) + if nParams%2 != 0 { + panic("dataurl: requires an even number of param pairs") + } + params := make(map[string]string) + for i := 0; i < nParams; i += 2 { + params[paramPairs[i]] = paramPairs[i+1] + } + + mt := MediaType{ + parts[0], + parts[1], + params, + } + return &DataURL{ + MediaType: mt, + Encoding: EncodingBase64, + Data: data, + } +} + +// String implements the Stringer interface. +// +// Note: it doesn't guarantee the returned string is equal to +// the initial source string that was used to create this DataURL. +// The reasons for that are: +// * Insertion of default values for MediaType that were maybe not in the initial string, +// * Various ways to encode the MediaType parameters (quoted string or url encoded string, the latter is used), +func (du *DataURL) String() string { + var buf bytes.Buffer + du.WriteTo(&buf) + return (&buf).String() +} + +// WriteTo implements the WriterTo interface. +// See the note about String(). +func (du *DataURL) WriteTo(w io.Writer) (n int64, err error) { + var ni int + ni, _ = fmt.Fprint(w, "data:") + n += int64(ni) + + ni, _ = fmt.Fprint(w, du.MediaType.String()) + n += int64(ni) + + if du.Encoding == EncodingBase64 { + ni, _ = fmt.Fprint(w, ";base64") + n += int64(ni) + } + + ni, _ = fmt.Fprint(w, ",") + n += int64(ni) + + if du.Encoding == EncodingBase64 { + encoder := base64.NewEncoder(base64.StdEncoding, w) + ni, err = encoder.Write(du.Data) + if err != nil { + return + } + encoder.Close() + } else if du.Encoding == EncodingASCII { + ni, _ = fmt.Fprint(w, Escape(du.Data)) + n += int64(ni) + } else { + err = fmt.Errorf("dataurl: invalid encoding %s", du.Encoding) + return + } + + return +} + +// UnmarshalText decodes a Data URL string and sets it to *du +func (du *DataURL) UnmarshalText(text []byte) error { + decoded, err := DecodeString(string(text)) + if err != nil { + return err + } + *du = *decoded + return nil +} + +// MarshalText writes du as a Data URL +func (du *DataURL) MarshalText() ([]byte, error) { + buf := bytes.NewBuffer(nil) + if _, err := du.WriteTo(buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +type encodedDataReader func(string) ([]byte, error) + +var asciiDataReader encodedDataReader = func(s string) ([]byte, error) { + us, err := Unescape(s) + if err != nil { + return nil, err + } + return []byte(us), nil +} + +var base64DataReader encodedDataReader = func(s string) ([]byte, error) { + data, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return nil, err + } + return []byte(data), nil +} + +type parser struct { + du *DataURL + l *lexer + currentAttr string + unquoteParamVal bool + encodedDataReaderFn encodedDataReader +} + +func (p *parser) parse() error { + for item := range p.l.items { + switch item.t { + case itemError: + return errors.New(item.String()) + case itemMediaType: + p.du.MediaType.Type = item.val + // Should we clear the default + // "charset" parameter at this point? + delete(p.du.MediaType.Params, "charset") + case itemMediaSubType: + p.du.MediaType.Subtype = item.val + case itemParamAttr: + p.currentAttr = item.val + case itemLeftStringQuote: + p.unquoteParamVal = true + case itemParamVal: + val := item.val + if p.unquoteParamVal { + p.unquoteParamVal = false + us, err := strconv.Unquote("\"" + val + "\"") + if err != nil { + return err + } + val = us + } else { + us, err := UnescapeToString(val) + if err != nil { + return err + } + val = us + } + p.du.MediaType.Params[p.currentAttr] = val + case itemBase64Enc: + p.du.Encoding = EncodingBase64 + p.encodedDataReaderFn = base64DataReader + case itemDataComma: + if p.encodedDataReaderFn == nil { + p.encodedDataReaderFn = asciiDataReader + } + case itemData: + reader, err := p.encodedDataReaderFn(item.val) + if err != nil { + return err + } + p.du.Data = reader + case itemEOF: + if p.du.Data == nil { + p.du.Data = []byte("") + } + return nil + } + } + panic("EOF not found") +} + +// DecodeString decodes a Data URL scheme string. +func DecodeString(s string) (*DataURL, error) { + du := &DataURL{ + MediaType: defaultMediaType(), + Encoding: EncodingASCII, + } + + parser := &parser{ + du: du, + l: lex(s), + } + if err := parser.parse(); err != nil { + return nil, err + } + return du, nil +} + +// Decode decodes a Data URL scheme from a io.Reader. +func Decode(r io.Reader) (*DataURL, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + return DecodeString(string(data)) +} + +// EncodeBytes encodes the data bytes into a Data URL string, using base 64 encoding. +// +// The media type of data is detected using http.DetectContentType. +func EncodeBytes(data []byte) string { + mt := http.DetectContentType(data) + // http.DetectContentType may add spurious spaces between ; and a parameter. + // The canonical way is to not have them. + cleanedMt := strings.Replace(mt, "; ", ";", -1) + + return New(data, cleanedMt).String() +} diff --git a/vendor/github.com/vincent-petithory/dataurl/doc.go b/vendor/github.com/vincent-petithory/dataurl/doc.go new file mode 100644 index 000000000000..56461d043ac2 --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/doc.go @@ -0,0 +1,28 @@ +/* +Package dataurl parses Data URL Schemes +according to RFC 2397 +(http://tools.ietf.org/html/rfc2397). + +Data URLs are small chunks of data commonly used in browsers to display inline data, +typically like small images, or when you use the FileReader API of the browser. + +A dataurl looks like: + + data:text/plain;charset=utf-8,A%20brief%20note + +Or, with base64 encoding: + + data:image/vnd.microsoft.icon;name=golang%20favicon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAD///8AVE44//7hdv/+4Xb//uF2//7hdv/+4Xb//uF2//7hdv/+4Xb//uF2//7hdv/+4Xb/ + /uF2/1ROOP////8A////AFROOP/+4Xb//uF2//7hdv/+4Xb//uF2//7hdv/+4Xb//uF2//7hdv/+ + ... + /6CcjP97c07/e3NO/1dOMf9BOiX/TkUn/2VXLf97c07/e3NO/6CcjP/h4uX/////AP///wD///8A + ////AP///wD///8A////AP///wDq6/H/3N/j/9fZ3f/q6/H/////AP///wD///8A////AP///wD/ + //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAA== + +Common functions are Decode and DecodeString to obtain a DataURL, +and DataURL.String() and DataURL.WriteTo to generate a Data URL string. + +*/ +package dataurl diff --git a/vendor/github.com/vincent-petithory/dataurl/lex.go b/vendor/github.com/vincent-petithory/dataurl/lex.go new file mode 100644 index 000000000000..1a8717f57989 --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/lex.go @@ -0,0 +1,521 @@ +package dataurl + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +type item struct { + t itemType + val string +} + +func (i item) String() string { + switch i.t { + case itemEOF: + return "EOF" + case itemError: + return i.val + } + if len(i.val) > 10 { + return fmt.Sprintf("%.10q...", i.val) + } + return fmt.Sprintf("%q", i.val) +} + +type itemType int + +const ( + itemError itemType = iota + itemEOF + + itemDataPrefix + + itemMediaType + itemMediaSep + itemMediaSubType + itemParamSemicolon + itemParamAttr + itemParamEqual + itemLeftStringQuote + itemRightStringQuote + itemParamVal + + itemBase64Enc + + itemDataComma + itemData +) + +const eof rune = -1 + +func isTokenRune(r rune) bool { + return r <= unicode.MaxASCII && + !unicode.IsControl(r) && + !unicode.IsSpace(r) && + !isTSpecialRune(r) +} + +func isTSpecialRune(r rune) bool { + return r == '(' || + r == ')' || + r == '<' || + r == '>' || + r == '@' || + r == ',' || + r == ';' || + r == ':' || + r == '\\' || + r == '"' || + r == '/' || + r == '[' || + r == ']' || + r == '?' || + r == '=' +} + +// See http://tools.ietf.org/html/rfc2045 +// This doesn't include extension-token case +// as it's handled separatly +func isDiscreteType(s string) bool { + if strings.HasPrefix(s, "text") || + strings.HasPrefix(s, "image") || + strings.HasPrefix(s, "audio") || + strings.HasPrefix(s, "video") || + strings.HasPrefix(s, "application") { + return true + } + return false +} + +// See http://tools.ietf.org/html/rfc2045 +// This doesn't include extension-token case +// as it's handled separatly +func isCompositeType(s string) bool { + if strings.HasPrefix(s, "message") || + strings.HasPrefix(s, "multipart") { + return true + } + return false +} + +func isURLCharRune(r rune) bool { + // We're a bit permissive here, + // by not including '%' in delims + // This is okay, since url unescaping will validate + // that later in the parser. + return r <= unicode.MaxASCII && + !(r >= 0x00 && r <= 0x1F) && r != 0x7F && /* control */ + // delims + r != ' ' && + r != '<' && + r != '>' && + r != '#' && + r != '"' && + // unwise + r != '{' && + r != '}' && + r != '|' && + r != '\\' && + r != '^' && + r != '[' && + r != ']' && + r != '`' +} + +func isBase64Rune(r rune) bool { + return (r >= 'a' && r <= 'z') || + (r >= 'A' && r <= 'Z') || + (r >= '0' && r <= '9') || + r == '+' || + r == '/' || + r == '=' || + r == '\n' +} + +type stateFn func(*lexer) stateFn + +// lexer lexes the data URL scheme input string. +// The implementation is from the text/template/parser package. +type lexer struct { + input string + start int + pos int + width int + seenBase64Item bool + items chan item +} + +func (l *lexer) run() { + for state := lexBeforeDataPrefix; state != nil; { + state = state(l) + } + close(l.items) +} + +func (l *lexer) emit(t itemType) { + l.items <- item{t, l.input[l.start:l.pos]} + l.start = l.pos +} + +func (l *lexer) next() (r rune) { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) + l.pos += l.width + return r +} + +func (l *lexer) backup() { + l.pos -= l.width +} + +func (l *lexer) ignore() { + l.start = l.pos +} + +func (l *lexer) errorf(format string, args ...interface{}) stateFn { + l.items <- item{itemError, fmt.Sprintf(format, args...)} + return nil +} + +func lex(input string) *lexer { + l := &lexer{ + input: input, + items: make(chan item), + } + go l.run() // Concurrently run state machine. + return l +} + +const ( + dataPrefix = "data:" + mediaSep = '/' + paramSemicolon = ';' + paramEqual = '=' + dataComma = ',' +) + +// start lexing by detecting data prefix +func lexBeforeDataPrefix(l *lexer) stateFn { + if strings.HasPrefix(l.input[l.pos:], dataPrefix) { + return lexDataPrefix + } + return l.errorf("missing data prefix") +} + +// lex data prefix +func lexDataPrefix(l *lexer) stateFn { + l.pos += len(dataPrefix) + l.emit(itemDataPrefix) + return lexAfterDataPrefix +} + +// lex what's after data prefix. +// it can be the media type/subtype separator, +// the base64 encoding, or the comma preceding the data +func lexAfterDataPrefix(l *lexer) stateFn { + switch r := l.next(); { + case r == paramSemicolon: + l.backup() + return lexParamSemicolon + case r == dataComma: + l.backup() + return lexDataComma + case r == eof: + return l.errorf("missing comma before data") + case r == 'x' || r == 'X': + if l.next() == '-' { + return lexXTokenMediaType + } + return lexInDiscreteMediaType + case isTokenRune(r): + return lexInDiscreteMediaType + default: + return l.errorf("invalid character after data prefix") + } +} + +func lexXTokenMediaType(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == mediaSep: + l.backup() + return lexMediaType + case r == eof: + return l.errorf("missing media type slash") + case isTokenRune(r): + default: + return l.errorf("invalid character for media type") + } + } +} + +func lexInDiscreteMediaType(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == mediaSep: + l.backup() + // check it's valid discrete type + if !isDiscreteType(l.input[l.start:l.pos]) && + !isCompositeType(l.input[l.start:l.pos]) { + return l.errorf("invalid media type") + } + return lexMediaType + case r == eof: + return l.errorf("missing media type slash") + case isTokenRune(r): + default: + return l.errorf("invalid character for media type") + } + } +} + +func lexMediaType(l *lexer) stateFn { + if l.pos > l.start { + l.emit(itemMediaType) + } + return lexMediaSep +} + +func lexMediaSep(l *lexer) stateFn { + l.next() + l.emit(itemMediaSep) + return lexAfterMediaSep +} + +func lexAfterMediaSep(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == paramSemicolon || r == dataComma: + l.backup() + return lexMediaSubType + case r == eof: + return l.errorf("incomplete media type") + case isTokenRune(r): + default: + return l.errorf("invalid character for media subtype") + } + } +} + +func lexMediaSubType(l *lexer) stateFn { + if l.pos > l.start { + l.emit(itemMediaSubType) + } + return lexAfterMediaSubType +} + +func lexAfterMediaSubType(l *lexer) stateFn { + switch r := l.next(); { + case r == paramSemicolon: + l.backup() + return lexParamSemicolon + case r == dataComma: + l.backup() + return lexDataComma + case r == eof: + return l.errorf("missing comma before data") + default: + return l.errorf("expected semicolon or comma") + } +} + +func lexParamSemicolon(l *lexer) stateFn { + l.next() + l.emit(itemParamSemicolon) + return lexAfterParamSemicolon +} + +func lexAfterParamSemicolon(l *lexer) stateFn { + switch r := l.next(); { + case r == eof: + return l.errorf("unterminated parameter sequence") + case r == paramEqual || r == dataComma: + return l.errorf("unterminated parameter sequence") + case isTokenRune(r): + l.backup() + return lexInParamAttr + default: + return l.errorf("invalid character for parameter attribute") + } +} + +func lexBase64Enc(l *lexer) stateFn { + if l.pos > l.start { + if v := l.input[l.start:l.pos]; v != "base64" { + return l.errorf("expected base64, got %s", v) + } + l.seenBase64Item = true + l.emit(itemBase64Enc) + } + return lexDataComma +} + +func lexInParamAttr(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == paramEqual: + l.backup() + return lexParamAttr + case r == dataComma: + l.backup() + return lexBase64Enc + case r == eof: + return l.errorf("unterminated parameter sequence") + case isTokenRune(r): + default: + return l.errorf("invalid character for parameter attribute") + } + } +} + +func lexParamAttr(l *lexer) stateFn { + if l.pos > l.start { + l.emit(itemParamAttr) + } + return lexParamEqual +} + +func lexParamEqual(l *lexer) stateFn { + l.next() + l.emit(itemParamEqual) + return lexAfterParamEqual +} + +func lexAfterParamEqual(l *lexer) stateFn { + switch r := l.next(); { + case r == '"': + l.emit(itemLeftStringQuote) + return lexInQuotedStringParamVal + case r == eof: + return l.errorf("missing comma before data") + case isTokenRune(r): + return lexInParamVal + default: + return l.errorf("invalid character for parameter value") + } +} + +func lexInQuotedStringParamVal(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == eof: + return l.errorf("unclosed quoted string") + case r == '\\': + return lexEscapedChar + case r == '"': + l.backup() + return lexQuotedStringParamVal + case r <= unicode.MaxASCII: + default: + return l.errorf("invalid character for parameter value") + } + } +} + +func lexEscapedChar(l *lexer) stateFn { + switch r := l.next(); { + case r <= unicode.MaxASCII: + return lexInQuotedStringParamVal + case r == eof: + return l.errorf("unexpected eof") + default: + return l.errorf("invalid escaped character") + } +} + +func lexInParamVal(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == paramSemicolon || r == dataComma: + l.backup() + return lexParamVal + case r == eof: + return l.errorf("missing comma before data") + case isTokenRune(r): + default: + return l.errorf("invalid character for parameter value") + } + } +} + +func lexQuotedStringParamVal(l *lexer) stateFn { + if l.pos > l.start { + l.emit(itemParamVal) + } + l.next() + l.emit(itemRightStringQuote) + return lexAfterParamVal +} + +func lexParamVal(l *lexer) stateFn { + if l.pos > l.start { + l.emit(itemParamVal) + } + return lexAfterParamVal +} + +func lexAfterParamVal(l *lexer) stateFn { + switch r := l.next(); { + case r == paramSemicolon: + l.backup() + return lexParamSemicolon + case r == dataComma: + l.backup() + return lexDataComma + case r == eof: + return l.errorf("missing comma before data") + default: + return l.errorf("expected semicolon or comma") + } +} + +func lexDataComma(l *lexer) stateFn { + l.next() + l.emit(itemDataComma) + if l.seenBase64Item { + return lexBase64Data + } + return lexData +} + +func lexData(l *lexer) stateFn { +Loop: + for { + switch r := l.next(); { + case r == eof: + break Loop + case isURLCharRune(r): + default: + return l.errorf("invalid data character") + } + } + if l.pos > l.start { + l.emit(itemData) + } + l.emit(itemEOF) + return nil +} + +func lexBase64Data(l *lexer) stateFn { +Loop: + for { + switch r := l.next(); { + case r == eof: + break Loop + case isBase64Rune(r): + default: + return l.errorf("invalid data character") + } + } + if l.pos > l.start { + l.emit(itemData) + } + l.emit(itemEOF) + return nil +} diff --git a/vendor/github.com/vincent-petithory/dataurl/rfc2396.go b/vendor/github.com/vincent-petithory/dataurl/rfc2396.go new file mode 100644 index 000000000000..e2ea0caca2ec --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/rfc2396.go @@ -0,0 +1,130 @@ +package dataurl + +import ( + "bytes" + "fmt" + "io" + "strings" +) + +// Escape implements URL escaping, as defined in RFC 2397 (http://tools.ietf.org/html/rfc2397). +// It differs a bit from net/url's QueryEscape and QueryUnescape, e.g how spaces are treated (+ instead of %20): +// +// Only ASCII chars are allowed. Reserved chars are escaped to their %xx form. +// Unreserved chars are [a-z], [A-Z], [0-9], and -_.!~*\(). +func Escape(data []byte) string { + var buf = new(bytes.Buffer) + for _, b := range data { + switch { + case isUnreserved(b): + buf.WriteByte(b) + default: + fmt.Fprintf(buf, "%%%02X", b) + } + } + return buf.String() +} + +// EscapeString is like Escape, but taking +// a string as argument. +func EscapeString(s string) string { + return Escape([]byte(s)) +} + +// isUnreserved return true +// if the byte c is an unreserved char, +// as defined in RFC 2396. +func isUnreserved(c byte) bool { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_' || + c == '.' || + c == '!' || + c == '~' || + c == '*' || + c == '\'' || + c == '(' || + c == ')' +} + +func isHex(c byte) bool { + switch { + case c >= 'a' && c <= 'f': + return true + case c >= 'A' && c <= 'F': + return true + case c >= '0' && c <= '9': + return true + } + return false +} + +// borrowed from net/url/url.go +func unhex(c byte) byte { + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + } + return 0 +} + +// Unescape unescapes a character sequence +// escaped with Escape(String?). +func Unescape(s string) ([]byte, error) { + var buf = new(bytes.Buffer) + reader := strings.NewReader(s) + + for { + r, size, err := reader.ReadRune() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if size > 1 { + return nil, fmt.Errorf("rfc2396: non-ASCII char detected") + } + + switch r { + case '%': + eb1, err := reader.ReadByte() + if err == io.EOF { + return nil, fmt.Errorf("rfc2396: unexpected end of unescape sequence") + } + if err != nil { + return nil, err + } + if !isHex(eb1) { + return nil, fmt.Errorf("rfc2396: invalid char 0x%x in unescape sequence", r) + } + eb0, err := reader.ReadByte() + if err == io.EOF { + return nil, fmt.Errorf("rfc2396: unexpected end of unescape sequence") + } + if err != nil { + return nil, err + } + if !isHex(eb0) { + return nil, fmt.Errorf("rfc2396: invalid char 0x%x in unescape sequence", r) + } + buf.WriteByte(unhex(eb0) + unhex(eb1)*16) + default: + buf.WriteByte(byte(r)) + } + } + return buf.Bytes(), nil +} + +// UnescapeToString is like Unescape, but returning +// a string. +func UnescapeToString(s string) (string, error) { + b, err := Unescape(s) + return string(b), err +} diff --git a/vendor/github.com/vincent-petithory/dataurl/wercker.yml b/vendor/github.com/vincent-petithory/dataurl/wercker.yml new file mode 100644 index 000000000000..3ab8084cc6a6 --- /dev/null +++ b/vendor/github.com/vincent-petithory/dataurl/wercker.yml @@ -0,0 +1 @@ +box: wercker/default \ No newline at end of file diff --git a/vendor/vendor.json b/vendor/vendor.json index b69911f0fd2a..83d4d8a0e1e7 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1037,6 +1037,18 @@ "path": "github.com/coreos/etcd/pkg/types", "revision": "e5527914aa42cae3063f52892e1ca4518da0e4ae" }, + { + "checksumSHA1": "0sYnSIFxkXCVRYj3afRu8EyIl0g=", + "path": "github.com/coreos/ignition/config/types", + "revision": "211ab9d5a30be883c39a47642d05fdc6ce32b76e", + "revisionTime": "2016-12-23T13:37:58Z" + }, + { + "checksumSHA1": "ViLVCc7CzzrGdJINAg8hv98CVkg=", + "path": "github.com/coreos/ignition/config/validate/report", + "revision": "211ab9d5a30be883c39a47642d05fdc6ce32b76e", + "revisionTime": "2016-12-23T13:37:58Z" + }, { "path": "github.com/cyberdelia/heroku-go/v3", "revision": "81c5afa1abcf69cc18ccc24fa3716b5a455c9208" @@ -2312,6 +2324,12 @@ "revision": "faddd6128c66c4708f45fdc007f575f75e592a3c", "revisionTime": "2016-09-28T01:52:44Z" }, + { + "checksumSHA1": "CdE9OUEGLn2pAv1UMuM5rSlaUpM=", + "path": "github.com/vincent-petithory/dataurl", + "revision": "9a301d65acbb728fcc3ace14f45f511a4cfeea9c", + "revisionTime": "2016-03-30T18:21:26Z" + }, { "comment": "v0.6.2", "path": "github.com/vmware/govmomi",