From db86f5606e2541f2aaecaf3ceb3012fd634119fa Mon Sep 17 00:00:00 2001 From: Dalton Hubble Date: Mon, 4 Jan 2016 14:02:12 -0800 Subject: [PATCH] Godeps: Add coreos/ignition to godeps --- .../coreos/ignition/src/config/cloudinit.go | 36 +++ .../coreos/ignition/src/config/config.go | 83 +++++ .../coreos/ignition/src/config/config_test.go | 87 +++++ .../coreos/ignition/src/config/devicepath.go | 50 +++ .../coreos/ignition/src/config/disk.go | 167 ++++++++++ .../coreos/ignition/src/config/file.go | 63 ++++ .../coreos/ignition/src/config/file_test.go | 139 ++++++++ .../coreos/ignition/src/config/filesystem.go | 96 ++++++ .../ignition/src/config/filesystem_test.go | 302 ++++++++++++++++++ .../coreos/ignition/src/config/group.go | 22 ++ .../coreos/ignition/src/config/networkd.go | 19 ++ .../coreos/ignition/src/config/partition.go | 137 ++++++++ .../coreos/ignition/src/config/passwd.go | 20 ++ .../coreos/ignition/src/config/raid.go | 65 ++++ .../coreos/ignition/src/config/storage.go | 21 ++ .../coreos/ignition/src/config/systemd.go | 19 ++ .../coreos/ignition/src/config/unit.go | 135 ++++++++ .../coreos/ignition/src/config/unit_test.go | 204 ++++++++++++ .../coreos/ignition/src/config/user.go | 35 ++ .../github.com/alecthomas/units/COPYING | 19 ++ .../github.com/alecthomas/units/README.md | 11 + .../github.com/alecthomas/units/bytes.go | 83 +++++ .../github.com/alecthomas/units/bytes_test.go | 49 +++ .../github.com/alecthomas/units/doc.go | 13 + .../github.com/alecthomas/units/si.go | 26 ++ .../github.com/alecthomas/units/util.go | 138 ++++++++ .../camlistore/pkg/errorutil/highlight.go | 58 ++++ 27 files changed, 2097 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/cloudinit.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/config.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/config_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/devicepath.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/disk.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/file.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/file_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/group.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/networkd.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/partition.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/passwd.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/raid.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/storage.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/systemd.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/src/config/user.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/COPYING create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/README.md create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/doc.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/si.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/util.go create mode 100644 Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/camlistore/camlistore/pkg/errorutil/highlight.go diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/cloudinit.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/cloudinit.go new file mode 100644 index 00000000..9937eaa9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/cloudinit.go @@ -0,0 +1,36 @@ +// 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. + +// These functions are copied from github.com/coreos/coreos-cloudinit/config. + +package config + +import ( + "strings" + "unicode" +) + +func isCloudConfig(userdata []byte) bool { + header := strings.SplitN(string(userdata), "\n", 2)[0] + + // Trim trailing whitespaces + header = strings.TrimRightFunc(header, unicode.IsSpace) + + return (header == "#cloud-config") +} + +func isScript(userdata []byte) bool { + header := strings.SplitN(string(userdata), "\n", 2)[0] + return strings.HasPrefix(header, "#!") +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/config.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/config.go new file mode 100644 index 00000000..d3a8a8e1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/config.go @@ -0,0 +1,83 @@ +// 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 config + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + + "github.com/coreos/ignition/third_party/github.com/camlistore/camlistore/pkg/errorutil" +) + +type Config struct { + Version int `json:"ignitionVersion" yaml:"ignition_version"` + Storage Storage `json:"storage,omitempty" yaml:"storage"` + Systemd Systemd `json:"systemd,omitempty" yaml:"systemd"` + Networkd Networkd `json:"networkd,omitempty" yaml:"networkd"` + Passwd Passwd `json:"passwd,omitempty" yaml:"passwd"` +} + +const ( + Version = 1 +) + +var ( + ErrVersion = errors.New("incorrect config version") + ErrCloudConfig = errors.New("not a config (found coreos-cloudconfig)") + ErrScript = errors.New("not a config (found coreos-cloudinit script)") + ErrEmpty = errors.New("not a config (empty)") +) + +func Parse(config []byte) (cfg Config, err error) { + if err = json.Unmarshal(config, &cfg); err == nil { + if cfg.Version != Version { + err = ErrVersion + } + } else if isCloudConfig(decompressIfGzipped(config)) { + err = ErrCloudConfig + } else if isScript(decompressIfGzipped(config)) { + err = ErrScript + } else if isEmpty(config) { + err = ErrEmpty + } + if serr, ok := err.(*json.SyntaxError); ok { + line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(config), serr.Offset) + err = fmt.Errorf("error at line %d, column %d\n%s%v", line, col, highlight, err) + } + + return +} + +func isEmpty(userdata []byte) bool { + return len(userdata) == 0 +} + +func decompressIfGzipped(data []byte) []byte { + if reader, err := gzip.NewReader(bytes.NewReader(data)); err == nil { + uncompressedData, err := ioutil.ReadAll(reader) + reader.Close() + if err == nil { + return uncompressedData + } else { + return data + } + } else { + return data + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/config_test.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/config_test.go new file mode 100644 index 00000000..c0bf650b --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/config_test.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 config + +import ( + "reflect" + "testing" +) + +func TestParse(t *testing.T) { + type in struct { + config []byte + } + type out struct { + config Config + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{config: []byte(`{"ignitionVersion": 1}`)}, + out: out{config: Config{Version: 1}}, + }, + { + in: in{config: []byte(`{}`)}, + out: out{err: ErrVersion}, + }, + { + in: in{config: []byte{}}, + out: out{err: ErrEmpty}, + }, + { + in: in{config: []byte("#cloud-config")}, + out: out{err: ErrCloudConfig}, + }, + { + in: in{config: []byte("#cloud-config ")}, + out: out{err: ErrCloudConfig}, + }, + { + in: in{config: []byte("#cloud-config\n\r")}, + out: out{err: ErrCloudConfig}, + }, + { + in: in{config: []byte{0x1f, 0x8b, 0x08, 0x00, 0x03, 0xd6, 0x79, 0x56, + 0x00, 0x03, 0x53, 0x4e, 0xce, 0xc9, 0x2f, 0x4d, 0xd1, 0x4d, 0xce, + 0xcf, 0x4b, 0xcb, 0x4c, 0xe7, 0x02, 0x00, 0x05, 0x56, 0xb3, 0xb8, + 0x0e, 0x00, 0x00, 0x00}}, + out: out{err: ErrCloudConfig}, + }, + { + in: in{config: []byte("#!/bin/sh")}, + out: out{err: ErrScript}, + }, + { + in: in{config: []byte{0x1f, 0x8b, 0x08, 0x00, 0x48, 0xda, 0x79, 0x56, + 0x00, 0x03, 0x53, 0x56, 0xd4, 0x4f, 0xca, 0xcc, 0xd3, 0x2f, 0xce, + 0xe0, 0x02, 0x00, 0x1d, 0x9d, 0xfb, 0x04, 0x0a, 0x00, 0x00, 0x00}}, + out: out{err: ErrScript}, + }, + } + + for i, test := range tests { + config, err := Parse(test.in.config) + if !reflect.DeepEqual(test.out.config, config) { + t.Errorf("#%d: bad config: want %+v, got %+v", i, test.out.config, config) + } + if test.out.err != err { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/devicepath.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/devicepath.go new file mode 100644 index 00000000..8f932b87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/devicepath.go @@ -0,0 +1,50 @@ +// 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 config + +import ( + "encoding/json" + "path/filepath" +) + +type DevicePath string + +func (d *DevicePath) UnmarshalYAML(unmarshal func(interface{}) error) error { + return d.unmarshal(unmarshal) +} + +func (d *DevicePath) UnmarshalJSON(data []byte) error { + return d.unmarshal(func(td interface{}) error { + return json.Unmarshal(data, td) + }) +} + +type devicePath DevicePath + +func (d *DevicePath) unmarshal(unmarshal func(interface{}) error) error { + td := devicePath(*d) + if err := unmarshal(&td); err != nil { + return err + } + *d = DevicePath(td) + return d.assertValid() +} + +func (d DevicePath) assertValid() error { + if !filepath.IsAbs(string(d)) { + return ErrFilesystemRelativePath + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/disk.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/disk.go new file mode 100644 index 00000000..c2c0e09b --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/disk.go @@ -0,0 +1,167 @@ +// 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 config + +import ( + "encoding/json" + "fmt" +) + +type Disk struct { + Device DevicePath `json:"device,omitempty" yaml:"device"` + WipeTable bool `json:"wipeTable,omitempty" yaml:"wipe_table"` + Partitions []Partition `json:"partitions,omitempty" yaml:"partitions"` +} + +func (n *Disk) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := n.unmarshal(unmarshal); err != nil { + return err + } + if err := n.preparePartitions(); err != nil { + return err + } + return n.assertValid() +} + +func (n *Disk) UnmarshalJSON(data []byte) error { + err := n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) + if err != nil { + return err + } + return n.assertValid() +} + +type disk Disk + +func (n *Disk) unmarshal(unmarshal func(interface{}) error) error { + tn := disk(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = Disk(tn) + return nil +} + +func (n Disk) assertValid() error { + // This applies to YAML (post-prepare) and JSON unmarshals equally: + if len(n.Device) == 0 { + return fmt.Errorf("disk device is required") + } + if n.partitionNumbersCollide() { + return fmt.Errorf("disk %q: partition numbers collide", n.Device) + } + if n.partitionsOverlap() { + return fmt.Errorf("disk %q: partitions overlap", n.Device) + } + if n.partitionsMisaligned() { + return fmt.Errorf("disk %q: partitions misaligned", n.Device) + } + // Disks which get to this point will likely succeed in sgdisk + return nil +} + +// partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique. +func (n Disk) partitionNumbersCollide() bool { + m := map[int][]Partition{} + for _, p := range n.Partitions { + m[p.Number] = append(m[p.Number], p) + } + for _, n := range m { + if len(n) > 1 { + // TODO(vc): return information describing the collision for logging + return true + } + } + return false +} + +// end returns the last sector of a partition. +func (p Partition) end() PartitionDimension { + if p.Size == 0 { + // a size of 0 means "fill available", just return the start as the end for those. + return p.Start + } + return p.Start + p.Size - 1 +} + +// partitionsOverlap returns true if any explicitly dimensioned partitions overlap +func (n Disk) partitionsOverlap() bool { + for _, p := range n.Partitions { + // Starts of 0 are placed by sgdisk into the "largest available block" at that time. + // We aren't going to check those for overlap since we don't have the disk geometry. + if p.Start == 0 { + continue + } + + for _, o := range n.Partitions { + if p == o || o.Start == 0 { + continue + } + + // is p.Start within o? + if p.Start >= o.Start && p.Start <= o.end() { + return true + } + + // is p.end() within o? + if p.end() >= o.Start && p.end() <= o.end() { + return true + } + + // do p.Start and p.end() straddle o? + if p.Start < o.Start && p.end() > o.end() { + return true + } + } + } + return false +} + +// partitionsMisaligned returns true if any of the partitions don't start on a 2048-sector (1MiB) boundary. +func (n Disk) partitionsMisaligned() bool { + for _, p := range n.Partitions { + if (p.Start & (2048 - 1)) != 0 { + return true + } + } + return false +} + +// preparePartitions performs some checks and potentially adjusts the partitions for alignment. +// This is only invoked when unmarshalling YAML, since there we parse human-friendly units. +func (n *Disk) preparePartitions() error { + // On the YAML side we accept human-friendly units which may require massaging. + + // align partition starts + for i := range n.Partitions { + // skip automatically placed partitions + if n.Partitions[i].Start == 0 { + continue + } + + // keep partitions out of the first 2048 sectors + if n.Partitions[i].Start < 2048 { + n.Partitions[i].Start = 2048 + } + + // toss the bottom 11 bits + n.Partitions[i].Start &= ^PartitionDimension(2048 - 1) + } + + // TODO(vc): may be interesting to do something about potentially overlapping partitions + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/file.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/file.go new file mode 100644 index 00000000..8d785e29 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/file.go @@ -0,0 +1,63 @@ +// 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 config + +import ( + "encoding/json" + "errors" + "os" +) + +var ( + ErrFileIllegalMode = errors.New("illegal file mode") +) + +type FileMode os.FileMode + +type File struct { + Path string `json:"path,omitempty" yaml:"path"` + Contents string `json:"contents,omitempty" yaml:"contents"` + Mode FileMode `json:"mode,omitempty" yaml:"mode"` + Uid int `json:"uid,omitempty" yaml:"uid"` + Gid int `json:"gid,omitempty" yaml:"gid"` +} + +func (m *FileMode) UnmarshalYAML(unmarshal func(interface{}) error) error { + return m.unmarshal(unmarshal) +} + +func (m *FileMode) UnmarshalJSON(data []byte) error { + return m.unmarshal(func(tm interface{}) error { + return json.Unmarshal(data, tm) + }) +} + +type fileMode FileMode + +func (m *FileMode) unmarshal(unmarshal func(interface{}) error) error { + tm := fileMode(*m) + if err := unmarshal(&tm); err != nil { + return err + } + *m = FileMode(tm) + return m.assertValid() +} + +func (m FileMode) assertValid() error { + if (m &^ 07777) != 0 { + return ErrFileIllegalMode + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/file_test.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/file_test.go new file mode 100644 index 00000000..74d7e1f3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/file_test.go @@ -0,0 +1,139 @@ +// 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 config + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/coreos/ignition/third_party/github.com/go-yaml/yaml" +) + +func TestFileModeUnmarshalJSON(t *testing.T) { + type in struct { + data string + } + type out struct { + mode FileMode + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `420`}, + out: out{mode: FileMode(420)}, + }, + { + in: in{data: `9999`}, + out: out{mode: FileMode(9999), err: ErrFileIllegalMode}, + }, + } + + for i, test := range tests { + var mode FileMode + err := json.Unmarshal([]byte(test.in.data), &mode) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.mode, mode) { + t.Errorf("#%d: bad mode: want %#o, got %#o", i, test.out.mode, mode) + } + } +} + +func TestFileModeUnmarshalYAML(t *testing.T) { + type in struct { + data string + } + type out struct { + mode FileMode + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `0644`}, + out: out{mode: FileMode(0644)}, + }, + { + in: in{data: `0420`}, + out: out{mode: FileMode(0420)}, + }, + { + in: in{data: `017777`}, + out: out{mode: FileMode(017777), err: ErrFileIllegalMode}, + }, + } + + for i, test := range tests { + var mode FileMode + err := yaml.Unmarshal([]byte(test.in.data), &mode) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.mode, mode) { + t.Errorf("#%d: bad mode: want %#o, got %#o", i, test.out.mode, mode) + } + } +} + +func TestFileAssertValid(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.assertValid() + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem.go new file mode 100644 index 00000000..f13426bd --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem.go @@ -0,0 +1,96 @@ +// 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 config + +import ( + "encoding/json" + "errors" +) + +var ( + ErrFilesystemRelativePath = errors.New("device path not absolute") + ErrFilesystemInvalidFormat = errors.New("invalid filesystem format") +) + +type Filesystem struct { + Device DevicePath `json:"device,omitempty" yaml:"device"` + Format FilesystemFormat `json:"format,omitempty" yaml:"format"` + Create *FilesystemCreate `json:"create,omitempty" yaml:"create"` + Files []File `json:"files,omitempty" yaml:"files"` +} + +type FilesystemCreate struct { + Force bool `json:"force,omitempty" yaml:"force"` + Options MkfsOptions `json:"options,omitempty" yaml:"options"` +} + +type FilesystemFormat string + +func (f *FilesystemFormat) UnmarshalYAML(unmarshal func(interface{}) error) error { + return f.unmarshal(unmarshal) +} + +func (f *FilesystemFormat) UnmarshalJSON(data []byte) error { + return f.unmarshal(func(tf interface{}) error { + return json.Unmarshal(data, tf) + }) +} + +type filesystemFormat FilesystemFormat + +func (f *FilesystemFormat) unmarshal(unmarshal func(interface{}) error) error { + tf := filesystemFormat(*f) + if err := unmarshal(&tf); err != nil { + return err + } + *f = FilesystemFormat(tf) + return f.assertValid() +} + +func (f FilesystemFormat) assertValid() error { + switch f { + case "ext4", "btrfs", "xfs": + return nil + default: + return ErrFilesystemInvalidFormat + } +} + +type MkfsOptions []string + +func (o *MkfsOptions) UnmarshalYAML(unmarshal func(interface{}) error) error { + return o.unmarshal(unmarshal) +} + +func (o *MkfsOptions) UnmarshalJSON(data []byte) error { + return o.unmarshal(func(to interface{}) error { + return json.Unmarshal(data, to) + }) +} + +type mkfsOptions MkfsOptions + +func (o *MkfsOptions) unmarshal(unmarshal func(interface{}) error) error { + to := mkfsOptions(*o) + if err := unmarshal(&to); err != nil { + return err + } + *o = MkfsOptions(to) + return o.assertValid() +} + +func (o MkfsOptions) assertValid() error { + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem_test.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem_test.go new file mode 100644 index 00000000..10ec8833 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/filesystem_test.go @@ -0,0 +1,302 @@ +// 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 config + +import ( + "encoding/json" + "errors" + "reflect" + "testing" + + "github.com/coreos/ignition/third_party/github.com/go-yaml/yaml" +) + +func TestDevicePathUnmarshalJSON(t *testing.T) { + type in struct { + data string + } + type out struct { + device DevicePath + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"/path"`}, + out: out{device: DevicePath("/path")}, + }, + { + in: in{data: `"bad"`}, + out: out{device: DevicePath("bad"), err: errors.New("device path not absolute")}, + }, + } + + for i, test := range tests { + var device DevicePath + err := json.Unmarshal([]byte(test.in.data), &device) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.device, device) { + t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.device, device) + } + } +} + +func TestDevicePathUnmarshalYAML(t *testing.T) { + type in struct { + data string + } + type out struct { + device DevicePath + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"/path"`}, + out: out{device: DevicePath("/path")}, + }, + { + in: in{data: `"bad"`}, + out: out{device: DevicePath("bad"), err: errors.New("device path not absolute")}, + }, + } + + for i, test := range tests { + var device DevicePath + err := yaml.Unmarshal([]byte(test.in.data), &device) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.device, device) { + t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.device, device) + } + } +} + +func TestDevicePathAssertValid(t *testing.T) { + type in struct { + device DevicePath + } + type out struct { + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{device: DevicePath("/good/path")}, + out: out{}, + }, + { + in: in{device: DevicePath("/name")}, + out: out{}, + }, + { + in: in{device: DevicePath("/this/is/a/fairly/long/path/to/a/device.")}, + out: out{}, + }, + { + in: in{device: DevicePath("/this one has spaces")}, + out: out{}, + }, + { + in: in{device: DevicePath("relative/path")}, + out: out{err: errors.New("device path not absolute")}, + }, + } + + for i, test := range tests { + err := test.in.device.assertValid() + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + } +} + +func TestFilesystemFormatUnmarshalJSON(t *testing.T) { + type in struct { + data string + } + type out struct { + format FilesystemFormat + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"ext4"`}, + out: out{format: FilesystemFormat("ext4")}, + }, + { + in: in{data: `"bad"`}, + out: out{format: FilesystemFormat("bad"), err: errors.New("invalid filesystem format")}, + }, + } + + for i, test := range tests { + var format FilesystemFormat + err := json.Unmarshal([]byte(test.in.data), &format) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.format, format) { + t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.format, format) + } + } +} + +func TestFilesystemFormatUnmarshalYAML(t *testing.T) { + type in struct { + data string + } + type out struct { + format FilesystemFormat + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"ext4"`}, + out: out{format: FilesystemFormat("ext4")}, + }, + { + in: in{data: `"bad"`}, + out: out{format: FilesystemFormat("bad"), err: errors.New("invalid filesystem format")}, + }, + } + + for i, test := range tests { + var format FilesystemFormat + err := yaml.Unmarshal([]byte(test.in.data), &format) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.format, format) { + t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.format, format) + } + } +} + +func TestFilesystemFormatAssertValid(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: errors.New("invalid filesystem format")}, + }, + } + + for i, test := range tests { + err := test.in.format.assertValid() + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + } +} + +func TestMkfsOptionsUnmarshalJSON(t *testing.T) { + type in struct { + data string + } + type out struct { + options MkfsOptions + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `["--label=ROOT"]`}, + out: out{options: MkfsOptions([]string{"--label=ROOT"})}, + }, + } + + for i, test := range tests { + var options MkfsOptions + err := json.Unmarshal([]byte(test.in.data), &options) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.options, options) { + t.Errorf("#%d: bad format: want %#v, got %#v", i, test.out.options, options) + } + } +} + +func TestMkfsOptionsUnmarshalYAML(t *testing.T) { + type in struct { + data string + } + type out struct { + options MkfsOptions + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `["--label=ROOT"]`}, + out: out{options: MkfsOptions([]string{"--label=ROOT"})}, + }, + } + + for i, test := range tests { + var options MkfsOptions + err := yaml.Unmarshal([]byte(test.in.data), &options) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if !reflect.DeepEqual(test.out.options, options) { + t.Errorf("#%d: bad device: want %#v, got %#v", i, test.out.options, options) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/group.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/group.go new file mode 100644 index 00000000..d91c4ca9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/group.go @@ -0,0 +1,22 @@ +// 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 config + +type Group struct { + Name string `json:"name,omitempty" yaml:"name"` + Gid *uint `json:"gid,omitempty" yaml:"gid"` + PasswordHash string `json:"passwordHash,omitempty" yaml:"password_hash"` + System bool `json:"system,omitempty" yaml:"system"` +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/networkd.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/networkd.go new file mode 100644 index 00000000..b17b3179 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/networkd.go @@ -0,0 +1,19 @@ +// 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 config + +type Networkd struct { + Units []NetworkdUnit `json:"units,omitempty" yaml:"units"` +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/partition.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/partition.go new file mode 100644 index 00000000..e1a935ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/partition.go @@ -0,0 +1,137 @@ +// 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 config + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/coreos/ignition/third_party/github.com/alecthomas/units" +) + +type Partition struct { + Label PartitionLabel `json:"label,omitempty" yaml:"label"` + Number int `json:"number" yaml:"number"` + Size PartitionDimension `json:"size" yaml:"size"` + Start PartitionDimension `json:"start" yaml:"start"` + TypeGUID PartitionTypeGUID `json:"typeGuid,omitempty" yaml:"type_guid"` +} + +type PartitionLabel string + +func (n *PartitionLabel) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *PartitionLabel) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type partitionLabel PartitionLabel + +func (n *PartitionLabel) unmarshal(unmarshal func(interface{}) error) error { + tn := partitionLabel(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = PartitionLabel(tn) + return n.assertValid() +} + +func (n PartitionLabel) assertValid() error { + // http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries: + // 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units) + + // XXX(vc): note GPT calls it a name, we're using label for consistency + // with udev naming /dev/disk/by-partlabel/*. + if len(string(n)) > 36 { + return fmt.Errorf("partition labels may not exceed 36 characters") + } + return nil +} + +type PartitionDimension uint64 + +func (n *PartitionDimension) UnmarshalYAML(unmarshal func(interface{}) error) error { + // In YAML we allow human-readable dimensions like GiB/TiB etc. + var str string + if err := unmarshal(&str); err != nil { + return err + } + + b2b, err := units.ParseBase2Bytes(str) // TODO(vc): replace the units package + if err != nil { + return err + } + if b2b < 0 { + return fmt.Errorf("negative value inappropriate: %q", str) + } + + // Translate bytes into sectors + sectors := (b2b / 512) + if b2b%512 != 0 { + sectors++ + } + *n = PartitionDimension(uint64(sectors)) + return nil +} + +func (n *PartitionDimension) UnmarshalJSON(data []byte) error { + // In JSON we expect plain integral sectors. + // The YAML->JSON conversion is responsible for normalizing human units to sectors. + var pd uint64 + if err := json.Unmarshal(data, &pd); err != nil { + return err + } + *n = PartitionDimension(pd) + return nil +} + +type PartitionTypeGUID string + +func (d *PartitionTypeGUID) UnmarshalYAML(unmarshal func(interface{}) error) error { + return d.unmarshal(unmarshal) +} + +func (d *PartitionTypeGUID) UnmarshalJSON(data []byte) error { + return d.unmarshal(func(td interface{}) error { + return json.Unmarshal(data, td) + }) +} + +type partitionTypeGUID PartitionTypeGUID + +func (d *PartitionTypeGUID) unmarshal(unmarshal func(interface{}) error) error { + td := partitionTypeGUID(*d) + if err := unmarshal(&td); err != nil { + return err + } + *d = PartitionTypeGUID(td) + return d.assertValid() +} + +func (d PartitionTypeGUID) assertValid() error { + ok, err := regexp.MatchString("[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}", string(d)) + if err != nil { + return fmt.Errorf("error matching type-guid regexp: %v", err) + } + if !ok { + return fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/passwd.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/passwd.go new file mode 100644 index 00000000..7633aa34 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/passwd.go @@ -0,0 +1,20 @@ +// 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 config + +type Passwd struct { + Users []User `json:"users,omitempty" yaml:"users"` + Groups []Group `json:"groups,omitempty" yaml:"groups"` +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/raid.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/raid.go new file mode 100644 index 00000000..96fe2cf7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/raid.go @@ -0,0 +1,65 @@ +// 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 config + +import ( + "encoding/json" + "fmt" +) + +type Raid struct { + Name string `json:"name" yaml:"name"` + Level string `json:"level" yaml:"level"` + Devices []DevicePath `json:"devices,omitempty" yaml:"devices"` + Spares int `json:"spares,omitempty" yaml:"spares"` +} + +func (n *Raid) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *Raid) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type raid Raid + +func (n *Raid) unmarshal(unmarshal func(interface{}) error) error { + tn := raid(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = Raid(tn) + return n.assertValid() +} + +func (n Raid) assertValid() error { + switch n.Level { + case "linear", "raid0", "0", "stripe": + if n.Spares != 0 { + return fmt.Errorf("spares unsupported for %q arrays", n.Level) + } + case "raid1", "1", "mirror": + case "raid4", "4": + case "raid5", "5": + case "raid6", "6": + case "raid10", "10": + default: + return fmt.Errorf("unrecognized raid level: %q", n.Level) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/storage.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/storage.go new file mode 100644 index 00000000..17d3a7e7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/storage.go @@ -0,0 +1,21 @@ +// 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 config + +type Storage struct { + Disks []Disk `json:"disks,omitempty" yaml:"disks"` + Arrays []Raid `json:"raid,omitempty" yaml:"raid"` + Filesystems []Filesystem `json:"filesystems,omitempty" yaml:"filesystems"` +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/systemd.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/systemd.go new file mode 100644 index 00000000..f18c0397 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/systemd.go @@ -0,0 +1,19 @@ +// 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 config + +type Systemd struct { + Units []SystemdUnit `json:"units,omitempty" yaml:"units"` +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit.go new file mode 100644 index 00000000..c1b94b10 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit.go @@ -0,0 +1,135 @@ +// 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 config + +import ( + "encoding/json" + "errors" + "path/filepath" +) + +type SystemdUnit struct { + Name SystemdUnitName `json:"name,omitempty" yaml:"name"` + Enable bool `json:"enable,omitempty" yaml:"enable"` + Mask bool `json:"mask,omitempty" yaml:"mask"` + Contents string `json:"contents,omitempty" yaml:"contents"` + DropIns []SystemdUnitDropIn `json:"dropins,omitempty" yaml:"dropins"` +} + +type SystemdUnitDropIn struct { + Name SystemdUnitDropInName `json:"name,omitempty" yaml:"name"` + Contents string `json:"contents,omitempty" yaml:"contents"` +} + +type SystemdUnitName string + +func (n *SystemdUnitName) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *SystemdUnitName) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type systemdUnitName SystemdUnitName + +func (n *SystemdUnitName) unmarshal(unmarshal func(interface{}) error) error { + tn := systemdUnitName(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = SystemdUnitName(tn) + return n.assertValid() +} + +func (n SystemdUnitName) assertValid() error { + switch filepath.Ext(string(n)) { + case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope": + return nil + default: + return errors.New("invalid systemd unit extension") + } +} + +type SystemdUnitDropInName string + +func (n *SystemdUnitDropInName) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *SystemdUnitDropInName) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type systemdUnitDropInName SystemdUnitDropInName + +func (n *SystemdUnitDropInName) unmarshal(unmarshal func(interface{}) error) error { + tn := systemdUnitDropInName(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = SystemdUnitDropInName(tn) + return n.assertValid() +} + +func (n SystemdUnitDropInName) assertValid() error { + switch filepath.Ext(string(n)) { + case ".conf": + return nil + default: + return errors.New("invalid systemd unit drop-in extension") + } +} + +type NetworkdUnit struct { + Name NetworkdUnitName `json:"name,omitempty" yaml:"name"` + Contents string `json:"contents,omitempty" yaml:"contents"` +} + +type NetworkdUnitName string + +func (n *NetworkdUnitName) UnmarshalYAML(unmarshal func(interface{}) error) error { + return n.unmarshal(unmarshal) +} + +func (n *NetworkdUnitName) UnmarshalJSON(data []byte) error { + return n.unmarshal(func(tn interface{}) error { + return json.Unmarshal(data, tn) + }) +} + +type networkdUnitName NetworkdUnitName + +func (n *NetworkdUnitName) unmarshal(unmarshal func(interface{}) error) error { + tn := networkdUnitName(*n) + if err := unmarshal(&tn); err != nil { + return err + } + *n = NetworkdUnitName(tn) + return n.assertValid() +} + +func (n NetworkdUnitName) assertValid() error { + switch filepath.Ext(string(n)) { + case ".link", ".netdev", ".network": + return nil + default: + return errors.New("invalid networkd unit extension") + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit_test.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit_test.go new file mode 100644 index 00000000..c4260bd5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/unit_test.go @@ -0,0 +1,204 @@ +// 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 config + +import ( + "encoding/json" + "errors" + "reflect" + "testing" + + "github.com/coreos/ignition/third_party/github.com/go-yaml/yaml" +) + +func TestSystemdUnitNameUnmarshalJSON(t *testing.T) { + type in struct { + data string + } + type out struct { + unit SystemdUnitName + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"test.service"`}, + out: out{unit: SystemdUnitName("test.service")}, + }, + { + in: in{data: `"test.socket"`}, + out: out{unit: SystemdUnitName("test.socket")}, + }, + { + in: in{data: `"test.blah"`}, + out: out{err: errors.New("invalid systemd unit extension")}, + }, + } + + for i, test := range tests { + var unit SystemdUnitName + err := json.Unmarshal([]byte(test.in.data), &unit) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if err != nil { + continue + } + + if !reflect.DeepEqual(test.out.unit, unit) { + t.Errorf("#%d: bad unit: want %#v, got %#v", i, test.out.unit, unit) + } + } +} + +func TestSystemdUnitNameUnmarshalYAML(t *testing.T) { + type in struct { + data string + } + type out struct { + unit SystemdUnitName + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"test.service"`}, + out: out{unit: SystemdUnitName("test.service")}, + }, + { + in: in{data: `"test.socket"`}, + out: out{unit: SystemdUnitName("test.socket")}, + }, + { + in: in{data: `"test.blah"`}, + out: out{err: errors.New("invalid systemd unit extension")}, + }, + } + + for i, test := range tests { + var unit SystemdUnitName + err := yaml.Unmarshal([]byte(test.in.data), &unit) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if err != nil { + continue + } + + if !reflect.DeepEqual(test.out.unit, unit) { + t.Errorf("#%d: bad unit: want %#v, got %#v", i, test.out.unit, unit) + } + } +} + +func TestNetworkdUnitNameUnmarshalJSON(t *testing.T) { + type in struct { + data string + } + type out struct { + unit NetworkdUnitName + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"test.network"`}, + out: out{unit: NetworkdUnitName("test.network")}, + }, + { + in: in{data: `"test.link"`}, + out: out{unit: NetworkdUnitName("test.link")}, + }, + { + in: in{data: `"test.netdev"`}, + out: out{unit: NetworkdUnitName("test.netdev")}, + }, + { + in: in{data: `"test.blah"`}, + out: out{err: errors.New("invalid networkd unit extension")}, + }, + } + + for i, test := range tests { + var unit NetworkdUnitName + err := json.Unmarshal([]byte(test.in.data), &unit) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if err != nil { + continue + } + + if !reflect.DeepEqual(test.out.unit, unit) { + t.Errorf("#%d: bad unit: want %#v, got %#v", i, test.out.unit, unit) + } + } +} + +func TestNetworkdUnitNameUnmarshalYAML(t *testing.T) { + type in struct { + data string + } + type out struct { + unit NetworkdUnitName + err error + } + + tests := []struct { + in in + out out + }{ + { + in: in{data: `"test.network"`}, + out: out{unit: NetworkdUnitName("test.network")}, + }, + { + in: in{data: `"test.link"`}, + out: out{unit: NetworkdUnitName("test.link")}, + }, + { + in: in{data: `"test.netdev"`}, + out: out{unit: NetworkdUnitName("test.netdev")}, + }, + { + in: in{data: `"test.blah"`}, + out: out{err: errors.New("invalid networkd unit extension")}, + }, + } + + for i, test := range tests { + var unit NetworkdUnitName + err := yaml.Unmarshal([]byte(test.in.data), &unit) + if !reflect.DeepEqual(test.out.err, err) { + t.Errorf("#%d: bad error: want %v, got %v", i, test.out.err, err) + } + if err != nil { + continue + } + + if !reflect.DeepEqual(test.out.unit, unit) { + t.Errorf("#%d: bad unit: want %#v, got %#v", i, test.out.unit, unit) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/src/config/user.go b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/user.go new file mode 100644 index 00000000..2e73d78c --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/src/config/user.go @@ -0,0 +1,35 @@ +// 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 config + +type User struct { + Name string `json:"name,omitempty" yaml:"name"` + PasswordHash string `json:"passwordHash,omitempty" yaml:"password_hash"` + SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty" yaml:"ssh_authorized_keys"` + Create *UserCreate `json:"create,omitempty" yaml:"create"` +} + +type UserCreate struct { + Uid *uint `json:"uid,omitempty" yaml:"uid"` + GECOS string `json:"gecos,omitempty" yaml:"gecos"` + Homedir string `json:"homeDir,omitempty" yaml:"home_dir"` + NoCreateHome bool `json:"noCreateHome,omitempty" yaml:"no_create_home"` + PrimaryGroup string `json:"primaryGroup,omitempty" yaml:"primary_group"` + Groups []string `json:"groups,omitempty" yaml:"groups"` + NoUserGroup bool `json:"noUserGroup,omitempty" yaml:"no_user_group"` + System bool `json:"system,omitempty" yaml:"system"` + NoLogInit bool `json:"noLogInit,omitempty" yaml:"no_log_init"` + Shell string `json:"shell,omitempty" yaml:"shell"` +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/COPYING b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/COPYING new file mode 100644 index 00000000..2993ec08 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/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/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/README.md b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/README.md new file mode 100644 index 00000000..bee884e3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/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/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes.go b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes.go new file mode 100644 index 00000000..eaadeb80 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/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/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes_test.go b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes_test.go new file mode 100644 index 00000000..d4317aa5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/bytes_test.go @@ -0,0 +1,49 @@ +package units + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBase2BytesString(t *testing.T) { + assert.Equal(t, Base2Bytes(0).String(), "0B") + assert.Equal(t, Base2Bytes(1025).String(), "1KiB1B") + assert.Equal(t, Base2Bytes(1048577).String(), "1MiB1B") +} + +func TestParseBase2Bytes(t *testing.T) { + n, err := ParseBase2Bytes("0B") + assert.NoError(t, err) + assert.Equal(t, 0, n) + n, err = ParseBase2Bytes("1KB") + assert.NoError(t, err) + assert.Equal(t, 1024, n) + n, err = ParseBase2Bytes("1MB1KB25B") + assert.NoError(t, err) + assert.Equal(t, 1049625, n) + n, err = ParseBase2Bytes("1.5MB") + assert.NoError(t, err) + assert.Equal(t, 1572864, n) +} + +func TestMetricBytesString(t *testing.T) { + assert.Equal(t, MetricBytes(0).String(), "0B") + assert.Equal(t, MetricBytes(1001).String(), "1KB1B") + assert.Equal(t, MetricBytes(1001025).String(), "1MB1KB25B") +} + +func TestParseMetricBytes(t *testing.T) { + n, err := ParseMetricBytes("0B") + assert.NoError(t, err) + assert.Equal(t, 0, n) + n, err = ParseMetricBytes("1KB1B") + assert.NoError(t, err) + assert.Equal(t, 1001, n) + n, err = ParseMetricBytes("1MB1KB25B") + assert.NoError(t, err) + assert.Equal(t, 1001025, n) + n, err = ParseMetricBytes("1.5MB") + assert.NoError(t, err) + assert.Equal(t, 1500000, n) +} diff --git a/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/doc.go b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/doc.go new file mode 100644 index 00000000..156ae386 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/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/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/si.go b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/si.go new file mode 100644 index 00000000..8234a9d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/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/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/util.go b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/alecthomas/units/util.go new file mode 100644 index 00000000..6527e92d --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/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/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/camlistore/camlistore/pkg/errorutil/highlight.go b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/camlistore/camlistore/pkg/errorutil/highlight.go new file mode 100644 index 00000000..aace6a46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/ignition/third_party/github.com/camlistore/camlistore/pkg/errorutil/highlight.go @@ -0,0 +1,58 @@ +/* +Copyright 2011 Google 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 errorutil helps make better error messages. +package errorutil + +import ( + "bufio" + "bytes" + "fmt" + "io" + "strings" +) + +// HighlightBytePosition takes a reader and the location in bytes of a parse +// error (for instance, from json.SyntaxError.Offset) and returns the line, column, +// and pretty-printed context around the error with an arrow indicating the exact +// position of the syntax error. +func HighlightBytePosition(f io.Reader, pos int64) (line, col int, highlight string) { + line = 1 + br := bufio.NewReader(f) + lastLine := "" + thisLine := new(bytes.Buffer) + for n := int64(0); n < pos; n++ { + b, err := br.ReadByte() + if err != nil { + break + } + if b == '\n' { + lastLine = thisLine.String() + thisLine.Reset() + line++ + col = 1 + } else { + col++ + thisLine.WriteByte(b) + } + } + if line > 1 { + highlight += fmt.Sprintf("%5d: %s\n", line-1, lastLine) + } + highlight += fmt.Sprintf("%5d: %s\n", line, thisLine.String()) + highlight += fmt.Sprintf("%s^\n", strings.Repeat(" ", col+5)) + return +}