Godeps: Add coreos/ignition to godeps

This commit is contained in:
Dalton Hubble
2016-01-04 14:02:12 -08:00
parent 3441b39f0b
commit db86f5606e
27 changed files with 2097 additions and 0 deletions

View File

@@ -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, "#!")
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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"`
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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"`
}

View File

@@ -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")
}
}

View File

@@ -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)
}
}
}

View File

@@ -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"`
}

View File

@@ -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.

View File

@@ -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
```

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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),
}
}

View File

@@ -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
}

View File

@@ -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
}