Example #1
0
func ConvertAs2_0_0(in Config) (types.Config, error) {
	out := types.Config{
		Ignition: types.Ignition{
			Version: types.IgnitionVersion{Major: 2, Minor: 0},
		},
	}

	for _, ref := range in.Ignition.Config.Append {
		newRef, err := convertConfigReference(ref)
		if err != nil {
			return types.Config{}, err
		}
		out.Ignition.Config.Append = append(out.Ignition.Config.Append, newRef)
	}

	if in.Ignition.Config.Replace != nil {
		newRef, err := convertConfigReference(*in.Ignition.Config.Replace)
		if err != nil {
			return types.Config{}, err
		}
		out.Ignition.Config.Replace = &newRef
	}

	for _, disk := range in.Storage.Disks {
		newDisk := types.Disk{
			Device:    types.Path(disk.Device),
			WipeTable: disk.WipeTable,
		}

		for _, partition := range disk.Partitions {
			size, err := convertPartitionDimension(partition.Size)
			if err != nil {
				return types.Config{}, err
			}
			start, err := convertPartitionDimension(partition.Start)
			if err != nil {
				return types.Config{}, err
			}

			newDisk.Partitions = append(newDisk.Partitions, types.Partition{
				Label:    types.PartitionLabel(partition.Label),
				Number:   partition.Number,
				Size:     size,
				Start:    start,
				TypeGUID: types.PartitionTypeGUID(partition.TypeGUID),
			})
		}

		out.Storage.Disks = append(out.Storage.Disks, newDisk)
	}

	for _, array := range in.Storage.Arrays {
		newArray := types.Raid{
			Name:   array.Name,
			Level:  array.Level,
			Spares: array.Spares,
		}

		for _, device := range array.Devices {
			newArray.Devices = append(newArray.Devices, types.Path(device))
		}

		out.Storage.Arrays = append(out.Storage.Arrays, newArray)
	}

	for _, filesystem := range in.Storage.Filesystems {
		newFilesystem := types.Filesystem{
			Name: filesystem.Name,
			Path: func(p types.Path) *types.Path {
				if p == "" {
					return nil
				}

				return &p
			}(types.Path(filesystem.Path)),
		}

		if filesystem.Mount != nil {
			newFilesystem.Mount = &types.FilesystemMount{
				Device: types.Path(filesystem.Mount.Device),
				Format: types.FilesystemFormat(filesystem.Mount.Format),
			}

			if filesystem.Mount.Create != nil {
				newFilesystem.Mount.Create = &types.FilesystemCreate{
					Force:   filesystem.Mount.Create.Force,
					Options: types.MkfsOptions(filesystem.Mount.Create.Options),
				}
			}
		}

		out.Storage.Filesystems = append(out.Storage.Filesystems, newFilesystem)
	}

	for _, file := range in.Storage.Files {
		newFile := types.File{
			Filesystem: file.Filesystem,
			Path:       types.Path(file.Path),
			Mode:       types.FileMode(file.Mode),
			User:       types.FileUser{Id: file.User.Id},
			Group:      types.FileGroup{Id: file.Group.Id},
		}

		if file.Contents.Inline != "" {
			newFile.Contents = types.FileContents{
				Source: types.Url{
					Scheme: "data",
					Opaque: "," + dataurl.EscapeString(file.Contents.Inline),
				},
			}
		}

		if file.Contents.Remote.Url != "" {
			source, err := url.Parse(file.Contents.Remote.Url)
			if err != nil {
				return types.Config{}, err
			}

			newFile.Contents = types.FileContents{Source: types.Url(*source)}
		}

		if newFile.Contents == (types.FileContents{}) {
			newFile.Contents = types.FileContents{
				Source: types.Url{
					Scheme: "data",
					Opaque: ",",
				},
			}
		}

		newFile.Contents.Compression = types.Compression(file.Contents.Remote.Compression)
		newFile.Contents.Verification = convertVerification(file.Contents.Remote.Verification)

		out.Storage.Files = append(out.Storage.Files, newFile)
	}

	for _, unit := range in.Systemd.Units {
		newUnit := types.SystemdUnit{
			Name:     types.SystemdUnitName(unit.Name),
			Enable:   unit.Enable,
			Mask:     unit.Mask,
			Contents: unit.Contents,
		}

		for _, dropIn := range unit.DropIns {
			newUnit.DropIns = append(newUnit.DropIns, types.SystemdUnitDropIn{
				Name:     types.SystemdUnitDropInName(dropIn.Name),
				Contents: dropIn.Contents,
			})
		}

		out.Systemd.Units = append(out.Systemd.Units, newUnit)
	}

	for _, unit := range in.Networkd.Units {
		out.Networkd.Units = append(out.Networkd.Units, types.NetworkdUnit{
			Name:     types.NetworkdUnitName(unit.Name),
			Contents: unit.Contents,
		})
	}

	for _, user := range in.Passwd.Users {
		newUser := types.User{
			Name:              user.Name,
			PasswordHash:      user.PasswordHash,
			SSHAuthorizedKeys: user.SSHAuthorizedKeys,
		}

		if user.Create != nil {
			newUser.Create = &types.UserCreate{
				Uid:          user.Create.Uid,
				GECOS:        user.Create.GECOS,
				Homedir:      user.Create.Homedir,
				NoCreateHome: user.Create.NoCreateHome,
				PrimaryGroup: user.Create.PrimaryGroup,
				Groups:       user.Create.Groups,
				NoUserGroup:  user.Create.NoUserGroup,
				System:       user.Create.System,
				NoLogInit:    user.Create.NoLogInit,
				Shell:        user.Create.Shell,
			}
		}

		out.Passwd.Users = append(out.Passwd.Users, newUser)
	}

	for _, group := range in.Passwd.Groups {
		out.Passwd.Groups = append(out.Passwd.Groups, types.Group{
			Name:         group.Name,
			Gid:          group.Gid,
			PasswordHash: group.PasswordHash,
			System:       group.System,
		})
	}

	if err := out.AssertValid(); err != nil {
		return types.Config{}, err
	}

	return out, nil
}
Example #2
0
func TestParseAsV2_0_0(t *testing.T) {
	type in struct {
		data string
	}
	type out struct {
		cfg types.Config
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{data: ``},
			out: out{cfg: types.Config{Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}}}},
		},

		// Errors
		{
			in:  in{data: `foo:`},
			out: out{err: ErrKeysUnrecognized{"foo"}},
		},
		{
			in: in{data: `
networkd:
  units:
    - name: bad.blah
      contents: not valid
`},
			out: out{err: errors.New("invalid networkd unit extension")},
		},

		// Config
		{
			in: in{data: `
ignition:
  config:
    append:
      - source: http://example.com/test1
        verification:
          hash:
            function: sha512
            sum: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
      - source: http://example.com/test2
    replace:
      source: http://example.com/test3
      verification:
        hash:
          function: sha512
          sum: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
`},
			out: out{cfg: types.Config{
				Ignition: types.Ignition{
					Version: types.IgnitionVersion{Major: 2},
					Config: types.IgnitionConfig{
						Append: []types.ConfigReference{
							{
								Source: types.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/test1",
								},
								Verification: types.Verification{
									Hash: &types.Hash{
										Function: "sha512",
										Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
									},
								},
							},
							{
								Source: types.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/test2",
								},
							},
						},
						Replace: &types.ConfigReference{
							Source: types.Url{
								Scheme: "http",
								Host:   "example.com",
								Path:   "/test3",
							},
							Verification: types.Verification{
								Hash: &types.Hash{
									Function: "sha512",
									Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
								},
							},
						},
					},
				},
			}},
		},

		// Storage
		{
			in: in{data: `
storage:
  disks:
    - device: /dev/sda
      wipe_table: true
      partitions:
        - label: ROOT
          number: 7
          size: 100MB
          start: 50MB
          type_guid: 11111111-1111-1111-1111-111111111111
        - label: DATA
          number: 12
          size: 1GB
          start: 300MB
          type_guid: 00000000-0000-0000-0000-000000000000
        - label: NOTHING
    - device: /dev/sdb
      wipe_table: true
  raid:
    - name: fast
      level: raid0
      devices:
        - /dev/sdc
        - /dev/sdd
    - name: durable
      level: raid1
      devices:
        - /dev/sde
        - /dev/sdf
        - /dev/sdg
      spares: 1
  filesystems:
    - name: filesystem1
      mount:
        device: /dev/disk/by-partlabel/ROOT
        format: btrfs
        create:
          force: true
          options:
            - -L
            - ROOT
    - name: filesystem2
      mount:
        device: /dev/disk/by-partlabel/DATA
        format: ext4
    - name: filesystem3
      path: /sysroot
  files:
    - path: /opt/file1
      filesystem: filesystem1
      contents:
        inline: file1
      mode: 0644
      user:
        id: 500
      group:
        id: 501
    - path: /opt/file2
      filesystem: filesystem1
      contents:
        remote:
          url: http://example.com/file2
          compression: gzip
          verification:
            hash:
              function: sha512
              sum: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
      mode: 0644
      user:
        id: 502
      group:
        id: 503
    - path: /opt/file3
      filesystem: filesystem2
      contents:
        remote:
          url: http://example.com/file3
          compression: gzip
      mode: 0400
      user:
        id: 1000
      group:
        id: 1001
    - path: /opt/file4
      filesystem: filesystem2
`},
			out: out{cfg: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Storage: types.Storage{
					Disks: []types.Disk{
						{
							Device:    types.Path("/dev/sda"),
							WipeTable: true,
							Partitions: []types.Partition{
								{
									Label:    types.PartitionLabel("ROOT"),
									Number:   7,
									Size:     types.PartitionDimension(0x32000),
									Start:    types.PartitionDimension(0x19000),
									TypeGUID: "11111111-1111-1111-1111-111111111111",
								},
								{
									Label:    types.PartitionLabel("DATA"),
									Number:   12,
									Size:     types.PartitionDimension(0x200000),
									Start:    types.PartitionDimension(0x96000),
									TypeGUID: "00000000-0000-0000-0000-000000000000",
								},
								{
									Label: types.PartitionLabel("NOTHING"),
								},
							},
						},
						{
							Device:    types.Path("/dev/sdb"),
							WipeTable: true,
						},
					},
					Arrays: []types.Raid{
						{
							Name:    "fast",
							Level:   "raid0",
							Devices: []types.Path{types.Path("/dev/sdc"), types.Path("/dev/sdd")},
						},
						{
							Name:    "durable",
							Level:   "raid1",
							Devices: []types.Path{types.Path("/dev/sde"), types.Path("/dev/sdf"), types.Path("/dev/sdg")},
							Spares:  1,
						},
					},
					Filesystems: []types.Filesystem{
						{
							Name: "filesystem1",
							Mount: &types.FilesystemMount{
								Device: types.Path("/dev/disk/by-partlabel/ROOT"),
								Format: types.FilesystemFormat("btrfs"),
								Create: &types.FilesystemCreate{
									Force:   true,
									Options: types.MkfsOptions([]string{"-L", "ROOT"}),
								},
							},
						},
						{
							Name: "filesystem2",
							Mount: &types.FilesystemMount{
								Device: types.Path("/dev/disk/by-partlabel/DATA"),
								Format: types.FilesystemFormat("ext4"),
							},
						},
						{
							Name: "filesystem3",
							Path: func(p types.Path) *types.Path { return &p }("/sysroot"),
						},
					},
					Files: []types.File{
						{
							Filesystem: "filesystem1",
							Path:       types.Path("/opt/file1"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "data",
									Opaque: ",file1",
								},
							},
							Mode:  types.FileMode(0644),
							User:  types.FileUser{Id: 500},
							Group: types.FileGroup{Id: 501},
						},
						{
							Filesystem: "filesystem1",
							Path:       types.Path("/opt/file2"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/file2",
								},
								Compression: "gzip",
								Verification: types.Verification{
									Hash: &types.Hash{
										Function: "sha512",
										Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
									},
								},
							},
							Mode:  types.FileMode(0644),
							User:  types.FileUser{Id: 502},
							Group: types.FileGroup{Id: 503},
						},
						{
							Filesystem: "filesystem2",
							Path:       types.Path("/opt/file3"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/file3",
								},
								Compression: "gzip",
							},
							Mode:  types.FileMode(0400),
							User:  types.FileUser{Id: 1000},
							Group: types.FileGroup{Id: 1001},
						},
						{
							Filesystem: "filesystem2",
							Path:       types.Path("/opt/file4"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "data",
									Opaque: ",",
								},
							},
						},
					},
				},
			}},
		},

		// systemd
		{
			in: in{data: `
systemd:
  units:
    - name: test1.service
      enable: true
      contents: test1 contents
      dropins:
        - name: conf1.conf
          contents: conf1 contents
        - name: conf2.conf
          contents: conf2 contents
    - name: test2.service
      mask: true
      contents: test2 contents
`},
			out: out{cfg: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Systemd: types.Systemd{
					Units: []types.SystemdUnit{
						{
							Name:     "test1.service",
							Enable:   true,
							Contents: "test1 contents",
							DropIns: []types.SystemdUnitDropIn{
								{
									Name:     "conf1.conf",
									Contents: "conf1 contents",
								},
								{
									Name:     "conf2.conf",
									Contents: "conf2 contents",
								},
							},
						},
						{
							Name:     "test2.service",
							Mask:     true,
							Contents: "test2 contents",
						},
					},
				},
			}},
		},

		// networkd
		{
			in: in{data: `
networkd:
  units:
    - name: empty.netdev
    - name: test.network
      contents: test config
`},
			out: out{cfg: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Networkd: types.Networkd{
					Units: []types.NetworkdUnit{
						{
							Name: "empty.netdev",
						},
						{
							Name:     "test.network",
							Contents: "test config",
						},
					},
				},
			}},
		},

		// passwd
		{
			in: in{data: `
passwd:
  users:
    - name: user 1
      password_hash: password 1
      ssh_authorized_keys:
        - key1
        - key2
    - name: user 2
      password_hash: password 2
      ssh_authorized_keys:
        - key3
        - key4
      create:
        uid: 123
        gecos: gecos
        home_dir: /home/user 2
        no_create_home: true
        primary_group: wheel
        groups:
          - wheel
          - plugdev
        no_user_group: true
        system: true
        no_log_init: true
        shell: /bin/zsh
    - name: user 3
      password_hash: password 3
      ssh_authorized_keys:
        - key5
        - key6
      create: {}
  groups:
    - name: group 1
      gid: 1000
      password_hash: password 1
      system: true
    - name: group 2
      password_hash: password 2
`},
			out: out{cfg: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Passwd: types.Passwd{
					Users: []types.User{
						{
							Name:              "user 1",
							PasswordHash:      "password 1",
							SSHAuthorizedKeys: []string{"key1", "key2"},
						},
						{
							Name:              "user 2",
							PasswordHash:      "password 2",
							SSHAuthorizedKeys: []string{"key3", "key4"},
							Create: &types.UserCreate{
								Uid:          func(i uint) *uint { return &i }(123),
								GECOS:        "gecos",
								Homedir:      "/home/user 2",
								NoCreateHome: true,
								PrimaryGroup: "wheel",
								Groups:       []string{"wheel", "plugdev"},
								NoUserGroup:  true,
								System:       true,
								NoLogInit:    true,
								Shell:        "/bin/zsh",
							},
						},
						{
							Name:              "user 3",
							PasswordHash:      "password 3",
							SSHAuthorizedKeys: []string{"key5", "key6"},
							Create:            &types.UserCreate{},
						},
					},
					Groups: []types.Group{
						{
							Name:         "group 1",
							Gid:          func(i uint) *uint { return &i }(1000),
							PasswordHash: "password 1",
							System:       true,
						},
						{
							Name:         "group 2",
							PasswordHash: "password 2",
						},
					},
				},
			}},
		},
	}

	for i, test := range tests {
		cfg, err := ParseAsV2_0_0([]byte(test.in.data))
		if !reflect.DeepEqual(err, test.out.err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(cfg, test.out.cfg) {
			t.Errorf("#%d: bad config: want %#v, got %#v", i, test.out.cfg, cfg)
		}
	}
}
Example #3
0
func TestConvertAs2_0_0(t *testing.T) {
	type in struct {
		cfg types.Config
	}
	type out struct {
		cfg ignTypes.Config
		r   report.Report
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{cfg: types.Config{}},
			out: out{cfg: ignTypes.Config{Ignition: ignTypes.Ignition{Version: ignTypes.IgnitionVersion{Major: 2}}}},
		},
		{
			in: in{cfg: types.Config{
				Networkd: types.Networkd{
					Units: []types.NetworkdUnit{
						{Name: "bad.blah", Contents: "not valid"},
					},
				},
			}},
			out: out{r: report.ReportFromError(errors.New("invalid networkd unit extension"), report.EntryError)},
		},

		// Config
		{
			in: in{cfg: types.Config{
				Ignition: types.Ignition{
					Config: types.IgnitionConfig{
						Append: []types.ConfigReference{
							{
								Source: "http://example.com/test1",
								Verification: types.Verification{
									Hash: types.Hash{
										Function: "sha512",
										Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
									},
								},
							},
							{
								Source: "http://example.com/test2",
							},
						},
						Replace: &types.ConfigReference{
							Source: "http://example.com/test3",
							Verification: types.Verification{
								Hash: types.Hash{
									Function: "sha512",
									Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
								},
							},
						},
					},
				},
			}},
			out: out{cfg: ignTypes.Config{
				Ignition: ignTypes.Ignition{
					Version: ignTypes.IgnitionVersion{Major: 2},
					Config: ignTypes.IgnitionConfig{
						Append: []ignTypes.ConfigReference{
							{
								Source: ignTypes.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/test1",
								},
								Verification: ignTypes.Verification{
									Hash: &ignTypes.Hash{
										Function: "sha512",
										Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
									},
								},
							},
							{
								Source: ignTypes.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/test2",
								},
							},
						},
						Replace: &ignTypes.ConfigReference{
							Source: ignTypes.Url{
								Scheme: "http",
								Host:   "example.com",
								Path:   "/test3",
							},
							Verification: ignTypes.Verification{
								Hash: &ignTypes.Hash{
									Function: "sha512",
									Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
								},
							},
						},
					},
				},
			}},
		},

		// Storage
		{
			in: in{cfg: types.Config{
				Storage: types.Storage{
					Disks: []types.Disk{
						{
							Device:    "/dev/sda",
							WipeTable: true,
							Partitions: []types.Partition{
								{
									Label:    "ROOT",
									Number:   7,
									Size:     "100MB",
									Start:    "50MB",
									TypeGUID: "11111111-1111-1111-1111-111111111111",
								},
								{
									Label:    "DATA",
									Number:   12,
									Size:     "1GB",
									Start:    "300MB",
									TypeGUID: "00000000-0000-0000-0000-000000000000",
								},
								{
									Label: "NOTHING",
								},
							},
						},
						{
							Device:    "/dev/sdb",
							WipeTable: true,
						},
					},
					Arrays: []types.Raid{
						{
							Name:    "fast",
							Level:   "raid0",
							Devices: []string{"/dev/sdc", "/dev/sdd"},
						},
						{
							Name:    "durable",
							Level:   "raid1",
							Devices: []string{"/dev/sde", "/dev/sdf", "/dev/sdg"},
							Spares:  1,
						},
					},
					Filesystems: []types.Filesystem{
						{
							Name: "filesystem1",
							Mount: &types.Mount{
								Device: "/dev/disk/by-partlabel/ROOT",
								Format: "btrfs",
								Create: &types.Create{
									Force:   true,
									Options: []string{"-L", "ROOT"},
								},
							},
						},
						{
							Name: "filesystem2",
							Mount: &types.Mount{
								Device: "/dev/disk/by-partlabel/DATA",
								Format: "ext4",
							},
						},
						{
							Name: "filesystem3",
							Path: "/sysroot",
						},
					},
					Files: []types.File{
						{
							Filesystem: "filesystem1",
							Path:       "/opt/file1",
							Contents: types.FileContents{
								Inline: "file1",
							},
							Mode:  0644,
							User:  types.FileUser{Id: 500},
							Group: types.FileGroup{Id: 501},
						},
						{
							Filesystem: "filesystem1",
							Path:       "/opt/file2",
							Contents: types.FileContents{
								Remote: types.Remote{
									Url:         "http://example.com/file2",
									Compression: "gzip",
									Verification: types.Verification{
										Hash: types.Hash{
											Function: "sha512",
											Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
										},
									},
								},
							},
							Mode:  0644,
							User:  types.FileUser{Id: 502},
							Group: types.FileGroup{Id: 503},
						},
						{
							Filesystem: "filesystem2",
							Path:       "/opt/file3",
							Contents: types.FileContents{
								Remote: types.Remote{
									Url:         "http://example.com/file3",
									Compression: "gzip",
								},
							},
							Mode:  0400,
							User:  types.FileUser{Id: 1000},
							Group: types.FileGroup{Id: 1001},
						},
						{
							Filesystem: "filesystem2",
							Path:       "/opt/file4",
							Contents: types.FileContents{
								Inline: "",
							},
						},
					},
				},
			}},
			out: out{cfg: ignTypes.Config{
				Ignition: ignTypes.Ignition{Version: ignTypes.IgnitionVersion{Major: 2}},
				Storage: ignTypes.Storage{
					Disks: []ignTypes.Disk{
						{
							Device:    ignTypes.Path("/dev/sda"),
							WipeTable: true,
							Partitions: []ignTypes.Partition{
								{
									Label:    ignTypes.PartitionLabel("ROOT"),
									Number:   7,
									Size:     ignTypes.PartitionDimension(0x32000),
									Start:    ignTypes.PartitionDimension(0x19000),
									TypeGUID: "11111111-1111-1111-1111-111111111111",
								},
								{
									Label:    ignTypes.PartitionLabel("DATA"),
									Number:   12,
									Size:     ignTypes.PartitionDimension(0x200000),
									Start:    ignTypes.PartitionDimension(0x96000),
									TypeGUID: "00000000-0000-0000-0000-000000000000",
								},
								{
									Label: ignTypes.PartitionLabel("NOTHING"),
								},
							},
						},
						{
							Device:    ignTypes.Path("/dev/sdb"),
							WipeTable: true,
						},
					},
					Arrays: []ignTypes.Raid{
						{
							Name:    "fast",
							Level:   "raid0",
							Devices: []ignTypes.Path{ignTypes.Path("/dev/sdc"), ignTypes.Path("/dev/sdd")},
						},
						{
							Name:    "durable",
							Level:   "raid1",
							Devices: []ignTypes.Path{ignTypes.Path("/dev/sde"), ignTypes.Path("/dev/sdf"), ignTypes.Path("/dev/sdg")},
							Spares:  1,
						},
					},
					Filesystems: []ignTypes.Filesystem{
						{
							Name: "filesystem1",
							Mount: &ignTypes.FilesystemMount{
								Device: ignTypes.Path("/dev/disk/by-partlabel/ROOT"),
								Format: ignTypes.FilesystemFormat("btrfs"),
								Create: &ignTypes.FilesystemCreate{
									Force:   true,
									Options: ignTypes.MkfsOptions([]string{"-L", "ROOT"}),
								},
							},
						},
						{
							Name: "filesystem2",
							Mount: &ignTypes.FilesystemMount{
								Device: ignTypes.Path("/dev/disk/by-partlabel/DATA"),
								Format: ignTypes.FilesystemFormat("ext4"),
							},
						},
						{
							Name: "filesystem3",
							Path: func(p ignTypes.Path) *ignTypes.Path { return &p }("/sysroot"),
						},
					},
					Files: []ignTypes.File{
						{
							Filesystem: "filesystem1",
							Path:       ignTypes.Path("/opt/file1"),
							Contents: ignTypes.FileContents{
								Source: ignTypes.Url{
									Scheme: "data",
									Opaque: ",file1",
								},
							},
							Mode:  ignTypes.FileMode(0644),
							User:  ignTypes.FileUser{Id: 500},
							Group: ignTypes.FileGroup{Id: 501},
						},
						{
							Filesystem: "filesystem1",
							Path:       ignTypes.Path("/opt/file2"),
							Contents: ignTypes.FileContents{
								Source: ignTypes.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/file2",
								},
								Compression: "gzip",
								Verification: ignTypes.Verification{
									Hash: &ignTypes.Hash{
										Function: "sha512",
										Sum:      "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
									},
								},
							},
							Mode:  ignTypes.FileMode(0644),
							User:  ignTypes.FileUser{Id: 502},
							Group: ignTypes.FileGroup{Id: 503},
						},
						{
							Filesystem: "filesystem2",
							Path:       ignTypes.Path("/opt/file3"),
							Contents: ignTypes.FileContents{
								Source: ignTypes.Url{
									Scheme: "http",
									Host:   "example.com",
									Path:   "/file3",
								},
								Compression: "gzip",
							},
							Mode:  ignTypes.FileMode(0400),
							User:  ignTypes.FileUser{Id: 1000},
							Group: ignTypes.FileGroup{Id: 1001},
						},
						{
							Filesystem: "filesystem2",
							Path:       ignTypes.Path("/opt/file4"),
							Contents: ignTypes.FileContents{
								Source: ignTypes.Url{
									Scheme: "data",
									Opaque: ",",
								},
							},
						},
					},
				},
			}},
		},

		// systemd
		{
			in: in{cfg: types.Config{
				Systemd: types.Systemd{
					Units: []types.SystemdUnit{
						{
							Name:     "test1.service",
							Enable:   true,
							Contents: "test1 contents",
							DropIns: []types.SystemdUnitDropIn{
								{
									Name:     "conf1.conf",
									Contents: "conf1 contents",
								},
								{
									Name:     "conf2.conf",
									Contents: "conf2 contents",
								},
							},
						},
						{
							Name:     "test2.service",
							Mask:     true,
							Contents: "test2 contents",
						},
					},
				},
			}},
			out: out{cfg: ignTypes.Config{
				Ignition: ignTypes.Ignition{Version: ignTypes.IgnitionVersion{Major: 2}},
				Systemd: ignTypes.Systemd{
					Units: []ignTypes.SystemdUnit{
						{
							Name:     "test1.service",
							Enable:   true,
							Contents: "test1 contents",
							DropIns: []ignTypes.SystemdUnitDropIn{
								{
									Name:     "conf1.conf",
									Contents: "conf1 contents",
								},
								{
									Name:     "conf2.conf",
									Contents: "conf2 contents",
								},
							},
						},
						{
							Name:     "test2.service",
							Mask:     true,
							Contents: "test2 contents",
						},
					},
				},
			}},
		},

		// networkd
		{
			in: in{cfg: types.Config{
				Networkd: types.Networkd{
					Units: []types.NetworkdUnit{
						{
							Name: "empty.netdev",
						},
						{
							Name:     "test.network",
							Contents: "test config",
						},
					},
				},
			}},
			out: out{cfg: ignTypes.Config{
				Ignition: ignTypes.Ignition{Version: ignTypes.IgnitionVersion{Major: 2}},
				Networkd: ignTypes.Networkd{
					Units: []ignTypes.NetworkdUnit{
						{
							Name: "empty.netdev",
						},
						{
							Name:     "test.network",
							Contents: "test config",
						},
					},
				},
			}},
		},

		// passwd
		{
			in: in{cfg: types.Config{
				Passwd: types.Passwd{
					Users: []types.User{
						{
							Name:              "user 1",
							PasswordHash:      "password 1",
							SSHAuthorizedKeys: []string{"key1", "key2"},
						},
						{
							Name:              "user 2",
							PasswordHash:      "password 2",
							SSHAuthorizedKeys: []string{"key3", "key4"},
							Create: &types.UserCreate{
								Uid:          func(i uint) *uint { return &i }(123),
								GECOS:        "gecos",
								Homedir:      "/home/user 2",
								NoCreateHome: true,
								PrimaryGroup: "wheel",
								Groups:       []string{"wheel", "plugdev"},
								NoUserGroup:  true,
								System:       true,
								NoLogInit:    true,
								Shell:        "/bin/zsh",
							},
						},
						{
							Name:              "user 3",
							PasswordHash:      "password 3",
							SSHAuthorizedKeys: []string{"key5", "key6"},
							Create:            &types.UserCreate{},
						},
					},
					Groups: []types.Group{
						{
							Name:         "group 1",
							Gid:          func(i uint) *uint { return &i }(1000),
							PasswordHash: "password 1",
							System:       true,
						},
						{
							Name:         "group 2",
							PasswordHash: "password 2",
						},
					},
				},
			}},
			out: out{cfg: ignTypes.Config{
				Ignition: ignTypes.Ignition{Version: ignTypes.IgnitionVersion{Major: 2}},
				Passwd: ignTypes.Passwd{
					Users: []ignTypes.User{
						{
							Name:              "user 1",
							PasswordHash:      "password 1",
							SSHAuthorizedKeys: []string{"key1", "key2"},
						},
						{
							Name:              "user 2",
							PasswordHash:      "password 2",
							SSHAuthorizedKeys: []string{"key3", "key4"},
							Create: &ignTypes.UserCreate{
								Uid:          func(i uint) *uint { return &i }(123),
								GECOS:        "gecos",
								Homedir:      "/home/user 2",
								NoCreateHome: true,
								PrimaryGroup: "wheel",
								Groups:       []string{"wheel", "plugdev"},
								NoUserGroup:  true,
								System:       true,
								NoLogInit:    true,
								Shell:        "/bin/zsh",
							},
						},
						{
							Name:              "user 3",
							PasswordHash:      "password 3",
							SSHAuthorizedKeys: []string{"key5", "key6"},
							Create:            &ignTypes.UserCreate{},
						},
					},
					Groups: []ignTypes.Group{
						{
							Name:         "group 1",
							Gid:          func(i uint) *uint { return &i }(1000),
							PasswordHash: "password 1",
							System:       true,
						},
						{
							Name:         "group 2",
							PasswordHash: "password 2",
						},
					},
				},
			}},
		},
	}

	for i, test := range tests {
		cfg, r := ConvertAs2_0_0(test.in.cfg)
		if !reflect.DeepEqual(r, test.out.r) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.r, r)
		}
		if !reflect.DeepEqual(cfg, test.out.cfg) {
			t.Errorf("#%d: bad config: want %#v, got %#v", i, test.out.cfg, cfg)
		}
	}
}
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 TestTranslateFromV1(t *testing.T) {
	type in struct {
		config v1.Config
	}
	type out struct {
		config types.Config
		err    error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{},
			out: out{config: types.Config{Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}}}},
		},
		{
			in: in{config: v1.Config{
				Storage: v1.Storage{
					Disks: []v1.Disk{
						{
							Device:    v1.Path("/dev/sda"),
							WipeTable: true,
							Partitions: []v1.Partition{
								{
									Label:    v1.PartitionLabel("ROOT"),
									Number:   7,
									Size:     v1.PartitionDimension(100),
									Start:    v1.PartitionDimension(50),
									TypeGUID: "HI",
								},
								{
									Label:    v1.PartitionLabel("DATA"),
									Number:   12,
									Size:     v1.PartitionDimension(1000),
									Start:    v1.PartitionDimension(300),
									TypeGUID: "LO",
								},
							},
						},
						{
							Device:    v1.Path("/dev/sdb"),
							WipeTable: true,
						},
					},
					Arrays: []v1.Raid{
						{
							Name:    "fast",
							Level:   "raid0",
							Devices: []v1.Path{v1.Path("/dev/sdc"), v1.Path("/dev/sdd")},
							Spares:  2,
						},
						{
							Name:    "durable",
							Level:   "raid1",
							Devices: []v1.Path{v1.Path("/dev/sde"), v1.Path("/dev/sdf")},
							Spares:  3,
						},
					},
					Filesystems: []v1.Filesystem{
						{
							Device: v1.Path("/dev/disk/by-partlabel/ROOT"),
							Format: v1.FilesystemFormat("btrfs"),
							Create: &v1.FilesystemCreate{
								Force:   true,
								Options: v1.MkfsOptions([]string{"-L", "ROOT"}),
							},
							Files: []v1.File{
								{
									Path:     v1.Path("/opt/file1"),
									Contents: "file1",
									Mode:     v1.FileMode(0664),
									Uid:      500,
									Gid:      501,
								},
								{
									Path:     v1.Path("/opt/file2"),
									Contents: "file2",
									Mode:     v1.FileMode(0644),
									Uid:      502,
									Gid:      503,
								},
							},
						},
						{
							Device: v1.Path("/dev/disk/by-partlabel/DATA"),
							Format: v1.FilesystemFormat("ext4"),
							Files: []v1.File{
								{
									Path:     v1.Path("/opt/file3"),
									Contents: "file3",
									Mode:     v1.FileMode(0400),
									Uid:      1000,
									Gid:      1001,
								},
							},
						},
					},
				},
			}},
			out: out{config: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Storage: types.Storage{
					Disks: []types.Disk{
						{
							Device:    types.Path("/dev/sda"),
							WipeTable: true,
							Partitions: []types.Partition{
								{
									Label:    types.PartitionLabel("ROOT"),
									Number:   7,
									Size:     types.PartitionDimension(100),
									Start:    types.PartitionDimension(50),
									TypeGUID: "HI",
								},
								{
									Label:    types.PartitionLabel("DATA"),
									Number:   12,
									Size:     types.PartitionDimension(1000),
									Start:    types.PartitionDimension(300),
									TypeGUID: "LO",
								},
							},
						},
						{
							Device:    types.Path("/dev/sdb"),
							WipeTable: true,
						},
					},
					Arrays: []types.Raid{
						{
							Name:    "fast",
							Level:   "raid0",
							Devices: []types.Path{types.Path("/dev/sdc"), types.Path("/dev/sdd")},
							Spares:  2,
						},
						{
							Name:    "durable",
							Level:   "raid1",
							Devices: []types.Path{types.Path("/dev/sde"), types.Path("/dev/sdf")},
							Spares:  3,
						},
					},
					Filesystems: []types.Filesystem{
						{
							Name: "_translate-filesystem-0",
							Mount: &types.FilesystemMount{
								Device: types.Path("/dev/disk/by-partlabel/ROOT"),
								Format: types.FilesystemFormat("btrfs"),
								Create: &types.FilesystemCreate{
									Force:   true,
									Options: types.MkfsOptions([]string{"-L", "ROOT"}),
								},
							},
						},
						{
							Name: "_translate-filesystem-1",
							Mount: &types.FilesystemMount{
								Device: types.Path("/dev/disk/by-partlabel/DATA"),
								Format: types.FilesystemFormat("ext4"),
							},
						},
					},
					Files: []types.File{
						{
							Filesystem: "_translate-filesystem-0",
							Path:       types.Path("/opt/file1"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "data",
									Opaque: ",file1",
								},
							},
							Mode:  types.FileMode(0664),
							User:  types.FileUser{Id: 500},
							Group: types.FileGroup{Id: 501},
						},
						{
							Filesystem: "_translate-filesystem-0",
							Path:       types.Path("/opt/file2"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "data",
									Opaque: ",file2",
								},
							},
							Mode:  types.FileMode(0644),
							User:  types.FileUser{Id: 502},
							Group: types.FileGroup{Id: 503},
						},
						{
							Filesystem: "_translate-filesystem-1",
							Path:       types.Path("/opt/file3"),
							Contents: types.FileContents{
								Source: types.Url{
									Scheme: "data",
									Opaque: ",file3",
								},
							},
							Mode:  types.FileMode(0400),
							User:  types.FileUser{Id: 1000},
							Group: types.FileGroup{Id: 1001},
						},
					},
				},
			}},
		},
		{
			in: in{v1.Config{
				Systemd: v1.Systemd{
					Units: []v1.SystemdUnit{
						{
							Name:     "test1.service",
							Enable:   true,
							Contents: "test1 contents",
							DropIns: []v1.SystemdUnitDropIn{
								{
									Name:     "conf1.conf",
									Contents: "conf1 contents",
								},
								{
									Name:     "conf2.conf",
									Contents: "conf2 contents",
								},
							},
						},
						{
							Name:     "test2.service",
							Mask:     true,
							Contents: "test2 contents",
						},
					},
				},
			}},
			out: out{config: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Systemd: types.Systemd{
					Units: []types.SystemdUnit{
						{
							Name:     "test1.service",
							Enable:   true,
							Contents: "test1 contents",
							DropIns: []types.SystemdUnitDropIn{
								{
									Name:     "conf1.conf",
									Contents: "conf1 contents",
								},
								{
									Name:     "conf2.conf",
									Contents: "conf2 contents",
								},
							},
						},
						{
							Name:     "test2.service",
							Mask:     true,
							Contents: "test2 contents",
						},
					},
				},
			}},
		},
		{
			in: in{v1.Config{
				Networkd: v1.Networkd{
					Units: []v1.NetworkdUnit{
						{
							Name:     "test1.network",
							Contents: "test1 contents",
						},
						{
							Name:     "test2.network",
							Contents: "test2 contents",
						},
					},
				},
			}},
			out: out{config: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Networkd: types.Networkd{
					Units: []types.NetworkdUnit{
						{
							Name:     "test1.network",
							Contents: "test1 contents",
						},
						{
							Name:     "test2.network",
							Contents: "test2 contents",
						},
					},
				},
			}},
		},
		{
			in: in{v1.Config{
				Passwd: v1.Passwd{
					Users: []v1.User{
						{
							Name:              "user 1",
							PasswordHash:      "password 1",
							SSHAuthorizedKeys: []string{"key1", "key2"},
						},
						{
							Name:              "user 2",
							PasswordHash:      "password 2",
							SSHAuthorizedKeys: []string{"key3", "key4"},
							Create: &v1.UserCreate{
								Uid:          func(i uint) *uint { return &i }(123),
								GECOS:        "gecos",
								Homedir:      "/home/user 2",
								NoCreateHome: true,
								PrimaryGroup: "wheel",
								Groups:       []string{"wheel", "plugdev"},
								NoUserGroup:  true,
								System:       true,
								NoLogInit:    true,
								Shell:        "/bin/zsh",
							},
						},
						{
							Name:              "user 3",
							PasswordHash:      "password 3",
							SSHAuthorizedKeys: []string{"key5", "key6"},
							Create:            &v1.UserCreate{},
						},
					},
					Groups: []v1.Group{
						{
							Name:         "group 1",
							Gid:          func(i uint) *uint { return &i }(1000),
							PasswordHash: "password 1",
							System:       true,
						},
						{
							Name:         "group 2",
							PasswordHash: "password 2",
						},
					},
				},
			}},
			out: out{config: types.Config{
				Ignition: types.Ignition{Version: types.IgnitionVersion{Major: 2}},
				Passwd: types.Passwd{
					Users: []types.User{
						{
							Name:              "user 1",
							PasswordHash:      "password 1",
							SSHAuthorizedKeys: []string{"key1", "key2"},
						},
						{
							Name:              "user 2",
							PasswordHash:      "password 2",
							SSHAuthorizedKeys: []string{"key3", "key4"},
							Create: &types.UserCreate{
								Uid:          func(i uint) *uint { return &i }(123),
								GECOS:        "gecos",
								Homedir:      "/home/user 2",
								NoCreateHome: true,
								PrimaryGroup: "wheel",
								Groups:       []string{"wheel", "plugdev"},
								NoUserGroup:  true,
								System:       true,
								NoLogInit:    true,
								Shell:        "/bin/zsh",
							},
						},
						{
							Name:              "user 3",
							PasswordHash:      "password 3",
							SSHAuthorizedKeys: []string{"key5", "key6"},
							Create:            &types.UserCreate{},
						},
					},
					Groups: []types.Group{
						{
							Name:         "group 1",
							Gid:          func(i uint) *uint { return &i }(1000),
							PasswordHash: "password 1",
							System:       true,
						},
						{
							Name:         "group 2",
							PasswordHash: "password 2",
						},
					},
				},
			}},
		},
	}

	for i, test := range tests {
		config, err := TranslateFromV1(test.in.config)
		if test.out.err != err {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
		if !reflect.DeepEqual(test.out.config, config) {
			t.Errorf("#%d: bad config: want %+v, got %+v", i, test.out.config, config)
		}
	}
}
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
	})
}
Example #7
0
func TranslateFromV1(old v1.Config) (types.Config, error) {
	config := types.Config{
		Ignition: types.Ignition{
			Version: types.IgnitionVersion{Major: 2},
		},
	}

	for _, oldDisk := range old.Storage.Disks {
		disk := types.Disk{
			Device:    types.Path(oldDisk.Device),
			WipeTable: oldDisk.WipeTable,
		}

		for _, oldPartition := range oldDisk.Partitions {
			disk.Partitions = append(disk.Partitions, types.Partition{
				Label:    types.PartitionLabel(oldPartition.Label),
				Number:   oldPartition.Number,
				Size:     types.PartitionDimension(oldPartition.Size),
				Start:    types.PartitionDimension(oldPartition.Start),
				TypeGUID: types.PartitionTypeGUID(oldPartition.TypeGUID),
			})
		}

		config.Storage.Disks = append(config.Storage.Disks, disk)
	}

	for _, oldArray := range old.Storage.Arrays {
		array := types.Raid{
			Name:   oldArray.Name,
			Level:  oldArray.Level,
			Spares: oldArray.Spares,
		}

		for _, oldDevice := range oldArray.Devices {
			array.Devices = append(array.Devices, types.Path(oldDevice))
		}

		config.Storage.Arrays = append(config.Storage.Arrays, array)
	}

	for i, oldFilesystem := range old.Storage.Filesystems {
		filesystem := types.Filesystem{
			Name: fmt.Sprintf("_translate-filesystem-%d", i),
			Mount: &types.FilesystemMount{
				Device: types.Path(oldFilesystem.Device),
				Format: types.FilesystemFormat(oldFilesystem.Format),
			},
		}

		if oldFilesystem.Create != nil {
			filesystem.Mount.Create = &types.FilesystemCreate{
				Force:   oldFilesystem.Create.Force,
				Options: types.MkfsOptions(oldFilesystem.Create.Options),
			}
		}

		config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)

		for _, oldFile := range oldFilesystem.Files {
			file := types.File{
				Filesystem: filesystem.Name,
				Path:       types.Path(oldFile.Path),
				Contents: types.FileContents{
					Source: types.Url{
						Scheme: "data",
						Opaque: "," + dataurl.EscapeString(oldFile.Contents),
					},
				},
				Mode:  types.FileMode(oldFile.Mode),
				User:  types.FileUser{Id: oldFile.Uid},
				Group: types.FileGroup{Id: oldFile.Gid},
			}

			config.Storage.Files = append(config.Storage.Files, file)
		}
	}

	for _, oldUnit := range old.Systemd.Units {
		unit := types.SystemdUnit{
			Name:     types.SystemdUnitName(oldUnit.Name),
			Enable:   oldUnit.Enable,
			Mask:     oldUnit.Mask,
			Contents: oldUnit.Contents,
		}

		for _, oldDropIn := range oldUnit.DropIns {
			unit.DropIns = append(unit.DropIns, types.SystemdUnitDropIn{
				Name:     types.SystemdUnitDropInName(oldDropIn.Name),
				Contents: oldDropIn.Contents,
			})
		}

		config.Systemd.Units = append(config.Systemd.Units, unit)
	}

	for _, oldUnit := range old.Networkd.Units {
		config.Networkd.Units = append(config.Networkd.Units, types.NetworkdUnit{
			Name:     types.NetworkdUnitName(oldUnit.Name),
			Contents: oldUnit.Contents,
		})
	}

	for _, oldUser := range old.Passwd.Users {
		user := types.User{
			Name:              oldUser.Name,
			PasswordHash:      oldUser.PasswordHash,
			SSHAuthorizedKeys: oldUser.SSHAuthorizedKeys,
		}

		if oldUser.Create != nil {
			user.Create = &types.UserCreate{
				Uid:          oldUser.Create.Uid,
				GECOS:        oldUser.Create.GECOS,
				Homedir:      oldUser.Create.Homedir,
				NoCreateHome: oldUser.Create.NoCreateHome,
				PrimaryGroup: oldUser.Create.PrimaryGroup,
				Groups:       oldUser.Create.Groups,
				NoUserGroup:  oldUser.Create.NoUserGroup,
				System:       oldUser.Create.System,
				NoLogInit:    oldUser.Create.NoLogInit,
				Shell:        oldUser.Create.Shell,
			}
		}

		config.Passwd.Users = append(config.Passwd.Users, user)
	}

	for _, oldGroup := range old.Passwd.Groups {
		config.Passwd.Groups = append(config.Passwd.Groups, types.Group{
			Name:         oldGroup.Name,
			Gid:          oldGroup.Gid,
			PasswordHash: oldGroup.PasswordHash,
			System:       oldGroup.System,
		})
	}

	return config, nil
}