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, + }) +} diff --git a/builtin/providers/ignition/provider.go b/builtin/providers/ignition/provider.go new file mode 100644 index 000000000000..d6b4372f3fc1 --- /dev/null +++ b/builtin/providers/ignition/provider.go @@ -0,0 +1,202 @@ +package ignition + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/url" + "sync" + + "github.com/coreos/go-systemd/unit" + "github.com/coreos/ignition/config/types" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +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_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), + 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 + systemdUnits map[string]*types.SystemdUnit + networkdUnits map[string]*types.NetworkdUnit + 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) 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) 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() + + 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)) +} + +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 +} + +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 +} + +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 +} + +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/provider_test.go b/builtin/providers/ignition/provider_test.go new file mode 100644 index 000000000000..d7e381471b85 --- /dev/null +++ b/builtin/providers/ignition/provider_test.go @@ -0,0 +1,32 @@ +package ignition + +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) + } +} + +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 new file mode 100644 index 000000000000..acecc077855f --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_config.go @@ -0,0 +1,320 @@ +package ignition + +import ( + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/coreos/ignition/config/types" +) + +var configReferenceResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "source": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "verification": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, + }, +} + +func resourceConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceIgnitionFileCreate, + Update: resourceIgnitionFileCreate, + Delete: resourceIgnitionFileDelete, + Exists: resourceIgnitionFileExists, + Read: resourceIgnitionFileRead, + Schema: map[string]*schema.Schema{ + "disks": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "arrays": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "filesystems": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "files": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "systemd": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "networkd": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "users": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "groups": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "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, + }, + }, + } +} + +func resourceIgnitionFileCreate(d *schema.ResourceData, meta interface{}) error { + rendered, err := renderConfig(d, meta.(*cache)) + 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 := renderConfig(d, meta.(*cache)) + if err != nil { + return false, err + } + + return hash(rendered) == d.Id(), nil +} + +func resourceIgnitionFileRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func renderConfig(d *schema.ResourceData, c *cache) (string, error) { + i, err := buildConfig(d, c) + if err != nil { + return "", err + } + + bytes, err := json.MarshalIndent(i, " ", " ") + + if err != nil { + return "", err + } + + return string(bytes), nil +} + +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.Storage, err = buildStorage(d, c) + if err != nil { + return nil, err + } + + 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 + } + + return config, 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("replace.0").(map[string]interface{}) + if len(rr) != 0 { + i.Config.Replace, err = buildConfigReference(rr) + if err != nil { + return i, err + } + } + + ar := d.Get("append").([]interface{}) + if len(ar) != 0 { + for _, rr := range ar { + r, err := buildConfigReference(rr.(map[string]interface{})) + if err != nil { + return i, 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 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{}) { + a, ok := c.arrays[id.(string)] + if !ok { + return storage, fmt.Errorf("invalid raid %q, unknown raid id", id) + } + + 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 + +} + +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 + +} diff --git a/builtin/providers/ignition/resource_ignition_config_test.go b/builtin/providers/ignition/resource_ignition_config_test.go new file mode 100644 index 000000000000..083d37301223 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_config_test.go @@ -0,0 +1,101 @@ +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_config" "test" { + replace { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + `, 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 + }) +} + +func TestIngnitionFileAppend(t *testing.T) { + testIgnition(t, ` + resource "ignition_config" "test" { + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + + append { + source = "foo" + verification = "sha512-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + `, func(c *types.Config) error { + a := c.Ignition.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.Config) error) { + check := func(s *terraform.State) error { + got := s.RootModule().Outputs["rendered"].Value.(string) + + c := &types.Config{} + err := json.Unmarshal([]byte(got), c) + if err != nil { + return err + } + + return assert(c) + } + + 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_config.test.rendered}" +} + +` 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..c6df2227e384 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_disk_test.go @@ -0,0 +1,60 @@ +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" { + 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_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..a3d029fdc0a6 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_file_test.go @@ -0,0 +1,92 @@ +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" { + 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..54cbe24ed2fb --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_filesystem.go @@ -0,0 +1,113 @@ +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, + Optional: 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{})), + } + } + } + + path := types.Path(d.Get("path").(string)) + + return c.addFilesystem(&types.Filesystem{ + Name: d.Get("name").(string), + Mount: mount, + Path: &path, + }), 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..fe8d53f6d0b9 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_filesystem_test.go @@ -0,0 +1,100 @@ +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" { + 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.Device) + } + + if string(*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 + }) +} 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..56a230463d36 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_group_test.go @@ -0,0 +1,59 @@ +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" { + 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_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..ad2a1508faf6 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_networkd_unit_test.go @@ -0,0 +1,39 @@ +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" { + 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_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..bb0fdbb670d2 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_raid_test.go @@ -0,0 +1,48 @@ +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" { + 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_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..4b8f8f84112b --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_systemd_unit_test.go @@ -0,0 +1,58 @@ +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" { + 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 + }) +} diff --git a/builtin/providers/ignition/resource_ignition_user.go b/builtin/providers/ignition/resource_ignition_user.go new file mode 100644 index 000000000000..70009c99bf73 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_user.go @@ -0,0 +1,126 @@ +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, + Optional: true, + ForceNew: true, + }, + "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, + Optional: 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) + 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) { + 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: getUInt(d, "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), + }, + }), 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..e31427aae205 --- /dev/null +++ b/builtin/providers/ignition/resource_ignition_user_test.go @@ -0,0 +1,104 @@ +package ignition + +import ( + "fmt" + "testing" + + "github.com/coreos/ignition/config/types" +) + +func TestIngnitionUser(t *testing.T) { + testIgnition(t, ` + resource "ignition_user" "foo" { + name = "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" + } + + resource "ignition_config" "test" { + users = [ + "${ignition_user.foo.id}", + "${ignition_user.qux.id}", + ] + } + `, func(c *types.Config) error { + 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 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 u.Create.Uid != nil { + return fmt.Errorf("uid, found %d", *u.Create.Uid) + } + + return nil + }) +} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index 54c4ae88aa60..0968ffee8f12 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -31,6 +31,7 @@ import ( grafanaprovider "github.com/hashicorp/terraform/builtin/providers/grafana" herokuprovider "github.com/hashicorp/terraform/builtin/providers/heroku" icinga2provider "github.com/hashicorp/terraform/builtin/providers/icinga2" + ignitionprovider "github.com/hashicorp/terraform/builtin/providers/ignition" influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb" libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato" logentriesprovider "github.com/hashicorp/terraform/builtin/providers/logentries" @@ -95,6 +96,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "grafana": grafanaprovider.Provider, "heroku": herokuprovider.Provider, "icinga2": icinga2provider.Provider, + "ignition": ignitionprovider.Provider, "influxdb": influxdbprovider.Provider, "librato": libratoprovider.Provider, "logentries": logentriesprovider.Provider, 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/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) +} 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..65c3e25cb18e --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/compression.go @@ -0,0 +1,36 @@ +// 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 ( + "errors" + + "github.com/coreos/ignition/config/validate/report" +) + +var ( + ErrCompressionInvalid = errors.New("invalid compression method") +) + +type Compression string + +func (c Compression) Validate() report.Report { + switch c { + case "", "gzip": + default: + return report.ReportFromError(ErrCompressionInvalid, report.EntryError) + } + 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 new file mode 100644 index 000000000000..385515891751 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/config.go @@ -0,0 +1,87 @@ +// 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 ( + "fmt" + + "github.com/coreos/go-semver/semver" + + "github.com/coreos/ignition/config/validate/report" +) + +var ( + MaxVersion = semver.Version{ + Major: 2, + Minor: 0, + } +) + +type Config struct { + 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 new file mode 100644 index 000000000000..275913b4ff34 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/disk.go @@ -0,0 +1,124 @@ +// 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 ( + "fmt" + + "github.com/coreos/ignition/config/validate/report" +) + +type Disk struct { + Device Path `json:"device,omitempty"` + WipeTable bool `json:"wipeTable,omitempty"` + Partitions []Partition `json:"partitions,omitempty"` +} + +func (n Disk) Validate() report.Report { + r := report.Report{} + if len(n.Device) == 0 { + r.Add(report.Entry{ + Message: "disk device is required", + Kind: report.EntryError, + }) + } + if n.partitionNumbersCollide() { + r.Add(report.Entry{ + Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device), + Kind: report.EntryError, + }) + } + if n.partitionsOverlap() { + r.Add(report.Entry{ + Message: fmt.Sprintf("disk %q: partitions overlap", n.Device), + Kind: report.EntryError, + }) + } + if n.partitionsMisaligned() { + r.Add(report.Entry{ + Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device), + Kind: report.EntryError, + }) + } + // 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. +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 +} 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..cc660b559c11 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/file.go @@ -0,0 +1,66 @@ +// 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 ( + "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"` + 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"` +} + +type FileGroup struct { + Id int `json:"id,omitempty"` +} + +type FileContents struct { + Compression Compression `json:"compression,omitempty"` + Source Url `json:"source,omitempty"` + Verification Verification `json:"verification,omitempty"` +} + +type FileMode os.FileMode + +func (m FileMode) Validate() report.Report { + if (m &^ 07777) != 0 { + return report.ReportFromError(ErrFileIllegalMode, report.EntryError) + } + 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 new file mode 100644 index 000000000000..bbd0c75bd807 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/filesystem.go @@ -0,0 +1,67 @@ +// 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 ( + "errors" + + "github.com/coreos/ignition/config/validate/report" +) + +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"` + Mount *FilesystemMount `json:"mount,omitempty"` + Path *Path `json:"path,omitempty"` +} + +type FilesystemMount struct { + Device Path `json:"device,omitempty"` + Format FilesystemFormat `json:"format,omitempty"` + Create *FilesystemCreate `json:"create,omitempty"` +} + +type FilesystemCreate struct { + Force bool `json:"force,omitempty"` + Options MkfsOptions `json:"options,omitempty"` +} + +func (f Filesystem) Validate() report.Report { + if f.Mount == nil && f.Path == nil { + return report.ReportFromError(ErrFilesystemNoMountPath, report.EntryError) + } + if f.Mount != nil && f.Path != nil { + return report.ReportFromError(ErrFilesystemMountAndPath, report.EntryError) + } + return report.Report{} +} + +type FilesystemFormat string + +func (f FilesystemFormat) Validate() report.Report { + switch f { + case "ext4", "btrfs", "xfs": + return report.Report{} + default: + return report.ReportFromError(ErrFilesystemInvalidFormat, report.EntryError) + } +} + +type MkfsOptions []string 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..27e510488705 --- /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"` + 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 new file mode 100644 index 000000000000..77d11d3ddede --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/hash.go @@ -0,0 +1,73 @@ +// 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" + + "github.com/coreos/ignition/config/validate/report" +) + +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) UnmarshalJSON(data []byte) error { + var th string + if err := json.Unmarshal(data, &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 nil +} + +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 report.ReportFromError(ErrHashUnrecognized, report.EntryError) + } + + if len(h.Sum) != hex.EncodedLen(hash.Size()) { + return report.ReportFromError(ErrHashWrongSize, report.EntryError) + } + + 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 new file mode 100644 index 000000000000..c9519a15a9af --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/ignition.go @@ -0,0 +1,69 @@ +// 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" + + "github.com/coreos/ignition/config/validate/report" +) + +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" merge:"old"` + Config IgnitionConfig `json:"config,omitempty" merge:"new"` +} + +type IgnitionConfig struct { + Append []ConfigReference `json:"append,omitempty"` + Replace *ConfigReference `json:"replace,omitempty"` +} + +type ConfigReference struct { + Source Url `json:"source,omitempty"` + Verification Verification `json:"verification,omitempty"` +} + +type IgnitionVersion semver.Version + +func (v *IgnitionVersion) UnmarshalJSON(data []byte) error { + tv := semver.Version(*v) + if err := json.Unmarshal(data, &tv); err != nil { + return err + } + *v = IgnitionVersion(tv) + return nil +} + +func (v IgnitionVersion) MarshalJSON() ([]byte, error) { + return semver.Version(v).MarshalJSON() +} + +func (v IgnitionVersion) Validate() report.Report { + if MaxVersion.Major > v.Major { + return report.ReportFromError(ErrOldVersion, report.EntryError) + } + if MaxVersion.LessThan(semver.Version(v)) { + return report.ReportFromError(ErrNewVersion, report.EntryError) + } + 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 new file mode 100644 index 000000000000..470c721106a6 --- /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"` +} 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..9ed03c32fd7e --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/partition.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 ( + "fmt" + "regexp" + + "github.com/coreos/ignition/config/validate/report" +) + +type Partition struct { + 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) 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 report.ReportFromError(fmt.Errorf("partition labels may not exceed 36 characters"), report.EntryError) + } + return report.Report{} +} + +type PartitionDimension uint64 + +type PartitionTypeGUID string + +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 report.ReportFromError(fmt.Errorf("error matching type-guid regexp: %v", err), report.EntryError) + } + if !ok { + return report.ReportFromError(fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)), report.EntryError) + } + 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 new file mode 100644 index 000000000000..0ffff43bb844 --- /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"` + 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 new file mode 100644 index 000000000000..9e00b37145da --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/path.go @@ -0,0 +1,39 @@ +// 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 ( + "errors" + "path/filepath" + + "github.com/coreos/ignition/config/validate/report" +) + +var ( + ErrPathRelative = errors.New("path not absolute") +) + +type Path string + +func (p Path) MarshalJSON() ([]byte, error) { + return []byte(`"` + string(p) + `"`), nil +} + +func (p Path) Validate() report.Report { + if !filepath.IsAbs(string(p)) { + return report.ReportFromError(ErrPathRelative, report.EntryError) + } + 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 new file mode 100644 index 000000000000..4236255d6f76 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/raid.go @@ -0,0 +1,45 @@ +// 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 ( + "fmt" + + "github.com/coreos/ignition/config/validate/report" +) + +type Raid struct { + Name string `json:"name"` + Level string `json:"level"` + Devices []Path `json:"devices,omitempty"` + Spares int `json:"spares,omitempty"` +} + +func (n Raid) Validate() report.Report { + switch n.Level { + case "linear", "raid0", "0", "stripe": + if n.Spares != 0 { + return report.ReportFromError(fmt.Errorf("spares unsupported for %q arrays", n.Level), report.EntryError) + } + case "raid1", "1", "mirror": + case "raid4", "4": + case "raid5", "5": + case "raid6", "6": + case "raid10", "10": + default: + return report.ReportFromError(fmt.Errorf("unrecognized raid level: %q", n.Level), report.EntryError) + } + 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 new file mode 100644 index 000000000000..bd7343778a9e --- /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"` + 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 new file mode 100644 index 000000000000..97194b911503 --- /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"` +} 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..44be844ea985 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/unit.go @@ -0,0 +1,73 @@ +// 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 ( + "errors" + "path/filepath" + + "github.com/coreos/ignition/config/validate/report" +) + +type SystemdUnit struct { + 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"` + Contents string `json:"contents,omitempty"` +} + +type SystemdUnitName string + +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 report.Report{} + default: + return report.ReportFromError(errors.New("invalid systemd unit extension"), report.EntryError) + } +} + +type SystemdUnitDropInName string + +func (n SystemdUnitDropInName) Validate() report.Report { + switch filepath.Ext(string(n)) { + case ".conf": + return report.Report{} + default: + return report.ReportFromError(errors.New("invalid systemd unit drop-in extension"), report.EntryError) + } +} + +type NetworkdUnit struct { + Name NetworkdUnitName `json:"name,omitempty"` + Contents string `json:"contents,omitempty"` +} + +type NetworkdUnitName string + +func (n NetworkdUnitName) Validate() report.Report { + switch filepath.Ext(string(n)) { + case ".link", ".netdev", ".network": + return report.Report{} + default: + 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 new file mode 100644 index 000000000000..8bf0e6eaab97 --- /dev/null +++ b/vendor/github.com/coreos/ignition/config/types/url.go @@ -0,0 +1,72 @@ +// 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" + "net/url" + + "github.com/coreos/ignition/config/validate/report" + "github.com/vincent-petithory/dataurl" +) + +var ( + ErrInvalidScheme = errors.New("invalid url scheme") +) + +type Url url.URL + +func (u *Url) UnmarshalJSON(data []byte) error { + var tu string + if err := json.Unmarshal(data, &tu); err != nil { + return err + } + + pu, err := url.Parse(tu) + if err != nil { + return err + } + + *u = Url(*pu) + 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 new file mode 100644 index 000000000000..f6653e27494c --- /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"` + PasswordHash string `json:"passwordHash,omitempty"` + SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"` + Create *UserCreate `json:"create,omitempty"` +} + +type UserCreate struct { + 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 new file mode 100644 index 000000000000..b7cef403e878 --- /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"` +} 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", diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index cd8ab8faa688..4d5c43115ed7 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -31,6 +31,7 @@ body.layout-grafana, body.layout-fastly, body.layout-google, body.layout-heroku, +body.layout-ignition, body.layout-icinga2, body.layout-influxdb, body.layout-librato, 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 fcb36b5838a9..c14db7389ab5 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -262,13 +262,17 @@ Heroku - > - Icinga2 - + > + Ignition + + + > + Icinga2 + > InfluxDB - + > Librato 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 %>