Esempio n. 1
0
func Parse(data []byte) (types.Config, report.Report) {
	var cfg types.Config
	var r report.Report

	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return types.Config{}, report.ReportFromError(err, report.EntryError)
	}

	nodes := yaml.UnmarshalToNode(data)
	if nodes == nil {
		r.Add(report.Entry{
			Kind:    report.EntryWarning,
			Message: "Configuration is empty",
		})
		r.Merge(validate.ValidateWithoutSource(reflect.ValueOf(cfg)))
	} else {
		root, err := fromYamlDocumentNode(*nodes)
		if err != nil {
			return types.Config{}, report.ReportFromError(err, report.EntryError)
		}

		r.Merge(validate.Validate(reflect.ValueOf(cfg), root, nil))
	}

	if r.IsFatal() {
		return types.Config{}, r
	}
	return cfg, r
}
Esempio n. 2
0
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{}
}
Esempio n. 3
0
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{}
}
Esempio n. 4
0
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{}
}
Esempio n. 5
0
// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
	switch majorVersion(rawConfig) {
	case 1:
		config, err := ParseFromV1(rawConfig)
		if err != nil {
			return types.Config{}, report.ReportFromError(err, report.EntryError), err
		}

		return config, report.ReportFromError(ErrDeprecated, report.EntryDeprecated), nil
	default:
		return ParseFromLatest(rawConfig)
	}
}
Esempio n. 6
0
File: hash.go Progetto: coreos/fuze
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{}
}
Esempio n. 7
0
func TestFilesystemFormatValidate(t *testing.T) {
	type in struct {
		format FilesystemFormat
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{format: FilesystemFormat("ext4")},
			out: out{},
		},
		{
			in:  in{format: FilesystemFormat("btrfs")},
			out: out{},
		},
		{
			in:  in{format: FilesystemFormat("")},
			out: out{err: ErrFilesystemInvalidFormat},
		},
	}

	for i, test := range tests {
		err := test.in.format.Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}
Esempio n. 8
0
File: raid.go Progetto: coreos/fuze
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{}
}
Esempio n. 9
0
func TestSystemdUnitNameValidate(t *testing.T) {
	type in struct {
		unit SystemdUnitName
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{unit: SystemdUnitName("test.service")},
			out: out{err: nil},
		},
		{
			in:  in{unit: SystemdUnitName("test.socket")},
			out: out{err: nil},
		},
		{
			in:  in{unit: SystemdUnitName("test.blah")},
			out: out{err: errors.New("invalid systemd unit extension")},
		},
	}

	for i, test := range tests {
		err := test.in.unit.Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}
Esempio n. 10
0
File: unit.go Progetto: coreos/fuze
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)
	}
}
Esempio n. 11
0
File: unit.go Progetto: coreos/fuze
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)
	}
}
Esempio n. 12
0
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)
	}
}
Esempio n. 13
0
func (c Compression) Validate() report.Report {
	switch c {
	case "", "gzip":
	default:
		return report.ReportFromError(ErrCompressionInvalid, report.EntryError)
	}
	return report.Report{}
}
Esempio n. 14
0
func (f FilesystemFormat) Validate() report.Report {
	switch f {
	case "ext4", "btrfs", "xfs":
		return report.Report{}
	default:
		return report.ReportFromError(ErrFilesystemInvalidFormat, report.EntryError)
	}
}
Esempio n. 15
0
File: unit.go Progetto: coreos/fuze
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)
	}
}
Esempio n. 16
0
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{}
}
Esempio n. 17
0
File: url.go Progetto: coreos/fuze
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", "data":
		return report.Report{}
	}

	return report.ReportFromError(ErrInvalidScheme, report.EntryError)
}
Esempio n. 18
0
func TestURLValidate(t *testing.T) {
	type in struct {
		u string
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{u: ""},
			out: out{},
		},
		{
			in:  in{u: "http://example.com"},
			out: out{},
		},
		{
			in:  in{u: "https://example.com"},
			out: out{},
		},
		{
			in:  in{u: "oem:///foobar"},
			out: out{},
		},
		{
			in:  in{u: "data:,example%20file%0A"},
			out: out{},
		},
		{
			in:  in{u: "bad://"},
			out: out{err: ErrInvalidScheme},
		},
	}

	for i, test := range tests {
		u, err := url.Parse(test.in.u)
		if err != nil {
			t.Errorf("URL failed to parse. This is an error with the test")
		}
		r := Url(*u).Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), r) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, r)
		}
	}
}
Esempio n. 19
0
func TestFileValidate(t *testing.T) {
	type in struct {
		mode FileMode
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{mode: FileMode(0)},
			out: out{},
		},
		{
			in:  in{mode: FileMode(0644)},
			out: out{},
		},
		{
			in:  in{mode: FileMode(01755)},
			out: out{},
		},
		{
			in:  in{mode: FileMode(07777)},
			out: out{},
		},
		{
			in:  in{mode: FileMode(010000)},
			out: out{err: ErrFileIllegalMode},
		},
	}

	for i, test := range tests {
		err := test.in.mode.Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}
Esempio n. 20
0
func TestPathValidate(t *testing.T) {
	type in struct {
		device Path
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{device: Path("/good/path")},
			out: out{},
		},
		{
			in:  in{device: Path("/name")},
			out: out{},
		},
		{
			in:  in{device: Path("/this/is/a/fairly/long/path/to/a/device.")},
			out: out{},
		},
		{
			in:  in{device: Path("/this one has spaces")},
			out: out{},
		},
		{
			in:  in{device: Path("relative/path")},
			out: out{err: ErrPathRelative},
		},
	}

	for i, test := range tests {
		err := test.in.device.Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}
Esempio n. 21
0
func TestHashValidate(t *testing.T) {
	type in struct {
		hash Hash
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{hash: Hash{}},
			out: out{err: ErrHashUnrecognized},
		},
		{
			in:  in{hash: Hash{Function: "xor"}},
			out: out{err: ErrHashUnrecognized},
		},
		{
			in:  in{hash: Hash{Function: "sha512", Sum: "123"}},
			out: out{err: ErrHashWrongSize},
		},
		{
			in:  in{hash: Hash{Function: "sha512", Sum: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}},
			out: out{},
		},
	}

	for i, test := range tests {
		err := test.in.hash.Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}
Esempio n. 22
0
func TestFilesystemValidate(t *testing.T) {
	type in struct {
		filesystem Filesystem
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{filesystem: Filesystem{Mount: &FilesystemMount{Device: "/foo", Format: "ext4"}}},
			out: out{},
		},
		{
			in:  in{filesystem: Filesystem{Path: func(p Path) *Path { return &p }("/mount")}},
			out: out{},
		},
		{
			in:  in{filesystem: Filesystem{Path: func(p Path) *Path { return &p }("/mount"), Mount: &FilesystemMount{Device: "/foo", Format: "ext4"}}},
			out: out{err: ErrFilesystemMountAndPath},
		},
		{
			in:  in{filesystem: Filesystem{}},
			out: out{err: ErrFilesystemNoMountPath},
		},
	}

	for i, test := range tests {
		err := test.in.filesystem.Validate()
		if !reflect.DeepEqual(report.ReportFromError(test.out.err, report.EntryError), err) {
			t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err)
		}
	}
}
Esempio n. 23
0
File: path.go Progetto: coreos/fuze
func (p Path) Validate() report.Report {
	if !filepath.IsAbs(string(p)) {
		return report.ReportFromError(ErrPathRelative, report.EntryError)
	}
	return report.Report{}
}
Esempio n. 24
0
File: file.go Progetto: coreos/fuze
func (f File) Validate() report.Report {
	if f.Filesystem == "" {
		return report.ReportFromError(ErrNoFilesystem, report.EntryError)
	}
	return report.Report{}
}
Esempio n. 25
0
File: file.go Progetto: coreos/fuze
func (m FileMode) Validate() report.Report {
	if (m &^ 07777) != 0 {
		return report.ReportFromError(ErrFileIllegalMode, report.EntryError)
	}
	return report.Report{}
}
Esempio n. 26
0
func ConvertAs2_0_0(in types.Config) (ignTypes.Config, report.Report) {
	out := ignTypes.Config{
		Ignition: ignTypes.Ignition{
			Version: ignTypes.IgnitionVersion{Major: 2, Minor: 0},
		},
	}

	for _, ref := range in.Ignition.Config.Append {
		newRef, err := convertConfigReference(ref)
		if err != nil {
			return ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
		}
		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 ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
		}
		out.Ignition.Config.Replace = &newRef
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

		if file.Contents.Inline != "" {
			newFile.Contents = ignTypes.FileContents{
				Source: ignTypes.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 ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
			}

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

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

		newFile.Contents.Compression = ignTypes.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 := ignTypes.SystemdUnit{
			Name:     ignTypes.SystemdUnitName(unit.Name),
			Enable:   unit.Enable,
			Mask:     unit.Mask,
			Contents: unit.Contents,
		}

		for _, dropIn := range unit.DropIns {
			newUnit.DropIns = append(newUnit.DropIns, ignTypes.SystemdUnitDropIn{
				Name:     ignTypes.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, ignTypes.NetworkdUnit{
			Name:     ignTypes.NetworkdUnitName(unit.Name),
			Contents: unit.Contents,
		})
	}

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

		if user.Create != nil {
			newUser.Create = &ignTypes.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, ignTypes.Group{
			Name:         group.Name,
			Gid:          group.Gid,
			PasswordHash: group.PasswordHash,
			System:       group.System,
		})
	}

	r := validate.ValidateWithoutSource(reflect.ValueOf(out))
	if r.IsFatal() {
		return ignTypes.Config{}, r
	}

	return out, r
}
Esempio n. 27
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)
		}
	}
}
Esempio n. 28
0
func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) {
	if isEmpty(rawConfig) {
		return types.Config{}, report.Report{}, ErrEmpty
	} else if isCloudConfig(rawConfig) {
		return types.Config{}, report.Report{}, ErrCloudConfig
	} else if isScript(rawConfig) {
		return types.Config{}, report.Report{}, ErrScript
	}

	var err error
	var config types.Config

	// These errors are fatal and the config should not be further validated
	if err = json.Unmarshal(rawConfig, &config); err == nil {
		versionReport := config.Ignition.Version.Validate()
		if versionReport.IsFatal() {
			return types.Config{}, versionReport, ErrInvalid
		}
	}

	// Handle json syntax and type errors first, since they are fatal but have offset info
	if serr, ok := err.(*json.SyntaxError); ok {
		line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset)
		return types.Config{},
			report.Report{
				Entries: []report.Entry{{
					Kind:      report.EntryError,
					Message:   serr.Error(),
					Line:      line,
					Column:    col,
					Highlight: highlight,
				}},
			},
			ErrInvalid
	}

	if terr, ok := err.(*json.UnmarshalTypeError); ok {
		line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset)
		return types.Config{},
			report.Report{
				Entries: []report.Entry{{
					Kind:      report.EntryError,
					Message:   terr.Error(),
					Line:      line,
					Column:    col,
					Highlight: highlight,
				}},
			},
			ErrInvalid
	}

	// Handle other fatal errors (i.e. invalid version)
	if err != nil {
		return types.Config{}, report.ReportFromError(err, report.EntryError), err
	}

	// Unmarshal again to a json.Node to get offset information for building a report
	var ast json.Node
	var r report.Report
	configValue := reflect.ValueOf(config)
	if err := json.Unmarshal(rawConfig, &ast); err != nil {
		r.Add(report.Entry{
			Kind:    report.EntryWarning,
			Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.",
		})
		r.Merge(validate.ValidateWithoutSource(configValue))
	} else {
		r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig)))
	}

	if r.IsFatal() {
		return types.Config{}, r, ErrInvalid
	}

	return config, r, nil
}
Esempio n. 29
0
func TestValidate(t *testing.T) {
	type in struct {
		cfg Config
	}
	type out struct {
		err error
	}

	tests := []struct {
		in  in
		out out
	}{
		{
			in:  in{cfg: Config{Ignition: Ignition{Version: IgnitionVersion{Major: 2}}}},
			out: out{},
		},
		{
			in:  in{cfg: Config{}},
			out: out{err: ErrOldVersion},
		},
		{
			in: in{cfg: Config{
				Ignition: Ignition{
					Version: IgnitionVersion{Major: 2},
					Config: IgnitionConfig{
						Replace: &ConfigReference{
							Verification: Verification{
								Hash: &Hash{Function: "foobar"},
							},
						},
					},
				},
			}},
			out: out{errors.New("unrecognized hash function")},
		},
		{
			in: in{cfg: Config{
				Ignition: Ignition{Version: IgnitionVersion{Major: 2}},
				Storage: Storage{
					Filesystems: []Filesystem{
						{
							Name: "filesystem1",
							Mount: &FilesystemMount{
								Device: Path("/dev/disk/by-partlabel/ROOT"),
								Format: FilesystemFormat("btrfs"),
							},
						},
					},
				},
			}},
			out: out{},
		},
		{
			in: in{cfg: Config{
				Ignition: Ignition{Version: IgnitionVersion{Major: 2}},
				Storage: Storage{
					Filesystems: []Filesystem{
						{
							Name: "filesystem1",
							Path: func(p Path) *Path { return &p }("/sysroot"),
						},
					},
				},
			}},
			out: out{},
		},
		{
			in: in{cfg: Config{
				Ignition: Ignition{Version: IgnitionVersion{Major: 2}},
				Systemd:  Systemd{Units: []SystemdUnit{{Name: "foo.bar"}}},
			}},
			out: out{err: errors.New("invalid systemd unit extension")},
		},
	}

	for i, test := range tests {
		r := ValidateWithoutSource(reflect.ValueOf(test.in.cfg))
		expectedReport := report.ReportFromError(test.out.err, report.EntryError)
		if !reflect.DeepEqual(expectedReport, r) {
			t.Errorf("#%d: bad error: want %v, got %v", i, expectedReport, r)
		}
	}
}